feat: skill debug — RED-first bug diagnoser with Iron Law
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
---
|
||||
name: debug
|
||||
description: 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-bencher` or `ailang-architect`
|
||||
that 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
|
||||
|
||||
1. Read the error message in full. Stack traces, line numbers, exit
|
||||
codes — none of them get skimmed.
|
||||
2. Reproduce with the shortest possible command. If you can't repro
|
||||
reliably, gather more data — do NOT guess.
|
||||
3. Check recent changes: `git log --oneline -20`,
|
||||
`git diff <previous-iter-commit>..HEAD`.
|
||||
4. 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.
|
||||
5. Trace data flow backward from symptom to source. Fix at source,
|
||||
not at symptom.
|
||||
|
||||
### Phase 2 — Pattern Analysis
|
||||
|
||||
1. Find a similar working example in the codebase. What's different?
|
||||
2. If the bug is in a pattern (e.g. ADT match lowering, RC drop
|
||||
emission), read the reference implementation completely. No
|
||||
skimming.
|
||||
3. List every difference between working and broken — however small.
|
||||
|
||||
### Phase 3 — Hypothesis and Test
|
||||
|
||||
1. State the hypothesis as a single falsifiable claim:
|
||||
*"X is the root cause because Y."*
|
||||
2. **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
|
||||
3. Run it to confirm it fails on the current code, with the symptom
|
||||
the bug-report described.
|
||||
4. 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.
|
||||
Reference in New Issue
Block a user