Old Coder: Review evidence, không review code
Tìm hiểu cách Old Coder thay review code bằng SPEC và báo cáo EVIDENCE có thể chạy lại, cùng vai trò của mutation testing vượt ngoài coverage.
Những gì skill Old Coder thực sự thay đổi
Skill Old Coder thay thế việc review code bằng việc review tài liệu. Coding agent của bạn viết SPEC trước khi viết bất kỳ code nào. Bạn phê duyệt tài liệu đó, rồi agent mới triển khai. Sau đó, agent tự kiểm tra phần việc của mình bằng một chuỗi cố định gồm các kiểm tra tự động gọi là gauntlet, rồi gửi cho bạn báo cáo EVIDENCE chứa các command chính xác và số liệu thực tế. Bạn đọc 2 tài liệu. Bạn không bao giờ đọc diff.
Cách làm này chỉ hiệu quả nếu 2 tài liệu đó tạo được mức độ tin cậy mà trước đây diff tạo ra. SPEC tạo được mức độ tin cậy đó vì bạn phê duyệt nó trước khi có code, nên agent không thể định hình SPEC theo code mà nó đã viết. Báo cáo EVIDENCE tạo được mức độ tin cậy đó vì mọi số liệu trong báo cáo đều đến từ một command mà bạn có thể tự chạy và theo dõi để xác nhận cùng một số liệu. Nếu một trong 2 phần bị nới lỏng, bạn đã thay việc review bằng bản tóm tắt của việc review. Cách này còn tệ hơn đọc diff vì nó tạo cảm giác công việc đã hoàn tất.
Skill này chỉ là markdown, nên hoạt động với bất kỳ agent nào tuân theo hướng dẫn bằng văn bản: Claude Code, Codex CLI, Cursor hoặc loop của riêng bạn. Nó thuộc cùng nhóm với skill persona senior developer lười biếng Ponytail. Nếu format file này còn mới với bạn, agent skill là gì và agent load skill như thế nào giải thích các cơ chế liên quan.
SPEC là quyết định duy nhất bạn vẫn phải đưa ra
SPEC là kế hoạch kiểm thử được viết trước khi code tồn tại. Skill file yêu cầu SPEC phải có 4 nội dung.
- Kịch bản cụ thể: input, output mong đợi, trường hợp biên và trường hợp lỗi.
divide(1, 0) raises ZeroDivisionError with message X, không phải "xử lý input không hợp lệ". - Ràng buộc phủ định: những thứ không được thay đổi, chẳng hạn các test hiện có và signature của public API.
- Kế hoạch setup: mọi tool và dependency mới, mỗi mục có một dòng giải thích lý do cần dùng.
- Đường dẫn file tuyệt đối để bạn có thể mở file mà không phải tìm.
Kế hoạch setup là nơi có thể thấy rõ giới hạn dữ liệu huấn luyện của model, vì một tool hoặc version được pin và nhớ từ trước có thể đã lỗi thời một năm. Vì vậy, nên cung cấp cho agent một instance SearXNG tự host để tìm kiếm trên web và yêu cầu agent kiểm tra version hiện tại trước khi đề xuất.
Hãy approve SPEC, rồi commit nó. Một SPEC có thể bị chỉnh sửa sau khi approve không phải là contract, còn commit là thứ cho phép bạn kiểm tra sau này rằng evidence đã được đo dựa trên đúng nội dung bạn ký.
Đây là quyết định yes hoặc no duy nhất còn lại dành cho bạn. Đó cũng là điểm cốt lõi và rủi ro của quy trình. Hãy xử lý việc này như một thay đổi trên production, vì đúng là như vậy. Nếu bạn đã chạy một approval gate rõ ràng trước các hành động của agent, việc approve SPEC có thể dùng cùng vị trí đó trong workflow của bạn.
Báo cáo EVIDENCE: số liệu kèm command
Báo cáo EVIDENCE là thứ bạn đọc ở cuối. Skill yêu cầu ánh xạ từng hành vi trong spec với test xác minh hành vi đó, báo cáo từng layer của gauntlet kèm command đã chạy và kết quả thực tế, lấy mọi số liệu từ một lần chạy mới sau lần sửa code cuối cùng, đồng thời liệt kê mọi layer bị bỏ qua cùng lý do. Tính từ không phải là evidence. “Cả 41 test đều pass, coverage 49/49 statement” là một kết quả. “Được kiểm thử tốt” thì không.
Báo cáo demo trong repository, demo-rate-limiter/evidence.md, xác định trạng thái nguồn bằng một commit và sha256 tree hash. Dòng đó quan trọng hơn bạn tưởng, vì nó cho biết chính xác những byte nào đã tạo ra các số liệu đó. Nếu thiếu dòng này, báo cáo có thể âm thầm mô tả một working tree không còn tồn tại.
Ba quy tắc chống gian lận giúp báo cáo trung thực, và file skill nêu chúng dưới dạng quy tắc tuyệt đối. Không bao giờ làm yếu test để test pass: không mở rộng assertion, không tăng tolerance. Không bao giờ sửa test và implementation trong cùng một bước để đạt trạng thái green, vì việc sửa đồng thời che giấu bên nào sai. Không bao giờ báo cáo một layer chưa chạy: “skipped, không có tool, đã thay bằng mutation thủ công” giúp duy trì độ tin cậy, còn kết quả bịa đặt sẽ phá hỏng toàn bộ cơ chế.
Cài skill và cố định commit đã cài
Repository là AmazingAng/old-coder và được cấp phép theo MIT. README cung cấp một lệnh cài đặt một dòng qua skills CLI:
npx skills add https://github.com/amazingang/old-coderLệnh này cài phiên bản mà main trỏ tới tại thời điểm bạn chạy lệnh. Bản thân CLI cũng có thể thay đổi. Kiểm tra vị trí các file trước khi cho rằng skill đã hoạt động:
ls ~/.claude/skills/old-coder/Bạn phải thấy SKILL.md và một thư mục references/. Nếu không có các mục này, skill không nằm ở vị trí Claude Code tìm kiếm. Các bản phát hành cũ của skills CLI ghi file vào ~/.agents/skills/ nhưng không tạo liên kết kết quả vào ~/.claude/skills/. Vì vậy, file vẫn tồn tại trên disk nhưng agent không bao giờ load chúng. Chạy npx skills@latest add ... để tránh trường hợp CLI cũ.
Nên cài thủ công vì cách này cho phép bạn ghi lại chính xác những gì đã cài:
git clone https://github.com/AmazingAng/old-coder.git
cd old-coder
git checkout acc5a89
git rev-parse HEAD
mkdir -p ~/.claude/skills
cp -r skills/old-coder ~/.claude/skills/acc5a89 là commit mới nhất của main vào ngày 17 August 2026. Bạn hãy chọn một commit và ghi lại. Repository đang được phát triển liên tục. Tài liệu tham chiếu gauntlet, template và protocol của verifier đã được chuyển giữa các file. Nếu các báo cáo EVIDENCE của bạn không ghi rõ version của skill đã chấm chúng, bạn không thể biết thay đổi là do code của mình hay do rules. Lưu hash của commit đó cạnh SPEC, trong cùng repository với code mà nó quản lý.
Với agent không đọc ~/.claude/skills, hãy thêm skills/old-coder/SKILL.md và skills/old-coder/references/gauntlet.md vào system prompt hoặc file rules của agent. Chỉ cần tích hợp như vậy.
Những gì chạy trong gauntlet
Gauntlet là một stack gồm nhiều layer, chạy một lần sau khi mọi behavior trong spec đều đạt. Skill này gồm:
- Toàn bộ test suite để phát hiện regression. Không được có failure mới; các failure đã tồn tại phải được ghi nhận làm baseline trước.
- Static type, cùng lint và format, để phát hiện cả nhóm lỗi và tình trạng drift.
- Coverage trên các dòng đã thay đổi. Layer này phải exit với mã khác 0 khi không đạt threshold.
- Mutation testing để phát hiện các test không kiểm tra điều gì.
- Property-based test cho những edge case không ai nghĩ đến.
- Complexity budget, một lần chạy thực tế chương trình, scan supply chain và secrets, cùng một lần kiểm tra sức khỏe suite theo thứ tự ngẫu nhiên.
- Các layer theo domain, được chọn dựa trên rủi ro của task: stress concurrency, compatibility của API, diễn tập rollback, benchmark latency.
Demo nối các layer này vào một script duy nhất, demo-rate-limiter/tools/gauntlet.sh, sử dụng toolchain được pin trong requirements-dev.txt: pytest, pytest-cov, coverage, hypothesis, mypy, ruff, pip-audit và pytest-randomly. Mỗi tool được pin về một version chính xác (pytest 9.1.1, ruff 0.16.0, tính đến tháng 8 năm 2026). Chạy như sau:
cd demo-rate-limiter
python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt -e .
./tools/gauntlet.shScript in một banner cho mỗi layer, chẳng hạn === tests + coverage === và === mutation ===, rồi kết thúc bằng === gauntlet: all layers green ===. Script chạy với set -e, nên layer đầu tiên bị lỗi sẽ dừng script và banner cuối cùng sẽ không được in. Banner cuối cùng là dấu hiệu kiểm tra: nếu thấy banner này, nghĩa là mọi layer bên trên đều exit với mã 0.
Có một chi tiết trong script này đáng để bạn áp dụng. Coverage layer được viết như sau:
pytest -q --cov=ratelimiter --cov-report=term-missing --cov-fail-under=100Nếu không có --cov-fail-under, pytest --cov chỉ in phần trăm coverage rồi exit với mã 0, bất kể coverage đã giảm bao xa. Đây là một layer fail-open nằm trong script mà dòng đầu tiên cam kết dừng ở layer đầu tiên bị lỗi. Một layer trong gauntlet không thể fail chỉ là phần trang trí.
Mutation testing bổ sung gì so với coverage
Coverage trả lời một câu hỏi: bộ test có chạy qua dòng này hay không. Nó không trả lời câu hỏi bạn thực sự quan tâm: nếu dòng đó bị sai, có assertion nào bị fail hay không. Một test gọi một function nhưng không kiểm tra gì vẫn báo coverage đầy đủ cho mọi dòng mà nó chạm tới. Coverage phát hiện code chưa được test. Nó không phát hiện các test không kiểm tra gì.
Mutation testing trả lời trực tiếp câu hỏi thứ hai. Nó cố ý thay đổi code, mỗi lần một chỉnh sửa nhỏ, rồi chạy lại bộ test. Nếu bộ test fail, mutant bị kill. Điều đó có nghĩa là một assertion nào đó đang kiểm tra behaviour này. Nếu bộ test vẫn pass, mutant đã sống sót: dòng code đã được chạy nhưng không có gì kiểm tra kết quả.
Demo thực hiện việc này với tools/mutants.py. Nó chèn các chỉnh sửa đơn được đánh số vào src/ratelimiter/__init__.py, chạy pytest sau mỗi chỉnh sửa, rồi khôi phục file. Đây là những lỗi một người mệt mỏi thường mắc: >= trở thành >, hoặc một return value bị bỏ đi.
Quy tắc kill trong runner đó là phần mà hầu hết mutation script tự viết đều xử lý sai. Chỉ exit code 1 của pytest mới được tính là kill, vì 1 có nghĩa là các test đã chạy và ít nhất một test bị fail. Exit code 0 có nghĩa là mutant đã sống sót. Mọi giá trị khác, chẳng hạn lỗi collection hoặc không thu thập được test nào, có nghĩa là chưa có gì được xác minh và không được tính. Script coi mọi giá trị “nonzero” là kill sẽ tính cả các lần tự crash của nó là thành công, khiến con số đó chỉ tăng lên.
Trong cùng file còn có 2 chi tiết quan trọng khác. Mutant M11 bị loại khỏi danh sách vì nó là equivalent mutant: xóa một entry đã hết hạn thay vì xóa tất cả entry đã hết hạn tạo ra cùng behaviour có thể quan sát được khi clock tăng đơn điệu, nên không test nào có thể kill nó. Runner đặt PYTHONDONTWRITEBYTECODE=1, còn gauntlet xóa mọi __pycache__ trước, vì 2 mutant có cùng kích thước được ghi trong cùng một giây có thể dùng chung một .pyc đã cache, khiến mutant thứ hai kế thừa verdict của mutant thứ nhất.
Rủi ro đó là lý do gauntlet chạy một negative control trước mutation pass thực tế:
.venv/bin/python tools/mutants.py --negative-control
.venv/bin/python tools/mutants.pyControl chạy 2 mutant với modification time cố định: một mutant chắc chắn phải bị kill và một mutant equivalent hoàn toàn chắc chắn phải sống sót. Nếu cả 2 đều trả về kết quả killed, bytecode cache đã bị rò rỉ giữa các lần chạy và mọi kill count trong report đều bị thổi phồng. Đây là cách cụ thể hóa quy tắc của skill về checker. pytest và mypy đã được kiểm chứng qua behaviour khi fail trong nhiều năm. Một script bạn viết tuần trước thì chưa, vì vậy hãy chứng minh nó có thể fail trước khi tin vào kết quả pass của nó, đồng thời ghi lại bằng chứng đó trong EVIDENCE.
The data behind this chart
[
{
"label": "Scenario tests",
"mutants_killed": 22,
"mutants_run": 22
},
{
"label": "Property tests",
"mutants_killed": 3,
"mutants_run": 22
}
]Report EVIDENCE của demo cho thấy vì sao một con số tổng hợp duy nhất vẫn che giấu nhiều thông tin. Bộ test scenario đã kill 22 trên tổng số 22 mutant. Các test property-based, khi chạy lại riêng trên cùng các mutant, đã kill 3. Một kill được gán cho test nào fail trước, vì vậy tổng số hoàn hảo xác nhận cả bộ test nhưng không cho biết gì về bất kỳ layer riêng lẻ nào bên trong. Các property đó vẫn xứng đáng có mặt, vì chúng bắt được những dạng input không ai liệt kê. Chúng không gánh phần chính của việc kiểm tra correctness, và bạn chỉ biết điều đó khi đo riêng từng layer.
Chạy toàn bộ kiểm tra trên server, không chạy trên laptop
Báo cáo EVIDENCE là một khẳng định rằng một số lệnh đã tạo ra một số giá trị. Người khác chỉ có thể kiểm tra khẳng định này nếu họ tạo ra được cùng các giá trị đó. Laptop là nơi tệ nhất để thử. Bản Python của bạn dùng một patch release khác, còn PATH của bạn có các tool mà máy tiếp theo sẽ không có. Mutation testing còn làm tình hình tệ hơn vì nó chạy lại toàn bộ test suite một lần cho mỗi mutant. Vì vậy, chỉ riêng danh sách trong bản demo đã có nghĩa là chạy thêm 22 test suite.
Đặt nó trong một container trên VPS. Container cố định operating system và interpreter. requirements-dev.txt được pin cố định các tool. VPS cung cấp một máy không đồng thời chạy browser của bạn.
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-venv git ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workdocker build -t gauntlet:24.04 .
docker run --rm -v "$PWD:/work" -w /work/demo-rate-limiter gauntlet:24.04 \
sh -c 'python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt -e . && ./tools/gauntlet.sh'Một lần chạy thành công kết thúc tại === gauntlet: all layers green ===. Có 2 bước cần network outbound: pip cài các tool đã pin và layer pip-audit kiểm tra dependency của bạn với một vulnerability service. Lần chạy offline không được âm thầm bỏ qua layer đó. Nó phải fail. Đây là behavior bạn cần ở một gate.
Để chạy sau mỗi lần push, cùng script đó trở thành một bước trong một CI job. Repository tự chạy toàn bộ kiểm tra trên GitHub Actions trên ubuntu-latest với Python 3.12. Trỏ runs-on đến một self-hosted runner để job chạy trên VPS của bạn:
name: gauntlet
on:
push:
branches: [main]
jobs:
gauntlet:
runs-on: self-hosted
defaults:
run:
working-directory: demo-rate-limiter
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: python -m venv .venv && .venv/bin/pip install -r requirements-dev.txt -e .
- run: ./tools/gauntlet.shChỉ đặt trigger cho các lần push lên những branch bạn kiểm soát. Một self-hosted runner đồng thời build pull request từ fork sẽ chạy code của người lạ trên server của bạn bằng credential của runner. Lập luận tương tự cũng áp dụng cho chính agent: cấp cho agent một VM dùng tạm có thể hủy bỏ thay vì workstation của bạn. Nếu muốn kết quả của toàn bộ kiểm tra xuất hiện tại nơi mọi người thảo luận về thay đổi, hãy kết nối nó với một self-hosted PR review agent.
Khi cách tiếp cận này thất bại
Thất bại đầu tiên mang tính cấu trúc, và không công cụ nào có thể loại bỏ nó. Gauntlet biến các ràng buộc trong SPEC thành bằng chứng có thể thực thi. Nó không thể cho biết SPEC có đúng hay không. Nếu phê duyệt một spec mã hóa sai yêu cầu, bạn sẽ nhận được báo cáo EVIDENCE hoàn hảo cho sai chương trình: độ bao phủ đầy đủ, mọi mutant đều bị loại, mọi layer đều xanh, nhưng phần mềm lại thực hiện điều bạn không muốn. Mỗi giờ bạn tiết kiệm được do không đọc diff nên dành để đọc spec.
Vấn đề thứ hai là các checker. Repository tự thừa nhận điều này trong file bằng chứng của chính nó. Protocol xác minh độc lập đã chạy 6 vòng, vòng 6 trả về failed, và các bản sửa sau vòng đó chưa bao giờ được xác minh lại. Vì vậy, trạng thái được release chưa được xác minh từ đầu đến cuối. Một layer lint cho shell được ghi nhận là không khả dụng, thay vì được tính là pass. Các vòng trước đã phát hiện những lỗi hành vi thực sự và một mutation runner không đáng tin cậy, dù các trạng thái trước đó đã báo xanh. Một gauntlet xanh không tự xác thực.
Vấn đề thứ ba là phạm vi. Danh sách mutant viết thủ công chỉ bao phủ các lỗi mà ai đó đã nghĩ đến và cố tình cài vào. Mỗi equivalent mutant bị loại khỏi danh sách cũng là một phán đoán mà bạn đang tin tưởng. Các mutation tool có sẵn trên thị trường (mutmut, cosmic-ray, Stryker, PIT) tạo mutant một cách có hệ thống và là lựa chọn mặc định tốt hơn nếu ngôn ngữ của bạn có tool phù hợp.
Điều chỉnh mức nỗ lực theo rủi ro
Skill này định nghĩa 3 cấp độ và yêu cầu agent nêu rõ đã chọn cấp nào.
- Cấp 1, đơn giản: lỗi gõ, comment hoặc giá trị cấu hình. Chạy đầy đủ test suite và lint, không viết test mới, đồng thời nêu trong một câu lý do không cần thêm test.
- Cấp 2, thông thường: sửa bug hoặc thêm một feature nhỏ. Thực hiện đầy đủ quy trình. Với bản sửa bug, phải bắt đầu bằng một test đang fail để tái hiện bug, để bug của hôm qua trở thành regression test của ngày mai.
- Cấp 3, rủi ro cao: tiền, authentication, mất dữ liệu, concurrency hoặc public API. Bắt đầu bằng failure model liệt kê các cách mà thay đổi này có thể gây hại. Thêm một lớp kiểm tra gauntlet cho từng chế độ lỗi. Sau đó chạy đầy đủ quy trình cùng property test, mutation testing và một lượt kiểm tra riêng để tấn công implementation bằng các input hostile.
Cấp 3 còn có một bước thử nghiệm. Một agent thứ hai với context mới chỉ được xem task contract, SPEC đã được phê duyệt và trạng thái source hiện tại. Agent này cố phá vỡ phần đã hoàn thiện trước khi EVIDENCE được ký. Agent đó không sửa gì, chỉ báo cáo, còn một người sẽ đánh giá những gì nó phát hiện. Cách này giảm correlation do dùng chung task context. Nó không giảm correlation do dùng chung model.
Nếu bạn muốn tự xây dựng một thứ theo cấu trúc này thay vì dùng skill này, viết skill agent của riêng bạn trình bày layout của file và trường description quyết định thời điểm agent load skill.
FAQ
Mutation testing phát hiện được gì mà code coverage bỏ sót?
Coverage ghi nhận một dòng đã được chạy. Nó không thể ghi nhận liệu assertion có thất bại nếu dòng đó bị sai hay không. Vì vậy, một test chỉ gọi function mà không kiểm tra gì vẫn báo coverage đầy đủ. Mutation testing cố ý thay đổi code, mỗi lần một thay đổi, rồi chạy lại test suite. Mutant còn tồn tại nghĩa là dòng đó đã được thực thi nhưng không có gì xác minh kết quả. Vì thế, skill này liệt kê quy tắc tuyệt đối là "không bao giờ chạy theo các con số coverage" và xác định mutation là lớp kiểm tra phát hiện việc đối phó với chỉ số.
Tôi vẫn cần đọc code mà agent viết sao?
Theo workflow này, bạn đọc SPEC trước khi code, đọc report EVIDENCE sau đó, rồi kiểm tra nhanh report bằng cách chạy lại các command được dẫn trong đó. Diff trở thành tùy chọn. Điểm cần lưu ý là toàn bộ phán đoán của bạn giờ tập trung vào một tài liệu, vì một spec chứa sai requirement sẽ tạo ra một gauntlet màu xanh cho chương trình mà bạn không hề muốn. Hãy dùng thời gian tiết kiệm được để kiểm tra spec.
Tôi có thể chạy gauntlet Old Coder trong CI trên server của mình không?
Có, và đó là nơi phù hợp hơn để chạy nó. Cài toolchain đã pin từ requirements-dev.txt bên trong một container, rồi chạy script gauntlet của project như một job step. Trên GitHub Actions, thiết lập runs-on: self-hosted và đăng ký một runner trên VPS của bạn. Chỉ bật trigger cho các lần push lên những branch bạn kiểm soát, vì self-hosted runner xây dựng pull request từ fork sẽ thực thi code không đáng tin cậy bằng credential của runner.
Tôi nên cài version nào của skill?
Hãy pin một version cụ thể. Repository vẫn đang được phát triển tích cực, và các file tham chiếu của nó đã được tách và di chuyển. Vì vậy, một report được tạo vào tháng trước có thể đã được chấm theo các rule khác. Clone repository, checkout một commit cụ thể, copy skills/old-coder vào ~/.claude/skills/, rồi ghi commit hash đó cạnh SPEC của bạn. Khi đó, thay đổi trong evidence của bạn sẽ phản ánh một thay đổi trong code.