@dzhechkov/skills-feature-adr 1.5.1 → 1.5.2

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/.dz-manifest.json CHANGED
@@ -13,7 +13,7 @@
13
13
  },
14
14
  {
15
15
  "path": "README.md",
16
- "sha256": "47f9402d4c3954d6d5a75ad906ec7b592010fdfd80f347d4a87dd14dd9e300ce"
16
+ "sha256": "fe7f99695df5590ae1474395e3a2b26754a6bfd63695c5d8c804fb290b8eb6b1"
17
17
  },
18
18
  {
19
19
  "path": "bin/cli.js",
@@ -25,15 +25,7 @@
25
25
  },
26
26
  {
27
27
  "path": "package.json",
28
- "sha256": "3fbe9ef86c9f6fa7c86bbe7760729094e80edb67577e397678b2d98dca1c2034"
29
- },
30
- {
31
- "path": "scripts/sync-templates.js",
32
- "sha256": "790d53c2e98a4b9aa589c72c6394c0e3aeb6b99d4a0bade4c881d2a8d11cf12c"
33
- },
34
- {
35
- "path": "sources.json",
36
- "sha256": "2e7f84f7fa7e97aa38514a94e920747e648a6f2ad0a503fc50e7519658116065"
28
+ "sha256": "e3ce1d2e92b37dae085819111a7a107cf9b170a7602ceea381423873523ad1d2"
37
29
  },
38
30
  {
39
31
  "path": "src/cli.js",
@@ -121,7 +113,7 @@
121
113
  },
122
114
  {
123
115
  "path": "templates/.claude/skills/feature-adr/SKILL.md",
124
- "sha256": "bdedfed69d985eb6969f158a72d92f63c6b1b993008c3661f7ffc2c6a7ccfbae"
116
+ "sha256": "67eb730c815e6b8f9b9b61084c67ceb85440548f00b21f52187d16e81e3d7af8"
125
117
  },
126
118
  {
127
119
  "path": "templates/.claude/skills/feature-adr/examples/sample-feature-output.md",
@@ -257,7 +249,7 @@
257
249
  },
258
250
  {
259
251
  "path": "templates/.claude/skills/feature-adr/scripts/check-plan-completeness.mjs",
260
- "sha256": "2e1afeb8cbc08ce872fc8280a0809e7712ee76879d08b48caa5a0d4082cb8647"
252
+ "sha256": "5cef77e68c58f163be64516013fb90866ef7201e415dc8888cc21a0f4d92b7d8"
261
253
  },
262
254
  {
263
255
  "path": "templates/.claude/skills/frontend-design/LICENSE.txt",
@@ -321,7 +313,7 @@
321
313
  },
322
314
  {
323
315
  "path": "templates/.claude/workflows/feature-adr.js",
324
- "sha256": "0c74e8995d82bb986ef3233e91beebfa0e761aae4434f24554077bb05d2199e9"
316
+ "sha256": "2d895ee2b348745dce23b6beb6f932908beb932f2de309afe812ed3e1b96a7db"
325
317
  },
326
318
  {
327
319
  "path": "templates/lib/memory-protocol.md",
@@ -330,24 +322,8 @@
330
322
  {
331
323
  "path": "templates/lib/reward-tracker.md",
332
324
  "sha256": "581feeda1b4b920f7013bde5938f5cb6ffe3e44f5d2426263c49f1047ade8d55"
333
- },
334
- {
335
- "path": "test/back-compat.test.js",
336
- "sha256": "74fa0b7f1e595eddef251b37b0bf6d6f97971f1c2d03348228a9913380128d54"
337
- },
338
- {
339
- "path": "test/helpers/sandbox.js",
340
- "sha256": "61e883fb74a00301b1578987aecfc5b0ff4f4db4ee64c90fae0035a24dcabc99"
341
- },
342
- {
343
- "path": "test/three-way.test.js",
344
- "sha256": "16c3b973996b6cdd2d070e413de54e9835a038bd37b0d3769f5cccccdd57e971"
345
- },
346
- {
347
- "path": "test/update-3way.test.js",
348
- "sha256": "8c34fb6b5202393b52cbfd29d3cd64b6d270675c2d8327d4c1a8366af8088ae7"
349
325
  }
350
326
  ]
351
327
  },
352
- "signature": "9WVoqK2D8eKSnmhhMqHPt31NedxwZCHiHyxJ5VLMxSVMecMqyXgrCJ2GQ3OOfIeiquB2D01bh7QjZW/EMVM9Aw=="
328
+ "signature": "RVit/JWzAfaMRgh3KVc200QVdpU0sNO6AcEkRGYt8c8fqq1TG01DKPXiGvV08hWRHH8/a/67+qOko8sYEgqEBw=="
353
329
  }
package/README.md CHANGED
@@ -169,7 +169,7 @@ refused BY NAME: `path traversal ('..' segment)`, `degenerate path segment`, `em
169
169
  `illegal character '<c>'`, `empty stem after the leading dot`. The old substring traversal test also
170
170
  rejected the ordinary filename `foo..bar.ts`; it is a segment test now.
171
171
 
172
- ### The Step-8 amendment gate runs a command instead of judging (v1.5.1)
172
+ ### The Step-8 amendment gate runs a command instead of judging (v1.5.2)
173
173
 
174
174
  Every `AM-N` row must resolve to a test found INSIDE the file the row names, and the check is
175
175
  `dz amendment-check --slug <slug> --json` rather than a paragraph asking the reviewer to confirm it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/skills-feature-adr",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "Adaptive Feature Development skill pack for Claude Code — 11-step pipeline with Complexity Router (S/M/L/XL), ADR-driven architecture, 15 agentic-qe skills, multi-agent fleet QE. Supports --full-qe, --full-qe-extended, --with-learning, and --knowledge-extractor modes.",
5
5
  "bin": {
6
6
  "skills-feature-adr": "./bin/cli.js"
package/sbom.json CHANGED
@@ -35,7 +35,7 @@
35
35
  "hashes": [
36
36
  {
37
37
  "alg": "SHA-256",
38
- "content": "47f9402d4c3954d6d5a75ad906ec7b592010fdfd80f347d4a87dd14dd9e300ce"
38
+ "content": "fe7f99695df5590ae1474395e3a2b26754a6bfd63695c5d8c804fb290b8eb6b1"
39
39
  }
40
40
  ]
41
41
  },
@@ -65,27 +65,7 @@
65
65
  "hashes": [
66
66
  {
67
67
  "alg": "SHA-256",
68
- "content": "3fbe9ef86c9f6fa7c86bbe7760729094e80edb67577e397678b2d98dca1c2034"
69
- }
70
- ]
71
- },
72
- {
73
- "type": "file",
74
- "name": "scripts/sync-templates.js",
75
- "hashes": [
76
- {
77
- "alg": "SHA-256",
78
- "content": "790d53c2e98a4b9aa589c72c6394c0e3aeb6b99d4a0bade4c881d2a8d11cf12c"
79
- }
80
- ]
81
- },
82
- {
83
- "type": "file",
84
- "name": "sources.json",
85
- "hashes": [
86
- {
87
- "alg": "SHA-256",
88
- "content": "2e7f84f7fa7e97aa38514a94e920747e648a6f2ad0a503fc50e7519658116065"
68
+ "content": "e3ce1d2e92b37dae085819111a7a107cf9b170a7602ceea381423873523ad1d2"
89
69
  }
90
70
  ]
91
71
  },
@@ -305,7 +285,7 @@
305
285
  "hashes": [
306
286
  {
307
287
  "alg": "SHA-256",
308
- "content": "bdedfed69d985eb6969f158a72d92f63c6b1b993008c3661f7ffc2c6a7ccfbae"
288
+ "content": "67eb730c815e6b8f9b9b61084c67ceb85440548f00b21f52187d16e81e3d7af8"
309
289
  }
310
290
  ]
311
291
  },
@@ -645,7 +625,7 @@
645
625
  "hashes": [
646
626
  {
647
627
  "alg": "SHA-256",
648
- "content": "2e1afeb8cbc08ce872fc8280a0809e7712ee76879d08b48caa5a0d4082cb8647"
628
+ "content": "5cef77e68c58f163be64516013fb90866ef7201e415dc8888cc21a0f4d92b7d8"
649
629
  }
650
630
  ]
651
631
  },
@@ -805,7 +785,7 @@
805
785
  "hashes": [
806
786
  {
807
787
  "alg": "SHA-256",
808
- "content": "0c74e8995d82bb986ef3233e91beebfa0e761aae4434f24554077bb05d2199e9"
788
+ "content": "2d895ee2b348745dce23b6beb6f932908beb932f2de309afe812ed3e1b96a7db"
809
789
  }
810
790
  ]
811
791
  },
@@ -828,46 +808,6 @@
828
808
  "content": "581feeda1b4b920f7013bde5938f5cb6ffe3e44f5d2426263c49f1047ade8d55"
829
809
  }
830
810
  ]
831
- },
832
- {
833
- "type": "file",
834
- "name": "test/back-compat.test.js",
835
- "hashes": [
836
- {
837
- "alg": "SHA-256",
838
- "content": "74fa0b7f1e595eddef251b37b0bf6d6f97971f1c2d03348228a9913380128d54"
839
- }
840
- ]
841
- },
842
- {
843
- "type": "file",
844
- "name": "test/helpers/sandbox.js",
845
- "hashes": [
846
- {
847
- "alg": "SHA-256",
848
- "content": "61e883fb74a00301b1578987aecfc5b0ff4f4db4ee64c90fae0035a24dcabc99"
849
- }
850
- ]
851
- },
852
- {
853
- "type": "file",
854
- "name": "test/three-way.test.js",
855
- "hashes": [
856
- {
857
- "alg": "SHA-256",
858
- "content": "16c3b973996b6cdd2d070e413de54e9835a038bd37b0d3769f5cccccdd57e971"
859
- }
860
- ]
861
- },
862
- {
863
- "type": "file",
864
- "name": "test/update-3way.test.js",
865
- "hashes": [
866
- {
867
- "alg": "SHA-256",
868
- "content": "8c34fb6b5202393b52cbfd29d3cd64b6d270675c2d8327d4c1a8366af8088ae7"
869
- }
870
- ]
871
811
  }
872
812
  ]
873
813
  }
@@ -100,6 +100,9 @@ ROUTER → REQUIREMENTS → RESEARCH → ADR → QCSD SWARM
100
100
  SPARC-GOAP PLAN
101
101
  (all)
102
102
 
103
+ K1 NAME-AVAILABILITY GATE
104
+ (every new command/module/export)
105
+
103
106
  K2 PLAN-COMPLETENESS GATE
104
107
  (all — exit 0 or Step 7 waits)
105
108
 
@@ -147,6 +150,27 @@ Step | S | M | L | XL | Model | Agentic QE Skill
147
150
  10 Delivery Gate (OPT-IN) | o | o | o | o | cross-family of coder | 4 planes: regressions ‖ security ‖ code-quality ‖ product-honesty (o = runs only when explicitly requested; absent ⇒ byte-identical) |
148
151
  ```
