Mười lập trình viên, một thư mục docs
Superpowers là một plugin của Claude Code (286.000 star trên GitHub) với các skill viết spec và implementation plan dưới dạng file markdown rồi commit chúng vào repository. Theo mặc định, mọi file như vậy đều đổ vào cùng một thư mục, docs/superpowers/. Vào tháng 6, các maintainer của Okama, một thư viện tài chính Python, phát hiện ra tám implementation plan mà một agent đã viết vào đó, commit đúng như plugin dự tính, đã bị render thành các trang web công khai trên Read the Docs. Họ chỉ nhận ra sau khi bản release đã lên.
Giờ hãy nhân quy mô đó lên: một team front-end mười lập trình viên, bốn squad, một repository, và mọi lập trình viên đều đang tạo ra các file này. Ba câu hỏi rơi lên bàn của lead. Chúng ta version cái gì? Drift có phải là vấn đề khi một spec nhắc tới một đường dẫn đã bị xoá từ hai sprint trước? Và ai quản trị thư mục đó? Vụ rò rỉ của Okama không phải do việc commit gây ra. Nó xảy ra do commit mà không có quy tắc. Bài viết này đưa ra cho bạn năm quy tắc.
Mọi công cụ đều commit các tệp
Superpowers không bao giờ hỏi có nên commit hay không. Dòng 18 của skill lập plan lưu mọi plan vào docs/superpowers/plans/, có ngày, một file cho mỗi feature. Skill brainstorming viết design document và commit nó ngay trong cùng bước, trước khi bạn kịp xem plan.
Đây không phải là một điểm lạ riêng của Superpowers. Mọi công cụ spec-driven lớn đều chọn cùng một cách:
| Tool | Publisher | Where specs live |
|---|---|---|
| Superpowers (286,000 stars) | obra | docs/superpowers/, một thư mục cho tất cả |
| Spec Kit (136,000 stars) | GitHub | một thư mục đánh số cho mỗi feature, trên branch của feature đó |
| Kiro | Amazon | .kiro/specs/, một thư mục cho mỗi feature |
| AI-native SDLC playbook | Anthropic | một artifact được commit cho mỗi giai đoạn: intent, spec, plan, diff, review findings, incident record |
Kiro giới thiệu các thư mục theo từng feature như một cách để các đồng nghiệp cùng cộng tác trên nhiều feature khác nhau đồng thời. Playbook của Anthropic, công bố vào tháng 8, đi xa hơn: mỗi giai đoạn commit một artifact mà giai đoạn kế tiếp có thể đọc. Vậy câu hỏi không bao giờ là có nên commit hay không, và câu hỏi lưu ở đâu thì công cụ nào cũng trả lời được. Ai sở hữu file, và khi nào nó đã hết hạn, thì không. Đó chính là khoảng trống mà năm quy tắc này lấp vào.
Ba cách nó đi sai hướng
Các plan đã commit thất bại theo ba cách khác nhau.
Thất bại 1: docs generator. Một bản build documentation render mọi file markdown dưới docs/, bất kể có được liệt trong table of contents hay không, sẽ đem cả thư mục plan đi cùng với manual. Đó chính xác là điều đã xảy ra với Okama.
Thất bại 2: branch. Issue 1246 của Superpowers ghi nhận rằng brainstorm commit plan thẳng vào main. Một người dùng đếm được 10 đến 15 commit mỗi session, một commit mới sau vài thay đổi nhỏ. Yêu cầu của thread đó gói trong một dòng: không được có plan và spec trên nhánh main.
Thất bại 3: drift. Spec drift là thất bại âm thầm nhất: code tiến triển còn spec thì không. Birgitta Böckeler tại Thoughtworks đặt tên cho ba cấp độ của spec-driven development:
| Level | Meaning |
|---|---|
| Spec-first | Được viết ra, dùng một lần |
| Spec-anchored | Được giữ và bảo trì suốt đời của feature |
| Spec-as-source | Con người chỉ chỉnh sửa spec |
Superpowers viết các document kiểu spec-first và giữ chúng vĩnh viễn. Theo mặc định bạn có một kiểu lưu trữ spec-anchored nhưng lại được bảo trì như bản nháp đầu: không ai cập nhật file, và một agent đọc nó vào quý sau như thể đó là sự thật. TrueFoundry nói thẳng: drift là không thể tránh trong thực hành spec-first và được quản lý trong thực hành spec-anchored, nhưng chỉ khi sự sai lệch đó có thể phát hiện được.
Một repository chứa 41 spec và 9 agent file không có vấn đề về gọn gàng. Nó có 41 behavior input chưa được version. Phản ứng của chính Böckeler: bà thích review code hơn là đọc hết đám file markdown này. Một plan mà không ai đọc lại là rác. Một plan mà agent đọc là một instruction, và một instruction cũ là một instruction sai.
Quy tắc 1: mỗi squad một nơi ở, có chủ
Quy tắc 1: thư mục phải có chủ, và chủ đó là squad, không phải plugin. Từ phiên bản 5, Superpowers ưu tiên các instruction trong CLAUDE.md của project hơn mặc định của chính nó. Nói rõ plan lưu ở đâu và chúng sẽ nằm ở đó.
Tuy vậy, một bảng đường dẫn thôi là chưa đủ. Trong issue 939, một người dùng viết một bảng output-path và model vẫn theo đường dẫn cụ thể trong skill và bỏ qua bảng đó. Ghi chú override của chính skill chỉ là một dòng trong ngoặc. Một dòng mệnh lệnh, in đậm, đã sửa được điều này ngay lần đầu: spec phải được lưu ở đây, không phải ở kia, với đường dẫn mặc định được nêu rõ như thứ cần tránh.
Cách bố trí là một thư mục cho mỗi squad (checkout, search, accounts, design-system), mỗi thư mục có spec và plan riêng của nó. Trong một monorepo, đặt dòng đó vào CLAUDE.md của riêng package. Claude Code nạp file của một subdirectory theo yêu cầu khi nó đọc ở đó, và documentation nói rằng chủ của mỗi thư mục thường là người bảo trì file đó. Các engineer chưa từng cài plugin không bị ảnh hưởng: dòng đó chỉ kích hoạt khi một skill hỏi nơi lưu.
Sau đó hãy để platform thực thi quyền sở hữu. Một dòng CODEOWNERS cho mỗi thư mục squad, và mọi spec review sẽ đến đúng những người sẽ phải sống cùng nó. Một lưu ý: override chỉ là một prompt, không phải một setting. Kiểm tra spec đầu tiên sau mỗi lần plugin release.
Quy tắc 2: cá nhân và dùng chung
Quy tắc 2: không phải plan nào cũng là artifact của team. Người đầu tiên yêu cầu Superpowers cho một vị trí lưu có thể cấu hình (issue 337) đã nói đúng nhất: file plan có thể là tài liệu làm việc cá nhân hơn là artifact của project.
Phía cá nhân có một file dành riêng cho nó. CLAUDE.local.md nằm cạnh file dùng chung, nạp sau nó, và documentation bảo bạn tự git-ignore nó. Đường dẫn riêng của bạn tới một thư mục cá nhân nằm ở đó và không ai khác thấy được. Đối với thư mục tự thân, Git có một ignore file không bao giờ chạm vào tree dùng chung: .git/info/exclude chỉ tồn tại per clone, không bao giờ được commit, và không cần pull request. Chính plugin đã làm điều này cho không gian scratch của nó: từ phiên bản 6.0.3, working directory của nó tự thả một .gitignore chứa * bên trong, nên nó tự ignore chính nó mà không đụng tới bất kỳ tracked file nào.
Phía dùng chung là bất cứ thứ gì đã qua được review gate mà brainstorm đã chạy sẵn: spec được viết, review được yêu cầu, được một người thứ hai approve. Được approve, nó chuyển vào thư mục squad. Không được approve, nó ở lại cục bộ. Cả hai đều được viết trên một branch, không bao giờ trên main. Hai dòng trong CLAUDE.md dùng chung làm được điều đó: coi spec và plan là điểm bắt đầu của một feature, và tạo worktree trước khi viết chúng.
Một giới hạn: một plan bị git-ignore không di chuyển được. Các maintainer của Okama đã thử ignore cả thư mục trước, rồi phải đảo ngược, vì các plan ngừng sync giữa các máy và các agent. Cá nhân nghĩa là cá nhân.
Quy tắc 3: một vòng đời, như ADR
Quy tắc 3: một spec có một status, và một spec đã chết phải nói rõ điều đó. Ý tưởng này đã 15 năm tuổi. Michael Nygard, vào năm 2011, đề xuất giữ các architecture decision record (ADR) trong repository, mỗi bản một file được đánh số. Một quyết định được đề xuất, rồi được chấp thuận. Khi một bản ghi sau đó thay đổi nó, bản cũ được đánh dấu deprecated hoặc superseded kèm tham chiếu tới bản thay thế. Nếu một quyết định bị đảo ngược, bản ghi cũ vẫn được giữ nhưng đánh dấu superseded.
Áp dụng vào spec, mỗi spec dùng chung mở đầu bằng một header năm dòng mà agent vẫn đọc:
| Field | Purpose |
|---|---|
| status | proposed, accepted, superseded |
| superseded-by | spec thay thế |
| owner | squad |
| touches | các đường dẫn spec đưa ra tuyên bố về |
| review date | lần cuối được kiểm tra |
Không bao giờ chỉnh sửa một spec đã accepted thành một quyết định khác. Viết bản kế tiếp, chỉ ngược về bản cũ, và lịch sử vẫn trung thực. Một ngoại lệ, được nêu trong playbook của Anthropic: khi implementation lệch khỏi plan, cập nhật plan trong cùng commit đó. Plan đi cùng với diff đã làm nó lệch, và một hook có thể thực thi điều này.
Spec Kit gọi tên ba cách một spec có thể sống. Flow-forward: một thư mục mới cho mỗi feature, các thư mục cũ được giữ như lịch sử. Living spec: spec là hợp đồng và phần còn lại được sinh lại từ nó. Flow-back: bản build định hình lại spec. Quy tắc của họ là không để một thay đổi trong task hay code nói khác với điều spec vẫn đang nói. Đó là chọn spec-anchored một cách có chủ đích.
Giới hạn: status là metadata do con người đặt. Agent sẽ không tự đánh dấu spec của chính nó là superseded trừ khi CLAUDE.md của bạn bảo nó làm vậy, và ngay cả khi đó review vẫn phải để ý.
Quy tắc 4: một drift gate trong CI
Quy tắc 4: một spec nói dối về tree sẽ làm build fail. Bài paper đặt tên cho vấn đề này, công bố vào tháng 6, gọi nó là silent spec-code drift: code tiến triển, specification không, và sự sai lệch vẫn vô hình cho tới khi việc sửa nó trở nên đắt đỏ. Câu trả lời của họ là một drift gate, một điều kiện merge có tính chặn.
Việc kiểm tra nhỏ hơn nghe có vẻ. Với mỗi spec mà header ghi accepted, lấy các đường dẫn nó nêu, trong backtick hoặc trong dòng touches, và kiểm tra từng đường dẫn có tồn tại. Đường dẫn thiếu, build fail, kèm tên spec trong log. Các file superseded và proposed được bỏ qua: chỉ spec accepted mới đưa ra tuyên bố về tree, nên chỉ spec accepted mới bị kiểm tra. TrueFoundry gọi đây là một diff theo lịch trình thay vì khảo cổ sau sự cố: một spec là scaffold, và một thay đổi spec là một thay đổi chính sách.
Người ta đã chạy phiên bản thủ công của việc này. Một người bình luận cho Claude so sánh các file spec với codebase và mở ticket cho những gì còn thiếu. Script làm điều đó trên mỗi pull request, miễn phí.
Lớp bảo vệ thứ hai là điều Okama đã triển khai: loại thư mục đó ra khỏi docs build. Một dòng trong cấu hình Sphinx giữ các file trong Git nhưng ngoài HTML.
Giới hạn: kiểm tra đường dẫn chỉ bắt được file đã xoá, không bắt được hành vi đã thay đổi. Một spec có thể nêu tên mọi file đang tồn tại và vẫn mô tả một API đã không còn nữa. Đó là việc mà review và quy tắc same-commit đảm nhiệm.
Quy tắc 5: một ngân sách kích cỡ, và cái giá
Quy tắc 5: hầu hết công việc không đáng có một spec. Bằng chứng về chi phí phụ trội của spec-driven là nhất quán:
| Experiment | Result |
|---|---|
| Marmelab, Spec Kit trên một feature chỉ hiển thị ngày hiện tại | 8 file, 1.300 dòng văn bản spec |
| Cuộc bake-off của OpenSpec, cùng yêu cầu với một spec tool so với Claude Code một mình | nhiều hơn 50% code, nhiều hơn 50% cyclomatic complexity, gấp đôi thời gian, gấp ba chi phí |
| Một team chạy spec-driven development trong nhiều tháng | 2 đến 3 lần số token, gấp khoảng đôi thời gian, chi phí điều phối lớn cho những thay đổi không cần điều đó; không có bằng chứng code tốt hơn |
Bake-off chỉ là một thí nghiệm, không phải một benchmark, và tác giả của nó tự nói vậy. Nhưng xu hướng vẫn đúng. Vậy quy tắc là: viết một spec khi công việc vượt qua ranh giới một squad hoặc sẽ được đọc lại trong 90 ngày. Mọi thứ khác chỉ là một prompt.
Superpowers đã tự phân loại mọi request vào ba nhánh: spike, bounded, architectural. Chỉ nhánh architectural mới viết spec. Giữ nhánh bounded thật gọn, và giữ spec quanh 300 dòng. Người thực hành thêm hai dòng nữa: đừng viết spec quá lớn, và hãy đọc các spec đã được sinh ra.
Giới hạn này đi cả hai chiều. Cùng cuộc bake-off đó tìm ra ba lỗ hổng mà bản chạy thường bỏ lỡ. Spec mua được coverage, không mua được tốc độ. Trả tiền cho nó ở nơi coverage thực sự quan trọng.
Kết luận: commit, rồi quản trị
Trở lại ba câu hỏi của lead. Version cái gì: các spec và plan đã accepted, trên một branch, trong thư mục của squad. Drift: một header status và một CI gate. Governance: chủ sở hữu và một quy tắc kích cỡ.
Năm quy tắc, và không cái nào được một công cụ thực thi hôm nay. Issue về override vẫn đang mở. Issue về main branch vẫn đang mở. Pull request sắp xếp lại instruction đã bị đóng mà không được merge. Team đã bị bỏng vẫn giữ thực hành này: 8 plan và 6 spec, đã commit, loại khỏi docs build.
Phản đối của Böckeler vẫn đúng. Với một header status và một ngân sách kích cỡ, bạn chỉ đọc các spec đã accepted, và ít hơn trong số đó. Điều bạn nhận lại là điều playbook của Anthropic gọi là audit trail: ai đã yêu cầu điều gì, agent đã tạo ra gì, và ai đã approve.
Đây là quy trình, không phải tooling: một file CLAUDE.md, một file CODEOWNERS, một header, và một script mười lăm dòng. Và nếu team không chịu review một yêu cầu CODEOWNERS, họ cũng sẽ không review một spec. Trong trường hợp đó, .gitignore mới là lựa chọn trung thực.
AIDive