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.
- package/LICENSE +21 -0
- package/README.md +203 -0
- package/bin/wdi-method.js +906 -0
- package/kit/.constitution/README.md +66 -0
- package/kit/.constitution/codebase/brownfield-guide.md +18 -0
- package/kit/.constitution/codebase/conventions-guide.md +18 -0
- package/kit/.constitution/codebase/stack-guide.md +18 -0
- package/kit/.constitution/constitution.md +135 -0
- package/kit/.constitution/document/architecture-guide.md +209 -0
- package/kit/.constitution/document/bmad-guide.md +151 -0
- package/kit/.constitution/document/bmad-skill-register.md +71 -0
- package/kit/.constitution/document/brief-guide.md +94 -0
- package/kit/.constitution/document/corpus-guide.md +341 -0
- package/kit/.constitution/document/decision-guide.md +134 -0
- package/kit/.constitution/document/delivery-flow-guide.md +338 -0
- package/kit/.constitution/document/prd-guide.md +202 -0
- package/kit/.constitution/document/sdd-guide.md +206 -0
- package/kit/.constitution/document/srs-guide.md +147 -0
- package/kit/.constitution/document/templates/addendum.md +71 -0
- package/kit/.constitution/document/templates/architecture-spine.md +100 -0
- package/kit/.constitution/document/templates/brief.md +110 -0
- package/kit/.constitution/document/templates/c4.md +77 -0
- package/kit/.constitution/document/templates/contract.md +52 -0
- package/kit/.constitution/document/templates/cross-cutting.md +80 -0
- package/kit/.constitution/document/templates/decision.md +64 -0
- package/kit/.constitution/document/templates/design-system.md +67 -0
- package/kit/.constitution/document/templates/flow.md +51 -0
- package/kit/.constitution/document/templates/integration.md +69 -0
- package/kit/.constitution/document/templates/inventory.md +69 -0
- package/kit/.constitution/document/templates/lc.md +47 -0
- package/kit/.constitution/document/templates/lifecycle.md +52 -0
- package/kit/.constitution/document/templates/meeting.md +47 -0
- package/kit/.constitution/document/templates/model.md +55 -0
- package/kit/.constitution/document/templates/oq.md +45 -0
- package/kit/.constitution/document/templates/prd.md +226 -0
- package/kit/.constitution/document/templates/questions.md +75 -0
- package/kit/.constitution/document/templates/rtr.md +61 -0
- package/kit/.constitution/document/templates/rules.md +47 -0
- package/kit/.constitution/document/templates/scn.md +40 -0
- package/kit/.constitution/document/templates/sdd.md +146 -0
- package/kit/.constitution/document/templates/srs.md +149 -0
- package/kit/.constitution/document/templates/structure-codebase.md +129 -0
- package/kit/.constitution/document/templates/structure-document.md +90 -0
- package/kit/.constitution/document/templates/uc.md +69 -0
- package/kit/.constitution/document/templates/ux.md +77 -0
- package/kit/.constitution/document/ux-guide.md +99 -0
- package/kit/.constitution/language-guide.md +69 -0
- package/kit/.constitution/method/README.md +169 -0
- package/kit/.constitution/method/artifact-map.md +155 -0
- package/kit/.constitution/method/portability.md +91 -0
- package/kit/.constitution/method/rationale.md +246 -0
- package/kit/.constitution/method-glossary.md +169 -0
- package/kit/.constitution/project/README.md +68 -0
- package/kit/.constitution/repo-guide.md +97 -0
- package/kit/.constitution/scripts/inventory.py +527 -0
- package/kit/.constitution/scripts/timeline.py +665 -0
- package/kit/.constitution/scripts/validate.py +1579 -0
- package/kit/.constitution/structure-guide.md +202 -0
- package/kit/assets/bmad-custom/bmad-advanced-elicitation.toml +15 -0
- package/kit/assets/bmad-custom/bmad-architecture.toml +31 -0
- package/kit/assets/bmad-custom/bmad-build-auto.toml +10 -0
- package/kit/assets/bmad-custom/bmad-build.toml +52 -0
- package/kit/assets/bmad-custom/bmad-code-review.toml +12 -0
- package/kit/assets/bmad-custom/bmad-correct-course.toml +27 -0
- package/kit/assets/bmad-custom/bmad-deep-recon.toml +11 -0
- package/kit/assets/bmad-custom/bmad-prd.toml +42 -0
- package/kit/assets/bmad-custom/bmad-product-brief.toml +34 -0
- package/kit/assets/bmad-custom/bmad-retrospective.toml +9 -0
- package/kit/assets/bmad-custom/bmad-spec.toml +15 -0
- package/kit/assets/bmad-custom/bmad-ux.toml +19 -0
- package/kit/assets/bmad-custom/config.toml +6 -0
- package/kit/skills/wdi-blueprint/SKILL.md +203 -0
- package/kit/skills/wdi-build/SKILL.md +244 -0
- package/kit/skills/wdi-component/SKILL.md +172 -0
- package/kit/skills/wdi-decision/.gitkeep +0 -0
- package/kit/skills/wdi-decision/SKILL.md +183 -0
- package/kit/skills/wdi-help/.gitkeep +0 -0
- package/kit/skills/wdi-help/SKILL.md +98 -0
- package/kit/skills/wdi-init/SKILL.md +149 -0
- package/kit/skills/wdi-log/SKILL.md +107 -0
- package/kit/skills/wdi-problem/SKILL.md +90 -0
- package/kit/skills/wdi-product/SKILL.md +127 -0
- package/kit/skills/wdi-question/.gitkeep +0 -0
- package/kit/skills/wdi-question/SKILL.md +88 -0
- package/kit/skills/wdi-reconcile/.gitkeep +0 -0
- package/kit/skills/wdi-reconcile/SKILL.md +146 -0
- package/kit/skills/wdi-report/SKILL.md +205 -0
- package/kit/skills/wdi-review/SKILL.md +118 -0
- package/kit/skills/wdi-systematic-debugging/SKILL.md +193 -0
- package/kit/skills/wdi-systematic-debugging/references/condition-based-waiting.md +115 -0
- package/kit/skills/wdi-systematic-debugging/references/defense-in-depth.md +122 -0
- package/kit/skills/wdi-systematic-debugging/references/find-polluter.sh +72 -0
- package/kit/skills/wdi-systematic-debugging/references/root-cause-tracing.md +169 -0
- package/kit/skills/wdi-ux/SKILL.md +145 -0
- package/kit-overlay/AGENTS.md +196 -0
- package/kit-overlay/README.md +66 -0
- package/kit-overlay/constitution.md +135 -0
- package/kit-overlay/portability.md +91 -0
- package/kit-overlay/repo-guide.md +97 -0
- package/lib/agents-block.mjs +52 -0
- package/lib/identity.mjs +65 -0
- package/package.json +37 -0
- package/scaffold/.control/decisions/.gitkeep +0 -0
- package/scaffold/.control/generated/.gitkeep +2 -0
- package/scaffold/.control/meetings/.gitkeep +0 -0
- package/scaffold/.control/memlog/.gitkeep +0 -0
- package/scaffold/.control/product-glossary.md +21 -0
- package/scaffold/.control/project-non-technical-log.md +23 -0
- package/scaffold/.control/questions/answered.md +11 -0
- package/scaffold/.control/questions/assumptions.md +15 -0
- package/scaffold/.control/questions/blocking.md +21 -0
- package/scaffold/.control/questions/external.md +11 -0
- package/scaffold/.control/registry/components.yaml +21 -0
- package/scaffold/.control/registry/decisions.yaml +6 -0
- package/scaffold/.control/registry/defects.yaml +3 -0
- package/scaffold/.control/registry/index.yaml +46 -0
- package/scaffold/.control/registry/requirements.yaml +15 -0
- package/scaffold/.control/registry/risks.yaml +5 -0
- package/scaffold/.control/registry/usecases.yaml +6 -0
- package/scaffold/.control/registry/waves.yaml +5 -0
- package/scaffold/.control/structure-codebase.md +19 -0
- 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
|