149
152
 
153
+ ### K1 name-availability gate (Step-6/7 boundary, MANDATORY, every new name)
154
+
155
+ Before Step 7 writes a line, every NEW name the plan introduces is checked:
156
+
157
+ ```bash
158
+ dz name-check --command <cmd> --module <basename> --export <a,b,c>
159
+ ```
160
+
161
+ `exit 0` → proceed · `exit 1` → rename in the plan first · `exit 2` → NOT ESTABLISHED (the sweep
162
+ found nothing of that kind — fix the invocation, never read it as free).
163
+
164
+ **Why this is a gate and not advice.** Twice on 2026-08-23/24 a collision broke the build outright —
165
+ `dz retro` was already a command and its star re-export clash stopped the CLI from importing at all;
166
+ `decideProvenance` was already an export. Both were answerable before any code. The check costs one
167
+ command; the miss costs a rename across every artifact of the feature. An agent's intention to
168
+ remember is layer 4 on the cost-of-detection ladder, and this line exists because that layer failed
169
+ twice in one day.
170
+
171
+ The check reads workspace SOURCE, never `dist` — a stale build answers "free" confidently. Its honest
172
+ limit is printed on the passing path: a re-export under a different name stays the build's job.
173
+
150
174
  ### K2 plan-completeness gate (Step-6/7 boundary, MANDATORY, all tiers)
151
175
 
152
176
  Step 7 does not start until the plan passes a SCRIPT — not a reviewer's impression:
@@ -292,6 +292,68 @@ if (acidArg) {
292
292
  } else if (acidTokens.length === 0) skips.push('C4: no acid corpus declared (no --acid, no `| A<n> |` table in 00_complexity_assessment.md) — acid-naming check SKIPPED');
