wdi-method 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +203 -0
  3. package/bin/wdi-method.js +906 -0
  4. package/kit/.constitution/README.md +66 -0
  5. package/kit/.constitution/codebase/brownfield-guide.md +18 -0
  6. package/kit/.constitution/codebase/conventions-guide.md +18 -0
  7. package/kit/.constitution/codebase/stack-guide.md +18 -0
  8. package/kit/.constitution/constitution.md +135 -0
  9. package/kit/.constitution/document/architecture-guide.md +209 -0
  10. package/kit/.constitution/document/bmad-guide.md +151 -0
  11. package/kit/.constitution/document/bmad-skill-register.md +71 -0
  12. package/kit/.constitution/document/brief-guide.md +94 -0
  13. package/kit/.constitution/document/corpus-guide.md +341 -0
  14. package/kit/.constitution/document/decision-guide.md +134 -0
  15. package/kit/.constitution/document/delivery-flow-guide.md +338 -0
  16. package/kit/.constitution/document/prd-guide.md +202 -0
  17. package/kit/.constitution/document/sdd-guide.md +206 -0
  18. package/kit/.constitution/document/srs-guide.md +147 -0
  19. package/kit/.constitution/document/templates/addendum.md +71 -0
  20. package/kit/.constitution/document/templates/architecture-spine.md +100 -0
  21. package/kit/.constitution/document/templates/brief.md +110 -0
  22. package/kit/.constitution/document/templates/c4.md +77 -0
  23. package/kit/.constitution/document/templates/contract.md +52 -0
  24. package/kit/.constitution/document/templates/cross-cutting.md +80 -0
  25. package/kit/.constitution/document/templates/decision.md +64 -0
  26. package/kit/.constitution/document/templates/design-system.md +67 -0
  27. package/kit/.constitution/document/templates/flow.md +51 -0
  28. package/kit/.constitution/document/templates/integration.md +69 -0
  29. package/kit/.constitution/document/templates/inventory.md +69 -0
  30. package/kit/.constitution/document/templates/lc.md +47 -0
  31. package/kit/.constitution/document/templates/lifecycle.md +52 -0
  32. package/kit/.constitution/document/templates/meeting.md +47 -0
  33. package/kit/.constitution/document/templates/model.md +55 -0
  34. package/kit/.constitution/document/templates/oq.md +45 -0
  35. package/kit/.constitution/document/templates/prd.md +226 -0
  36. package/kit/.constitution/document/templates/questions.md +75 -0
  37. package/kit/.constitution/document/templates/rtr.md +61 -0
  38. package/kit/.constitution/document/templates/rules.md +47 -0
  39. package/kit/.constitution/document/templates/scn.md +40 -0
  40. package/kit/.constitution/document/templates/sdd.md +146 -0
  41. package/kit/.constitution/document/templates/srs.md +149 -0
  42. package/kit/.constitution/document/templates/structure-codebase.md +129 -0
  43. package/kit/.constitution/document/templates/structure-document.md +90 -0
  44. package/kit/.constitution/document/templates/uc.md +69 -0
  45. package/kit/.constitution/document/templates/ux.md +77 -0
  46. package/kit/.constitution/document/ux-guide.md +99 -0
  47. package/kit/.constitution/language-guide.md +69 -0
  48. package/kit/.constitution/method/README.md +169 -0
  49. package/kit/.constitution/method/artifact-map.md +155 -0
  50. package/kit/.constitution/method/portability.md +91 -0
  51. package/kit/.constitution/method/rationale.md +246 -0
  52. package/kit/.constitution/method-glossary.md +169 -0
  53. package/kit/.constitution/project/README.md +68 -0
  54. package/kit/.constitution/repo-guide.md +97 -0
  55. package/kit/.constitution/scripts/inventory.py +527 -0
  56. package/kit/.constitution/scripts/timeline.py +665 -0
  57. package/kit/.constitution/scripts/validate.py +1579 -0
  58. package/kit/.constitution/structure-guide.md +202 -0
  59. package/kit/assets/bmad-custom/bmad-advanced-elicitation.toml +15 -0
  60. package/kit/assets/bmad-custom/bmad-architecture.toml +31 -0
  61. package/kit/assets/bmad-custom/bmad-build-auto.toml +10 -0
  62. package/kit/assets/bmad-custom/bmad-build.toml +52 -0
  63. package/kit/assets/bmad-custom/bmad-code-review.toml +12 -0
  64. package/kit/assets/bmad-custom/bmad-correct-course.toml +27 -0
  65. package/kit/assets/bmad-custom/bmad-deep-recon.toml +11 -0
  66. package/kit/assets/bmad-custom/bmad-prd.toml +42 -0
  67. package/kit/assets/bmad-custom/bmad-product-brief.toml +34 -0
  68. package/kit/assets/bmad-custom/bmad-retrospective.toml +9 -0
  69. package/kit/assets/bmad-custom/bmad-spec.toml +15 -0
  70. package/kit/assets/bmad-custom/bmad-ux.toml +19 -0
  71. package/kit/assets/bmad-custom/config.toml +6 -0
  72. package/kit/skills/wdi-blueprint/SKILL.md +203 -0
  73. package/kit/skills/wdi-build/SKILL.md +244 -0
  74. package/kit/skills/wdi-component/SKILL.md +172 -0
  75. package/kit/skills/wdi-decision/.gitkeep +0 -0
  76. package/kit/skills/wdi-decision/SKILL.md +183 -0
  77. package/kit/skills/wdi-help/.gitkeep +0 -0
  78. package/kit/skills/wdi-help/SKILL.md +98 -0
  79. package/kit/skills/wdi-init/SKILL.md +149 -0
  80. package/kit/skills/wdi-log/SKILL.md +107 -0
  81. package/kit/skills/wdi-problem/SKILL.md +90 -0
  82. package/kit/skills/wdi-product/SKILL.md +127 -0
  83. package/kit/skills/wdi-question/.gitkeep +0 -0
  84. package/kit/skills/wdi-question/SKILL.md +88 -0
  85. package/kit/skills/wdi-reconcile/.gitkeep +0 -0
  86. package/kit/skills/wdi-reconcile/SKILL.md +146 -0
  87. package/kit/skills/wdi-report/SKILL.md +205 -0
  88. package/kit/skills/wdi-review/SKILL.md +118 -0
  89. package/kit/skills/wdi-systematic-debugging/SKILL.md +193 -0
  90. package/kit/skills/wdi-systematic-debugging/references/condition-based-waiting.md +115 -0
  91. package/kit/skills/wdi-systematic-debugging/references/defense-in-depth.md +122 -0
  92. package/kit/skills/wdi-systematic-debugging/references/find-polluter.sh +72 -0
  93. package/kit/skills/wdi-systematic-debugging/references/root-cause-tracing.md +169 -0
  94. package/kit/skills/wdi-ux/SKILL.md +145 -0
  95. package/kit-overlay/AGENTS.md +196 -0
  96. package/kit-overlay/README.md +66 -0
  97. package/kit-overlay/constitution.md +135 -0
  98. package/kit-overlay/portability.md +91 -0
  99. package/kit-overlay/repo-guide.md +97 -0
  100. package/lib/agents-block.mjs +52 -0
  101. package/lib/identity.mjs +65 -0
  102. package/package.json +37 -0
  103. package/scaffold/.control/decisions/.gitkeep +0 -0
  104. package/scaffold/.control/generated/.gitkeep +2 -0
  105. package/scaffold/.control/meetings/.gitkeep +0 -0
  106. package/scaffold/.control/memlog/.gitkeep +0 -0
  107. package/scaffold/.control/product-glossary.md +21 -0
  108. package/scaffold/.control/project-non-technical-log.md +23 -0
  109. package/scaffold/.control/questions/answered.md +11 -0
  110. package/scaffold/.control/questions/assumptions.md +15 -0
  111. package/scaffold/.control/questions/blocking.md +21 -0
  112. package/scaffold/.control/questions/external.md +11 -0
  113. package/scaffold/.control/registry/components.yaml +21 -0
  114. package/scaffold/.control/registry/decisions.yaml +6 -0
  115. package/scaffold/.control/registry/defects.yaml +3 -0
  116. package/scaffold/.control/registry/index.yaml +46 -0
  117. package/scaffold/.control/registry/requirements.yaml +15 -0
  118. package/scaffold/.control/registry/risks.yaml +5 -0
  119. package/scaffold/.control/registry/usecases.yaml +6 -0
  120. package/scaffold/.control/registry/waves.yaml +5 -0
  121. package/scaffold/.control/structure-codebase.md +19 -0
  122. package/scaffold/.control/structure-document.md +25 -0
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: wdi-review
3
+ description: Use to review any corpus document at any time, and always before a gate on the four artifacts no doc_standards covers — the architecture spine, SRS, SDD, and SPEC. Reads the lens set from the component's risk_accepted, dispatches bmad-review, and stamps the V13 trace on those four only. Not for code review.
4
+ ---
5
+
6
+ # WDI Review
7
+
8
+ Five BMad skills review their own output through `doc_standards`. Four artifacts have no such trigger,
9
+ and they are the most binding ones in the corpus. This skill covers exactly those four.
10
+
11
+ It exists for two reasons `bmad-review` cannot serve on its own: the lens set is not a property of the
12
+ artifact but of the component's `risk_accepted`, and defaulting to structure + prose silently drops the
13
+ one lens that matters for behaviour; and `bmad-review` is class D — it writes nothing, so nothing proves
14
+ it ran.
15
+
16
+ You MUST NOT use this for code or diffs. That is `bmad-code-review` and the two-family Review Panel.
17
+
18
+ ## What it covers
19
+
20
+ | Artifact | Trace lands in |
21
+ |---|---|
22
+ | `.how/_platform/ARCHITECTURE-SPINE.md` | `reviewed:` in its frontmatter |
23
+ | `SRS-<pc>.md` + slots `02`–`05` | `reviewed:` in the SRS frontmatter |
24
+ | `SDD-<pc>.md` + slots `01`–`06` | `reviewed:` in the SDD frontmatter |
25
+ | `SPEC.md` | `spec_reviewed:` on the wave in `waves.yaml` |
26
+
27
+ **The lens set comes from the component's `risk_accepted`, never from `mode` and never from the artifact
28
+ type.** `delivery-flow-guide.md` owns the mapping and it MUST NOT be restated as a second copy here; what
29
+ this skill owns is reading it and refusing to run a lighter set than it names.
30
+
31
+ | `risk_accepted` | Lenses | And on the code |
32
+ |---|---|---|
33
+ | `low` | structure · prose · **edge-case-hunter** | a two-reviewer panel is required |
34
+ | `medium` | structure · prose · **edge-case-hunter** | — |
35
+ | `high` | structure · prose | — |
36
+
37
+ `SPEC.md` always carries `edge-case-hunter`: it is the contract a builder works from, and a branch missed
38
+ there surfaces as a bug at G5 instead.
39
+
40
+ **V13 stamps only components at `risk_accepted` `low` or `medium`.** At `high` the owner has already said
41
+ they accept the risk, and demanding the trace there is bookkeeping with no buyer.
42
+
43
+ SPEC keeps its trace in the registry because `bmad-spec` is its sole author and overwrites hand
44
+ edits. A trace written into `SPEC.md` disappears on the next run.
45
+
46
+ **Anything in the corpus MAY be reviewed here, at any time** — a `DEC-`, minutes, an `OQ-`, a guide, a
47
+ brief, a PRD, a `DESIGN.md`. What is restricted is the **stamp**, not the reading: only the four rows
48
+ above have a trace V13 reads, and only they MAY be stamped.
49
+
50
+ The five artifacts carrying `doc_standards` review themselves at finalize, so a review here is never
51
+ required for them. Asking for one anyway is legitimate — after hand edits, before a gate, when a
52
+ finding is suspected — and it MUST NOT leave a `reviewed:` block behind. A second trace on an
53
+ artifact whose first review is automatic implies that first one was optional.
54
+
55
+ ## Step 1 — Read the lens set off the component
56
+
57
+ Find the artifact's component, read its `risk_accepted` from `components.yaml`, and state the lens set in
58
+ one line before dispatching. Do not ask the user which lenses to run — the field decides, and it is the
59
+ owner's field.
60
+
61
+ For an artifact with no component — a guide, minutes, the spine — use structure · prose.
62
+
63
+ The adversarial lens is in no table. It MAY be added when the artifact touches money, personal data, or a
64
+ third-party integration. It demands at least ten concrete findings and treats an empty result as a signal
65
+ to re-check, so adding it to a routine review buys noise.
66
+
67
+ ## Step 2 — Dispatch
68
+
69
+ Invoke `bmad-review` with the artifact path and the chosen lenses. Slots are part of the artifact:
70
+ reviewing `SRS-<pc>.md` without `04-usecases/` and `05-scenarios/` reviews the kernel and misses
71
+ where the branches live.
72
+
73
+ ## Step 3 — Resolve before stamping
74
+
75
+ Findings MUST be resolved or explicitly deferred before the trace is written. A deferred finding
76
+ MUST be filed through `wdi-question`, or opened as a `DEC-` through `wdi-decision` — never a note in the
77
+ chat that dies with the session.
78
+
79
+ You MUST NOT stamp an artifact whose findings are still open. A trace on unresolved findings is
80
+ worse than no trace: V13 goes green and the gate opens on a review nobody acted on.
81
+
82
+ ## Step 4 — Stamp
83
+
84
+ Write the trace, and nothing else:
85
+
86
+ ```yaml
87
+ reviewed:
88
+ date: '<YYYY-MM-DD>'
89
+ sha: '<commit sha at review time>'
90
+ lenses: [structure, prose, edge-case-hunter]
91
+ ```
92
+
93
+ - `sha` MUST be the commit the artifact was reviewed at. Without it staleness cannot be measured, only
94
+ felt — the same reason a structure map requires one. V13 no longer reads the stamping commit itself as
95
+ a change, so a fresh stamp does not make its own review look stale.
96
+ - You MUST NOT write the trace unless `bmad-review` actually ran in this session. Filling it as a
97
+ formality turns V13 into a rubber stamp, which is worse than having no validator.
98
+ - You MUST NOT touch `status:` while stamping. `status: reviewed` states a **stage**; the `reviewed:`
99
+ block states an **event**. Raising the status is a separate act.
100
+ - You MUST NOT edit the artifact's content. Fixing a finding is the author's act, not the
101
+ reviewer's — say what is wrong and stop.
102
+
103
+ ## Rules
104
+
105
+ - You MUST NOT stamp anything outside the four rows in the table. Brief, PRD, `DESIGN.md`,
106
+ `EXPERIENCE.md`, and research MAY be reviewed on request; the finding report is the whole output,
107
+ and no `reviewed:` block is written.
108
+ - You MUST NOT stamp on behalf of a review someone else ran earlier. Re-run it; the run is cheap and
109
+ the claim is not.
110
+ - When the artifact changed after the review, the trace is stale by definition. You MUST re-run
111
+ rather than bump the date.
112
+ - When findings reveal the requirement itself is wrong rather than the writing, this stops being a
113
+ review. Route to `wdi-decision`, and let the `DEC-` change the artifact.
114
+
115
+ ## Output
116
+
117
+ One short report: artifact, lenses run, findings by severity, what was resolved, what was deferred
118
+ and where it landed, and whether the trace was written — with the reason when it was not.
@@ -0,0 +1,193 @@
1
+ ---
2
+ name: wdi-systematic-debugging
3
+ description: Use when encountering any bug, test failure, build failure, or unexpected behavior, before proposing any fix. Covers root-cause investigation, hypothesis testing, and the escalation rule when repeated fixes fail.
4
+ ---
5
+
6
+ # Systematic Debugging
7
+
8
+ BMad has no debugging workflow — `bmad-build` assumes you already know what to build. This skill
9
+ covers the gap: something is broken and the cause is unknown.
10
+
11
+ ## The Iron Law
12
+
13
+ **NO FIX WITHOUT ROOT CAUSE INVESTIGATION FIRST.**
14
+
15
+ You MUST complete Phase 1 before proposing any fix. A fix that treats a symptom is a failure even
16
+ when the symptom disappears.
17
+
18
+ ## When to use
19
+
20
+ Use for any technical issue: test failure, production bug, unexpected behavior, performance problem,
21
+ build failure, integration failure.
22
+
23
+ **Its home is G5, but its trigger is not positional.** Most invocations happen while coding, and
24
+ that is why `AGENTS.md`, `bmad-build`, and the ship-story orchestration carry this rule in their
25
+ worker context. What actually fires it is a pair of conditions — *something is broken* **and** *the
26
+ cause is unknown* — so it MAY be invoked in any stage, by anyone, to investigate: a red
27
+ `validate.py` while writing an SRS at G3, a `wdi-reconcile` report that makes no sense, a
28
+ generator producing an empty timeline. None of those wait for G5, and none of them are a
29
+ misuse of this skill.
30
+
31
+ The second condition matters as much as the first. A test failing because of a missing import is not
32
+ a trigger — you already know the cause. A test failing for a reason you cannot name is.
33
+
34
+ Nor does the finding always land in code: three of the four `root_cause` categories send it back to
35
+ `.what/` or `.how/` — see [Where the finding lands](#where-the-finding-lands).
36
+
37
+ Use it *especially* when the pressure argues against it — an emergency, an "obvious one-line fix", a
38
+ previous fix that did not hold. Systematic debugging is faster than guess-and-check, and the
39
+ pressure cases are exactly where guessing costs the most.
40
+
41
+ A simple-looking bug has a root cause too. You MUST NOT skip phases because the issue looks small.
42
+
43
+ ## Phase 1 — Root cause investigation
44
+
45
+ 1. **Read the error completely.** Full stack trace, line numbers, file paths, error codes. The
46
+ answer is often already in it.
47
+ 2. **Reproduce it.** Exact steps, every time. If it is not reproducible, gather more data — you
48
+ MUST NOT proceed on a guess.
49
+ 3. **Check what changed.** `git diff`, recent commits, new dependencies, config, environment.
50
+ 4. **Instrument the boundaries.** When more than one component is involved (browser → API →
51
+ service → database; CI → build → deploy), add logging at *each* boundary before proposing
52
+ anything: what enters, what exits, what config propagated. Run once to find *which* boundary
53
+ fails, then investigate only that one.
54
+ 5. **Trace the bad value backward** to where it originates — see
55
+ [references/root-cause-tracing.md](references/root-cause-tracing.md). Fix at the source, never
56
+ where the symptom surfaced.
57
+
58
+ ## Phase 2 — Pattern analysis
59
+
60
+ 1. Find working code in this repo that does the same kind of thing.
61
+ 2. Read the reference implementation **completely**. Skimming produces partial understanding, and
62
+ partial understanding produces the next bug.
63
+ 3. List every difference between working and broken, however small. You MUST NOT dismiss a
64
+ difference as irrelevant before testing it.
65
+ 4. Name the dependencies the broken path assumes: config, environment, state, ordering.
66
+
67
+ ## Phase 3 — Hypothesis
68
+
69
+ 1. State one hypothesis in writing: "X is the root cause because Y."
70
+ 2. Test it with the **smallest possible change**. One variable at a time.
71
+ 3. Worked → Phase 4. Did not work → form a *new* hypothesis. You MUST NOT stack a second fix on
72
+ top of a failed one.
73
+ 4. If you do not understand something, say so plainly and investigate further. Pretending to know
74
+ is what produces fix #4.
75
+
76
+ ## Phase 4 — Implementation
77
+
78
+ 1. **Write the failing test first.** Simplest reproduction that fails for the right reason. Run it
79
+ and confirm it fails before writing any fix. A fix without a test that failed first does not
80
+ stick and cannot be proven.
81
+ 2. **One fix, addressing the root cause.** No bundled refactoring, no "while I'm here" improvements.
82
+ 3. **Verify with evidence.** The test passes, no other test broke, and you have the command output
83
+ to show it. Claiming success without the output is prohibited.
84
+ 4. **If the fix fails, count your attempts.**
85
+ - Fewer than 3 → return to Phase 1 with what you now know.
86
+ - **3 or more → STOP. Do not attempt fix #4.**
87
+
88
+ ## Phase 5 — Record the finding
89
+
90
+ An investigation that ends in chat has to be repeated. Before you close out — whether the fix landed
91
+ or the three-fix rule stopped you — record what you found in `.control/registry/defects.yaml`. This
92
+ is the only registry write this skill owns, and it exists so one question can be answered with a
93
+ number rather than a memory: **how many of our defects turned out to be a wrong requirement rather
94
+ than wrong code.**
95
+
96
+ ### Find the row, or open one
97
+
98
+ A defect found by a tester is usually already there, carrying only `id`, `title`, `found_in`, and
99
+ `reported`. Anyone MAY open such a row without running any skill, and an empty `root_cause` is a
100
+ legitimate state meaning *not yet diagnosed* — you are the step that fills it, not the step that
101
+ guards it. When no row exists, open one yourself, taking the next number in the `BUG-` sequence
102
+ (`HOT-` when it arrived through the hotfix path).
103
+
104
+ You MUST NOT require a row to exist before investigating. A defect nobody recorded is still a
105
+ defect; refusing to work until the paperwork is right is how the file becomes a formality.
106
+
107
+ ### Write three fields, and only these three
108
+
109
+ | Field | Where it comes from |
110
+ |---|---|
111
+ | `root_cause` | Your Phase 1–3 conclusion, in the four categories of the table above |
112
+ | `violates` | The `FR-` or `UC-` the defect breaks. MUST be filled for `requirement` and `architecture`: a defect that violates something without naming what cannot be traced back to the corpus, and V20 fails it |
113
+ | `fix` | `fastpath` for a pure code defect, the wave id otherwise. The table above already decides this — `requirement` and `architecture` MUST NOT be `fastpath` |
114
+
115
+ You MUST NOT write a closure date. It is derived from the history of `defects.yaml` itself by
116
+ `.constitution/scripts/timeline.py`, and a hand-written date would be a second home for one fact —
117
+ the stored copy being the one that goes wrong.
118
+
119
+ You MUST NOT edit `title`, `found_in`, or `reported`. Whoever opened the row owns those; correcting
120
+ them silently erases what was actually reported.
121
+
122
+ ### `status: fixed` follows the route, not the merge
123
+
124
+ The moment the patch merges is not the moment the defect closes. What closes it depends on the row's
125
+ own `root_cause`:
126
+
127
+ | `root_cause` | Closes when |
128
+ |---|---|
129
+ | `code` | The failing test from Phase 4 passes and the fix is merged |
130
+ | `requirement` | The `FR-` or `UC-` in `violates` has actually changed — the code alone MUST NOT close it |
131
+ | `architecture` | The `DEC-` is `accepted` and named on the row. V20 fails a `fixed` row without one |
132
+ | `environment` | The handling is in place and the monitoring exists |
133
+
134
+ Setting `fixed` before its row's condition is met is the failure this whole registry exists to
135
+ prevent: it turns "we patched the code" into "the requirement was right all along".
136
+
137
+ ### When the three-fix rule stopped you
138
+
139
+ You MUST still record. The rule triggering *is* the finding — `root_cause: architecture`, `violates`
140
+ naming what the architecture broke, and the row left `open` until the `DEC-` exists. An escalation that
141
+ leaves no trace looks identical to a bug that was never investigated.
142
+
143
+ ## The three-fix rule
144
+
145
+ Three failed fixes is not three failed hypotheses — it is a signal that the architecture is wrong.
146
+
147
+ Symptoms: each fix uncovers new coupling or shared state somewhere else; each fix needs "a bit of
148
+ refactoring" to land; each fix creates a new symptom elsewhere.
149
+
150
+ When this happens you MUST stop and raise it with the owner rather than continue. In this project's
151
+ terms, the outcome is a `DEC-` — a correction of course is one too — not another patch.
152
+
153
+ ## Where the finding lands
154
+
155
+ | What the root cause turns out to be | Where it goes |
156
+ |---|---|
157
+ | Genuine code defect, no artifact was wrong | Fast Path — fix, test, done |
158
+ | A requirement was missing or wrong | Back to the Impact Matrix in `.constitution/document/delivery-flow-guide.md`; the FR/UC changes before the code does |
159
+ | An architecture decision was wrong | `wdi-decision`, which wraps `bmad-correct-course`; MUST NOT be absorbed as a code patch |
160
+ | Environmental, timing-dependent, or external | Document the investigation, implement handling (retry, timeout, clear error), add monitoring |
161
+
162
+ These four categories are exactly the values of `root_cause` in `.control/registry/defects.yaml`.
163
+ Phase 5 is where your verdict lands in that file.
164
+
165
+ The last row is real but rare. Most "no root cause" conclusions are incomplete investigations —
166
+ treat that verdict with suspicion in yourself.
167
+
168
+ ## Red flags — stop and return to Phase 1
169
+
170
+ If you catch yourself thinking any of these, the process has already broken down:
171
+
172
+ | Thought | Reality |
173
+ |---|---|
174
+ | "Quick fix now, investigate later" | The first fix sets the pattern. There is no later. |
175
+ | "Just change X and see if it works" | That is guessing with extra steps. |
176
+ | "It's probably X, let me fix that" | Seeing a symptom is not understanding a cause. |
177
+ | "I'll skip the test and verify manually" | Untested fixes regress silently. |
178
+ | "Several changes at once saves time" | You will not know which one worked, or what else broke. |
179
+ | "The reference is long, I'll adapt the pattern" | Partial reading guarantees the next bug. |
180
+ | "One more fix attempt" (after 2+) | Three failures means the architecture is the problem. |
181
+ | "Emergency — no time for process" | Thrashing is slower. Always. |
182
+
183
+ Signals from the owner that mean the same thing: *"stop guessing"*, *"is that actually
184
+ happening?"*, *"will that show us anything?"*, *"we're stuck?"*
185
+
186
+ ## Supporting techniques
187
+
188
+ | File | Use when |
189
+ |---|---|
190
+ | [references/root-cause-tracing.md](references/root-cause-tracing.md) | The error surfaces deep in a call stack and you must trace backward to the origin |
191
+ | [references/defense-in-depth.md](references/defense-in-depth.md) | Root cause is found and you are deciding where validation belongs |
192
+ | [references/condition-based-waiting.md](references/condition-based-waiting.md) | Flaky timing, arbitrary sleeps, race conditions |
193
+ | [references/find-polluter.sh](references/find-polluter.sh) | A test passes alone but fails in the suite — bisects to find the polluting test |
@@ -0,0 +1,115 @@
1
+ # Condition-Based Waiting
2
+
3
+ ## Overview
4
+
5
+ Flaky tests often guess at timing with arbitrary delays. This creates race conditions where tests pass on fast machines but fail under load or in CI.
6
+
7
+ **Core principle:** Wait for the actual condition you care about, not a guess about how long it takes.
8
+
9
+ ## When to Use
10
+
11
+ ```dot
12
+ digraph when_to_use {
13
+ "Test uses setTimeout/sleep?" [shape=diamond];
14
+ "Testing timing behavior?" [shape=diamond];
15
+ "Document WHY timeout needed" [shape=box];
16
+ "Use condition-based waiting" [shape=box];
17
+
18
+ "Test uses setTimeout/sleep?" -> "Testing timing behavior?" [label="yes"];
19
+ "Testing timing behavior?" -> "Document WHY timeout needed" [label="yes"];
20
+ "Testing timing behavior?" -> "Use condition-based waiting" [label="no"];
21
+ }
22
+ ```
23
+
24
+ **Use when:**
25
+ - Tests have arbitrary delays (`setTimeout`, `sleep`, `time.sleep()`)
26
+ - Tests are flaky (pass sometimes, fail under load)
27
+ - Tests timeout when run in parallel
28
+ - Waiting for async operations to complete
29
+
30
+ **Don't use when:**
31
+ - Testing actual timing behavior (debounce, throttle intervals)
32
+ - Always document WHY if using arbitrary timeout
33
+
34
+ ## Core Pattern
35
+
36
+ ```typescript
37
+ // ❌ BEFORE: Guessing at timing
38
+ await new Promise(r => setTimeout(r, 50));
39
+ const result = getResult();
40
+ expect(result).toBeDefined();
41
+
42
+ // ✅ AFTER: Waiting for condition
43
+ await waitFor(() => getResult() !== undefined);
44
+ const result = getResult();
45
+ expect(result).toBeDefined();
46
+ ```
47
+
48
+ ## Quick Patterns
49
+
50
+ | Scenario | Pattern |
51
+ |----------|---------|
52
+ | Wait for event | `waitFor(() => events.find(e => e.type === 'DONE'))` |
53
+ | Wait for state | `waitFor(() => machine.state === 'ready')` |
54
+ | Wait for count | `waitFor(() => items.length >= 5)` |
55
+ | Wait for file | `waitFor(() => fs.existsSync(path))` |
56
+ | Complex condition | `waitFor(() => obj.ready && obj.value > 10)` |
57
+
58
+ ## Implementation
59
+
60
+ Generic polling function:
61
+ ```typescript
62
+ async function waitFor<T>(
63
+ condition: () => T | undefined | null | false,
64
+ description: string,
65
+ timeoutMs = 5000
66
+ ): Promise<T> {
67
+ const startTime = Date.now();
68
+
69
+ while (true) {
70
+ const result = condition();
71
+ if (result) return result;
72
+
73
+ if (Date.now() - startTime > timeoutMs) {
74
+ throw new Error(`Timeout waiting for ${description} after ${timeoutMs}ms`);
75
+ }
76
+
77
+ await new Promise(r => setTimeout(r, 10)); // Poll every 10ms
78
+ }
79
+ }
80
+ ```
81
+
82
+ See `condition-based-waiting-example.ts` in this directory for complete implementation with domain-specific helpers (`waitForEvent`, `waitForEventCount`, `waitForEventMatch`) from actual debugging session.
83
+
84
+ ## Common Mistakes
85
+
86
+ **❌ Polling too fast:** `setTimeout(check, 1)` - wastes CPU
87
+ **✅ Fix:** Poll every 10ms
88
+
89
+ **❌ No timeout:** Loop forever if condition never met
90
+ **✅ Fix:** Always include timeout with clear error
91
+
92
+ **❌ Stale data:** Cache state before loop
93
+ **✅ Fix:** Call getter inside loop for fresh data
94
+
95
+ ## When Arbitrary Timeout IS Correct
96
+
97
+ ```typescript
98
+ // Tool ticks every 100ms - need 2 ticks to verify partial output
99
+ await waitForEvent(manager, 'TOOL_STARTED'); // First: wait for condition
100
+ await new Promise(r => setTimeout(r, 200)); // Then: wait for timed behavior
101
+ // 200ms = 2 ticks at 100ms intervals - documented and justified
102
+ ```
103
+
104
+ **Requirements:**
105
+ 1. First wait for triggering condition
106
+ 2. Based on known timing (not guessing)
107
+ 3. Comment explaining WHY
108
+
109
+ ## Real-World Impact
110
+
111
+ From debugging session (2025-10-03):
112
+ - Fixed 15 flaky tests across 3 files
113
+ - Pass rate: 60% → 100%
114
+ - Execution time: 40% faster
115
+ - No more race conditions
@@ -0,0 +1,122 @@
1
+ # Defense-in-Depth Validation
2
+
3
+ ## Overview
4
+
5
+ When you fix a bug caused by invalid data, adding validation at one place feels sufficient. But that single check can be bypassed by different code paths, refactoring, or mocks.
6
+
7
+ **Core principle:** Validate at EVERY layer data passes through. Make the bug structurally impossible.
8
+
9
+ ## Why Multiple Layers
10
+
11
+ Single validation: "We fixed the bug"
12
+ Multiple layers: "We made the bug impossible"
13
+
14
+ Different layers catch different cases:
15
+ - Entry validation catches most bugs
16
+ - Business logic catches edge cases
17
+ - Environment guards prevent context-specific dangers
18
+ - Debug logging helps when other layers fail
19
+
20
+ ## The Four Layers
21
+
22
+ ### Layer 1: Entry Point Validation
23
+ **Purpose:** Reject obviously invalid input at API boundary
24
+
25
+ ```typescript
26
+ function createProject(name: string, workingDirectory: string) {
27
+ if (!workingDirectory || workingDirectory.trim() === '') {
28
+ throw new Error('workingDirectory cannot be empty');
29
+ }
30
+ if (!existsSync(workingDirectory)) {
31
+ throw new Error(`workingDirectory does not exist: ${workingDirectory}`);
32
+ }
33
+ if (!statSync(workingDirectory).isDirectory()) {
34
+ throw new Error(`workingDirectory is not a directory: ${workingDirectory}`);
35
+ }
36
+ // ... proceed
37
+ }
38
+ ```
39
+
40
+ ### Layer 2: Business Logic Validation
41
+ **Purpose:** Ensure data makes sense for this operation
42
+
43
+ ```typescript
44
+ function initializeWorkspace(projectDir: string, sessionId: string) {
45
+ if (!projectDir) {
46
+ throw new Error('projectDir required for workspace initialization');
47
+ }
48
+ // ... proceed
49
+ }
50
+ ```
51
+
52
+ ### Layer 3: Environment Guards
53
+ **Purpose:** Prevent dangerous operations in specific contexts
54
+
55
+ ```typescript
56
+ async function gitInit(directory: string) {
57
+ // In tests, refuse git init outside temp directories
58
+ if (process.env.NODE_ENV === 'test') {
59
+ const normalized = normalize(resolve(directory));
60
+ const tmpDir = normalize(resolve(tmpdir()));
61
+
62
+ if (!normalized.startsWith(tmpDir)) {
63
+ throw new Error(
64
+ `Refusing git init outside temp dir during tests: ${directory}`
65
+ );
66
+ }
67
+ }
68
+ // ... proceed
69
+ }
70
+ ```
71
+
72
+ ### Layer 4: Debug Instrumentation
73
+ **Purpose:** Capture context for forensics
74
+
75
+ ```typescript
76
+ async function gitInit(directory: string) {
77
+ const stack = new Error().stack;
78
+ logger.debug('About to git init', {
79
+ directory,
80
+ cwd: process.cwd(),
81
+ stack,
82
+ });
83
+ // ... proceed
84
+ }
85
+ ```
86
+
87
+ ## Applying the Pattern
88
+
89
+ When you find a bug:
90
+
91
+ 1. **Trace the data flow** - Where does bad value originate? Where used?
92
+ 2. **Map all checkpoints** - List every point data passes through
93
+ 3. **Add validation at each layer** - Entry, business, environment, debug
94
+ 4. **Test each layer** - Try to bypass layer 1, verify layer 2 catches it
95
+
96
+ ## Example from Session
97
+
98
+ Bug: Empty `projectDir` caused `git init` in source code
99
+
100
+ **Data flow:**
101
+ 1. Test setup → empty string
102
+ 2. `Project.create(name, '')`
103
+ 3. `WorkspaceManager.createWorkspace('')`
104
+ 4. `git init` runs in `process.cwd()`
105
+
106
+ **Four layers added:**
107
+ - Layer 1: `Project.create()` validates not empty/exists/writable
108
+ - Layer 2: `WorkspaceManager` validates projectDir not empty
109
+ - Layer 3: `WorktreeManager` refuses git init outside tmpdir in tests
110
+ - Layer 4: Stack trace logging before git init
111
+
112
+ **Result:** All 1847 tests passed, bug impossible to reproduce
113
+
114
+ ## Key Insight
115
+
116
+ All four layers were necessary. During testing, each layer caught bugs the others missed:
117
+ - Different code paths bypassed entry validation
118
+ - Mocks bypassed business logic checks
119
+ - Edge cases on different platforms needed environment guards
120
+ - Debug logging identified structural misuse
121
+
122
+ **Don't stop at one validation point.** Add checks at every layer.
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bash
2
+ # Bisection script to find which test creates unwanted files/state
3
+ # Usage: ./find-polluter.sh <file_or_dir_to_check> <test_pattern>
4
+ # Example: ./find-polluter.sh '.git' 'src/**/*.test.ts'
5
+
6
+ set -e
7
+
8
+ if [ $# -ne 2 ]; then
9
+ echo "Usage: $0 <file_to_check> <test_pattern>"
10
+ echo "Example: $0 '.git' 'src/**/*.test.ts'"
11
+ exit 1
12
+ fi
13
+
14
+ POLLUTION_CHECK="$1"
15
+ TEST_PATTERN="$2"
16
+
17
+ echo "🔍 Searching for test that creates: $POLLUTION_CHECK"
18
+ echo "Test pattern: $TEST_PATTERN"
19
+ echo ""
20
+
21
+ # Get list of test files (find . emits ./-prefixed paths, so accept the
22
+ # pattern written with or without a leading ./)
23
+ TEST_PATTERN="${TEST_PATTERN#./}"
24
+ # find -path can't match '**/' against zero directory levels, so a pattern
25
+ # like src/**/*.test.ts would skip src/top.test.ts; also try the pattern
26
+ # with '**/' collapsed to cover files directly under the base directory.
27
+ TEST_FILES=$(find . \( -path "./$TEST_PATTERN" -o -path "./${TEST_PATTERN//\*\*\//}" \) | sort -u)
28
+ if [ -z "$TEST_FILES" ]; then
29
+ TOTAL=0
30
+ else
31
+ TOTAL=$(printf '%s\n' "$TEST_FILES" | wc -l | tr -d ' ')
32
+ fi
33
+
34
+ echo "Found $TOTAL test files"
35
+ echo ""
36
+
37
+ COUNT=0
38
+ for TEST_FILE in $TEST_FILES; do
39
+ COUNT=$((COUNT + 1))
40
+
41
+ # Skip if pollution already exists
42
+ if [ -e "$POLLUTION_CHECK" ]; then
43
+ echo "⚠️ Pollution already exists before test $COUNT/$TOTAL"
44
+ echo " Skipping: $TEST_FILE"
45
+ continue
46
+ fi
47
+
48
+ echo "[$COUNT/$TOTAL] Testing: $TEST_FILE"
49
+
50
+ # Run the test
51
+ npm test "$TEST_FILE" > /dev/null 2>&1 || true
52
+
53
+ # Check if pollution appeared
54
+ if [ -e "$POLLUTION_CHECK" ]; then
55
+ echo ""
56
+ echo "🎯 FOUND POLLUTER!"
57
+ echo " Test: $TEST_FILE"
58
+ echo " Created: $POLLUTION_CHECK"
59
+ echo ""
60
+ echo "Pollution details:"
61
+ ls -la "$POLLUTION_CHECK"
62
+ echo ""
63
+ echo "To investigate:"
64
+ echo " npm test $TEST_FILE # Run just this test"
65
+ echo " cat $TEST_FILE # Review test code"
66
+ exit 1
67
+ fi
68
+ done
69
+
70
+ echo ""
71
+ echo "✅ No polluter found - all tests clean!"
72
+ exit 0