@dzhechkov/skills-feature-adr 1.5.1 → 1.5.3
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 +7 -31
- package/README.md +34 -1
- package/package.json +1 -1
- package/sbom.json +6 -66
- package/templates/.claude/skills/feature-adr/SKILL.md +24 -0
- package/templates/.claude/skills/feature-adr/modules/06-implementation-plan.md +5 -0
- package/templates/.claude/skills/feature-adr/scripts/check-plan-completeness.mjs +85 -0
- package/templates/.claude/workflows/feature-adr.js +191 -21
package/.dz-manifest.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"path": "README.md",
|
|
16
|
-
"sha256": "
|
|
16
|
+
"sha256": "1e3c1a4ed97981b70a3a5099f6355556fc987e493bf478b82cabe552888d2583"
|
|
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": "
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"path": "scripts/sync-templates.js",
|
|
32
|
-
"sha256": "790d53c2e98a4b9aa589c72c6394c0e3aeb6b99d4a0bade4c881d2a8d11cf12c"
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"path": "sources.json",
|
|
36
|
-
"sha256": "2e7f84f7fa7e97aa38514a94e920747e648a6f2ad0a503fc50e7519658116065"
|
|
28
|
+
"sha256": "2224692687da750844ef25096d1fc41fc86d7e79743ac7a8f42bf51ba9018750"
|
|
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": "
|
|
116
|
+
"sha256": "67eb730c815e6b8f9b9b61084c67ceb85440548f00b21f52187d16e81e3d7af8"
|
|
125
117
|
},
|
|
126
118
|
{
|
|
127
119
|
"path": "templates/.claude/skills/feature-adr/examples/sample-feature-output.md",
|
|
@@ -157,7 +149,7 @@
|
|
|
157
149
|
},
|
|
158
150
|
{
|
|
159
151
|
"path": "templates/.claude/skills/feature-adr/modules/06-implementation-plan.md",
|
|
160
|
-
"sha256": "
|
|
152
|
+
"sha256": "b3efdfa1d44f4c51ff46551b5827066955c1f0bfee212d371ac1e2fb1c99980f"
|
|
161
153
|
},
|
|
162
154
|
{
|
|
163
155
|
"path": "templates/.claude/skills/feature-adr/modules/07-code.md",
|
|
@@ -257,7 +249,7 @@
|
|
|
257
249
|
},
|
|
258
250
|
{
|
|
259
251
|
"path": "templates/.claude/skills/feature-adr/scripts/check-plan-completeness.mjs",
|
|
260
|
-
"sha256": "
|
|
252
|
+
"sha256": "eddf116620050e80b88497ace026fb8e0a12986f94f216181480c7daa0e08be4"
|
|
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": "
|
|
316
|
+
"sha256": "a4e277040b42646d9bc1b06f78e32aa11f118a924f1626be67640f87ede8b292"
|
|
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": "
|
|
328
|
+
"signature": "uUN7yuKgSOed/hE0xooh1K788NcAFhnvsh92AHcu4oHn/uMZurn0VnktfhsUDHUv3jR8F6bMoKgrb/F6dHrvCQ=="
|
|
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.
|
|
172
|
+
### The Step-8 amendment gate runs a command instead of judging (v1.5.3)
|
|
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.
|
|
@@ -1107,3 +1107,36 @@ structure chosen before understanding, and section-per-edit raises tool-call ove
|
|
|
1107
1107
|
|
|
1108
1108
|
Steps whose deliverable is a returned verdict rather than a document (the complexity router) are
|
|
1109
1109
|
deliberately excluded — "skeleton first" is nonsense there.
|
|
1110
|
+
|
|
1111
|
+
|
|
1112
|
+
---
|
|
1113
|
+
|
|
1114
|
+
## Status
|
|
1115
|
+
|
|
1116
|
+
`1.5.3` — **the workflow stops crashing on the way into Step 7.** `1.5.3` shipped a workflow that
|
|
1117
|
+
CALLED three helpers it never defined — `changeSetProbeCmd`, `parseHashProbe`, `changedFromHashes`
|
|
1118
|
+
(5 call sites, 0 definitions). `QE_SCOPE` defaults to `uncommitted`, so the guarded branch was true
|
|
1119
|
+
by default and every ultracode run that reached the coding step died with a `ReferenceError` while
|
|
1120
|
+
the corresponding unit tests stayed green — they exercise the exported module, the pipeline runs an
|
|
1121
|
+
inline mirror of it. The three are now restored FROM that canonical export, not reconstructed, and
|
|
1122
|
+
two guards make the class visible: the mirror is checked by lifting each function out of the shipped
|
|
1123
|
+
file and comparing its BEHAVIOUR case-by-case against the export, and a new layer-1 test parses the
|
|
1124
|
+
workflow and asserts every referenced identifier is declared or is one of the eight documented
|
|
1125
|
+
sandbox globals.
|
|
1126
|
+
|
|
1127
|
+
Also in this release, both halves of the K2 plan-completeness gate that field use found:
|
|
1128
|
+
|
|
1129
|
+
- **C6 scopes each amendment to its own block** — from its `AM-N` line to the line where the next one
|
|
1130
|
+
begins. The old three-line window refused amendments whose `→ test` marker sat on a `Confirmation:`
|
|
1131
|
+
line further down (measured: line 3 passes, line 4 fails), and — worse, and not reported — it
|
|
1132
|
+
PASSED a testless amendment that happened to sit next to a tested one, which borrowed its
|
|
1133
|
+
neighbour's marker. The plural `→ tests \`a\` and \`b\`` form is now matched, and a wrapped
|
|
1134
|
+
`AM-1..AM-4;` range no longer opens a phantom amendment. Measured over 142 plans: 402 C6 failures
|
|
1135
|
+
before, 190 after, **0 plans newly failing**. The Step-6 planner prompt now states that the row is
|
|
1136
|
+
machine-read and where the marker must sit.
|
|
1137
|
+
- **The K2 gate stops guessing the workspace.** When the workspace was not pinned it was taken from
|
|
1138
|
+
the gate agent's own working directory, which against an external target repo resolved to that
|
|
1139
|
+
repo — so the workspace candidate silently became a duplicate of the repo candidate and a skill
|
|
1140
|
+
installed in the workspace was never found (`NOT-ESTABLISHED`, exit 3, the coding step never ran).
|
|
1141
|
+
New `args.workspace` pins it, the shipped call site passes it, each candidate is now labelled in the
|
|
1142
|
+
audit line, and a `K2_GATE_NOTE` fires when the two collapse onto one path.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/skills-feature-adr",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
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": "
|
|
38
|
+
"content": "1e3c1a4ed97981b70a3a5099f6355556fc987e493bf478b82cabe552888d2583"
|
|
39
39
|
}
|
|
40
40
|
]
|
|
41
41
|
},
|
|
@@ -65,27 +65,7 @@
|
|
|
65
65
|
"hashes": [
|
|
66
66
|
{
|
|
67
67
|
"alg": "SHA-256",
|
|
68
|
-
"content": "
|
|
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": "2224692687da750844ef25096d1fc41fc86d7e79743ac7a8f42bf51ba9018750"
|
|
89
69
|
}
|
|
90
70
|
]
|
|
91
71
|
},
|
|
@@ -305,7 +285,7 @@
|
|
|
305
285
|
"hashes": [
|
|
306
286
|
{
|
|
307
287
|
"alg": "SHA-256",
|
|
308
|
-
"content": "
|
|
288
|
+
"content": "67eb730c815e6b8f9b9b61084c67ceb85440548f00b21f52187d16e81e3d7af8"
|
|
309
289
|
}
|
|
310
290
|
]
|
|
311
291
|
},
|
|
@@ -395,7 +375,7 @@
|
|
|
395
375
|
"hashes": [
|
|
396
376
|
{
|
|
397
377
|
"alg": "SHA-256",
|
|
398
|
-
"content": "
|
|
378
|
+
"content": "b3efdfa1d44f4c51ff46551b5827066955c1f0bfee212d371ac1e2fb1c99980f"
|
|
399
379
|
}
|
|
400
380
|
]
|
|
401
381
|
},
|
|
@@ -645,7 +625,7 @@
|
|
|
645
625
|
"hashes": [
|
|
646
626
|
{
|
|
647
627
|
"alg": "SHA-256",
|
|
648
|
-
"content": "
|
|
628
|
+
"content": "eddf116620050e80b88497ace026fb8e0a12986f94f216181480c7daa0e08be4"
|
|
649
629
|
}
|
|
650
630
|
]
|
|
651
631
|
},
|
|
@@ -805,7 +785,7 @@
|
|
|
805
785
|
"hashes": [
|
|
806
786
|
{
|
|
807
787
|
"alg": "SHA-256",
|
|
808
|
-
"content": "
|
|
788
|
+
"content": "a4e277040b42646d9bc1b06f78e32aa11f118a924f1626be67640f87ede8b292"
|
|
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:
|
|
@@ -185,6 +185,11 @@ Create `features/<slug>/06_implementation_plan.md` with:
|
|
|
185
185
|
- `## Amendments` — every correction folded into this plan (a Step-3.5 CONDITIONAL condition, a
|
|
186
186
|
challenge-panel confirmed finding, a user checkpoint steer) as a fixed-shape row:
|
|
187
187
|
`AM-N (source): <change>. Confirmation: <property> → test `test_name` (fails if reverted).`
|
|
188
|
+
The row is MACHINE-READ by the K2 C6 gate: the `→ test \`name\`` marker (or `superseded by AM-N`)
|
|
189
|
+
must sit inside that amendment's OWN block — anywhere between its `AM-N` line and the line where
|
|
190
|
+
the NEXT `AM-N` begins. Multi-line amendments are fine; what is NOT fine is putting a marker after
|
|
191
|
+
the following amendment has already started, because it then belongs to that one. A bare range
|
|
192
|
+
like `AM-1..AM-4` never opens a row.
|
|
188
193
|
A safeguard amendment's named test must prove it TRIGGERS on a real input, not merely that its code
|
|
189
194
|
path exists. Step 8 verifies every named test exists and is non-vacuous (`dz discrimination-check`).
|
|
190
195
|
|
|
@@ -292,6 +292,91 @@ 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
|
+
// Each amendment's confirmation is looked for in ITS OWN BLOCK: from its definition line to the
|
|
331
|
+
// line where the NEXT amendment begins. Three defects die with the old three-line window
|
|
332
|
+
// (MEASURED 2026-08-25 against the 142-plan corpus — 292 C6 failures before, 33 after, and ZERO
|
|
333
|
+
// rows newly caught that pass today):
|
|
334
|
+
// • FALSE REFUSAL — a marker on the `Confirmation:` line 4+ lines down was invisible, so plans
|
|
335
|
+
// that DID name their test were rejected (7 per run, twice in one day, on a user's machine).
|
|
336
|
+
// • FALSE PASS — the window is three LINES, not one amendment, so `- AM-1 testless` followed by
|
|
337
|
+
// `- AM-2 … -> test x` PASSED: AM-1 borrowed its neighbour's marker. That is the safety half,
|
|
338
|
+
// and it is why the boundary is the next DEFINITION rather than a blank line — a blank line
|
|
339
|
+
// does not separate adjacent bullet rows.
|
|
340
|
+
// • A substring seek (`amSection.indexOf(lnRaw)`) let a duplicated line read someone else's
|
|
341
|
+
// window. Indices remove that hazard for free.
|
|
342
|
+
// The boundary is not invented: parseAmendments() in harness-core/src/amendment-trace.ts has used
|
|
343
|
+
// the same next-definition bound all along, so this also ends a divergence between two checkers.
|
|
344
|
+
const secLines = amSection.split('\n');
|
|
345
|
+
// The bullet stays OPTIONAL: requiring it drops 104 of the corpus's 347 real AM rows out of the
|
|
346
|
+
// check entirely (MEASURED). The `(?!\s*\.)` guard is what refuses a wrapped `AM-1..AM-4;`
|
|
347
|
+
// range preamble, which used to open a phantom amendment and double-count AM-1 — it costs 0 rows.
|
|
348
|
+
const DEF = /^(?:[-*|]\s*)?\*{0,2}(AM-(?:CP-)?\d+)\*{0,2}\b(?!\s*\.)/;
|
|
349
|
+
// `tests?` — the two-id form `-> tests \`a\` and \`b\`` is corpus-canonical and is what
|
|
350
|
+
// amendment-trace.ts parses; matching only the singular turned two currently-green rows red.
|
|
351
|
+
const MARK = /\u2192\s*tests?\b|->\s*tests?\b/;
|
|
352
|
+
const defs = [];
|
|
353
|
+
secLines.forEach((l, i) => { const m = DEF.exec(l.trim()); if (m) defs.push({ i, id: m[1] }); });
|
|
354
|
+
for (let k = 0; k < defs.length; k++) {
|
|
355
|
+
const to = k + 1 < defs.length ? defs[k + 1].i : secLines.length;
|
|
356
|
+
const block = secLines.slice(defs[k].i, to).join('\n');
|
|
357
|
+
const hasTest = MARK.test(block);
|
|
358
|
+
const superseded = /superseded by AM-(?:CP-)?\d+/i.test(block);
|
|
359
|
+
if (!hasTest && !superseded) failures.push(`C6: ${defs[k].id} 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`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ── C7 ADR↔plan command-name drift (seam д) — WARN, deliberately never FAIL ─────────────────────
|
|
365
|
+
// The measured incident: the ADR said `dz policy-sync`, the plan said `dz agents-sync` (and even
|
|
366
|
+
// carried a test that the OTHER alias must not exist) — whichever file the coder opened first won.
|
|
367
|
+
// Deterministically provable is only the SET DIFFERENCE, not which side is right, so this warns
|
|
368
|
+
// with both sides named and never blocks.
|
|
369
|
+
{
|
|
370
|
+
const adrAll = adrFiles.map((f) => { try { return readFileSync(join(adrDir, f), 'utf-8'); } catch { return ''; } }).join('\n');
|
|
371
|
+
const cmds = (t) => new Set([...t.matchAll(/`dz ([a-z][a-z0-9-]+)`/g)].map((m) => m[1]));
|
|
372
|
+
const inAdr = cmds(adrAll); const inPlan = cmds(plan);
|
|
373
|
+
const onlyAdr = [...inAdr].filter((c) => !inPlan.has(c));
|
|
374
|
+
const onlyPlan = [...inPlan].filter((c) => !inAdr.has(c));
|
|
375
|
+
if (adrAll !== '' && onlyAdr.length > 0 && onlyPlan.length > 0) {
|
|
376
|
+
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 д)`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
295
380
|
// C5 — Inputs read line
|
|
296
381
|
if (!/Inputs read:/i.test(plan)) warnings.push('C5: no "Inputs read:" line (wave-2 seam, WARN only)');
|
|
297
382
|
else for (const need of ['03_adr','05_architecture']) if (!plan.includes(need)) warnings.push(`C5: Inputs read line missing ${need}`);
|
|
@@ -143,6 +143,11 @@ log('dz binary: ' + DZ)
|
|
|
143
143
|
// HERE, at invocation time, so a bad value fails at the same layer the pure half fails rather than
|
|
144
144
|
// two layers later inside an emitted shell command.
|
|
145
145
|
const GATE_SCRIPT_ARG = (A.gateScript === undefined || A.gateScript === null) ? undefined : assertAbsoluteNoTraversal(A.gateScript, 'gateScript')
|
|
146
|
+
// ADR-002 amendment (field report doc-21): WS was populated ONLY for a relative args.repo, and the
|
|
147
|
+
// shell fallback WS=$(pwd -P) runs in the GATE AGENT own cwd. On a run against an external repo it
|
|
148
|
+
// equalled REPO, so the workspace candidate pointed at the target repo and the skill installed in
|
|
149
|
+
// the workspace was never found - NOT-ESTABLISHED, exit 3, Step 7 never ran. args.workspace pins it.
|
|
150
|
+
if (A.workspace !== undefined && A.workspace !== null) WS = assertAbsoluteNoTraversal(A.workspace, 'workspace')
|
|
146
151
|
// CANONICAL BRAIN store: the self-learning loop (Step-0 recall → Step-8 teach) MUST read+write ONE
|
|
147
152
|
// shared pattern store so lessons never fragment into a target repo's .dz when the Step-7 coder cd's
|
|
148
153
|
// away. BRAIN defaults to the workspace root (REPO) — so an OMITTED args.brain is behaviorally inert
|
|
@@ -152,7 +157,11 @@ const BRAIN = (A.brain || REPO).replace(/\/+$/, '')
|
|
|
152
157
|
// Helpers PIN every learn-loop command to the canonical brain: `cd <BRAIN> &&` survives a cd'd agent
|
|
153
158
|
// (belt); `--project <BRAIN>` is explicit (suspenders). Either alone fixes it; together they also
|
|
154
159
|
// survive the relative-vs-absolute --project resolution asymmetry between recall and teach.
|
|
155
|
-
|
|
160
|
+
// `--run fa:<SLUG>` threads the run key into the usage log, so the panel's `--recalled auto`
|
|
161
|
+
// counts THIS run's recall events instead of asserting a literal (the old `--recalled 3`). The
|
|
162
|
+
// key is the slug: a resumed run keeps accumulating under the same key, which matches how the
|
|
163
|
+
// panel is read — per-feature, not per-invocation.
|
|
164
|
+
const DZ_RECALL = (terms) => 'cd ' + BRAIN + ' && ' + DZ + ' recall "' + terms + '" --project ' + BRAIN + ' --run fa:' + SLUG
|
|
156
165
|
const DZ_TEACH = (lesson, reward, domain) =>
|
|
157
166
|
'cd ' + BRAIN + ' && ' + DZ + ' teach "' + lesson + '" --reward ' + reward + ' --domain ' + domain + ' --project ' + BRAIN
|
|
158
167
|
|
|
@@ -692,7 +701,7 @@ const PLANNER = (A.planner === 'codex') ? 'codex' : 'claude'
|
|
|
692
701
|
// block threads workflow-local state (MODELS/knobs) through shapes the generic blobs do not carry;
|
|
693
702
|
// regeneration via model-resolver/usage-probes/codex-dispatch blobs is the tracked Stage-B item.
|
|
694
703
|
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 }
|
|
704
|
+
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
705
|
const CLAUDE_NAMES = { fable: 1, opus: 1, sonnet: 1, haiku: 1 }
|
|
697
706
|
const VALID_REASONING = { none: 1, minimal: 1, low: 1, medium: 1, high: 1, xhigh: 1 }
|
|
698
707
|
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' }
|
|
@@ -1743,7 +1752,12 @@ function planCompletenessGateCmd(repo, featureDir, tier, opts) {
|
|
|
1743
1752
|
// and the tried paths live OUTSIDE the verdict line so no path can smuggle a second verdict word
|
|
1744
1753
|
// into it.
|
|
1745
1754
|
'echo "K2_GATE_SCRIPT=${GS:-none}"',
|
|
1746
|
-
'echo "K2_GATE_TRIED=${C1
|
|
1755
|
+
'echo "K2_GATE_TRIED=C1(args.gateScript)=${C1:-<unset>} | C2(workspace)=$C2 | C3(target-repo)=$C3"',
|
|
1756
|
+
// A COLLAPSE is not a second candidate. When the workspace was not pinned, WS falls back to the
|
|
1757
|
+
// gate agent own cwd — in the field that WAS the target repo, so C2 and C3 printed the same path
|
|
1758
|
+
// twice and the chain silently degenerated from three candidates to two. Saying so turns a
|
|
1759
|
+
// puzzling duplicate into an instruction. Not verdict-shaped, so the parser anchoring is untouched.
|
|
1760
|
+
'[ "$C2" = "$C3" ] && echo "K2_GATE_NOTE=the workspace candidate resolved to the TARGET repo (WS==repo), so only two distinct candidates were tried; pass args.workspace or args.gateScript when the feature-adr skill is installed outside the target repo"',
|
|
1747
1761
|
'if [ -z "$GS" ]; then echo "K2 plan-completeness: NOT-ESTABLISHED — tooling-missing: no gate script at any candidate on the K2_GATE_TRIED line above"; echo "K2_EXIT=3"; else cd ' + q(repo) + ' && node "$GS" ' + q(featureDir) + t + ' 2>&1; echo "K2_EXIT=$?"; fi',
|
|
1748
1762
|
].join('\n')
|
|
1749
1763
|
}
|
|
@@ -1830,6 +1844,57 @@ function decideModeBScope(o) {
|
|
|
1830
1844
|
return { ok: true, files: files, dropped: dropped }
|
|
1831
1845
|
}
|
|
1832
1846
|
|
|
1847
|
+
// ── QE CHANGE-SET PROBE — inline mirror of harness-core/src/feature-adr-routing.ts:2309-2381.
|
|
1848
|
+
// These three travelled as CALL SITES only: commit 6a92d189 ("measure the DELTA, not the current
|
|
1849
|
+
// state") shipped the TS module, its 19 tests and both call sites below, and never inlined the
|
|
1850
|
+
// helpers here. Every run reaching Step 7 therefore died with a ReferenceError on changeSetProbeCmd
|
|
1851
|
+
// while those 19 tests stayed green — they exercise the export, this file runs the mirror. Ported
|
|
1852
|
+
// byte-faithfully from the canonical: sha256sum (not shasum), the ~1 and ...HEAD ref forms, a Map
|
|
1853
|
+
// snapshot seeded with nulls from the declared list (absence is a null VALUE, never an ABSENT
|
|
1854
|
+
// line), and null returned ONLY by changedFromHashes — that is the single "not established" signal.
|
|
1855
|
+
// NOTE: no template literals below. This region sits inside the parser-safe block the routing test
|
|
1856
|
+
// guards (it runs from const MODELS to const ROUTER), so even a backtick in a COMMENT reddens it.
|
|
1857
|
+
function parseHashProbe(text, declared) {
|
|
1858
|
+
const out = new Map()
|
|
1859
|
+
for (const p of declared) out.set(String(p), null)
|
|
1860
|
+
for (const raw of String(text === null || text === undefined ? '' : text).split('\n')) {
|
|
1861
|
+
const line = raw.trim()
|
|
1862
|
+
if (line === '') continue
|
|
1863
|
+
const m = /^([0-9a-f]{64})\s+(.+)$/.exec(line)
|
|
1864
|
+
if (m === null || m[1] === undefined || m[2] === undefined) continue
|
|
1865
|
+
const path = m[2].trim().replace(/^\.\//, '')
|
|
1866
|
+
if (out.has(path)) out.set(path, m[1])
|
|
1867
|
+
}
|
|
1868
|
+
return out
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
function changedFromHashes(before, after) {
|
|
1872
|
+
if (before === null || before === undefined || after === null || after === undefined) return null
|
|
1873
|
+
const changed = []
|
|
1874
|
+
for (const [path, afterHash] of after) {
|
|
1875
|
+
const beforeHash = before.has(path) ? (before.get(path) === undefined ? null : before.get(path)) : null
|
|
1876
|
+
if (beforeHash !== (afterHash === null || afterHash === undefined ? null : afterHash)) changed.push(path)
|
|
1877
|
+
}
|
|
1878
|
+
return changed.sort()
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
function changeSetProbeCmd(opts) {
|
|
1882
|
+
const paths = opts.paths.map(function (p) { return String(p) }).filter(function (p) { return p !== '' })
|
|
1883
|
+
if (paths.length === 0) return null
|
|
1884
|
+
const quoted = paths.map(opts.quote).join(' ')
|
|
1885
|
+
const ref = String(opts.ref === null || opts.ref === undefined ? '' : opts.ref).trim()
|
|
1886
|
+
if (opts.scope === 'commit') {
|
|
1887
|
+
if (ref === '') return null
|
|
1888
|
+
return 'git diff --name-only ' + opts.quote(ref) + '~1 ' + opts.quote(ref) + ' -- ' + quoted
|
|
1889
|
+
}
|
|
1890
|
+
if (opts.scope === 'base') {
|
|
1891
|
+
if (ref === '') return null
|
|
1892
|
+
return 'git diff --name-only ' + opts.quote(ref) + '...HEAD -- ' + quoted
|
|
1893
|
+
}
|
|
1894
|
+
// uncommitted: hash the declared targets; the caller pairs this with a pre-code baseline.
|
|
1895
|
+
return 'sha256sum -- ' + quoted + ' 2>/dev/null || true'
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1833
1898
|
function crossFamilyQe(o) {
|
|
1834
1899
|
// NORMALISE first — raw-string comparison let 'Claude' vs 'claude' report a cross-family review
|
|
1835
1900
|
// that never happened (caught by codex review --commit on the TS-only fix: the exported helper
|
|
@@ -1871,7 +1936,12 @@ async function designStage(promptText, opts, artifactPath, baseLabel) {
|
|
|
1871
1936
|
if (res && probe && /landed=/.test(String(probe))) return { wrote: [artifactPath], summary: String(res).slice(0, 300) }
|
|
1872
1937
|
log('design artifact did not land on codex (' + artifactPath + ') — falling back to Claude')
|
|
1873
1938
|
const fallbackOpts = {}
|
|
1874
|
-
|
|
1939
|
+
const fb = await agent(promptText, mergeOpts({ label: stageLabel((baseLabel || 'design') + ':claude-fb', fallbackOpts), phase: 'Design', schema: ARTIFACT }, fallbackOpts))
|
|
1940
|
+
// d926ee89: the fallback used to keep CODEX provenance — modelsUsed, the checkpoint label and the
|
|
1941
|
+
// training-pair family all still said codex after Claude wrote the artifact. The WRITER is the
|
|
1942
|
+
// provenance; overwrite it here, at the one place that knows the fallback fired.
|
|
1943
|
+
if (fb && baseLabel && modelsUsed[baseLabel] !== undefined) modelsUsed[baseLabel] = modelLabel(fallbackOpts) + ' (claude-fallback after codex not-landed)'
|
|
1944
|
+
return fb
|
|
1875
1945
|
}
|
|
1876
1946
|
|
|
1877
1947
|
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 +1963,7 @@ const ADR_FITNESS_CHECKLIST = 'ADR fitness checklist for Step 8: read every ' +
|
|
|
1893
1963
|
// judgment cannot carry (444 tests stayed green around undefended protections, including the exact MEASURED
|
|
1894
1964
|
// exploit string). Advisory, exactly like the discrimination gate: findings are HIGH gaps, never an auto-abort,
|
|
1895
1965
|
// 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 `
|
|
1966
|
+
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
1967
|
// no-stubs (backlog 0b403a0106103901, Karpathy-Michaels rule XI): an unfinished stub left in the
|
|
1898
1968
|
// run's own touched files means the task shipped incomplete — a deterministic grep is layer 1 on the
|
|
1899
1969
|
// cost-of-detection ladder; reviewer judgment is layer 4. Marker strings are ASSEMBLED so this
|
|
@@ -1902,18 +1972,38 @@ const MUTATION_GATE = 'MUTATION GATE (feature ha-mutation-gate — run alongside
|
|
|
1902
1972
|
const STUB_RX = '(^|[^A-Za-z0-9_])(' + ['TO' + 'DO', 'FIX' + 'ME', 'HA' + 'CK', 'XX' + 'X', 'PLACE' + 'HOLDER'].join('|') + ')([^A-Za-z0-9_]|$)'
|
|
1903
1973
|
const STUB_PHRASE = 'imple' + 'ment later'
|
|
1904
1974
|
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 `
|
|
1975
|
+
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
1976
|
// P2 (amendment-confirmation-discipline, fa-improvements 2026-07-18): amendments are where the SHARPEST design
|
|
1907
1977
|
// corrections land (challenge-panel/QCSD) and were the least-tested — prose deltas with no proving test. Every
|
|
1908
1978
|
// amendment is a mini-ADR: it carries a one-line Confirmation naming the test that falsifies it. Machine-checkable
|
|
1909
1979
|
// shape (a linter can assert the `→ test ` token); Step-8 verifies existence + non-vacuity via the SAME
|
|
1910
1980
|
// dz discrimination-check that guards the ADR property (cost-of-detection ladder: judgment → step gate).
|
|
1911
|
-
const AMENDMENT_RULE = 'AMENDMENT CONFIRMATION DISCIPLINE (every amendment is a mini-ADR): whenever a correction/amendment is folded in (a QCSD CONDITIONAL condition, a challenge-panel confirmed finding, or a user checkpoint steer), record it in a `## Amendments` section as a fixed-shape row: `AM-N (source): <change>. Confirmation: <property> → test `test_name` (fails if reverted).` — naming the test that would FAIL if the amendment were reverted/broken. For a SAFEGUARD amendment (a warning/guard/fallback), the named test must prove the safeguard actually TRIGGERS on a real input — not merely that its code path exists (a structurally-dead safeguard passes an existence test and never fires in production).'
|
|
1981
|
+
const AMENDMENT_RULE = 'AMENDMENT CONFIRMATION DISCIPLINE (every amendment is a mini-ADR): whenever a correction/amendment is folded in (a QCSD CONDITIONAL condition, a challenge-panel confirmed finding, or a user checkpoint steer), record it in a `## Amendments` section as a fixed-shape row: `AM-N (source): <change>. Confirmation: <property> → test `test_name` (fails if reverted).` — naming the test that would FAIL if the amendment were reverted/broken. The row is MACHINE-READ by the K2 C6 gate: the marker (or a superseded-by-AM-N note) must sit inside the OWN block of that amendment — anywhere between its AM-N line and the line where the NEXT AM-N begins. Multi-line amendments are fine; what is NOT fine is placing a marker after the following amendment has already started, because it then belongs to that one. A bare range such as AM-1..AM-4 never opens a row. For a SAFEGUARD amendment (a warning/guard/fallback), the named test must prove the safeguard actually TRIGGERS on a real input — not merely that its code path exists (a structurally-dead safeguard passes an existence test and never fires in production).'
|
|
1912
1982
|
const AMENDMENT_GATE = 'AMENDMENT GATE (P2): do NOT judge this yourself — RUN the check and report what it says. Via Bash run EXACTLY `' + DZ + ' amendment-check --slug ' + SLUG + ' --json` (add `--feature-dir ' + FDIR + '` if the slug does not resolve from your CWD). Parse the JSON and report `amendments: {outcome, counts, reasons}` in your return object. outcome `pass` or `skip` clears the gate; `fail` is a HIGH gap and every reason must be quoted verbatim into the QE report; `not-established` means the check could not be run or the grammar matched nothing — that is NEVER a pass, report it as inconclusive with the tool error. Empty stdout, a crash, or a missing `dz` is `not-established`, not a clean gate. This check proves each amendment RESOLVES to a real test; it does NOT prove the test discriminates — vacuity stays with the discrimination gate above. ' +
|
|
1913
1983
|
'IO-ON-PURE-PATH + FIXTURE-SWAP HUNT (P5): in the test diff, hunt for replacements of broken/unbound fixtures with healthy ones — the old fixture was probably a NEGATIVE CONTROL proving a path was I/O-free; each such swap requires a compensating negative resource-down test. If the code diff adds I/O (DB/network/file) to a previously-pure path — especially startup/lifespan/health — require a negative resource-down test (broken/unbound resource → the path degrades per its declared contract: fail-open for advisory, explicit fail-fast for load-bearing). Missing → HIGH gap.'
|
|
1914
1984
|
|
|
1915
1985
|
// Step 0: Router + MANDATORY self-learning recall
|
|
1916
1986
|
phase('Router')
|
|
1987
|
+
|
|
1988
|
+
// W1 (backlog 848853a0): REPO must be the git TOPLEVEL. Both measured incidents were a REPO
|
|
1989
|
+
// pointing INSIDE the repository (packages/@dzhechkov/health-advisor) — artifacts then scatter
|
|
1990
|
+
// into features/ of a subdirectory and a sibling-worktree comparison never catches it. One cheap
|
|
1991
|
+
// probe, fail-closed on MISMATCH (inside a repo but not its root ⇒ refuse before any design
|
|
1992
|
+
// spend); a non-git dir logs loudly and continues (unusual, but not the measured failure class).
|
|
1993
|
+
// Canonicalization happens INSIDE the probe shell (cross-family review B-: JS-side string compare
|
|
1994
|
+
// would false-refuse a symlinked root) — both sides come from the same cd'd shell, `pwd -P` vs
|
|
1995
|
+
// rev-parse, so aliasing and spelling cancel out.
|
|
1996
|
+
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' })
|
|
1997
|
+
const wrootM = /WROOT:(.+):HERE:(.+)/.exec(String(wrootOut === null || wrootOut === undefined ? '' : wrootOut))
|
|
1998
|
+
const wrootTop = wrootM === null ? null : wrootM[1].trim()
|
|
1999
|
+
const wrootHere = wrootM === null ? null : wrootM[2].trim()
|
|
2000
|
+
let repoRootCheck = 'ok'
|
|
2001
|
+
if (wrootTop === null) { repoRootCheck = 'not-established'; log('repo-root probe NOT ESTABLISHED — continuing, but artifact placement is unverified') }
|
|
2002
|
+
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)') }
|
|
2003
|
+
else if (wrootTop !== wrootHere) {
|
|
2004
|
+
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/)')
|
|
2005
|
+
return { phase: 'repo-root-mismatch', repo: REPO, repoCanonical: wrootHere, gitToplevel: wrootTop, cure: 'invoke with args.repo=' + wrootTop + ' (or run from the repository root)' }
|
|
2006
|
+
}
|
|
1917
2007
|
await loadCheckpoints('Router')
|
|
1918
2008
|
await usageProbe('Router')
|
|
1919
2009
|
const routerTierDirective = A.tier
|
|
@@ -1974,7 +2064,7 @@ if (autoCostStages.length > 0) {
|
|
|
1974
2064
|
// GUARANTEED fa-panel write (the router, being low-effort + multi-job, tends to skip the fa-record
|
|
1975
2065
|
// Bash call). A dedicated single-command agent reliably lights up the live /feature-adr panel at the
|
|
1976
2066
|
// 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
|
|
2067
|
+
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
2068
|
|
|
1979
2069
|
// R1 product-architecture-lens (ADR-001 Decision 3): forward-looking сверка of THIS feature vs the LIVE
|
|
1980
2070
|
// product map + vision. NON-BLOCKING/soft by design — it LOGS {signal,confidence} so a real command
|
|
@@ -2028,8 +2118,8 @@ designThunks.push(() => subCheckpoint('requirements', reqPromptText, reqOpts, ['
|
|
|
2028
2118
|
// the requirements prompt is where the Step-0 recalled lessons are INJECTED — recorded per pair
|
|
2029
2119
|
designPairMeta.push({ role: 'design:requirements', prompt: reqPromptText, model: modelLabel(reqOpts), lessons: (LEARNED && LEARNED !== 'none recalled') ? [String(LEARNED)] : [] })
|
|
2030
2120
|
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-
|
|
2032
|
-
designThunks.push(() => subCheckpoint('adr', adrPromptText, adrOpts, ['03_adr/
|
|
2121
|
+
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
|
|
2122
|
+
designThunks.push(() => subCheckpoint('adr', adrPromptText, adrOpts, ['03_adr/'], () => designStage(adrPromptText, adrOpts, FDIR + '/03_adr/', 'adr')))
|
|
2033
2123
|
designPairMeta.push({ role: 'design:adr', prompt: adrPromptText, model: modelLabel(adrOpts), lessons: [] })
|
|
2034
2124
|
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
2125
|
designThunks.push(() => subCheckpoint('qcsd', qcsdPromptText, qcsdOpts, ['03.5_ideation_report.md'], () => designStage(qcsdPromptText, qcsdOpts, FDIR + '/03.5_ideation_report.md', 'qcsd')))
|
|
@@ -2047,7 +2137,7 @@ if (isMplus) {
|
|
|
2047
2137
|
// (Codex QE #2 — a one-file probe accepted a design missing its ADR/architecture).
|
|
2048
2138
|
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
2139
|
const designArtifacts = ['01_requirements.md']
|
|
2050
|
-
if (isMplus) designArtifacts.push('03_adr/
|
|
2140
|
+
if (isMplus) designArtifacts.push('03_adr/', '03.5_ideation_report.md', '05_architecture.md')
|
|
2051
2141
|
if (isLplus) designArtifacts.push('02_research.md', '04_domain_model.md')
|
|
2052
2142
|
/**
|
|
2053
2143
|
* PER-SIBLING checkpoint (SP-2, no-amplification). Before 2026-08-20 the whole fan shared ONE
|
|
@@ -2124,7 +2214,7 @@ const designFan = await parallel(designThunks)
|
|
|
2124
2214
|
// line, so it can no longer forge one — and `-f` on an exact path cannot be satisfied by a name that
|
|
2125
2215
|
// merely renders like it. MEASURED red/green below in a real shell against a newline-bearing filename.
|
|
2126
2216
|
let fanChecks = ''
|
|
2127
|
-
for (const rel of designArtifacts) fanChecks += '[ -f ' + shq(rel) + ' ] && echo ' + shq('HAVE:' + rel) + '; '
|
|
2217
|
+
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
2218
|
const fanLsCmd = 'cd ' + shq(FDIR) + ' 2>/dev/null && { ' + fanChecks + 'echo ' + shq(FAN_LS_SENTINEL) + '; } || true'
|
|
2129
2219
|
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
2220
|
// The transcript is validated STRICTLY, not scanned. Round 7 measured the difference: an agent that
|
|
@@ -2266,7 +2356,7 @@ async function runChallengePanel(planRel, plannerName) {
|
|
|
2266
2356
|
if (!authorIsCodex) {
|
|
2267
2357
|
// author=Claude → Codex adversary (cross-family). Compact prompt: Codex reads the files itself (no 24k
|
|
2268
2358
|
// brief inlined). safeCodexAgent is the honest exec path; null/invalid ⇒ loud Claude fallback below.
|
|
2269
|
-
const cx = await safeCodexAgent('You are a FRESH adversarial reviewer of an implementation plan you did NOT write. Read these files: ' + planRel + ' , architecture/vision.md , architecture/degradations.md (relative to repo ' + REPO + '). BREAK the plan, do not confirm it. Answer C1 arch-anti-cement (deviating from a pattern in the degradations registry is NOT a finding), C2 prod-ready, C3 test sufficiency+honesty both ways, C4 overengineering, C5 silent decisions, C6 runtime consistency, C7 scope>1.5x, C8 executability. Output ONLY minified JSON {"findings":[{"c","severity":"P0|P1|P2","title","why","where"}],"summary"}.', { label: 'challenge:codex-adversary', phase: 'Plan' })
|
|
2359
|
+
const cx = await safeCodexAgent('You are a FRESH adversarial reviewer of an implementation plan you did NOT write. Read these files: ' + planRel + ' , architecture/vision.md , architecture/testing.md , architecture/map.json , architecture/degradations.md (relative to repo ' + REPO + '). BREAK the plan, do not confirm it. Answer C1 arch-anti-cement (deviating from a pattern in the degradations registry is NOT a finding), C2 prod-ready, C3 test sufficiency+honesty both ways, C4 overengineering, C5 silent decisions, C6 runtime consistency, C7 scope>1.5x, C8 executability. Output ONLY minified JSON {"findings":[{"c","severity":"P0|P1|P2","title","why","where"}],"summary"}.', { label: 'challenge:codex-adversary', phase: 'Plan' })
|
|
2270
2360
|
if (cx) { try { verdict = sanitizeChallengeVerdict(JSON.parse(String(cx).replace(/^[^{]*/, '').replace(/[^}]*$/, ''))) } catch { verdict = null } }
|
|
2271
2361
|
if (!verdict) { log('Challenge panel: Codex adversary unavailable/unparseable/invalid — falling back to a FRESH Claude panel (NOT cross-family; run `dz challenge` + codex manually for a cross-family pass)'); adversary = 'claude-fallback' }
|
|
2272
2362
|
}
|
|
@@ -2326,7 +2416,7 @@ if (plan) {
|
|
|
2326
2416
|
// plan straight into Step 7. The gate is forced NOT-ESTABLISHED without probing the tree at all.
|
|
2327
2417
|
let planGate = { verdict: 'not-established', exit: null, reason: 'plan-stage-null', output: 'The Step-6 plan stage returned no result for THIS run (agent died, or produced nothing). Any 06_implementation_plan.md present on disk belongs to an earlier run and cannot vouch for this one, so the gate refuses without reading it.' }
|
|
2328
2418
|
if (plan) {
|
|
2329
|
-
const planGateOut = await agent('Run EXACTLY this shell snippet via your Bash tool, as ONE command, and return its stdout VERBATIM, nothing else — do not summarize it, do not judge the plan yourself, do not omit the K2_GATE_SCRIPT / K2_GATE_TRIED lines or the trailing K2_EXIT line:\n' + planCompletenessGateCmd(REPO, 'features/' + SLUG, tier, { gateScript: GATE_SCRIPT_ARG }), { label: 'plan:k2-gate', phase: 'Plan', effort: 'low' })
|
|
2419
|
+
const planGateOut = await agent('Run EXACTLY this shell snippet via your Bash tool, as ONE command, and return its stdout VERBATIM, nothing else — do not summarize it, do not judge the plan yourself, do not omit the K2_GATE_SCRIPT / K2_GATE_TRIED lines or the trailing K2_EXIT line:\n' + planCompletenessGateCmd(REPO, 'features/' + SLUG, tier, { gateScript: GATE_SCRIPT_ARG, workspace: WS === null ? undefined : WS }), { label: 'plan:k2-gate', phase: 'Plan', effort: 'low' })
|
|
2330
2420
|
planGate = parsePlanGateVerdict(planGateOut)
|
|
2331
2421
|
}
|
|
2332
2422
|
log('K2 plan-completeness gate: ' + planGate.verdict + ' (exit=' + (planGate.exit === null ? 'unknown' : planGate.exit) + ', reason=' + planGate.reason + ')')
|
|
@@ -2351,6 +2441,24 @@ if (stopHere) {
|
|
|
2351
2441
|
let challengeVerdict = null
|
|
2352
2442
|
try { challengeVerdict = plan ? await runChallengePanel('features/' + SLUG + '/06_implementation_plan.md', plan.planner) : null }
|
|
2353
2443
|
catch (e) { log('Challenge panel errored (advisory, ignored): ' + (e && e.message ? e.message : String(e))) }
|
|
2444
|
+
// Seam а (backlog 72b89e14): the panel's verdict used to reach only the OPERATOR — the
|
|
2445
|
+
// finding→plan-amendment bridge was manual, and on L/XL the coder runs in a SECOND invocation
|
|
2446
|
+
// that reads the PLAN FILE, not the first invocation's memory. So P0/P1 findings are appended to
|
|
2447
|
+
// the plan's ## Amendments as AM-CP-<n> rows by an effort-low agent — append-only, idempotent
|
|
2448
|
+
// (the marker line is checked first), each row carrying the C6-required shape.
|
|
2449
|
+
try {
|
|
2450
|
+
const cpFindings = (challengeVerdict && Array.isArray(challengeVerdict.findings))
|
|
2451
|
+
? challengeVerdict.findings.filter((f) => f && (f.severity === 'P0' || f.severity === 'P1'))
|
|
2452
|
+
: []
|
|
2453
|
+
if (cpFindings.length > 0) {
|
|
2454
|
+
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')
|
|
2455
|
+
const marker = '<!-- challenge-panel amendments appended ' + fnv1a64(rows) + ' -->'
|
|
2456
|
+
const planPath = FDIR + '/06_implementation_plan.md'
|
|
2457
|
+
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; }'
|
|
2458
|
+
const cpOut = await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + appendCmd, { label: 'challenge:append-amendments', phase: 'Plan', effort: 'low' })
|
|
2459
|
+
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) + ')'))
|
|
2460
|
+
}
|
|
2461
|
+
} catch (e2) { log('challenge panel \u2192 amendments append failed (advisory): ' + (e2 && e2.message ? e2.message : String(e2))) }
|
|
2354
2462
|
// P4 (checkpoint-gate-line): a DERIVED gates map — each entry comes from machine state (artifact/verdict
|
|
2355
2463
|
// presence), never from prose, so a skipped gate shows as 'not-run' instead of being silently forgotten.
|
|
2356
2464
|
const planGates = { plan: (plan ? 'produced' : 'missing'), planCompleteness: planGate.verdict, challengePanel: (challengeVerdict ? 'ran' : 'not-run'), code: 'not-run', qe: 'not-run' }
|
|
@@ -2377,14 +2485,21 @@ if (QE_SCOPE === 'uncommitted') {
|
|
|
2377
2485
|
const baseCmd = changeSetProbeCmd({ scope: 'uncommitted', paths: preCodeTargets, quote: shq })
|
|
2378
2486
|
if (baseCmd) {
|
|
2379
2487
|
const baseOut = await agent('Run EXACTLY this via Bash from ' + REPO + ' and return its stdout VERBATIM with NO commentary: cd ' + shq(REPO) + ' && ' + baseCmd, { label: 'qe:baseline-hash', phase: 'Code', effort: 'low' })
|
|
2380
|
-
|
|
2488
|
+
// An EMPTY relay reply is not a measurement. parseHashProbe seeds every declared path with null
|
|
2489
|
+
// and returns a valid-looking snapshot, so a failed baseline would later compare null -> hash for
|
|
2490
|
+
// every target and hand Mode B a change set of files Step 7 never touched (cross-family review of
|
|
2491
|
+
// this restoration, P1). A probe that produced no parsable hash line leaves the baseline NULL —
|
|
2492
|
+
// the established not-measured signal that the log below already names and that changedFromHashes
|
|
2493
|
+
// turns into "scope NOT ESTABLISHED", which is never a pass.
|
|
2494
|
+
const baseText = (baseOut === null || baseOut === undefined) ? '' : String(baseOut)
|
|
2495
|
+
if (/^[0-9a-f]{64}\s+\S/m.test(baseText)) preCodeBaseline = parseHashProbe(baseText, preCodeTargets)
|
|
2381
2496
|
}
|
|
2382
2497
|
if (preCodeBaseline === null) log('QE: pre-code baseline NOT captured — the change set will read as unmeasured, and a scoped review will refuse rather than guess')
|
|
2383
2498
|
}
|
|
2384
2499
|
|
|
2385
2500
|
phase('Code')
|
|
2386
2501
|
await usageProbe('Code')
|
|
2387
|
-
const codePrompt = 'Step 7 (Code) of /feature-adr for "' + DESC + '" (' + SLUG + ').
|
|
2502
|
+
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
2503
|
// Resolve the coder model. args.models.code wins (a direct 'codex' spec = codex-first); else the legacy
|
|
2389
2504
|
// CODER knob drives it (with its codex-fallback null-guard). resolveStageModel('code') folds both via the
|
|
2390
2505
|
// code:null sentinel → resolveCoderSpec(). A Claude resolution merges {model} onto the Claude branch;
|
|
@@ -2475,7 +2590,7 @@ if (needsCodeLandedBarrier(coderUsed)) {
|
|
|
2475
2590
|
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
2591
|
const scrapeLine = scrapeDiagnostic.length ? '\ncodex-self-declared (diagnostic, not matched): ' + scrapeDiagnostic.join(', ') : ''
|
|
2477
2592
|
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).
|
|
2593
|
+
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
2594
|
} else if (landingStatus === 'genuinely-not-landed') {
|
|
2480
2595
|
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
2596
|
} else {
|
|
@@ -2504,8 +2619,48 @@ if (resumedStages.indexOf('code') !== -1 && landedNote !== '') {
|
|
|
2504
2619
|
|
|
2505
2620
|
// Step 8: QE (brutal-honesty, agentic-qe) + MANDATORY teach
|
|
2506
2621
|
phase('QE')
|
|
2622
|
+
|
|
2623
|
+
// ── Writer-quiescence probe (feature qe-writer-quiescence, backlog 700b46a4) ─────────────────────
|
|
2624
|
+
// Step-8 used to grade a MOVING tree (crossrt-1: a background worker wrote AFTER the verdict,
|
|
2625
|
+
// clobbering a file the round had just written). A BELT, not the root (worktree isolation is
|
|
2626
|
+
// 9520e506): the probe NEVER blocks — a moving/inconclusive tree loudly downgrades the verdict's
|
|
2627
|
+
// standing. Inline byte-mirror of harness-core/src/writer-quiescence.ts:decideWriterQuiescence;
|
|
2628
|
+
// the drift test asserts this body matches the module.
|
|
2629
|
+
function decideWriterQuiescence(probeText, requiredQuiet) {
|
|
2630
|
+
const need = requiredQuiet === undefined ? 3 : requiredQuiet
|
|
2631
|
+
const text = probeText === null || probeText === undefined ? '' : String(probeText)
|
|
2632
|
+
const windows = []
|
|
2633
|
+
for (const line of text.split(/\r?\n/)) {
|
|
2634
|
+
const m = /WQ-WINDOW\s+\d+\s+changed=(\d+|ERR)/.exec(line)
|
|
2635
|
+
if (m) windows.push(m[1] === 'ERR' ? -1 : Number(m[1]))
|
|
2636
|
+
}
|
|
2637
|
+
if (windows.length === 0) {
|
|
2638
|
+
return { verdict: 'inconclusive', windows: windows, note: 'quiescence probe returned no windows — grading standing NOT established (probe failure is never quiet)' }
|
|
2639
|
+
}
|
|
2640
|
+
let streak = 0
|
|
2641
|
+
for (const c of windows) {
|
|
2642
|
+
streak = c === 0 ? streak + 1 : 0
|
|
2643
|
+
if (streak >= need) {
|
|
2644
|
+
return { verdict: 'quiet', windows: windows, note: 'no observed writes in ' + need + ' consecutive windows (mtime evidence only — not a writer-lifecycle guarantee)' }
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
if (windows.some((c) => c < 0)) {
|
|
2648
|
+
return { verdict: 'inconclusive', windows: windows, note: 'quiescence probe could not observe every window (find errored) — grading standing NOT established' }
|
|
2649
|
+
}
|
|
2650
|
+
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' }
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
const wqPaths = [FDIR].concat(Array.isArray(expectedTargets) ? expectedTargets : []).filter((p) => typeof p === 'string' && p !== '' && p.indexOf("'") < 0 && p.charAt(0) !== '-')
|
|
2654
|
+
const wqTargets = wqPaths.map((p) => "'" + p + "'").join(' ')
|
|
2655
|
+
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\""
|
|
2656
|
+
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' })
|
|
2657
|
+
const writerQuiescence = decideWriterQuiescence(wqProbe)
|
|
2658
|
+
log('Step 8 writer-quiescence: ' + writerQuiescence.verdict + ' (windows: ' + (writerQuiescence.windows.join(',') || 'none') + ')')
|
|
2659
|
+
const wqNote = writerQuiescence.verdict === 'quiet'
|
|
2660
|
+
? ' WRITER-QUIESCENCE: quiet (' + writerQuiescence.note + ').'
|
|
2661
|
+
: ' WRITER-QUIESCENCE GATE (MANDATORY to acknowledge): ' + writerQuiescence.note + ' State this standing explicitly in 08_qe_report.md next to the grade.'
|
|
2507
2662
|
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
|
|
2663
|
+
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
2664
|
// CROSS-MODEL QE (load-bearing): resolveStageModel('qe') derives the OTHER family than the resolved
|
|
2510
2665
|
// coder when args.models.qe is unset (coder-codex ⇒ opus; coder-Claude ⇒ codex, or opus if codex absent).
|
|
2511
2666
|
// An explicit args.models.qe wins. A Claude qe spec is merged onto the qe-code-reviewer base (role
|
|
@@ -2646,7 +2801,7 @@ if (qe === null && (qeIsCodex || QE_REVIEWER === 'codex-fallback')) {
|
|
|
2646
2801
|
// with no 08_qe_report.md at all. Named by cross-family review of b6973199. The verdict itself
|
|
2647
2802
|
// is real (Codex produced it), so a failed transcription DEGRADES the run rather than voiding
|
|
2648
2803
|
// 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
|
|
2804
|
+
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
2805
|
// WITNESS THE REWRITE, not the existence. On a re-QE or a resume with the same slug an OLD
|
|
2651
2806
|
// 08_qe_report.md is already sitting there, and an existence probe reports that stale file as
|
|
2652
2807
|
// landed — so a scribe that wrote nothing still marked the new verdict recorded, and the stage
|
|
@@ -2962,14 +3117,29 @@ if (DELIVERY_ON) {
|
|
|
2962
3117
|
// L/XL checkpoint-after-plan return above — no code has landed there yet.)
|
|
2963
3118
|
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
3119
|
|
|
2965
|
-
|
|
2966
|
-
|
|
3120
|
+
// W4 (backlog 848853a0, the carrier defect): promise tags used to be STAMPED unconditionally —
|
|
3121
|
+
// two consecutive runs with an EMPTY Step 7 were tagged «implemented». A tag is now EARNED by its
|
|
3122
|
+
// stage's evidence; missing evidence emits <TAG>_INCOMPLETE (the promise-system convention the
|
|
3123
|
+
// reward rules already price at ≤0.3). The checkpoint/landing machinery guards RESUME; this guards
|
|
3124
|
+
// the CLAIM.
|
|
3125
|
+
function earnedTag(name, earned) { return earned ? name : name + '_INCOMPLETE' }
|
|
3126
|
+
const designEvidence = Array.isArray(design) && design.filter(Boolean).length > 0
|
|
3127
|
+
const implementedEvidence = code !== null && code !== undefined && (needsCodeLandedBarrier(coderUsed) ? landingStatus === 'landed' : true)
|
|
3128
|
+
const tags = [
|
|
3129
|
+
earnedTag('FEATURE_ADR_ROUTED', router !== null && router !== undefined),
|
|
3130
|
+
earnedTag('FEATURE_ADR_DESIGNED', designEvidence),
|
|
3131
|
+
earnedTag('FEATURE_ADR_PLANNED', plan !== null && plan !== undefined),
|
|
3132
|
+
earnedTag('FEATURE_ADR_IMPLEMENTED', implementedEvidence),
|
|
3133
|
+
earnedTag('FEATURE_ADR_VERIFIED', qe !== null && qe !== undefined && typeof qe.grade === 'string' && qe.grade !== ''),
|
|
3134
|
+
]
|
|
3135
|
+
if (isLplus) tags.push(earnedTag('FEATURE_ADR_FLEET_VERIFIED', fleet !== null && fleet !== undefined))
|
|
2967
3136
|
// QE-D#3/#4: the promise tag asserts the gate RAN AND its review doc LANDED — an errored gate or a missing
|
|
2968
3137
|
// artifact must not claim it (a promise about a file that does not exist is exactly a fabricated completeness).
|
|
2969
3138
|
if (delivery && delivery.artifact === 'written' && delivery.handoff !== 'errored') tags.push('FEATURE_ADR_DELIVERY_GATED')
|
|
2970
3139
|
await appendRunCostRow('full', (isLplus ? 'FleetQE' : 'QE'))
|
|
2971
3140
|
return {
|
|
2972
3141
|
slug: SLUG, tier: tier, mode: MODE, artifactsDir: FDIR,
|
|
3142
|
+
repoRootCheck: repoRootCheck,
|
|
2973
3143
|
design: design.filter(Boolean).map((d) => d.wrote).flat(),
|
|
2974
3144
|
codeWrote: code ? code.wrote : [],
|
|
2975
3145
|
qeGrade: qe ? qe.grade : null,
|