systematic-debugging
Structured debugging workflow adapted for Compiler Driven Development in scala-ts. Use when fixing bugs, investigating wrong generated TypeScript, macro failures, or test failures.
SKILL.md.claude/skills/systematic-debugging/SKILL.md
GitHubSystematic Debugging
<EXTREMELY-IMPORTANT> One hypothesis at a time, tested by ONE minimal change. Revert the change the moment the hypothesis misses. After 3 failed hypotheses, stop and re-read the problem. The compiler is the first diagnostic tool, not the last. </EXTREMELY-IMPORTANT>Phase 1: Read the actual signal
Confirm the stack underneath before debugging the generator:
- TS toolchain installed? The round-trip tests run
yarn ts-nodeintests/; a missingtests/node_modulesfails every suite's second property. Runcd tests && yarn. - Stale output?
tests/output/is deleted after each suite; a crash mid-run can leave files behind. They are gitignored and safe to delete. - Right scala-ts in the consumer? BondLink uses this checkout only when sbt runs with
SCALATS_DEVELOPMENT=1and the checkout is at~/scala-ts; otherwise it uses the published version.
Then read the compile-time signal:
- Run
sbt Test/compile— read compiler errors first. - Categorize:
| Category | First action |
|---|---|
| Scala type error / warning | Read the full message; -Werror makes warnings fatal |
Macro error at a parse[T] call site |
The error is reported at the call site but comes from TsParser; find which parse branch handles T |
| Golden mismatch | Read the munit diff; identify which renderer produced the differing line |
ts-node TSError in the round-trip |
The generated TS doesn't type-check; reproduce with the printed file under tests/output/ |
| Round-trip value mismatch | Compare the circe JSON (_tag discriminator) with what the codec decodes/encodes |
UnresolvedTsImportException |
A referenced type was never generated in any file passed to writeAll |
Phase 2: Find the difference
- Find a suite whose output for a similar shape is correct (
src/test/scala/scalats/tests/). - Trace the model: what
TsModeldoesparse[T]produce? (Print it from a scratch test.) - Trace the render: which of
generateCodecType/generateValueType/generateCodecInstance/generateValueInstanceemits the wrong text?
Phase 3: One hypothesis at a time
- Form a single hypothesis about the root cause.
- Make ONE minimal change to test it.
- Run the narrowest check (
sbt Test/compileorsbt 'testOnly scalats.tests.<Suite>'). - If it resolves the specific failure, proceed to Phase 4; if not, revert and form a new hypothesis.
- Log each hypothesis and result.
3+ failed hypotheses — STOP
Re-read the original failure, question the model, consider whether TsModel needs a new case rather than a patch in one renderer, and ask the human for context with your debugging log.
Phase 4: Fix at the root, then verify
- Can this bug become a type constraint? If an invalid state was representable in
TsModelor the generator, change the type so it isn't. - Pin the corrected output with a golden expectation.
- Lift the fix out and confirm the test fails again for the reason you claim — a fix never seen failing is unproven.
- Run full verification per
verification-before-completion.
Debugging log
Bug: [description]
Category: [compile / macro / golden / ts-node / round-trip / import resolution]
Hypothesis 1: [what you think is wrong]
Change: [what you changed]
Result: [what happened] ← REVERT if wrong
Anti-patterns
- "One more thing" after 3 failures — STOP.
- Editing the golden string to match wrong output.
- Adding a runtime check for something the model should rule out.
asInstanceOfor@nowarnto get past an error while debugging.- Declaring victory without lifting the fix out to confirm the failure returns.