wdi-method 0.6.2 → 0.6.4

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/README.md CHANGED
@@ -25,7 +25,10 @@ page it is on.
25
25
  ## Install
26
26
 
27
27
  Three steps, in this order. The first is required before step 3 will run; the second can wait until
28
- you reach G5, but doing it now means nothing stops you later.
28
+ **Both are required, and the installer refuses without either.** BMad has always been checked; the ticket
29
+ engines are checked too, because every repo that learned they were missing learned it inside `wdi-build`
30
+ with a spec already open. `--skip-engines-check` is the escape for the two cases that earn it: CI, and a
31
+ repo that will never reach G5.
29
32
 
30
33
  **1. BMad Method** — in the product repo, picking the same agents you will give this installer:
31
34
 
@@ -44,8 +47,16 @@ claude plugins install mattpocock-skills # or, inside a session: /plugin i
44
47
  npx skills@latest add mattpocock/skills
45
48
  ```
46
49
 
47
- Then, once, inside your agent, run `/setup-matt-pocock-skills`: it asks which issue tracker `to-tickets`
48
- should publish to (local files, GitHub, Linear, …).
50
+ **You do not need to run `/setup-matt-pocock-skills` to get started.** Step 3 seeds `docs/agents/` with
51
+ the two answers WDI Method actually has a requirement on, so the engines are aligned from the first
52
+ install. Run the setup skill only to *change* something — to point at GitHub or Jira instead of local
53
+ markdown — and keep the three invariants the seeded `issue-tracker.md` names.
54
+
55
+ The seeding exists because the interview's own defaults are wrong here in one specific way: they send every
56
+ engineering skill looking for a root `CONTEXT.md` and `docs/adr/`, and Article 3 says this method has no
57
+ `docs/` layer for corpus or rules — `wdi-reconcile` reports both as findings. A repo that ran the setup
58
+ before installing this package keeps its own file, and the installer names the contradiction rather than
59
+ overwriting it.
49
60
 
50
61
  **3. WDI Method:**
51
62
 
@@ -97,6 +108,36 @@ answers with the gate you are at, not with a menu.
97
108
 
98
109
  ---
99
110
 
111
+ ## What to do right after install, and right after update
112
+
113
+ Two questions, and the honest answer to most of them is *nothing*. `wdi-help` answers them from the
114
+ registry at any time; this table is the same answer written down.
115
+
116
+ ### After `install`
117
+
118
+ | | |
119
+ |---|---|
120
+ | Engines missing | You never get here — `install` refuses, and names both install paths. Install them, run it again |
121
+ | `/setup-matt-pocock-skills`? | **No.** The installer seeded `docs/agents/` already answered for this method. Run that interview only to *change* tracker |
122
+ | So what is first? | **`wdi-init` intent `setup`** — it sets the global `mode`, and nothing has started until it is |
123
+ | Then? | `wdi-problem` for G1. Or ask `wdi-help`, which reads the registry rather than this table |
124
+
125
+ ### After `update`
126
+
127
+ | The summary said | Do |
128
+ |---|---|
129
+ | an **`upgrade`** line, naming content still in the old shape | **`wdi-upgrade`, before any other skill.** It moves every sentence into its new home, invents nothing, reports what it could not place, and ends in one commit. `wdi-help` and the validators read the new shape; a corpus half in the old one answers them wrongly |
130
+ | **no** `upgrade` line | Nothing. The update was mechanical and complete — carry on from wherever the gates say you are |
131
+ | `seeded docs/agents/` | Nothing. An older repo just received the engines' config, pre-answered. Read it if you like; do not run the setup interview to redo it |
132
+ | a warning that `domain.md` still points at a root `CONTEXT.md` | Add the correction that warning names to the top of that file. It was written by the setup interview before this package was installed, and it sends every engineering skill at two paths Article 3 forbids |
133
+ | a warning naming a **mandate** and `ad-n` | Only if you run `wdi-autopilot`. Decide whether that mandate should now park `AD-N` contradictions, and edit its `parked` list yourself — `update` never edits an authority you granted |
134
+
135
+ **`wdi-upgrade` is only ever about corpus content** — a brief, a PRD, an SRS, registry rows in the old
136
+ shape. It is not the answer to a missing engine, a missing tracker config, or anything under
137
+ `.control/memlog/`; each of those is handled by the installer itself or by the skill that owns it.
138
+
139
+ ---
140
+
100
141
  ## How to use it — the walk
101
142
 
102
143
  A gate is a moment where a human reads **one page** and decides. Between gates the AI works in a
package/bin/wdi-method.js CHANGED
@@ -123,6 +123,7 @@ function usage() {
123
123
  --doc-language <text> prose of working documents; free text, default English
124
124
  --doc-filename-language <text> slug part of document filenames; free text, default English
125
125
  --skip-bmad-check
126
+ --skip-engines-check install without to-spec / to-tickets / implement
126
127
 
127
128
  BMad first, then this package. ${WDI_REPO}
128
129
  `);
