superpowers-systematic-debugging restated debug's core (hypothesis before fix) across 296 lines, with a fix-implementation phase that belongs to tdd and a rationalization table that mostly repeats debug's own Anti-Patterns. Two things in it were not restatement, and both are now in debug: instrument every component boundary before hypothesising when a bug spans components, and treat three failed fixes as evidence the mechanism was never identified rather than as a reason to try a fourth. Not deleted with it, contrary to the issue: find-polluter.sh and the three technique files (root-cause-tracing, defense-in-depth, condition-based-waiting) are real assets, one of them an actual tool. They move to debug/references/ with a stated load trigger each, because a reference nobody knows when to open is dead weight. The pressure-test transcripts and CREATION-LOG from the upstream fork go; they document obra's process, not ours. Pressure-tested per #19, and the RED phase refuted the issue's premise. Two subagents without the skill got the multi-component scenario and the four-failed- fixes scenario; both already instrumented boundaries unprompted, and the second refused to write a fifth fix on its own initiative. So these sections are not correcting a demonstrated gap in this model — they are insurance for weaker models and for prompts less well-posed than a test scenario. Recorded as such rather than claimed as a fix. The GREEN run applied both sections correctly and then found four defects in them, all fixed here: the boundary rule read as suspending Iron Law 2's 3-5 hypotheses; the trigger said "reduced" when fixes that change nothing are the stronger signal; "stop proposing a fourth" read as licence to return no hypotheses at all; and the references had no load triggers. Its remaining findings are pre-existing and filed as #36. 41 skills. debug 146 -> 188 lines. Closes #14 Bump-Type: minor
8.8 KiB
name, surfaces, summary, use_when, description
| name | surfaces | summary | use_when | description | ||
|---|---|---|---|---|---|---|
| debug |
|
Hypothesis-first debugging discipline | Any test failure or bug whose cause is not immediately obvious | Systematic hypothesis-first debugging. Generate 3-5 ranked hypotheses with verification steps before suggesting any fix. Use when encountering a bug, test failure, or unexpected behavior. |
Debug
Overview
Debugging is hypothesis generation, not fix generation. The first instinct — "try this and see if it works" — wastes time and teaches nothing. A disciplined debug session produces a small set of ranked, falsifiable hypotheses, each with a concrete verification command. The fix comes after one hypothesis is confirmed, not before.
Core principle: A hypothesis you cannot verify in one command is not a hypothesis — it is a guess.
When to Use
- Any test failure whose cause is not immediately obvious
- Any production error or unexpected behavior
- Any bug report from a user or stakeholder
- Before reaching for a debugger or adding print statements: form hypotheses first
Do not use for:
- Compile errors with clear messages (just fix the typo)
- Already-diagnosed bugs where you know the cause (go straight to TDD with a regression test)
Iron Laws
- Never suggest "try X and see what happens." Every hypothesis must have a specific expected outcome if correct.
- Generate 3-5 hypotheses, ordered by likelihood (most likely first). Fewer than 3 means you stopped thinking; more than 5 means you are not prioritizing.
- Diagnose only — do not fix in this skill. The fix happens in a separate TDD cycle (load
tddskill) once a hypothesis is confirmed.
When the bug spans components
A system with boundaries (CI → build → signing, API → service → database) hides the question "which component is wrong?" behind the question "why is the output wrong?". Specify, as a pre-step, what to log entering and leaving each boundary, plus whether config and environment actually propagated. One run of that narrows the search to a single component.
This does not suspend Iron Law 2. Still emit 3-5 hypotheses — the boundary spec comes first in the output, and each hypothesis says which boundary observation would kill it. What the pre-step forbids is hypotheses that quietly assume a component is the faulty one before any evidence says so.
When several fixes have already failed
Three or more plausible fixes that failed to eliminate a symptom is not a run of bad luck — it is evidence that nobody has identified the mechanism. (Fixes that changed nothing at all count double: they are stronger evidence of a misidentified mechanism than fixes that partially worked.) Each one has been narrowing a window rather than closing it, and a fourth guess buys another tenth of the rate plus a layer of interacting guards nobody can reason about.
Say that explicitly — the refusal is the finding, not a preamble to it. Then aim the hypotheses one level up: the premise those fixes share is itself the thing to falsify. This is not an exemption from the output format. It is the format, pointed at the assumption instead of at the symptom.
Process
Step 1: Read the failure
Before forming hypotheses:
- Read the full error message and stack trace, not just the headline
- Read the file where the failure originated, around the failing line
- If the failure is from a test, read the test and the code under test
- Identify the failure mode — what actually went wrong (e.g. "nil pointer dereference in goroutine spawned by handler") not just what the error says ("runtime error")
Step 2: Generate hypotheses
For each hypothesis, capture three things:
- Mechanism: what specific code path or state would produce this exact failure
- Verification: the single command or file inspection that confirms or denies it
- Expected outcome if correct: the specific output you would see
Order by likelihood. The most likely cause is hypothesis 1.
Step 3: Output
Use this format:
HYPOTHESIS 1 (likelihood: high): <mechanism in one sentence>
VERIFY: <exact command or file:line to inspect>
EXPECTED IF CORRECT: <specific output, value, or condition>
HYPOTHESIS 2 (likelihood: medium): <mechanism>
VERIFY: <exact command>
EXPECTED IF CORRECT: <specific output>
[... up to 5 ...]
RECOMMENDED NEXT STEP: Run VERIFY for hypothesis 1 first.
End with the recommendation, not a fix.
Worked Example
Failure:
--- FAIL: TestInvoiceParser_HandlesEmptyPDF (0.00s)
parser_test.go:47: panic: runtime error: index out of range [0] with length 0
Output:
HYPOTHESIS 1 (likelihood: high): parser indexes into pages[0] before checking len(pages) > 0; empty PDFs produce a zero-page document
VERIFY: rg -n 'pages\[0\]' internal/parser/
EXPECTED IF CORRECT: at least one site reads pages[0] without a preceding length check
HYPOTHESIS 2 (likelihood: medium): the test fixture is a zero-byte file rather than a valid empty PDF; pdf library returns nil pages slice instead of empty
VERIFY: ls -la testdata/empty.pdf && file testdata/empty.pdf
EXPECTED IF CORRECT: file size 0 bytes or "data" rather than "PDF document"
HYPOTHESIS 3 (likelihood: low): a recently changed dependency reordered the page-extraction API; pages[0] now refers to metadata, not content
VERIFY: git log --oneline -10 -- go.sum | grep -i pdf
EXPECTED IF CORRECT: a pdf-library bump in the last few commits
RECOMMENDED NEXT STEP: Run VERIFY for hypothesis 1 first.
Anti-Patterns
| Anti-Pattern | Why It Fails |
|---|---|
| "Maybe try restarting the service" | Not a mechanism. Not falsifiable. Teaches nothing if it works. |
| "Could be a race condition" | Mechanism without specifics. Which two operations race? On what state? |
| "Let me add some print statements and see" | Skips hypothesis generation. Generates noise, not understanding. |
| Single hypothesis presented as fact | If you are sure, write the regression test, do not run a debug skill. |
| Mixing hypotheses with fix suggestions | The skill is diagnose-only. The fix is a TDD task on its hypothesis. |
Brain MCP Integration
The brain holds prior debug sessions across the project. Use it to skip rediscovering known failure modes.
At debug start:
- Run
brain_querywith the error message snippet + the package name. Past sessions may have logged identical or similar failures with their resolved hypotheses.
After a hypothesis is confirmed:
- Run
brain_writewith the failure signature → confirmed mechanism. Future debug sessions on the same area get the answer immediately.
Never:
- Run
brain_writefor an unconfirmed hypothesis. Speculation in the brain pollutes future queries.
Logging
Call session_log once at the end of every phase to record the outcome.
Pass-rate is computed downstream by the /pass-rate HTTP endpoint, which
treats pass as success, fail as failure, skip as neither.
At end of each phase:
session_logwith{skill: "debug", phase: "<phase-name>", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", duration_ms: <wall-clock>, project_root: "<absolute path>"}
Phases for this skill: read-failure, generate-hypotheses, output
Status semantics:
pass— the phase's intended outcome was reached.fail— the phase's intended outcome was NOT reached.skip— phase was skipped intentionally.
Why this matters: the routing pod (Plan 6) reads pass-rate to decide whether to route a future call to a local model. If your skill never logs, the routing pod sees no data.
Mode 2 Routing Note
This skill produces high-volume mechanical output (hypothesis enumeration) and is a candidate for Mode 2 routing to a local model in the future. Until Plan 6 ships the routing pod, treat as Mode 1 only. The hypothesis format and discipline are identical regardless of which model generates them.
Cross-References
- After a hypothesis is confirmed, load
tddskill — write a failing regression test that proves the bug exists, then fix it. - For test failures specifically caused by mock-vs-real divergence, also load
tdd/references/testing-anti-patterns.md. - Load
code-reviewskill if the diagnosis surfaces a structural issue (god object, shotgun surgery) rather than a pointwise bug.
Techniques kept in debug/references/, inherited from the deleted
superpowers-systematic-debugging (#14):
root-cause-tracing.md— load when the failure surfaces far from its trigger and the stack is the only thread back to it.defense-in-depth.md— load after a root cause is confirmed, to decide which layers should have caught it.condition-based-waiting.md— load whenever a timeout appears anywhere in the report, including as one of the fixes already tried. A bumped timeout is a symptom.find-polluter.sh— run when a test passes alone and fails in the suite, or when something in the tree appears that no test admits to creating.