293
293
  else for (const t of acidTokens) if (!new RegExp(`\\b${t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i').test(plan)) failures.push(`C4: acid token "${safe(t)}" (from ${acidSource}) not named in the plan`);
294
294
 
295
+ // ── C6 amendment integrity (backlog 72b89e14, seams б/в/г) ──────────────────────────────────────
296
+ // The measured shape: AM-rows living OUTSIDE `## Amendments` were invisible to every gate; four
297
+ // amendments named no test at all; a retracted amendment (AM-23 cancelling AM-20) left the old one
298
+ // standing. One deterministic check for all three: every AM-token belongs to the section, and every
299
+ // AM row in the section carries `→ test ...` OR an explicit `superseded by AM-N`.
300
+ {
301
+ // Line-scan, not regex-over-document: \Z is Python, and $-lookahead under /m matches every
302
+ // line end — a section with no FOLLOWING heading silently failed to parse (caught by the
303
+ // fixture the moment the tests ran).
304
+ const planLines = plan.split('\n');
305
+ let sectionStart = -1, sectionEnd = -1, cursor = 0;
306
+ for (const pl of planLines) {
307
+ if (sectionStart < 0 && /^##\s*Amendments\s*$/.test(pl)) sectionStart = cursor + pl.length + 1;
308
+ else if (sectionStart >= 0 && sectionEnd < 0 && /^##\s/.test(pl)) sectionEnd = cursor;
309
+ cursor += pl.length + 1;
310
+ }
311
+ if (sectionStart >= 0 && sectionEnd < 0) sectionEnd = plan.length;
312
+ const amSection = sectionStart >= 0 ? plan.slice(sectionStart, sectionEnd) : '';
313
+ // Only a DEFINITION-shaped line counts as a stray: a list item opening with the AM token.
314
+ // A mid-prose REFERENCE («per 01_requirements.md (AM-1..AM-6)», «(AM-1: the clause stays
315
+ // dropped)») cites an amendment defined in ANOTHER artifact and is legitimate — the first cut
316
+ // failed the real wave1 corpus on exactly that (caught by the standing acid test).
317
+ {
318
+ let cursor2 = 0;
319
+ for (const pl of planLines) {
320
+ const isDef = /^\s*(?:[-*|]\s*)\*{0,2}AM-(?:CP-)?\d+\b/.test(pl);
321
+ const inSection = sectionStart >= 0 && cursor2 >= sectionStart && cursor2 < sectionEnd;
322
+ if (isDef && !inSection) {
323
+ const tok = (/AM-(?:CP-)?\d+/.exec(pl) || ['AM-?'])[0];
324
+ failures.push(`C6: ${tok} is DEFINED outside the \`## Amendments\` section (line: "${pl.trim().slice(0, 80)}") — an amendment outside the section is invisible to every downstream reader`);
325
+ }
326
+ cursor2 += pl.length + 1;
327
+ }
328
+ }
329
+ if (sectionStart >= 0) {
330
+ for (const lnRaw of amSection.split('\n')) {
331
+ const ln = lnRaw.trim();
332
+ const am = /^(?:[-*|]\s*)?\*{0,2}(AM-(?:CP-)?\d+)\*{0,2}\b/.exec(ln);
333
+ if (!am) continue;
334
+ const hasTest = /\u2192\s*test\b|->\s*test\b/.test(ln) || /\u2192\s*test\b|->\s*test\b/.test(amSection.slice(amSection.indexOf(lnRaw)).split('\n').slice(0, 3).join('\n'));
335
+ const superseded = /superseded by AM-(?:CP-)?\d+/i.test(ln);
336
+ if (!hasTest && !superseded) failures.push(`C6: ${am[1]} carries neither \`\u2192 test <name>\` nor \`superseded by AM-N\` — an amendment without a confirmation is a wish, and a retracted one must say its successor`);
337
+ }
338
+ }
339
+ }
340
+
341
+ // ── C7 ADR↔plan command-name drift (seam д) — WARN, deliberately never FAIL ─────────────────────
342
+ // The measured incident: the ADR said `dz policy-sync`, the plan said `dz agents-sync` (and even
343
+ // carried a test that the OTHER alias must not exist) — whichever file the coder opened first won.
344
+ // Deterministically provable is only the SET DIFFERENCE, not which side is right, so this warns
345
+ // with both sides named and never blocks.
346
+ {
347
+ const adrAll = adrFiles.map((f) => { try { return readFileSync(join(adrDir, f), 'utf-8'); } catch { return ''; } }).join('\n');
348
+ const cmds = (t) => new Set([...t.matchAll(/`dz ([a-z][a-z0-9-]+)`/g)].map((m) => m[1]));
349
+ const inAdr = cmds(adrAll); const inPlan = cmds(plan);
350
+ const onlyAdr = [...inAdr].filter((c) => !inPlan.has(c));
351
+ const onlyPlan = [...inPlan].filter((c) => !inAdr.has(c));
352
+ if (adrAll !== '' && onlyAdr.length > 0 && onlyPlan.length > 0) {
353
+ warnings.push(`C7: ADR and plan cite DIFFERENT dz commands — ADR-only: ${onlyAdr.join(', ')}; plan-only: ${onlyPlan.join(', ')} — if these name the SAME thing, one of the two files is lying to the coder (seam д)`);
354
+ }
355
+ }
356
+
295
357
  // C5 — Inputs read line
296
358
  if (!/Inputs read:/i.test(plan)) warnings.push('C5: no "Inputs read:" line (wave-2 seam, WARN only)');
297
359
  else for (const need of ['03_adr','05_architecture']) if (!plan.includes(need)) warnings.push(`C5: Inputs read line missing ${need}`);
@@ -152,7 +152,11 @@ const BRAIN = (A.brain || REPO).replace(/\/+$/, '')
152
152
  // Helpers PIN every learn-loop command to the canonical brain: `cd <BRAIN> &&` survives a cd'd agent
153
153
  // (belt); `--project <BRAIN>` is explicit (suspenders). Either alone fixes it; together they also
154
154
  // survive the relative-vs-absolute --project resolution asymmetry between recall and teach.
155
- const DZ_RECALL = (terms) => 'cd ' + BRAIN + ' && ' + DZ + ' recall "' + terms + '" --project ' + BRAIN
155
+ // `--run fa:<SLUG>` threads the run key into the usage log, so the panel's `--recalled auto`
156
+ // counts THIS run's recall events instead of asserting a literal (the old `--recalled 3`). The
157
+ // key is the slug: a resumed run keeps accumulating under the same key, which matches how the
158
+ // panel is read — per-feature, not per-invocation.
159
+ const DZ_RECALL = (terms) => 'cd ' + BRAIN + ' && ' + DZ + ' recall "' + terms + '" --project ' + BRAIN + ' --run fa:' + SLUG
156
160
  const DZ_TEACH = (lesson, reward, domain) =>
157
161
  'cd ' + BRAIN + ' && ' + DZ + ' teach "' + lesson + '" --reward ' + reward + ' --domain ' + domain + ' --project ' + BRAIN
158
162
 
@@ -692,7 +696,7 @@ const PLANNER = (A.planner === 'codex') ? 'codex' : 'claude'
692
696
  // block threads workflow-local state (MODELS/knobs) through shapes the generic blobs do not carry;
693
697
  // regeneration via model-resolver/usage-probes/codex-dispatch blobs is the tracked Stage-B item.
694
698
  const MODELS = (A.models && typeof A.models === 'object') ? A.models : {}
695
- const KNOWN_CODEX = { 'auto': 1, 'gpt-5.5': 1, 'gpt-5.6': 1, 'gpt-5.6-sol': 1 }
699
+ const KNOWN_CODEX = { 'auto': 1, 'gpt-5.5': 1, 'gpt-5.6': 1, 'gpt-5.6-luna': 1, 'gpt-5.6-terra': 1, 'gpt-5.6-sol': 1 }
696
700
  const CLAUDE_NAMES = { fable: 1, opus: 1, sonnet: 1, haiku: 1 }
697
701
  const VALID_REASONING = { none: 1, minimal: 1, low: 1, medium: 1, high: 1, xhigh: 1 }
698
702
  const DEFAULT_MODELS = { router: 'fable', requirements: 'sonnet', research: 'sonnet', adr: 'opus', ideation: 'sonnet', ddd: 'opus', architecture: 'opus', plan: 'sonnet', code: null, qe: null, fleet: 'sonnet' }
@@ -1871,7 +1875,12 @@ async function designStage(promptText, opts, artifactPath, baseLabel) {
1871
1875
  if (res && probe && /landed=/.test(String(probe))) return { wrote: [artifactPath], summary: String(res).slice(0, 300) }
1872
1876
  log('design artifact did not land on codex (' + artifactPath + ') — falling back to Claude')
1873
1877
  const fallbackOpts = {}
1874
- return await agent(promptText, mergeOpts({ label: stageLabel((baseLabel || 'design') + ':claude-fb', fallbackOpts), phase: 'Design', schema: ARTIFACT }, fallbackOpts))
1878
+ const fb = await agent(promptText, mergeOpts({ label: stageLabel((baseLabel || 'design') + ':claude-fb', fallbackOpts), phase: 'Design', schema: ARTIFACT }, fallbackOpts))
1879
+ // d926ee89: the fallback used to keep CODEX provenance — modelsUsed, the checkpoint label and the
1880
+ // training-pair family all still said codex after Claude wrote the artifact. The WRITER is the
1881
+ // provenance; overwrite it here, at the one place that knows the fallback fired.
1882
+ if (fb && baseLabel && modelsUsed[baseLabel] !== undefined) modelsUsed[baseLabel] = modelLabel(fallbackOpts) + ' (claude-fallback after codex not-landed)'
1883
+ return fb
1875
1884
  }
1876
1885
 
1877
1886
  const ROUTER = { type: 'object', additionalProperties: false, required: ['tier', 'activeSteps', 'rationale'], properties: { tier: { type: 'string', enum: ['S', 'M', 'L', 'XL'] }, activeSteps: { type: 'array', items: { type: 'number' } }, rationale: { type: 'string' } } }
@@ -1893,7 +1902,7 @@ const ADR_FITNESS_CHECKLIST = 'ADR fitness checklist for Step 8: read every ' +
1893
1902
  // judgment cannot carry (444 tests stayed green around undefended protections, including the exact MEASURED
1894
1903
  // exploit string). Advisory, exactly like the discrimination gate: findings are HIGH gaps, never an auto-abort,
1895
1904
  // and the pipeline never blocks on the tool.
1896
- const MUTATION_GATE = 'MUTATION GATE (feature ha-mutation-gate — run alongside the discrimination gate): if a touched package carries a mutation registry (test/mutation-registry.json or mutation-registry.json), via Bash run EXACTLY `dz mutation-gate --package <that package dir> --json` and parse {results, summary, exitCode}. Any `UNDEFENDED` result = a named protection whose suite stays GREEN with the protection deleted → record a HIGH gap naming the property (advisory — the owner decides). `NOT_APPLIED` = the registry drifted from the code → HIGH gap "mutation registry drifted: <id>" (a skipped mutation proves nothing — inconclusive is never a pass). A result with `drop: true` (failing count below the recorded `observed`) is the early warning that a protection is LOSING test coverage — note it in the report before the property breaks. If this feature ADDED or FIXED a named safety property in a package that has a registry, ADD a registry entry for it (an exact {find, replace} that deletes the protection, with the measured failing count as `observed`) so the property stays machine-defended after this run. If no registry exists or `dz mutation-gate` is unavailable, note that and continue never block on the tool. Record the verdict in the 08_qe_report.md ADR Fitness section.'
1905
+ const MUTATION_GATE = 'MUTATION GATE (feature ha-mutation-gate — run alongside the discrimination gate): if a touched package carries a mutation registry (test/mutation-registry.json or mutation-registry.json), via Bash run EXACTLY `' + DZ + ' mutation-gate --package <that package dir> --json` (the PINNED workspace bin — the global `dz` on this host measurably LACKS the command: `dz --help | grep -c mutation-gate` -> 0 while the workspace dist has it, so a bare `dz` silently loses the gate) and parse {results, summary, exitCode}. Any `UNDEFENDED` result = a named protection whose suite stays GREEN with the protection deleted → record a HIGH gap naming the property (advisory — the owner decides). `NOT_APPLIED` = the registry drifted from the code → HIGH gap "mutation registry drifted: <id>" (a skipped mutation proves nothing — inconclusive is never a pass). A result with `drop: true` (failing count below the recorded `observed`) is the early warning that a protection is LOSING test coverage — note it in the report before the property breaks. If this feature ADDED or FIXED a named safety property in a package that has a registry, ADD a registry entry for it (an exact {find, replace} that deletes the protection, with the measured failing count as `observed`) so the property stays machine-defended after this run. If NO REGISTRY exists that is a clean skip — say so. But instrument-failure is NOT a skip (backlog 52d0ed08): `mutation-gate` unavailable at the pinned path, erroring, or overrunning its window → record a HIGH gap `mutation gate INCONCLUSIVE: <unavailable|error|timeout>` — an instrument that could not run proves nothing and must not read as «не применимо». Still never abort the run. Record the verdict in the 08_qe_report.md ADR Fitness section.'
1897
1906
  // no-stubs (backlog 0b403a0106103901, Karpathy-Michaels rule XI): an unfinished stub left in the
1898
1907
  // run's own touched files means the task shipped incomplete — a deterministic grep is layer 1 on the
1899
1908
  // cost-of-detection ladder; reviewer judgment is layer 4. Marker strings are ASSEMBLED so this
@@ -1902,7 +1911,7 @@ const MUTATION_GATE = 'MUTATION GATE (feature ha-mutation-gate — run alongside
1902
1911
  const STUB_RX = '(^|[^A-Za-z0-9_])(' + ['TO' + 'DO', 'FIX' + 'ME', 'HA' + 'CK', 'XX' + 'X', 'PLACE' + 'HOLDER'].join('|') + ')([^A-Za-z0-9_]|$)'
1903
1912
  const STUB_PHRASE = 'imple' + 'ment later'
1904
1913
  const NO_STUBS_GATE = 'NO-STUBS GATE (backlog 0b403a0106103901 — layer 1 of the cost-of-detection ladder): over the files THIS RUN touched (the Step-7 change list; for a Codex coder, the landed-barrier file list), via Bash run EXACTLY `grep -nE \'' + STUB_RX + '\' <touched files>` (case-SENSITIVE — never add -i) plus `grep -niE \'' + STUB_PHRASE.replace(' ', '[[:space:]]+') + '\' <touched files>`. ANY match = the task shipped incomplete → HIGH gap naming file:line, UNLESS the line carries an inline `no-stubs: <reason>` waiver WITH a non-empty reason, or `.dz/guard.json` stubWaivers lists the path WITH a reason — a REASONLESS waiver is itself a HIGH gap, never an exemption. Cross-check mechanically: `dz guard check --op publish --json` runs the same scan as the SOFT `no-stubs` rule over the working-tree diff. When you QUOTE a marker in 08_qe_report.md, backtick it so the report itself scans clean (the same convention as the claim-check forbidden-phrase escape). Record the verdict in the 08_qe_report.md ADR Fitness section.'
1905
- const DISCRIMINATION_GATE = '\u00a742 TEST-DISCRIMINATION GATE (run right after asserting the property has a test): the ADR Confirmation names `Required automated check: <test file>` for the load-bearing property. Prove that test DISCRIMINATES \u2014 via Bash run EXACTLY `dz discrimination-check --test <that test file> --base HEAD --json` (the Step-7 feature diff is UNCOMMITTED, so HEAD is the pre-feature base). Parse the JSON: read `perTest[]` (each row carries verdict + reason), `findings[]` (ALL entries, not only the first), `measurementValid`, and `primaryAction` \u2014 the singular `finding` is a DEPRECATED alias; do not consume it. The SEVEN verdicts and the required QE action for each: `DISCRIMINATES` (assertion-red at base, execution-evidenced) = PASS. `DISCRIMINATES_VIA_ERROR` (evidenced load-error at base + evidenced pass at tip) = PASS \u2014 note the inference. `NON_DISCRIMINATING` (evidenced pass at base \u2014 a proven false green) \u2192 HIGH gap "property test does not discriminate: <file>"; advisory, not an automatic blocker. `TEST_FILE_ABSENT` (the named test is not a regular file) \u2192 HIGH gap; action create-missing-test; NEVER a pass. `LOAD_ERROR_AT_BOTH_REVS` (the instrument could not execute the test at either rev \u2014 zero signal) \u2192 HIGH gap; action fix-runner-invocation. `FAILS_AT_TIP` (the feature\'s own test is red WITH the feature present) \u2192 HIGH gap; action fix-red-feature-test \u2014 grade the feature code accordingly. `CANNOT_ISOLATE` (no established observation; the row\'s `reason` is one of no-execution-evidence | unrecognised-runner-output | no-tests-executed | inconsistent-evidence | tip-control-missing | tip-evidence-missing | timeout) \u2192 HIGH gap NAMING the reason; action per `primaryAction` (map-a-test or fix-runner-invocation). `measurementValid` false or \'partial\' means the instrument did not (fully) measure \u2014 report it verbatim; never convert a degraded reading into a pass. Record every verdict + reason in the 08_qe_report.md ADR Fitness section. If `dz discrimination-check` is unavailable, note it and continue \u2014 never block on the tool.'
1914
+ const DISCRIMINATION_GATE = '\u00a742 TEST-DISCRIMINATION GATE (run right after asserting the property has a test): the ADR Confirmation names `Required automated check: <test file>` for the load-bearing property. Prove that test DISCRIMINATES \u2014 via Bash run EXACTLY `' + DZ + ' discrimination-check --test <that test file> --base HEAD --json` (the PINNED workspace bin, never bare `dz` — the global install measurably lags the workspace) (the Step-7 feature diff is UNCOMMITTED, so HEAD is the pre-feature base). Parse the JSON: read `perTest[]` (each row carries verdict + reason), `findings[]` (ALL entries, not only the first), `measurementValid`, and `primaryAction` \u2014 the singular `finding` is a DEPRECATED alias; do not consume it. The SEVEN verdicts and the required QE action for each: `DISCRIMINATES` (assertion-red at base, execution-evidenced) = PASS. `DISCRIMINATES_VIA_ERROR` (evidenced load-error at base + evidenced pass at tip) = PASS \u2014 note the inference. `NON_DISCRIMINATING` (evidenced pass at base \u2014 a proven false green) \u2192 HIGH gap "property test does not discriminate: <file>"; advisory, not an automatic blocker. `TEST_FILE_ABSENT` (the named test is not a regular file) \u2192 HIGH gap; action create-missing-test; NEVER a pass. `LOAD_ERROR_AT_BOTH_REVS` (the instrument could not execute the test at either rev \u2014 zero signal) \u2192 HIGH gap; action fix-runner-invocation. `FAILS_AT_TIP` (the feature\'s own test is red WITH the feature present) \u2192 HIGH gap; action fix-red-feature-test \u2014 grade the feature code accordingly. `CANNOT_ISOLATE` (no established observation; the row\'s `reason` is one of no-execution-evidence | unrecognised-runner-output | no-tests-executed | inconsistent-evidence | tip-control-missing | tip-evidence-missing | timeout) \u2192 HIGH gap NAMING the reason; action per `primaryAction` (map-a-test or fix-runner-invocation). `measurementValid` false or \'partial\' means the instrument did not (fully) measure \u2014 report it verbatim; never convert a degraded reading into a pass. Record every verdict + reason in the 08_qe_report.md ADR Fitness section. If `discrimination-check` is unavailable at the pinned path, errors, or overruns its window \u2192 record a HIGH gap `discrimination gate INCONCLUSIVE: <unavailable|error|timeout>` (backlog 52d0ed08: an instrument that could not run is never a pass and never applicable-by-silence). Still never abort the run.'
1906
1915
  // P2 (amendment-confirmation-discipline, fa-improvements 2026-07-18): amendments are where the SHARPEST design
1907
1916
  // corrections land (challenge-panel/QCSD) and were the least-tested — prose deltas with no proving test. Every
1908
1917
  // amendment is a mini-ADR: it carries a one-line Confirmation naming the test that falsifies it. Machine-checkable
@@ -1914,6 +1923,26 @@ const AMENDMENT_GATE = 'AMENDMENT GATE (P2): do NOT judge this yourself — RUN
1914
1923
 
1915
1924
  // Step 0: Router + MANDATORY self-learning recall
1916
1925
  phase('Router')
1926
+
1927
+ // W1 (backlog 848853a0): REPO must be the git TOPLEVEL. Both measured incidents were a REPO
1928
+ // pointing INSIDE the repository (packages/@dzhechkov/health-advisor) — artifacts then scatter
1929
+ // into features/ of a subdirectory and a sibling-worktree comparison never catches it. One cheap
1930
+ // probe, fail-closed on MISMATCH (inside a repo but not its root ⇒ refuse before any design
1931
+ // spend); a non-git dir logs loudly and continues (unusual, but not the measured failure class).
1932
+ // Canonicalization happens INSIDE the probe shell (cross-family review B-: JS-side string compare
1933
+ // would false-refuse a symlinked root) — both sides come from the same cd'd shell, `pwd -P` vs
1934
+ // rev-parse, so aliasing and spelling cancel out.
1935
+ const wrootOut = await agent('Run EXACTLY this via Bash and return its stdout VERBATIM, nothing else: cd ' + shq(REPO) + " && echo \"WROOT:$(git rev-parse --show-toplevel 2>/dev/null || echo none):HERE:$(pwd -P)\"", { label: 'router:repo-root', phase: 'Router', effort: 'low' })
1936
+ const wrootM = /WROOT:(.+):HERE:(.+)/.exec(String(wrootOut === null || wrootOut === undefined ? '' : wrootOut))
1937
+ const wrootTop = wrootM === null ? null : wrootM[1].trim()
1938
+ const wrootHere = wrootM === null ? null : wrootM[2].trim()
1939
+ let repoRootCheck = 'ok'
1940
+ if (wrootTop === null) { repoRootCheck = 'not-established'; log('repo-root probe NOT ESTABLISHED — continuing, but artifact placement is unverified') }
1941
+ else if (wrootTop === 'none') { repoRootCheck = 'non-git'; log('REPO is not a git repository (' + REPO + ') — continuing in DEGRADED mode: lineage/diff/landing checks have no git to stand on (the result carries repoRootCheck=non-git)') }
1942
+ else if (wrootTop !== wrootHere) {
1943
+ log('REPO ROOT MISMATCH: REPO canonicalizes to ' + wrootHere + ' but the git toplevel is ' + wrootTop + ' — refusing before any design spend (the measured incident class: artifacts scattered into a subdirectory features/)')
1944
+ return { phase: 'repo-root-mismatch', repo: REPO, repoCanonical: wrootHere, gitToplevel: wrootTop, cure: 'invoke with args.repo=' + wrootTop + ' (or run from the repository root)' }
1945
+ }
1917
1946
  await loadCheckpoints('Router')
1918
1947
  await usageProbe('Router')
1919
1948
  const routerTierDirective = A.tier
@@ -1974,7 +2003,7 @@ if (autoCostStages.length > 0) {
1974
2003
  // GUARANTEED fa-panel write (the router, being low-effort + multi-job, tends to skip the fa-record
1975
2004
  // Bash call). A dedicated single-command agent reliably lights up the live /feature-adr panel at the
1976
2005
  // most visible moment. Uses the workspace bin (PATH-independent). Best-effort — never blocks.
1977
- if (resumedStages.indexOf('router') === -1) await agent('Run EXACTLY this one shell command via your Bash tool and report its stdout verbatim — do nothing else, do not summarize: ' + DZ + ' statusline --fa-record --slug ' + SLUG + ' --step "Step 0 recall" --recalled 3 --stored 0 --mode ' + MODE + ' --project ' + REPO, { label: 'fa-record:step0', phase: 'Router', effort: 'low' })
2006
+ if (resumedStages.indexOf('router') === -1) await agent('Run EXACTLY this one shell command via your Bash tool and report its stdout verbatim — do nothing else, do not summarize: ' + DZ + ' statusline --fa-record --slug ' + SLUG + ' --step "Step 0 recall" --recalled auto --run fa:' + SLUG + ' --count-project ' + BRAIN + ' --stored 0 --mode ' + MODE + ' --project ' + REPO, { label: 'fa-record:step0', phase: 'Router', effort: 'low' })
1978
2007
 
1979
2008
  // R1 product-architecture-lens (ADR-001 Decision 3): forward-looking сверка of THIS feature vs the LIVE
1980
2009
  // product map + vision. NON-BLOCKING/soft by design — it LOGS {signal,confidence} so a real command
@@ -2028,8 +2057,8 @@ designThunks.push(() => subCheckpoint('requirements', reqPromptText, reqOpts, ['
2028
2057
  // the requirements prompt is where the Step-0 recalled lessons are INJECTED — recorded per pair
2029
2058
  designPairMeta.push({ role: 'design:requirements', prompt: reqPromptText, model: modelLabel(reqOpts), lessons: (LEARNED && LEARNED !== 'none recalled') ? [String(LEARNED)] : [] })
2030
2059
  if (isMplus) {
2031
- const adrPromptText = 'Step 3 (ADR + shift-left testability) of /feature-adr for "' + DESC + '" (' + SLUG + '). READ the actual code (' + CODE_HINT + ') to ground it. ' + ADR_TEMPLATE_GUIDE + ' Write ' + FDIR + '/03_adr/001-' + SLUG + '.md as a MADR-structured ADR that PASSES the Step-8 ADR fitness checklist (do NOT emit the legacy shape). Emit ALL of these sections, in order: a decision-shaped # Title (present-tense imperative verb the auto-filename tracks the feature slug, so the IMPERATIVE signal lives in the title); ## Status (proposed/accepted/rejected/deprecated/superseded + a reversibility/revisit clause); ## Context (neutral, problem-first, BEFORE the Decision); ## Decision Drivers (ranked/weighted D1, D2, …); ## Considered Options (frame the CHOSEN approach as one option ALONGSIDE the rejected ones, each with symmetric Pros:/Cons:); ## Decision (concrete/testable — exact names, versions, paths, commands); ## Rationale (map each point to a driver Dn + why the losers lost); ## Consequences (Positive + Negative/Accepted Downsides + Follow-up ADRs + After-action Review with owner + date); a REQUIRED ## Confirmation stanza with Method:, Monitoring:, Success metric:, Owner:, Load-bearing property:, and Required automated check: `<test file>` NAMING the load-bearing property that MUST have a Step-8 test (the recurring lesson: the key safety property is often the untested one); and a ## Links traceability block (requirements, driving use case, related ADRs). Add a one-line provenance note (model-generated, edited for clarity) and, for a long ADR, a top-of-file table of contents. Do NOT use an "Alternatives considered" or "Testability/shift-left" heading in place of Considered Options / Confirmation. When creating ADDITIONAL ADRs, name them 03_adr/NNN-{decision-slug}.md with a lowercase-kebab, present-tense imperative, dateless, ticketless slug. Return wrote[] + summary.' + WRITE_DISCIPLINE
2032
- designThunks.push(() => subCheckpoint('adr', adrPromptText, adrOpts, ['03_adr/001-' + SLUG + '.md'], () => designStage(adrPromptText, adrOpts, FDIR + '/03_adr/001-' + SLUG + '.md', 'adr')))
2060
+ const adrPromptText = 'Step 3 (ADR + shift-left testability) of /feature-adr for "' + DESC + '" (' + SLUG + '). READ the actual code (' + CODE_HINT + ') to ground it. ' + ADR_TEMPLATE_GUIDE + ' Write ' + FDIR + '/03_adr/001-<imperative-decision-slug>.md the SLUG IS THE DECISION in lowercase-kebab present-tense imperative (e.g. 001-pin-the-trust-root.md), NEVER the feature name (three ADR-fitness FAILs on 2026-08-17 were exactly first-ADRs named after their feature) — as a MADR-structured ADR that PASSES the Step-8 ADR fitness checklist (do NOT emit the legacy shape). Emit ALL of these sections, in order: a decision-shaped # Title (present-tense imperative verb, matching the imperative filename slug); ## Status (proposed/accepted/rejected/deprecated/superseded + a reversibility/revisit clause); ## Context (neutral, problem-first, BEFORE the Decision); ## Decision Drivers (ranked/weighted D1, D2, …); ## Considered Options (frame the CHOSEN approach as one option ALONGSIDE the rejected ones, each with symmetric Pros:/Cons:); ## Decision (concrete/testable — exact names, versions, paths, commands); ## Rationale (map each point to a driver Dn + why the losers lost); ## Consequences (Positive + Negative/Accepted Downsides + Follow-up ADRs + After-action Review with owner + date); a REQUIRED ## Confirmation stanza with Method:, Monitoring:, Success metric:, Owner:, Load-bearing property:, and Required automated check: `<test file>` NAMING the load-bearing property that MUST have a Step-8 test (the recurring lesson: the key safety property is often the untested one); and a ## Links traceability block (requirements, driving use case, related ADRs). Add a one-line provenance note (model-generated, edited for clarity) and, for a long ADR, a top-of-file table of contents. Do NOT use an "Alternatives considered" or "Testability/shift-left" heading in place of Considered Options / Confirmation. When creating ADDITIONAL ADRs, name them 03_adr/NNN-{decision-slug}.md with a lowercase-kebab, present-tense imperative, dateless, ticketless slug. Return wrote[] + summary.' + WRITE_DISCIPLINE
2061
+ designThunks.push(() => subCheckpoint('adr', adrPromptText, adrOpts, ['03_adr/'], () => designStage(adrPromptText, adrOpts, FDIR + '/03_adr/', 'adr')))
2033
2062
  designPairMeta.push({ role: 'design:adr', prompt: adrPromptText, model: modelLabel(adrOpts), lessons: [] })
2034
2063
  const qcsdPromptText = 'Step 3.5 (QCSD ideation swarm - HTSM quality criteria + SFDIPOT risk) of /feature-adr for "' + DESC + '" (' + SLUG + '). Assess quality criteria + product-factors risk. Write ' + FDIR + '/03.5_ideation_report.md with a GO/CONDITIONAL/NO-GO verdict + top quality risks for QE. On a CONDITIONAL verdict, write each condition as an amendment row in a `## Amendments` section. ' + AMENDMENT_RULE + ' Return wrote[] + summary.' + WRITE_DISCIPLINE
2035
2064
  designThunks.push(() => subCheckpoint('qcsd', qcsdPromptText, qcsdOpts, ['03.5_ideation_report.md'], () => designStage(qcsdPromptText, qcsdOpts, FDIR + '/03.5_ideation_report.md', 'qcsd')))
@@ -2047,7 +2076,7 @@ if (isMplus) {
2047
2076
  // (Codex QE #2 — a one-file probe accepted a design missing its ADR/architecture).
2048
2077
  const designHash = ckptHash('design', [tier, DESC, CODE_HINT, fnv1a64(String(LEARNED)), MODELS.requirements === undefined ? null : MODELS.requirements, MODELS.adr === undefined ? null : MODELS.adr, MODELS.ideation === undefined ? null : MODELS.ideation, MODELS.architecture === undefined ? null : MODELS.architecture, POLY.hasManifest, fnv1a64(String(POLY.report || '')), usageOverride, CODEX_MODEL])
2049
2078
  const designArtifacts = ['01_requirements.md']
2050
- if (isMplus) designArtifacts.push('03_adr/001-' + SLUG + '.md', '03.5_ideation_report.md', '05_architecture.md')
2079
+ if (isMplus) designArtifacts.push('03_adr/', '03.5_ideation_report.md', '05_architecture.md')
2051
2080
  if (isLplus) designArtifacts.push('02_research.md', '04_domain_model.md')
2052
2081
  /**
2053
2082
  * PER-SIBLING checkpoint (SP-2, no-amplification). Before 2026-08-20 the whole fan shared ONE
@@ -2124,7 +2153,7 @@ const designFan = await parallel(designThunks)
2124
2153
  // line, so it can no longer forge one — and `-f` on an exact path cannot be satisfied by a name that
2125
2154
  // merely renders like it. MEASURED red/green below in a real shell against a newline-bearing filename.
2126
2155
  let fanChecks = ''
2127
- for (const rel of designArtifacts) fanChecks += '[ -f ' + shq(rel) + ' ] && echo ' + shq('HAVE:' + rel) + '; '
2156
+ for (const rel of designArtifacts) fanChecks += (rel.endsWith('/') ? 'ls ' + shq(rel) + ' 2>/dev/null | grep -q "^001-.*\\.md$" && echo ' + shq('HAVE:' + rel) + '; ' : '[ -f ' + shq(rel) + ' ] && echo ' + shq('HAVE:' + rel) + '; ')
2128
2157
  const fanLsCmd = 'cd ' + shq(FDIR) + ' 2>/dev/null && { ' + fanChecks + 'echo ' + shq(FAN_LS_SENTINEL) + '; } || true'
2129
2158
  const fanLsOut = await agent('Run EXACTLY this via Bash and return its stdout VERBATIM (it may be empty) with NO code fences and NO commentary: ' + fanLsCmd, { label: 'design:artifact-probe', phase: 'Design', effort: 'low' })
2130
2159
  // The transcript is validated STRICTLY, not scanned. Round 7 measured the difference: an agent that
@@ -2351,6 +2380,24 @@ if (stopHere) {
2351
2380
  let challengeVerdict = null
2352
2381
  try { challengeVerdict = plan ? await runChallengePanel('features/' + SLUG + '/06_implementation_plan.md', plan.planner) : null }
2353
2382
  catch (e) { log('Challenge panel errored (advisory, ignored): ' + (e && e.message ? e.message : String(e))) }
2383
+ // Seam а (backlog 72b89e14): the panel's verdict used to reach only the OPERATOR — the
2384
+ // finding→plan-amendment bridge was manual, and on L/XL the coder runs in a SECOND invocation
2385
+ // that reads the PLAN FILE, not the first invocation's memory. So P0/P1 findings are appended to
2386
+ // the plan's ## Amendments as AM-CP-<n> rows by an effort-low agent — append-only, idempotent
2387
+ // (the marker line is checked first), each row carrying the C6-required shape.
2388
+ try {
2389
+ const cpFindings = (challengeVerdict && Array.isArray(challengeVerdict.findings))
2390
+ ? challengeVerdict.findings.filter((f) => f && (f.severity === 'P0' || f.severity === 'P1'))
2391
+ : []
2392
+ if (cpFindings.length > 0) {
2393
+ const rows = cpFindings.map((f, i) => '- AM-CP-' + (i + 1) + ' [' + f.severity + '] ' + String(f.title || '').replace(/[\r\n`]/g, ' ').slice(0, 160) + ' \u2192 test `названный кодером при реализации — заменить на имя реального теста` (panel ' + String(f.c || '') + ')').join('\n')
2394
+ const marker = '<!-- challenge-panel amendments appended ' + fnv1a64(rows) + ' -->'
2395
+ const planPath = FDIR + '/06_implementation_plan.md'
2396
+ const appendCmd = 'cd ' + shq(REPO) + ' && grep -qF ' + shq(marker) + ' ' + shq(planPath) + ' && echo CP-DUP || { grep -q "^## Amendments" ' + shq(planPath) + ' || printf "\n## Amendments\n" >> ' + shq(planPath) + '; printf "%s\n%s\n" ' + shq(marker) + ' ' + shq(rows) + ' >> ' + shq(planPath) + '; echo CP-APPENDED; }'
2397
+ const cpOut = await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + appendCmd, { label: 'challenge:append-amendments', phase: 'Plan', effort: 'low' })
2398
+ log('challenge panel \u2192 plan amendments: ' + (/CP-APPENDED/.test(String(cpOut || '')) ? cpFindings.length + ' AM-CP row(s) appended' : /CP-DUP/.test(String(cpOut || '')) ? 'already appended (idempotent)' : 'NOT appended (probe answered: ' + String(cpOut || '').slice(0, 80) + ')'))
2399
+ }
2400
+ } catch (e2) { log('challenge panel \u2192 amendments append failed (advisory): ' + (e2 && e2.message ? e2.message : String(e2))) }
2354
2401
  // P4 (checkpoint-gate-line): a DERIVED gates map — each entry comes from machine state (artifact/verdict
2355
2402
  // presence), never from prose, so a skipped gate shows as 'not-run' instead of being silently forgotten.
2356
2403
  const planGates = { plan: (plan ? 'produced' : 'missing'), planCompleteness: planGate.verdict, challengePanel: (challengeVerdict ? 'ran' : 'not-run'), code: 'not-run', qe: 'not-run' }
@@ -2384,7 +2431,7 @@ if (QE_SCOPE === 'uncommitted') {
2384
2431
 
2385
2432
  phase('Code')
2386
2433
  await usageProbe('Code')
2387
- const codePrompt = 'Step 7 (Code) of /feature-adr for "' + DESC + '" (' + SLUG + '). Implement the feature per the plan + ADR + architecture in ' + FDIR + '. Write the ACTUAL production code + its tests (mirror the closest existing implementation named in research/architecture). If the plan carries a `## Amendments` section, implement every AM-N row AND its named Confirmation test (for a safeguard amendment: a test proving it FIRES on a real input). IO-ON-PURE-PATH RULE: if your diff adds I/O (DB/network/file) to a previously-pure path — especially a startup/lifespan/health path — also write a NEGATIVE resource-down test (broken/unbound resource handle → the path degrades per its declared contract: fail-open for an advisory feature, explicit fail-fast for a load-bearing one) alongside the happy-path test; never fix a failing test by swapping a broken fixture for a healthy one without keeping BOTH cases. Follow repo conventions; build must pass. Write a change manifest ' + FDIR + '/07_code_changes/change_manifest.md listing every file touched. Return wrote[] (incl. real source files) + summary.' + ABSOLUTE_PATH_NOTE + PS_GUIDANCE('code')
2434
+ const codePrompt = 'Step 7 (Code) of /feature-adr for "' + DESC + '" (' + SLUG + '). READ THESE INPUTS FIRST, by name (0691e163: the coder used to get one directory pointer; measured over three real runs, the plan was opened by all coders but the ADR unevenly and requirements/domain model not at all): ' + FDIR + '/06_implementation_plan.md (the tasks + EXPECTED_CODE_TARGETS + Amendments), every ' + FDIR + '/03_adr/NNN-*.md (each names a load-bearing property and its Required automated check), ' + FDIR + '/05_architecture.md, ' + FDIR + '/01_requirements.md, and ' + FDIR + '/04_domain_model.md when present (L/XL). Then implement the feature. Write the ACTUAL production code + its tests (mirror the closest existing implementation named in research/architecture). If the plan carries a `## Amendments` section, implement every AM-N row AND its named Confirmation test (for a safeguard amendment: a test proving it FIRES on a real input). IO-ON-PURE-PATH RULE: if your diff adds I/O (DB/network/file) to a previously-pure path — especially a startup/lifespan/health path — also write a NEGATIVE resource-down test (broken/unbound resource handle → the path degrades per its declared contract: fail-open for an advisory feature, explicit fail-fast for a load-bearing one) alongside the happy-path test; never fix a failing test by swapping a broken fixture for a healthy one without keeping BOTH cases. Follow repo conventions; build must pass. Write a change manifest ' + FDIR + '/07_code_changes/change_manifest.md listing every file touched. Return wrote[] (incl. real source files) + summary.' + ABSOLUTE_PATH_NOTE + PS_GUIDANCE('code')
2388
2435
  // Resolve the coder model. args.models.code wins (a direct 'codex' spec = codex-first); else the legacy
2389
2436
  // CODER knob drives it (with its codex-fallback null-guard). resolveStageModel('code') folds both via the
2390
2437
  // code:null sentinel → resolveCoderSpec(). A Claude resolution merges {model} onto the Claude branch;
@@ -2475,7 +2522,7 @@ if (needsCodeLandedBarrier(coderUsed)) {
2475
2522
  const targetsLine = 'Expected code targets (' + (sourcing.establishedBy === null ? 'NONE ESTABLISHED, reason=' + sourcing.reason : 'established by ' + sourcing.establishedBy) + '): ' + (barrierPlan.expectedPaths.length ? barrierPlan.expectedPaths.join(', ') : '(none)')
2476
2523
  const scrapeLine = scrapeDiagnostic.length ? '\ncodex-self-declared (diagnostic, not matched): ' + scrapeDiagnostic.join(', ') : ''
2477
2524
  if (landingStatus === 'landed') {
2478
- landedNote = '\n\nCODEX-CODED (out-of-band): Step 7.5 landing barrier verdict LANDED (mode=' + barrierPlan.mode + ', window=' + barrierPlan.pollWindowSeconds + 's). Read the listed files and do NOT report "Step 7 never ran".\n' + targetsLine + scrapeLine + '\n' + probeText
2525
+ landedNote = '\n\nCODEX-CODED (out-of-band): Step 7.5 landing barrier verdict LANDED (mode=' + barrierPlan.mode + ', window=' + barrierPlan.pollWindowSeconds + 's). Treat the verdict as EVIDENCE, not authority: read the listed files and CHECK the declared-target diff yourself if it is empty, SAY SO and grade accordingly (a barrier can be wrong; your own reading outranks it).\n' + targetsLine + scrapeLine + '\n' + probeText
2479
2526
  } else if (landingStatus === 'genuinely-not-landed') {
2480
2527
  landedNote = '\n\nCODEX-CODED (out-of-band): Step 7.5 landing barrier verdict GENUINELY-NOT-LANDED — the bounded ' + barrierPlan.pollWindowSeconds + 's window found no newly-changed expected target.\n' + targetsLine + scrapeLine + '\n' + probeText
2481
2528
  } else {
@@ -2504,8 +2551,48 @@ if (resumedStages.indexOf('code') !== -1 && landedNote !== '') {
2504
2551
 
2505
2552
  // Step 8: QE (brutal-honesty, agentic-qe) + MANDATORY teach
2506
2553
  phase('QE')
2554
+
2555
+ // ── Writer-quiescence probe (feature qe-writer-quiescence, backlog 700b46a4) ─────────────────────
2556
+ // Step-8 used to grade a MOVING tree (crossrt-1: a background worker wrote AFTER the verdict,
2557
+ // clobbering a file the round had just written). A BELT, not the root (worktree isolation is
2558
+ // 9520e506): the probe NEVER blocks — a moving/inconclusive tree loudly downgrades the verdict's
2559
+ // standing. Inline byte-mirror of harness-core/src/writer-quiescence.ts:decideWriterQuiescence;
2560
+ // the drift test asserts this body matches the module.
2561
+ function decideWriterQuiescence(probeText, requiredQuiet) {
2562
+ const need = requiredQuiet === undefined ? 3 : requiredQuiet
2563
+ const text = probeText === null || probeText === undefined ? '' : String(probeText)
2564
+ const windows = []
2565
+ for (const line of text.split(/\r?\n/)) {
2566
+ const m = /WQ-WINDOW\s+\d+\s+changed=(\d+|ERR)/.exec(line)
2567
+ if (m) windows.push(m[1] === 'ERR' ? -1 : Number(m[1]))
2568
+ }
2569
+ if (windows.length === 0) {
2570
+ return { verdict: 'inconclusive', windows: windows, note: 'quiescence probe returned no windows — grading standing NOT established (probe failure is never quiet)' }
2571
+ }
2572
+ let streak = 0
2573
+ for (const c of windows) {
2574
+ streak = c === 0 ? streak + 1 : 0
2575
+ if (streak >= need) {
2576
+ return { verdict: 'quiet', windows: windows, note: 'no observed writes in ' + need + ' consecutive windows (mtime evidence only — not a writer-lifecycle guarantee)' }
2577
+ }
2578
+ }
2579
+ if (windows.some((c) => c < 0)) {
2580
+ return { verdict: 'inconclusive', windows: windows, note: 'quiescence probe could not observe every window (find errored) — grading standing NOT established' }
2581
+ }
2582
+ return { verdict: 'moving', windows: windows, note: 'tree is MOVING: no ' + need + ' consecutive quiet windows within budget (per-window changed counts: ' + windows.join(',') + ') — the verdict below was graded on a moving tree and must say so' }
2583
+ }
2584
+
2585
+ const wqPaths = [FDIR].concat(Array.isArray(expectedTargets) ? expectedTargets : []).filter((p) => typeof p === 'string' && p !== '' && p.indexOf("'") < 0 && p.charAt(0) !== '-')
2586
+ const wqTargets = wqPaths.map((p) => "'" + p + "'").join(' ')
2587
+ const wqScript = 'cd ' + shq(REPO) + ' && quiet=0; n=0; while [ $n -lt 9 ]; do n=$((n+1)); sleep 20; out=$(find ' + wqTargets + " -type f -newermt '-25 seconds' 2>&1 >/tmp/wq-list.$$); st=$?; if [ $st -ne 0 ] || [ -n \"$out\" ]; then c=ERR; else c=$(wc -l < /tmp/wq-list.$$); fi; rm -f /tmp/wq-list.$$; echo \"WQ-WINDOW $n changed=$c\"; if [ \"$c\" = \"0\" ]; then quiet=$((quiet+1)); if [ $quiet -ge 3 ]; then echo \"WQ-DONE quiet\"; exit 0; fi; else quiet=0; fi; done; echo \"WQ-DONE budget\""
2588
+ const wqProbe = await agent('Run EXACTLY this via Bash and return its stdout VERBATIM with NO commentary (it takes ~1-3 minutes of sleeping; that is the point): ' + wqScript, { label: 'qe:writer-quiescence', phase: 'QE', effort: 'low' })
2589
+ const writerQuiescence = decideWriterQuiescence(wqProbe)
2590
+ log('Step 8 writer-quiescence: ' + writerQuiescence.verdict + ' (windows: ' + (writerQuiescence.windows.join(',') || 'none') + ')')
2591
+ const wqNote = writerQuiescence.verdict === 'quiet'
2592
+ ? ' WRITER-QUIESCENCE: quiet (' + writerQuiescence.note + ').'
2593
+ : ' WRITER-QUIESCENCE GATE (MANDATORY to acknowledge): ' + writerQuiescence.note + ' State this standing explicitly in 08_qe_report.md next to the grade.'
2507
2594
  await usageProbe('QE')
2508
- const qePrompt = 'Step 8 (QE - brutal-honesty review, agentic-qe) of /feature-adr for "' + DESC + '" (' + SLUG + '). Adversarially review the SHIPPED code (read it): correctness, edge cases, error handling, and the LOAD-BEARING property the ADR named (ASSERT it has a test that DISCRIMINATES - the recurring lesson: a test that would still pass with the protection deleted is documentation, not a gate). Run this ADR gate before final grading: ' + ADR_FITNESS_CHECKLIST + ' ' + DISCRIMINATION_GATE + ' ' + MUTATION_GATE + ' ' + NO_STUBS_GATE + ' ' + AMENDMENT_GATE + ' Grade A/B/C/D honestly. Assess code-test adequacy + doc-test presence. List CONFIRMED gaps with severity. Write ' + FDIR + '/08_qe_report.md with an ADR Fitness Checklist section showing PASS/FAIL per ADR and evidence for the Confirmation-linked test. MANDATORY SELF-LEARNING STORE (close the loop, never skip): compare every candidate lesson against the Step-0 recalled LEARNED patterns above. Teach ONLY lessons NOT covered by Step-0 recall. On overlap, run `dz teach --reinforce "<recalled pattern id or exact text>" --project ' + BRAIN + '` instead of minting a near-duplicate; if --reinforce is unavailable, skip the duplicate teach and report `reinforced existing pattern <id>` in the QE report. Store every genuinely new lesson in the CANONICAL BRAIN store at `' + BRAIN + '` so it is NOT lost to a target repo you may have cd`d into. Via Bash run EXACTLY `' + DZ_TEACH('<a durable reusable lesson from this feature - a rule/pattern/pitfall, NOT a checkpoint echo>', '<0.7-0.95>', '<area>') + '` for each genuine NEW lesson (1-3 max, high-signal) — the `cd ' + BRAIN + ' &&` prefix + `--project ' + BRAIN + '` pin guarantee the lesson lands in the brain regardless of your CWD. Then run `' + DZ + ' statusline --fa-record --slug ' + SLUG + ' --step "Step 8 QE" --recalled 3 --stored <count taught> --reinforced <count reinforced> --mode ' + MODE + ' --project ' + REPO + '` (run it verbatim via Bash, do not skip). Do NOT teach trivia or invent gaps. AUTHORING-TIME CLAIM-CHECK (Deliverable of claim-check-authoring-time): after writing ' + FDIR + '/08_qe_report.md, run EXACTLY `dz claim-check ' + FDIR + '/08_qe_report.md --json --fail-on none` via Bash, parse the {ok, findings, scanned} JSON, and report claimCheck: {findings: N, high: N, medium: N} (counts by severity) in your return object. TAG EVERY QUANTITATIVE CLAIM you write in the report using the convention the checker recognizes as honest — write "1131 tests pass (MEASURED — `npx vitest run`)", never a bare "1131 tests pass" — and where you QUOTE a forbidden phrase as an example (e.g. the retracted "100% passing" framing), backtick the literal so it reads as code, not an assertion, so your own compliant report scans clean. Return {grade, gaps, codeTestsAdequate, docTestsPresent, claimCheck}.' + ABSOLUTE_PATH_NOTE + landedNote + PS_GUIDANCE('qe')
2595
+ const qePrompt = 'Step 8 (QE - brutal-honesty review, agentic-qe) of /feature-adr for "' + DESC + '" (' + SLUG + '). Adversarially review the SHIPPED code (read it): correctness, edge cases, error handling, and the LOAD-BEARING property the ADR named (ASSERT it has a test that DISCRIMINATES - the recurring lesson: a test that would still pass with the protection deleted is documentation, not a gate). Run this ADR gate before final grading: ' + ADR_FITNESS_CHECKLIST + ' ' + DISCRIMINATION_GATE + ' ' + MUTATION_GATE + ' ' + NO_STUBS_GATE + ' ' + AMENDMENT_GATE + ' Grade A/B/C/D honestly. Assess code-test adequacy + doc-test presence. List CONFIRMED gaps with severity. Write ' + FDIR + '/08_qe_report.md with an ADR Fitness Checklist section showing PASS/FAIL per ADR and evidence for the Confirmation-linked test. MANDATORY SELF-LEARNING STORE (close the loop, never skip): compare every candidate lesson against the Step-0 recalled LEARNED patterns above. Teach ONLY lessons NOT covered by Step-0 recall. On overlap, run `dz teach --reinforce "<recalled pattern id or exact text>" --project ' + BRAIN + '` instead of minting a near-duplicate; if --reinforce is unavailable, skip the duplicate teach and report `reinforced existing pattern <id>` in the QE report. Store every genuinely new lesson in the CANONICAL BRAIN store at `' + BRAIN + '` so it is NOT lost to a target repo you may have cd`d into. Via Bash run EXACTLY `' + DZ_TEACH('<a durable reusable lesson from this feature - a rule/pattern/pitfall, NOT a checkpoint echo>', '<0.7-0.95>', '<area>') + '` for each genuine NEW lesson (1-3 max, high-signal) — the `cd ' + BRAIN + ' &&` prefix + `--project ' + BRAIN + '` pin guarantee the lesson lands in the brain regardless of your CWD. Then run `' + DZ + ' statusline --fa-record --slug ' + SLUG + ' --step "Step 8 QE" --recalled auto --run fa:' + SLUG + ' --count-project ' + BRAIN + ' --stored <count taught> --reinforced <count reinforced> --mode ' + MODE + ' --project ' + REPO + '` (run it verbatim via Bash, do not skip). Do NOT teach trivia or invent gaps. AUTHORING-TIME CLAIM-CHECK (Deliverable of claim-check-authoring-time): after writing ' + FDIR + '/08_qe_report.md, run EXACTLY `dz claim-check ' + FDIR + '/08_qe_report.md --json --fail-on none` via Bash, parse the {ok, findings, scanned} JSON, and report claimCheck: {findings: N, high: N, medium: N} (counts by severity) in your return object. TAG EVERY QUANTITATIVE CLAIM you write in the report using the convention the checker recognizes as honest — write "1131 tests pass (MEASURED — `npx vitest run`)", never a bare "1131 tests pass" — and where you QUOTE a forbidden phrase as an example (e.g. the retracted "100% passing" framing), backtick the literal so it reads as code, not an assertion, so your own compliant report scans clean. Return {grade, gaps, codeTestsAdequate, docTestsPresent, claimCheck}.' + ABSOLUTE_PATH_NOTE + landedNote + wqNote + PS_GUIDANCE('qe')
2509
2596
  // CROSS-MODEL QE (load-bearing): resolveStageModel('qe') derives the OTHER family than the resolved
2510
2597
  // coder when args.models.qe is unset (coder-codex ⇒ opus; coder-Claude ⇒ codex, or opus if codex absent).
2511
2598
  // An explicit args.models.qe wins. A Claude qe spec is merged onto the qe-code-reviewer base (role
@@ -2646,7 +2733,7 @@ if (qe === null && (qeIsCodex || QE_REVIEWER === 'codex-fallback')) {
2646
2733
  // with no 08_qe_report.md at all. Named by cross-family review of b6973199. The verdict itself
2647
2734
  // is real (Codex produced it), so a failed transcription DEGRADES the run rather than voiding
2648
2735
  // it — but it must be visible, and it must never read as a clean QE.
2649
- const scribePrompt = 'Step 8 (QE) of /feature-adr for "' + DESC + '" (' + SLUG + '). The independent cross-family review has ALREADY BEEN DONE, by Codex. You are the SCRIBE, not the reviewer: RECORD it, do NOT re-grade it, do NOT soften it, do NOT add a verdict of your own, and do NOT mark anything resolved that the reviewer flagged. The grade is ' + codexQe.grade + ' and it is FINAL.\n\nWrite ' + FDIR + '/08_qe_report.md with: (1) the grade ' + codexQe.grade + ' stated verbatim; (2) HOW it was obtained — dispatch mode ' + codexQe.mode + ', scope ' + codexQe.scopeRef + ', wall-clock ' + codexQe.elapsedSeconds + 's, gradeSource ' + codexQe.gradeSource + ' (a DERIVED grade means the reviewer could not be asked for a letter and it was computed from the severities it reported — say so plainly); (3) the reviewer text below, verbatim, as the findings section; (4) an ADR Fitness Checklist section with PASS/FAIL per ADR and the evidence pointer for the Confirmation-linked test.\n\nREVIEWER TEXT (verbatim, do not edit or summarise):\n' + String(codexQe.text) + '\n\nMANDATORY SELF-LEARNING STORE (close the loop, never skip): compare candidate lessons against the Step-0 recalled LEARNED patterns. Teach ONLY lessons NOT already covered; on overlap run `dz teach --reinforce "<recalled pattern id or exact text>" --project ' + BRAIN + '` instead of minting a near-duplicate. Via Bash run EXACTLY `' + DZ_TEACH('<a durable reusable lesson from this feature - a rule/pattern/pitfall, NOT a checkpoint echo>', '<0.7-0.95>', '<area>') + '` for each genuine NEW lesson (1-3 max, high-signal). Then run `' + DZ + ' statusline --fa-record --slug ' + SLUG + ' --step "Step 8 QE" --recalled 3 --stored <count taught> --reinforced <count reinforced> --mode ' + MODE + ' --project ' + REPO + '` verbatim via Bash. Finally run EXACTLY `dz claim-check ' + FDIR + '/08_qe_report.md --json --fail-on none` via Bash and TAG every quantitative claim you write the way the checker recognises as honest.' + ABSOLUTE_PATH_NOTE
2736
+ const scribePrompt = 'Step 8 (QE) of /feature-adr for "' + DESC + '" (' + SLUG + '). The independent cross-family review has ALREADY BEEN DONE, by Codex. You are the SCRIBE, not the reviewer: RECORD it, do NOT re-grade it, do NOT soften it, do NOT add a verdict of your own, and do NOT mark anything resolved that the reviewer flagged. The grade is ' + codexQe.grade + ' and it is FINAL.\n\nWrite ' + FDIR + '/08_qe_report.md with: (1) the grade ' + codexQe.grade + ' stated verbatim; (2) HOW it was obtained — dispatch mode ' + codexQe.mode + ', scope ' + codexQe.scopeRef + ', wall-clock ' + codexQe.elapsedSeconds + 's, gradeSource ' + codexQe.gradeSource + ' (a DERIVED grade means the reviewer could not be asked for a letter and it was computed from the severities it reported — say so plainly); (3) the reviewer text below, verbatim, as the findings section; (4) an ADR Fitness Checklist section with PASS/FAIL per ADR and the evidence pointer for the Confirmation-linked test.\n\nREVIEWER TEXT (verbatim, do not edit or summarise):\n' + String(codexQe.text) + '\n\nMANDATORY SELF-LEARNING STORE (close the loop, never skip): compare candidate lessons against the Step-0 recalled LEARNED patterns. Teach ONLY lessons NOT already covered; on overlap run `dz teach --reinforce "<recalled pattern id or exact text>" --project ' + BRAIN + '` instead of minting a near-duplicate. Via Bash run EXACTLY `' + DZ_TEACH('<a durable reusable lesson from this feature - a rule/pattern/pitfall, NOT a checkpoint echo>', '<0.7-0.95>', '<area>') + '` for each genuine NEW lesson (1-3 max, high-signal). Then run `' + DZ + ' statusline --fa-record --slug ' + SLUG + ' --step "Step 8 QE" --recalled auto --run fa:' + SLUG + ' --count-project ' + BRAIN + ' --stored <count taught> --reinforced <count reinforced> --mode ' + MODE + ' --project ' + REPO + '` verbatim via Bash. Finally run EXACTLY `dz claim-check ' + FDIR + '/08_qe_report.md --json --fail-on none` via Bash and TAG every quantitative claim you write the way the checker recognises as honest.' + ABSOLUTE_PATH_NOTE
2650
2737
  // WITNESS THE REWRITE, not the existence. On a re-QE or a resume with the same slug an OLD
2651
2738
  // 08_qe_report.md is already sitting there, and an existence probe reports that stale file as
2652
2739
  // landed — so a scribe that wrote nothing still marked the new verdict recorded, and the stage
@@ -2962,14 +3049,29 @@ if (DELIVERY_ON) {
2962
3049
  // L/XL checkpoint-after-plan return above — no code has landed there yet.)
2963
3050
  await agent('Run EXACTLY this one shell command via your Bash tool and report its stdout verbatim — do nothing else, do not summarize: cd ' + REPO + ' && ' + DZ + ' architecture --json > architecture/map.json && echo arch-map-updated', { label: 'arch-map:refresh', phase: (isLplus ? 'FleetQE' : 'QE'), effort: 'low' })
2964
3051
 
2965
- const tags = ['FEATURE_ADR_ROUTED', 'FEATURE_ADR_DESIGNED', 'FEATURE_ADR_PLANNED', 'FEATURE_ADR_IMPLEMENTED', 'FEATURE_ADR_VERIFIED']
2966
- if (isLplus) tags.push('FEATURE_ADR_FLEET_VERIFIED')
3052
+ // W4 (backlog 848853a0, the carrier defect): promise tags used to be STAMPED unconditionally —
3053
+ // two consecutive runs with an EMPTY Step 7 were tagged «implemented». A tag is now EARNED by its
3054
+ // stage's evidence; missing evidence emits <TAG>_INCOMPLETE (the promise-system convention the
3055
+ // reward rules already price at ≤0.3). The checkpoint/landing machinery guards RESUME; this guards
3056
+ // the CLAIM.
3057
+ function earnedTag(name, earned) { return earned ? name : name + '_INCOMPLETE' }
3058
+ const designEvidence = Array.isArray(design) && design.filter(Boolean).length > 0
3059
+ const implementedEvidence = code !== null && code !== undefined && (needsCodeLandedBarrier(coderUsed) ? landingStatus === 'landed' : true)
3060
+ const tags = [
3061
+ earnedTag('FEATURE_ADR_ROUTED', router !== null && router !== undefined),
3062
+ earnedTag('FEATURE_ADR_DESIGNED', designEvidence),
3063
+ earnedTag('FEATURE_ADR_PLANNED', plan !== null && plan !== undefined),
3064
+ earnedTag('FEATURE_ADR_IMPLEMENTED', implementedEvidence),
3065
+ earnedTag('FEATURE_ADR_VERIFIED', qe !== null && qe !== undefined && typeof qe.grade === 'string' && qe.grade !== ''),
3066
+ ]
3067
+ if (isLplus) tags.push(earnedTag('FEATURE_ADR_FLEET_VERIFIED', fleet !== null && fleet !== undefined))
2967
3068
  // QE-D#3/#4: the promise tag asserts the gate RAN AND its review doc LANDED — an errored gate or a missing
2968
3069
  // artifact must not claim it (a promise about a file that does not exist is exactly a fabricated completeness).
2969
3070
  if (delivery && delivery.artifact === 'written' && delivery.handoff !== 'errored') tags.push('FEATURE_ADR_DELIVERY_GATED')
2970
3071
  await appendRunCostRow('full', (isLplus ? 'FleetQE' : 'QE'))
2971
3072
  return {
2972
3073
  slug: SLUG, tier: tier, mode: MODE, artifactsDir: FDIR,
3074
+ repoRootCheck: repoRootCheck,
2973
3075
  design: design.filter(Boolean).map((d) => d.wrote).flat(),
2974
3076
  codeWrote: code ? code.wrote : [],
2975
3077
  qeGrade: qe ? qe.grade : null,