@@ -166,6 +167,7 @@ function parseArgs(argv) {
166
167
  while (rest.length) {
167
168
  const t = rest.shift();
168
169
  if (t === "--skip-bmad-check") args.skipBmad = true;
170
+ else if (t === "--skip-engines-check") args.skipEngines = true;
169
171
  else if (t === "--rescue") args.rescue = true;
170
172
  else if (t === "--yes" || t === "-y") args.yes = true;
171
173
  else if (t === "--agents") {
@@ -318,6 +320,29 @@ function bmadMissingMessage() {
318
320
  ].join("\n");
319
321
  }
320
322
 
323
+ // The engines used to WARN and let the install through, on the reasoning that G1-G4 run without them and
324
+ // a first install has no G5 yet. Both halves are still true, and the reasoning stopped being enough:
325
+ // `wdi-autopilot` needs all three from its first iteration, and a warning inside a forty-line summary is
326
+ // read exactly as often as it is skipped. The failure it was meant to prevent — learning they are missing
327
+ // inside `wdi-build`, with a spec already open — kept happening anyway.
328
+ //
329
+ // So it blocks, and `--skip-engines-check` is the escape, exactly as `--skip-bmad-check` is for BMad. The
330
+ // escape matters: CI installs into a bare checkout, and a repo that will never reach G5 is a real case.
331
+ function enginesMissingMessage() {
332
+ return [
333
+ "The ticket engines are not installed. G5 (wdi-build) and wdi-autopilot need all three.",
334
+ "",
335
+ ` Claude Code: ${ENGINES_INSTALL}`,
336
+ ` Other agents: ${ENGINES_INSTALL_ANY}`,
337
+ "",
338
+ "You do NOT need to run the setup skill after this — the installer seeds docs/agents/ already",
339
+ `answered for this method. Run ${ENGINES_SETUP} only to change tracker.`,
340
+ `Source: ${ENGINES_REPO}`,
341
+ "",
342
+ "G1-G4 run without them. To install anyway and add them later: --skip-engines-check",
343
+ ].join("\n");
344
+ }
345
+
321
346
  // The product's custom room. Three properties, and all three MUST hold together:
322
347
  // install/update seeds its content ONLY when absent — never written again after that
323
348
  // promote SKIPS it entirely, so a product's own rules can never reach the public repo
@@ -425,6 +450,117 @@ function splitProductConstitution(file) {
425
450
  // Two refusals matter more than the move. It never writes over an existing `specs.yaml`, and it
426
451
  // never deletes a `waves.yaml` whose content has nowhere to go: a half-finished hand migration
427
452
  // leaves BOTH files present, and which one is real is not something an installer can know.
453
+ // `wdi-autopilot` named its ledger for the DAY before 0.6.2 — `autopilot-<YYYY-MM-DD>.md`. The mandate
454
+ // it belongs to is named for the MANDATE now — `autopilot-<DEC-id>.md` — because two mandates opened
455
+ // on the same day would otherwise append to one file and destroy both as a record, and because
456
+ // `mandate-accept` (the validator introduced alongside the rename) looks for the file at that path and
457
+ // nowhere else. This is a pure rename, like `waves.yaml` → `specs.yaml`: the ledger's own content is
458
+ // never touched, only found and moved. Renaming it is what a script can safely do; restructuring its
459
+ // CONTENT into the `## Resume` / `## Decisions` split is not — that has to read git and the registry to
460
+ // know where the run actually stands, so it is the skill's own job on the next iteration it runs, not
461
+ // this installer's.
462
+ // `/setup-matt-pocock-skills` interviews the owner and writes `docs/agents/`. Two of its answers are
463
+ // wrong for a WDI repo, and BOTH repos that ran it had to hand-correct the SAME file afterwards:
464
+ //
465
+ // - `domain.md` tells agents to read and lazily create a root `CONTEXT.md` and `docs/adr/`. Article 3
466
+ // says this method has no `docs/` layer for corpus or rules, and `wdi-reconcile` reports both as
467
+ // findings. The homes already exist: `.control/product-glossary.md`, `.what/`, `.how/`, `DEC-`.
468
+ // - `issue-tracker.md`'s local-markdown default puts every ticket under `.scratch/<feature>/`, while
469
+ // `wdi-build` owns tickets at `{spec_folder}/issues/`. Two homes for one ticket set.
470
+ //
471
+ // Seeding them removes the interview for the answers WDI Method actually has a requirement on. Seeded
472
+ // ONCE and never overwritten — after the first install they are the product's, like every other file
473
+ // under a path the product owns. An owner who wants a different tracker re-runs the setup skill; the
474
+ // seeded file says which three invariants have to survive that.
475
+ function seedAgentDocs(target) {
476
+ const dir = path.join(target, "docs", "agents");
477
+ let wrote = 0;
478
+ for (const name of ["domain.md", "issue-tracker.md"]) {
479
+ const to = path.join(dir, name);
480
+ if (fs.existsSync(to)) continue;
481
+ const seed = path.join(ROOT, "scaffold", "docs", "agents", name);
482
+ if (!fs.existsSync(seed)) continue;
483
+ copyFile(seed, to);
484
+ wrote += 1;
485
+ }
486
+ if (wrote) {
487
+ note(`seeded docs/agents/ (${wrote} file${wrote === 1 ? "" : "s"}) — the engines' config, pre-answered`);
488
+ note(" do NOT run /setup-matt-pocock-skills to redo these; re-run it only to change tracker");
489
+ }
490
+ return wrote > 0;
491
+ }
492
+
493
+ // A repo that ran the setup skill BEFORE installing this package still carries the default `domain.md`,
494
+ // and it is actively misleading: it sends every engineering skill looking for a root `CONTEXT.md` and
495
+ // `docs/adr/`, and tells them to create both lazily. Seeding cannot fix it, because the file already
496
+ // exists and a file under a product-owned path is never overwritten. So it is named instead.
497
+ function warnStaleAgentDocs(target) {
498
+ const file = path.join(target, "docs", "agents", "domain.md");
499
+ if (!fs.existsSync(file)) return;
500
+ const text = fs.readFileSync(file, "utf8");
501
+ if (!/CONTEXT\.md|docs\/adr/.test(text)) return;
502
+ // An override note is what both real repos added by hand. Recognising it is what stops this warning
503
+ // from firing forever on a file somebody already fixed.
504
+ if (/does not use|MUST NOT be created|no `docs\/` layer/i.test(text)) return;
505
+ note("docs/agents/domain.md still points agents at a root CONTEXT.md and docs/adr/");
506
+ note(" Article 3: this method has no `docs/` layer for corpus or rules, and wdi-reconcile");
507
+ note(" reports both as findings. Say so at the top of that file — the glossary is at");
508
+ note(" .control/product-glossary.md and a decision is a DEC-, never an ADR");
509
+ }
510
+
511
+ function migrateAutopilotLedgers(target) {
512
+ const dir = path.join(target, ".control", "memlog");
513
+ if (!fs.existsSync(dir)) return;
514
+ const OLD = /^autopilot-(\d{4}-\d{2}-\d{2})\.md$/;
515
+ for (const name of fs.readdirSync(dir)) {
516
+ const m = OLD.exec(name);
517
+ if (!m) continue;
518
+ const from = path.join(dir, name);
519
+ const text = fs.readFileSync(from, "utf8");
520
+ const artifact = /^artifact:\s*(\S.*)$/m.exec(text)?.[1]?.trim();
521
+ const id = artifact && /(DEC-\d+)/.exec(artifact)?.[1];
522
+ if (!id) {
523
+ note(`.control/memlog/${name} looks like a pre-0.6.2 autopilot ledger, but its \`artifact:\` does`);
524
+ note(` not resolve to a DEC- id — rename it to autopilot-<the mandate's DEC- id>.md yourself`);
525
+ continue;
526
+ }
527
+ const to = path.join(dir, `autopilot-${id}.md`);
528
+ if (fs.existsSync(to)) {
529
+ note(`BOTH .control/memlog/${name} and autopilot-${id}.md exist — neither was touched`);
530
+ note(` the run's ledger is in one of them and I cannot tell which. Merge them, then delete the other`);
531
+ continue;
532
+ }
533
+ mv(from, to);
534
+ note(`renamed .control/memlog/${name} → autopilot-${id}.md (content unchanged)`);
535
+ note(` \`mandate-accept\` looks for a mandate's ledger at this exact path`);
536
+ }
537
+ }
538
+
539
+ // A mandate opened before 0.6.2 recorded `parked: []` under the OLD default — full authority, AD-N
540
+ // contradictions included. 0.6.2 changed the DEFAULT for a NEW mandate to park `ad-n`, because
541
+ // decision-guide.md says narrowing an invariant MUST NOT be softened further. A default only applies
542
+ // at the moment a mandate is written, so an EXISTING accepted mandate keeps whatever it already says —
543
+ // silently adding `ad-n` to it would be overwriting a value the owner already chose, which `update`
544
+ // MUST NOT do to anything in the product's own registry. So this only ever WARNS, naming the mandate
545
+ // and the one line that would close the gap, and leaves the decision to whoever reads the summary.
546
+ function warnStaleMandates(target) {
547
+ const file = path.join(target, ".control", "registry", "decisions.yaml");
548
+ if (!fs.existsSync(file)) return;
549
+ const text = fs.readFileSync(file, "utf8");
550
+ const blocks = text.split(/\n(?=\s*-\s*id:\s*DEC-)/);
551
+ for (const block of blocks) {
552
+ if (!/type:\s*mandate/.test(block)) continue;
553
+ if (!/status:\s*accepted/.test(block)) continue;
554
+ const id = /id:\s*(DEC-\d+)/.exec(block)?.[1];
555
+ const parkedLine = /parked:\s*(\[[^\]]*\]|.*)$/m.exec(block)?.[0] || "";
556
+ const parkedBlockList = /parked:\s*\n((?:\s+-\s*\S.*\n?)*)/.exec(block)?.[1] || "";
557
+ if (/ad-n/.test(parkedLine) || /ad-n/.test(parkedBlockList)) continue;
558
+ note(`${id || "a mandate"} predates the \`ad-n\`-parked-by-default protection (0.6.2) — its \`parked\``);
559
+ note(` list does not name it, so it still decides an AD-N contradiction on its own`);
560
+ note(` add \`ad-n\` to its \`parked\` list in decisions.yaml yourself if you want the new default`);
561
+ }
562
+ }
563
+
428
564
  function migrateRegistryNames(target) {
429
565
  const reg = path.join(target, ".control", "registry");
430
566
  const from = path.join(reg, "waves.yaml");
@@ -1027,6 +1163,10 @@ function apply(target, agents,
1027
1163
  // is about to occupy. Running it after would leave two copies of most guides.
1028
1164
  const migrated = migrateToTwoFolders(target);
1029
1165
  migrateRegistryNames(target);
1166
+ migrateAutopilotLedgers(target);
1167
+ warnStaleMandates(target);
1168
+ seedAgentDocs(target);
1169
+ warnStaleAgentDocs(target);
1030
1170
  seedRequirementSplit(target);
1031
1171
  // The split MUST also be reachable without a migration. 0.5.2 only ran it from inside
1032
1172
  // migrateToTwoFolders, which returns early when the old layout is absent — so a repo that took
@@ -1436,6 +1576,9 @@ function runNonInteractive(args) {
1436
1576
  if (!args.skipBmad && !bmadPresent(target)) {
1437
1577
  die(bmadMissingMessage());
1438
1578
  }
1579
+ if (!args.skipEngines && !enginesPresent(target)) {
1580
+ die(enginesMissingMessage());
1581
+ }
1439
1582
  const existing = readIndexIdentity(target);
1440
1583
  const product = args.product || existing.name;
1441
1584
  const client = args.client ?? existing.client;
@@ -105,6 +105,14 @@ Open with three reads, in this order:
105
105
  2. **Reconcile `## Resume` against git.** Compare the run branch HEAD with the commit Resume names. A
106
106
  difference is work that landed before the last iteration died — rebuild Resume from what git shows
107
107
  **before** starting anything new. Trusting a stale Resume is how a merged ticket gets implemented twice.
108
+
109
+ **A ledger with no `## Resume` heading at all is the same case, one step further back.** It is a
110
+ pre-0.6.2 ledger — a flat table with nothing else — and `update` only ever renames the file, never
111
+ restructures its content, because that restructuring needs exactly this read. Heal it once, here,
112
+ before touching anything else: wrap the existing table under a `## Decisions` heading if it is not
113
+ already, then **build a fresh `## Resume`** the same way as a stale one above — from the mandate row,
114
+ `.control/generated/status`, and the run branch's actual HEAD, never from the table's last row read as
115
+ prose. The table's own rows are untouched; only the missing head is added.
108
116
  3. `.control/generated/status`, the mandate row, and **the ledger's `## Resume` only** — see § The ledger
109
117
  for why that is a section and not a file.
110
118
 
@@ -330,6 +338,7 @@ When § The work table reaches § Finish:
330
338
  - A mandate accepted by delegation, or with no `expires`
331
339
  - Deciding something the mandate parks, or parking something the mandate did not
332
340
  - A decision taken and not written to the ledger
341
+ - A pre-0.6.2 ledger left un-healed — no `## Resume` wrapped and rebuilt on first touch
333
342
  - Reading the whole ledger when the run is alive — `## Resume` is what an iteration loads
334
343
  - A `## Resume` that restates a decision, or repeats what the registry already answers
335
344
  - A ledger cell longer than a line, instead of a pointer to a companion document
@@ -49,6 +49,10 @@ mis-route in this flow, because every other gate is the same for every component
49
49
  | State | Next |
50
50
  |---|---|
51
51
  | `wdi-method update` just ran and its summary printed an `upgrade` line | `wdi-upgrade` — **before anything else**. Content is still in the old shape, and every skill below reads the new one |
52
+ | `wdi-method update` just ran and printed **no** `upgrade` line | Nothing. The update was mechanical and complete; carry on from wherever the gates say you are |
53
+ | `wdi-method install` just ran, first time in this repo | `wdi-init` intent `setup` — the global `mode`, and nothing has started until it is set |
54
+ | Someone asks whether to run `/setup-matt-pocock-skills` | **No**, unless they are changing tracker. `install` and `update` seed `docs/agents/` already answered for this method; re-running the interview restores defaults that contradict Article 3 |
55
+ | The installer refused, naming the ticket engines | Not a skill. Install them — `/plugin install mattpocock-skills`, or `npx skills@latest add mattpocock/skills` — then run the installer again |
52
56
  | No registry, or no global `mode` set | `wdi-init` intent `setup` — nothing has started |
53
57
  | No `.what/_product-brief/brief.md` | `wdi-problem` — G1 has not started |
54
58
  | A brief exists, and no PRD covers the area in play | `wdi-product` intent `prd` |
@@ -87,6 +91,10 @@ it claims, or when one of its eight required sections is missing outright.
87
91
  as blocking a design gate; `assumptions.md` holds nothing.
88
92
  - You MUST NOT invent progress. If `.control/generated/status` is missing or stale, say so and name
89
93
  `validate.py --generate`.
94
+ - You MUST NOT route anyone to `/setup-matt-pocock-skills` to *finish an install*. The installer seeds
95
+ `docs/agents/` pre-answered, and that interview's own defaults send every engineering skill looking for a
96
+ root `CONTEXT.md` and `docs/adr/` — which Article 3 forbids and `wdi-reconcile` reports. It is for
97
+ changing tracker, and nothing else.
90
98
  - You MUST NOT run other skills on the user's behalf. Name the skill; let them invoke it. The one skill that
91
99
  runs others is `wdi-autopilot`, and only under a mandate the owner accepted — that is what the mandate is.
92
100
  - When the next step is blocked by a decision rather than by work, route to `wdi-question` or
@@ -10,6 +10,14 @@ content needs no judgment, seeds what is new, prunes what is retired. It stops e
10
10
  about **content** begins — which PRD an `FR` belongs to, whether a sentence in the old brief was an
11
11
  assumption or a constraint. Those are this skill's half.
12
12
 
13
+ **This skill's content is `.what/` and `.how/`** — the corpus a human reads. It does NOT cover
14
+ `.control/memlog/` or a registry row: an autopilot ledger's filename is `update`'s own mechanical rename
15
+ (the same way `waves.yaml` became `specs.yaml`), a stale mandate setting is a printed warning at `update`
16
+ time because overwriting it would be overwriting a value the owner already chose, and a ledger's internal
17
+ shape is healed by `wdi-autopilot` itself on its next run, because only it can read git and the registry to
18
+ know where that run actually stands. Route those three there; this skill's report would have nothing to say
19
+ about any of them.
20
+
13
21
  **This is the one skill allowed to edit `brief.md`, `prd.md`, an SRS, an SDD, or a C4 file directly.**
14
22
  Every other skill is forbidden, because a hand edit makes the memlog lie about how the document was
15
23
  produced. An upgrade produces nothing: it moves sentences that already exist into the home the new
@@ -154,6 +154,9 @@ verifies the result, and lands the memlog.
154
154
  not diligence.
155
155
  - `.claude/skills/bmad-*/customize.toml` MUST NOT be edited — it is overwritten on every BMad update;
156
156
  customise through `_bmad/custom/`.
157
+ - `CONTEXT.md`, `CONTEXT-MAP.md`, and `docs/adr/` MUST NOT be created. `docs/agents/domain.md` is the
158
+ engines' config and says so too: the vocabulary is `.control/product-glossary.md`, domain knowledge is
159
+ `.what/`, design is `.how/`, and a decision is a `DEC-` — never an ADR. Article 3 owns the rule.
157
160
 
158
161
  ## Routing — load a guide when the task matches
159
162
 
@@ -184,6 +187,7 @@ verifies the result, and lands the memlog.
184
187
  | Looking for where code lives, or placing new code | `.control/structure-codebase.md` |
185
188
  | Looking for where a document lives | `.control/structure-document.md` |
186
189
  | Writing or reviewing code | `.constitution/project/codebase-stack-guide.md` · `.constitution/project/codebase-conventions-guide.md` · `.constitution/project/codebase-brownfield-guide.md` |
190
+ | Running `to-spec`, `to-tickets`, `implement`, or `triage` | `docs/agents/issue-tracker.md` · `docs/agents/domain.md` — the engines' own config, seeded by `wdi-method` already answered for this method |
187
191
 
188
192
  All three `.constitution/project/codebase-*-guide.md` start as `status: Draft`. While they are, their contents MAY be read
189
193
  as guidance but MUST NOT be used to reject a change.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wdi-method",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "WDI Method — software delivery method that wraps BMad",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,36 @@
1
+ # Domain Docs
2
+
3
+ How the engineering skills should consume this repo's domain documentation when exploring the codebase.
4
+
5
+ **This repo does not use `CONTEXT.md`, `CONTEXT-MAP.md`, or `docs/adr/`, and none of them MUST be
6
+ created.** WDI Method already owns every home they would occupy, and a second home for the same fact is
7
+ drift rather than tidiness. `.constitution/method/constitution.md` Article 3 states it outright: this
8
+ method has no `docs/` layer for corpus or rules, and `wdi-reconcile` reports a root `CONTEXT.md` or a
9
+ `docs/adr/` as a finding against that article.
10
+
11
+ This file is seeded by `wdi-method` so the answer is right from the first install. `/setup-matt-pocock-skills`
12
+ would otherwise write its own default here — one that points at exactly the two paths above — and every
13
+ repo that ran it has had to hand-correct this file afterwards.
14
+
15
+ ## Where each thing actually lives
16
+
17
+ | Looking for | Read |
18
+ |---|---|
19
+ | What a domain term means | `.control/product-glossary.md` |
20
+ | What is promised — a capability, an `FR`, a use case, a business rule | `.what/` |
21
+ | How it is built — the spine, C4, an inventory, an SDD, a contract | `.how/` |
22
+ | Why it is like this — a decision worth remembering | `.control/decisions/`, indexed by the generated decision table under `.control/generated/` |
23
+ | An architectural invariant | an `AD-N` in the architecture spine, under `.how/_platform/` |
24
+ | Which contexts exist and what each owns | `.control/registry/components.yaml` |
25
+ | Where code lives | `.control/structure-codebase.md` |
26
+ | Where documents live | `.control/structure-document.md` |
27
+
28
+ `AGENTS.md` § *The thing in your hand → its folder* is the short version of this table and is the
29
+ authority. Where this file and `AGENTS.md` disagree, `AGENTS.md` wins.
30
+
31
+ ## A decision is a `DEC-`, never an ADR
32
+
33
+ `decision-guide.md` owns the shape, the one test that decides whether a decision is recorded at all, and
34
+ the `draft → accepted → applied` ladder. An ADR file under `docs/adr/` MUST NOT be written in its place:
35
+ the numbering is global and lives in `.control/registry/decisions.yaml`, and a decision recorded outside
36
+ it is invisible to `refs-resolve`, to the RTM, and to the generated decision table.
@@ -0,0 +1,43 @@
1
+ # Issue tracker
2
+
3
+ Where issues live for this repo, and what `to-spec`, `to-tickets`, and `triage` read and write.
4
+
5
+ This file is seeded by `wdi-method`. It is the product's from here on: change the tracker whenever you
6
+ like, but keep the three invariants below, because `wdi-build` and the validators read them.
7
+
8
+ ## Two places, and they are not the same place
9
+
10
+ | | Owned by | Lives at |
11
+ |---|---|---|
12
+ | **A spec's tickets** — the work behind an `FR` | `wdi-build`, at G5 | `{spec_folder}/issues/<NN>-<slug>.md`, `spec_folder` from `.control/registry/specs.yaml` |
13
+ | **Ad hoc work** — a quick bug report, a small idea, engineering-skill scratch | this file's convention | `.scratch/<slug>/` |
14
+
15
+ `.scratch/` MUST NOT become a second place to plan a feature that already has an `FR`. The moment ad hoc
16
+ work turns out to touch an `FR`, it stops and becomes a spec through `wdi-build` — the Fast Path rule in
17
+ `delivery-flow-guide.md` owns that boundary.
18
+
19
+ ## The three invariants
20
+
21
+ Whatever tracker this repo uses — local markdown, GitHub, GitLab, Jira — these MUST hold:
22
+
23
+ 1. **One parent per spec, one issue per ticket.** A ticket is an issue, never a sub-task: only an issue
24
+ carries native blocking edges, and the frontier is read from them.
25
+ 2. **Status lives on the ticket itself and nowhere else.** A `**Status:**` line near the top of the
26
+ ticket file, or `status:` in its frontmatter. `ticket-status-one-home` reads it there, and copying it
27
+ into `specs.yaml` is what that validator exists to refuse.
28
+ 3. **Every ticket names what it `satisfies`** — the `UC` or `FR` behind it. Without it the chain
29
+ `FR → UC → ticket → test` breaks and the RTM cannot say which promise went green.
30
+
31
+ ## Conventions — local markdown
32
+
33
+ - One effort per directory: `.scratch/<slug>/`
34
+ - One file per ticket at `.scratch/<slug>/issues/<NN>-<slug>.md`, numbered from `01`, never a single
35
+ combined file
36
+ - Blocking edges as a `Blocked by: NN, NN` line near the top
37
+ - Comments append at the bottom under a `## Comments` heading
38
+
39
+ ## Switching to a real tracker
40
+
41
+ Re-run `/setup-matt-pocock-skills` and pick it, then keep the three invariants above. The mapping WDI
42
+ Method expects is in `delivery-flow-guide.md` § *Mapping to a tracker*: parent issue is the spec, issue is
43
+ the ticket, Fix Version is the release, and the `CAP`/`FR` travel as labels.