6.3 KiB
name, description
| name | description |
|---|---|
| debug | Use when a bug surfaces — failing test, segfault, wrong stdout, panic, or any observable misbehaviour. Bug fixes are RED-first TDD; no fix is attempted before the failing test is committed. Mandatory for any bug, including ones that look trivial. |
debug — RED-first bug diagnoser
Violating the letter of these rules is violating the spirit.
Overview
Bugs in AILang are diagnosed and fixed by a two-stage handoff: this
skill produces the RED test and the cause analysis; the implement
skill then drives the fix to GREEN. Skipping the RED stage — even for
"trivial" bugs — produces fixes that don't stick and tests that don't
exist to catch the next regression.
When to Use / Skipping
Trigger this skill on:
- a failing
cargo test(any crate) - wrong stdout from
ail run <example> - a segfault from a built binary
- a panic in the compiler
- a structured diagnostic from
ailang-bencherorailang-architectthat names a concrete misbehaviour
Never skipped. A bug without a regression test is a code change, not a fix. Ad-hoc judgement that a bug is "too small for TDD" is the exact failure mode this skill exists to prevent.
The Iron Law
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
NO FIX WITHOUT A FAILING TEST FIRST
Both clauses are non-negotiable. The first is from
superpowers:systematic-debugging. The second is the project's
own rule (formerly in CLAUDE.md "Bug fixes — TDD, always").
The Process — four phases
Each phase must complete before the next starts.
Phase 1 — Root Cause Investigation
- Read the error message in full. Stack traces, line numbers, exit codes — none of them get skimmed.
- Reproduce with the shortest possible command. If you can't repro reliably, gather more data — do NOT guess.
- Check recent changes:
git log --oneline -20,git diff <previous-iter-commit>..HEAD. - For multi-component issues (compiler → emitter → linker; or runtime → C glue → binary), instrument every boundary. Find the layer where the data goes wrong before deciding which layer to fix.
- Trace data flow backward from symptom to source. Fix at source, not at symptom.
Phase 2 — Pattern Analysis
- Find a similar working example in the codebase. What's different?
- If the bug is in a pattern (e.g. ADT match lowering, RC drop emission), read the reference implementation completely. No skimming.
- List every difference between working and broken — however small.
Phase 3 — Hypothesis and Test
- State the hypothesis as a single falsifiable claim: "X is the root cause because Y."
- Write the failing test FIRST, before any fix attempt:
- smallest possible reproducer (E2E in
crates/ail/tests/e2e.rs, or unit test in the affected crate) - automated, deterministic — same input always same output
- doc comment names the property the test protects, not just what it does
- smallest possible reproducer (E2E in
- Run it to confirm it fails on the current code, with the symptom the bug-report described.
- Commit the failing test as a separate commit:
git commit -m "test: red for <symptom>".
Phase 4 — Hand off to implement
The fix itself is implement's job, run in mini-mode. Hand it:
- path to the RED-test file
- 1-2 sentence cause summary from Phase 1
- hard constraint: "minimal fix, no surrounding cleanup, no opportunistic refactor"
implement runs the GREEN side and commits fix: <symptom>.
Phase 4.5 — 3+ failures = architecture question
If the third hypothesis fails:
STOP. Do not attempt Fix #4.
Three failed fixes in a row indicate the architecture is wrong, not that the latest hypothesis is wrong. Each fix likely revealed new shared state or coupling in a different place. Surface the architecture question to the user; do not guess again.
Handoff Contract
The carrier debug hands to implement:
| Field | Content |
|---|---|
red_test_path |
absolute path to the failing test file |
cause_summary |
1-2 sentences naming the file + function + why |
constraint |
"minimal fix, no surrounding cleanup" |
Anything else (broader refactor, doc rewrite, new feature) is OUT of scope for the bug-fix iteration and gets queued for a separate one.
Common Rationalisations
| Excuse | Reality |
|---|---|
| "The implementer says it's obviously the new tag-extract emit" | Confident guesses paper over real causes. Repro, isolate, RED test, then fix. |
| "Trivial bug, existing tests will catch it if we break something" | The existing tests already passed while this bug shipped — by definition they don't cover it. |
| "30 seconds to repro, 30 seconds to fix, skip the test" | 30 seconds to repro is also 30 seconds to capture as a regression fixture. Don't trade durable coverage for nothing. |
| "I'll write the test after the fix works" | Test-after proves nothing about whether the test would have caught the bug pre-fix. |
| "Two fixes failed; the third hypothesis is the right one" | Two failures means the hypothesis space is wrong. STOP, dispatch the debugger agent, do not guess again. |
| "End-of-day, just push the fix and write the test tomorrow" | End-of-day pressure is exactly when shotgun fixes corrupt the tree. Clean tree + open bug > three speculative half-fixes merged. |
| "Architecture question is for next milestone, fix-and-revisit" | Phase 4.5 fires NOW or never. Carrying the wrong shape forward compounds. |
Red Flags — STOP and follow process
- "Quick fix for now, investigate later"
- "Just try changing X and see if it works"
- "I see the symptom, let me fix it"
- "The test is redundant, I manually verified"
- "Pattern says X but I'll adapt it differently"
- "One more fix attempt" (when ≥ 2 already failed)
All of these mean: return to Phase 1.
Cross-references
- Upstream pattern:
superpowers:systematic-debugging— source of the four-phase Iron Law and Phase 4.5. - Agent dispatched:
skills/debug/agents/ailang-debugger.md— carries the actual diagnostic work (read CLAUDE.md / DESIGN.md / JOURNAL, reproduce, write RED test, propose minimal fix). - Hand-off target:
skills/implement/SKILL.md— runs the GREEN side after the RED test is committed. - Project source: former CLAUDE.md "Bug fixes — TDD, always" section is now superseded by this file; CLAUDE.md keeps a one-line pointer.