@dzhechkov/skills-feature-adr 1.5.0 → 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 +8 -32
- package/CHANGELOG.md +18 -0
- package/README.md +19 -0
- package/package.json +1 -1
- package/sbom.json +7 -67
- package/templates/.claude/skills/feature-adr/SKILL.md +24 -0
- package/templates/.claude/skills/feature-adr/modules/08-qe.md +26 -12
- package/templates/.claude/skills/feature-adr/scripts/check-plan-completeness.mjs +62 -0
- package/templates/.claude/workflows/feature-adr.js +175 -59
package/.dz-manifest.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"files": [
|
|
6
6
|
{
|
|
7
7
|
"path": "CHANGELOG.md",
|
|
8
|
-
"sha256": "
|
|
8
|
+
"sha256": "88e0389016f697b90731ee8743a35e523f0ccb079426d59861d894f33e516392"
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"path": "LICENSE",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"path": "README.md",
|
|
16
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
116
|
+
"sha256": "67eb730c815e6b8f9b9b61084c67ceb85440548f00b21f52187d16e81e3d7af8"
|
|
125
117
|
},
|
|
126
118
|
{
|
|
127
119
|
"path": "templates/.claude/skills/feature-adr/examples/sample-feature-output.md",
|
|
@@ -165,7 +157,7 @@
|
|
|
165
157
|
},
|
|
166
158
|
{
|
|
167
159
|
"path": "templates/.claude/skills/feature-adr/modules/08-qe.md",
|
|
168
|
-
"sha256": "
|
|
160
|
+
"sha256": "bd5b5809142c12fa1378de446b68027a0c342a7a91ad17e4fb3395ca8f6a8508"
|
|
169
161
|
},
|
|
170
162
|
{
|
|
171
163
|
"path": "templates/.claude/skills/feature-adr/modules/09-fleet-qe.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": "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": "
|
|
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": "
|
|
328
|
+
"signature": "RVit/JWzAfaMRgh3KVc200QVdpU0sNO6AcEkRGYt8c8fqq1TG01DKPXiGvV08hWRHH8/a/67+qOko8sYEgqEBw=="
|
|
353
329
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.5.1] - 2026-08-21
|
|
4
|
+
|
|
5
|
+
### Changed — the Step-8 amendment gate is a COMMAND, and the durable writers are witnessed
|
|
6
|
+
|
|
7
|
+
- `AMENDMENT_GATE` no longer asks the QE agent to judge whether every `AM-N` row names a real test.
|
|
8
|
+
It runs `dz amendment-check --slug <slug> --json` and reports the parsed verdict; the old judgement
|
|
9
|
+
wording is REMOVED from both workflow copies and all four `modules/08-qe.md` copies, not
|
|
10
|
+
accompanied. MEASURED 2026-08-21: `features/qe-scoped-review` shipped with five named amendment
|
|
11
|
+
test ids of which none existed, while its plan recorded `## Amendments: None`.
|
|
12
|
+
- the run-cost ledger and the training-pair capture (including backfill) stop handing a subagent a
|
|
13
|
+
pre-baked shell string carrying their payload — the shape a security classifier blocked nine times
|
|
14
|
+
in one run. Both now call `dz feature-adr-record`. The `sed` that rewrote `"date":null` / `"ts":null`
|
|
15
|
+
inside an already-serialised document is gone: the command stamps before serialising.
|
|
16
|
+
- a record failure still NEVER fails the run, but now survives it: `recordFailures` is returned from
|
|
17
|
+
every exit instead of scrolling past inside one log line.
|
|
18
|
+
|
|
19
|
+
Requires `@dzhechkov/harness-core >= 0.6.1`.
|
|
20
|
+
|
|
3
21
|
## [1.5.0] - 2026-08-21
|
|
4
22
|
|
|
5
23
|
### Changed — feature `qe-scoped-review`
|
package/README.md
CHANGED
|
@@ -169,6 +169,25 @@ 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.2)
|
|
173
|
+
|
|
174
|
+
Every `AM-N` row must resolve to a test found INSIDE the file the row names, and the check is
|
|
175
|
+
`dz amendment-check --slug <slug> --json` rather than a paragraph asking the reviewer to confirm it.
|
|
176
|
+
MEASURED 2026-08-21: `features/qe-scoped-review` shipped with five named amendment test ids of which
|
|
177
|
+
none existed, while its plan recorded `## Amendments: None`. The gate did not fail — a prompt cannot.
|
|
178
|
+
|
|
179
|
+
The PLAN is authoritative when it carries rows (Step 6 owes "carry AM-N into the plan verbatim"), and
|
|
180
|
+
the rule that keeps that honest is coverage: an amendment the plan drops — or rewords under the same
|
|
181
|
+
id — is a failure. Renaming the TEST stays legitimate; tests are named later than ideation guesses.
|
|
182
|
+
|
|
183
|
+
The durable writers were migrated in the same release: the run-cost ledger and the training-pair
|
|
184
|
+
capture no longer hand a subagent a pre-baked shell string with their payload baked in — the shape a
|
|
185
|
+
security classifier blocked nine times in one run. Both call `dz feature-adr-record`, which refuses
|
|
186
|
+
before writing and verifies the append by re-reading the tail. A record failure still never fails the
|
|
187
|
+
run; it now survives it in `recordFailures`.
|
|
188
|
+
|
|
189
|
+
Requires `@dzhechkov/harness-core >= 0.6.1`.
|
|
190
|
+
|
|
172
191
|
### Step 0 writes the assessment down, and the acid check gets its input back (v1.5.0)
|
|
173
192
|
|
|
174
193
|
Step 0 classifies the feature and now **writes `00_complexity_assessment.md` before it returns** — the
|
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.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
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"hashes": [
|
|
16
16
|
{
|
|
17
17
|
"alg": "SHA-256",
|
|
18
|
-
"content": "
|
|
18
|
+
"content": "88e0389016f697b90731ee8743a35e523f0ccb079426d59861d894f33e516392"
|
|
19
19
|
}
|
|
20
20
|
]
|
|
21
21
|
},
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"hashes": [
|
|
36
36
|
{
|
|
37
37
|
"alg": "SHA-256",
|
|
38
|
-
"content": "
|
|
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": "
|
|
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": "
|
|
288
|
+
"content": "67eb730c815e6b8f9b9b61084c67ceb85440548f00b21f52187d16e81e3d7af8"
|
|
309
289
|
}
|
|
310
290
|
]
|
|
311
291
|
},
|
|
@@ -415,7 +395,7 @@
|
|
|
415
395
|
"hashes": [
|
|
416
396
|
{
|
|
417
397
|
"alg": "SHA-256",
|
|
418
|
-
"content": "
|
|
398
|
+
"content": "bd5b5809142c12fa1378de446b68027a0c342a7a91ad17e4fb3395ca8f6a8508"
|
|
419
399
|
}
|
|
420
400
|
]
|
|
421
401
|
},
|
|
@@ -645,7 +625,7 @@
|
|
|
645
625
|
"hashes": [
|
|
646
626
|
{
|
|
647
627
|
"alg": "SHA-256",
|
|
648
|
-
"content": "
|
|
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": "
|
|
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:
|
|
@@ -160,18 +160,32 @@ The Confirmation-to-test link is load-bearing. If absent, grade no better than C
|
|
|
160
160
|
|
|
161
161
|
### 3b. Amendment Gate (P2 — amendments inherit the Confirmation discipline)
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
163
|
+
**Do not judge this yourself — RUN the check.** Resolution of every `AM-N` row to a real test is a
|
|
164
|
+
deterministic question, and a deterministic question must not live in prose (cost-of-detection ladder).
|
|
165
|
+
It used to live here, and `features/qe-scoped-review` shipped with five named amendment test ids of
|
|
166
|
+
which none existed, while its plan recorded `## Amendments: None`.
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
dz amendment-check --slug <feature-slug> --json # or --feature-dir <abs path>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
| Outcome | Exit | What it means for the gate |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| `pass` | 0 | every row resolves to a test found inside the file the row names |
|
|
175
|
+
| `skip` | 0 | no `## Amendments` section — an absence, stated, never a silent pass |
|
|
176
|
+
| `fail` | 1 | a HIGH gap: quote every reason verbatim into the QE report |
|
|
177
|
+
| `not-established` | 3 | the grammar matched nothing, or the inputs were unreadable — **never a pass** |
|
|
178
|
+
|
|
179
|
+
Empty stdout, a crash, or a missing `dz` is `not-established`, not a clean gate.
|
|
180
|
+
|
|
181
|
+
**What the command does NOT prove:** that a resolved test discriminates. Vacuity stays yours, in the
|
|
182
|
+
SAME `dz discrimination-check --test <f1,f2,...> --base HEAD --json` run as the ADR property test — an
|
|
183
|
+
amendment test that stays green at pre-feature base proves nothing about its amendment. A **safeguard**
|
|
184
|
+
amendment additionally needs its test to prove the safeguard FIRES on a real input: a structurally-dead
|
|
185
|
+
safeguard passes an existence check and never fires in production.
|
|
186
|
+
|
|
187
|
+
This closes the distance between "we caught the design flaw" (challenge-panel/QCSD) and "we proved the
|
|
188
|
+
fix works".
|
|
175
189
|
|
|
176
190
|
### 3c. I/O-on-pure-path + fixture-swap hunt (P5)
|
|
177
191
|
|
|
@@ -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
|
-
|
|
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
|
|
|
@@ -459,6 +463,10 @@ const TP_MAX_IO = 48000
|
|
|
459
463
|
const TP_DIR = REPO + '/.dz/fa-training/' + SLUG
|
|
460
464
|
const TP_MARK_DIR = REPO + '/.dz/fa-training/.backfill-marks'
|
|
461
465
|
const LEDGER_FILE = REPO + '/.dz/feature-adr/run-cost-ledger.jsonl'
|
|
466
|
+
// ADR-003: a record failure NEVER fails the run — a cost row is observability, and observability must
|
|
467
|
+
// not take a six-hour run down with it. But today's silence is not "secondary", it is invisible: one
|
|
468
|
+
// log line among thousands. Failures accumulate here and are RETURNED, so they survive the run.
|
|
469
|
+
const recordFailures = []
|
|
462
470
|
const TP_README = REPO + '/.dz/fa-training/README.md'
|
|
463
471
|
const TP_BACKFILL_OK = 'TP-BACKFILL-OK'
|
|
464
472
|
const TP_BACKFILL_SKIP = 'TP-BACKFILL-SKIP'
|
|
@@ -554,40 +562,49 @@ async function capturePairs(stage, phaseName, records, resumeGuardStage) {
|
|
|
554
562
|
const markStage = String(stage).replace(/\.\./g, '_').replace(/\//g, '_')
|
|
555
563
|
// The workflow pair line carries the stable feature SLUG, so this content key stays stable across resumes.
|
|
556
564
|
const mark = TP_MARK_DIR + '/' + markStage + '-' + fnv1a64(stage + '\0' + lines.join('\n'))
|
|
557
|
-
|
|
558
|
-
//
|
|
565
|
+
// WITNESSED WRITE (ADR-001), backfill mode. The mark is still an atomic mkdir — but it is the
|
|
566
|
+
// COMMAND that takes it, so the same invocation that claims the mark is the one that validates
|
|
567
|
+
// and writes. The old form claimed the mark in a shell pipeline the courier never inspected.
|
|
559
568
|
if (mode === 'backfill') {
|
|
560
|
-
|
|
569
|
+
let outcome = 'written'
|
|
561
570
|
for (let i = 0; i < lines.length; i++) {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
} else {
|
|
576
|
-
log('training-pair: ' + stage + ' backfill UNVERIFIED (no TP-BACKFILL readback) — the pair may be missing on disk (non-blocking; the run continues)')
|
|
577
|
-
captureFailures.push(captureFailureRecord(stage, mode, 'backfill-unverified', 'no TP-BACKFILL-OK or TP-BACKFILL-SKIP readback'))
|
|
571
|
+
const cmdOne = DZ + ' feature-adr-record --kind training-pair --slug ' + shq(SLUG) + ' --stage ' + shq(stage)
|
|
572
|
+
+ ' --project ' + shq(REPO) + ' --mark ' + shq(markStage + '-' + fnv1a64(stage + '\0' + lines.join('\n')) + '-' + i)
|
|
573
|
+
+ ' --once --pair ' + shq(lines[i]) + ' --json'
|
|
574
|
+
const out = await agent('Run this command via your Bash tool and reply with only its stdout: ' + cmdOne, { label: 'trainpair:backfill:' + stage, phase: phaseName, effort: 'low' })
|
|
575
|
+
const readback = String(out == null ? '' : out)
|
|
576
|
+
const m = /"verdict"\s*:\s*"(written|duplicate|skipped)"/.exec(readback)
|
|
577
|
+
if (m === null) {
|
|
578
|
+
outcome = 'unverified'
|
|
579
|
+
captureFailures.push(captureFailureRecord(stage, mode, 'backfill-unverified', readback === '' ? 'no output from the record command' : readback.slice(0, 200)))
|
|
580
|
+
recordFailures.push({ kind: 'training-pair', stage: stage, reason: 'backfill: ' + (readback === '' ? 'no output from the record command' : readback.slice(0, 300)) })
|
|
581
|
+
break
|
|
582
|
+
}
|
|
583
|
+
if (m[1] !== 'written' && outcome === 'written') outcome = m[1]
|
|
578
584
|
}
|
|
585
|
+
if (outcome === 'written') log('training-pair: ' + stage + ' missing pair backfilled from the checkpoint')
|
|
586
|
+
else if (outcome === 'duplicate') log('training-pair: ' + stage + ' another run already captured this pair (nothing written)')
|
|
587
|
+
else if (outcome === 'skipped') log('training-pair: ' + stage + ' pair already existed (nothing written)')
|
|
588
|
+
else log('training-pair: ' + stage + ' backfill NOT WRITTEN (non-blocking; the run continues)')
|
|
579
589
|
return
|
|
580
590
|
}
|
|
591
|
+
// WITNESSED WRITE (ADR-001): one command invocation per pair, data passed as an ARGUMENT. The
|
|
592
|
+
// `sed` that used to rewrite `"ts":null` inside an already-serialised document is gone — the
|
|
593
|
+
// command stamps the timestamp BEFORE serialising, so no text surgery touches a JSON value.
|
|
594
|
+
let allWritten = true
|
|
581
595
|
for (const line of lines) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
596
|
+
const cmdOne = DZ + ' feature-adr-record --kind training-pair --slug ' + shq(SLUG) + ' --stage ' + shq(stage)
|
|
597
|
+
+ ' --project ' + shq(REPO) + ' --pair ' + shq(line) + ' --json'
|
|
598
|
+
const out = await agent('Run this command via your Bash tool and reply with only its stdout: ' + cmdOne, { label: 'trainpair:' + stage, phase: phaseName, effort: 'low' })
|
|
599
|
+
const readback = String(out == null ? '' : out)
|
|
600
|
+
if (!/"verdict"\s*:\s*"(written|duplicate|skipped)"/.test(readback)) {
|
|
601
|
+
allWritten = false
|
|
602
|
+
captureFailures.push(captureFailureRecord(stage, mode, 'unverified', readback === '' ? 'no output from the record command' : readback.slice(0, 200)))
|
|
603
|
+
recordFailures.push({ kind: 'training-pair', stage: stage, reason: readback === '' ? 'no output from the record command' : readback.slice(0, 300) })
|
|
604
|
+
}
|
|
585
605
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
if (!/TP-OK/.test(String(out == null ? '' : out))) {
|
|
589
|
-
log('training-pair: ' + stage + ' capture UNVERIFIED (no TP-OK readback) — the pair may be missing on disk (non-blocking; the run continues)')
|
|
590
|
-
captureFailures.push(captureFailureRecord(stage, mode, 'unverified', 'no TP-OK readback'))
|
|
606
|
+
if (!allWritten) {
|
|
607
|
+
log('training-pair: ' + stage + ' capture NOT WRITTEN for at least one pair (non-blocking; the run continues)')
|
|
591
608
|
}
|
|
592
609
|
} catch (e) {
|
|
593
610
|
const message = e && e.message ? e.message : String(e)
|
|
@@ -615,17 +632,18 @@ async function appendRunCostRow(stage, phaseName) {
|
|
|
615
632
|
auto: true,
|
|
616
633
|
mode: (typeof MODE === 'string' && MODE !== '') ? MODE : null,
|
|
617
634
|
})
|
|
618
|
-
|
|
619
|
-
//
|
|
620
|
-
//
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
+ '
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
635
|
+
// WITNESSED WRITE (ADR-001): the subagent RUNS a command with data arguments; it is no longer
|
|
636
|
+
// handed a shell pipeline with the row baked in. The command refuses a malformed row, stamps the
|
|
637
|
+
// date BEFORE serialising (no sed over a serialised document) and verifies the append by
|
|
638
|
+
// re-reading the tail. A courier could do none of those three.
|
|
639
|
+
const cmd = DZ + ' feature-adr-record --kind ledger --stage ' + shq(stage) + ' --project ' + shq(REPO)
|
|
640
|
+
+ ' --row ' + shq(line) + ' --json'
|
|
641
|
+
const out = await agent('Run this command via your Bash tool and reply with only its stdout: ' + cmd, { label: 'ledger:append', phase: phaseName, effort: 'low' })
|
|
642
|
+
const readback = String(out == null ? '' : out)
|
|
643
|
+
if (!/"verdict"\s*:\s*"written"/.test(readback)) {
|
|
644
|
+
// ADR-003: SECONDARY — never fails the run — but the failure now SURVIVES it.
|
|
645
|
+
recordFailures.push({ kind: 'ledger', stage: stage, reason: readback === '' ? 'no output from the record command' : readback.slice(0, 300) })
|
|
646
|
+
log('run-cost ledger: ' + stage + ' row NOT WRITTEN (SECONDARY; the run continues) — ' + readback.slice(0, 200))
|
|
629
647
|
}
|
|
630
648
|
} catch (e) {
|
|
631
649
|
log('run-cost ledger: ' + stage + ' append failed (SECONDARY; non-blocking): ' + (e && e.message ? e.message : String(e)))
|
|
@@ -678,7 +696,7 @@ const PLANNER = (A.planner === 'codex') ? 'codex' : 'claude'
|
|
|
678
696
|
// block threads workflow-local state (MODELS/knobs) through shapes the generic blobs do not carry;
|
|
679
697
|
// regeneration via model-resolver/usage-probes/codex-dispatch blobs is the tracked Stage-B item.
|
|
680
698
|
const MODELS = (A.models && typeof A.models === 'object') ? A.models : {}
|
|
681
|
-
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 }
|
|
682
700
|
const CLAUDE_NAMES = { fable: 1, opus: 1, sonnet: 1, haiku: 1 }
|
|
683
701
|
const VALID_REASONING = { none: 1, minimal: 1, low: 1, medium: 1, high: 1, xhigh: 1 }
|
|
684
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' }
|
|
@@ -1857,7 +1875,12 @@ async function designStage(promptText, opts, artifactPath, baseLabel) {
|
|
|
1857
1875
|
if (res && probe && /landed=/.test(String(probe))) return { wrote: [artifactPath], summary: String(res).slice(0, 300) }
|
|
1858
1876
|
log('design artifact did not land on codex (' + artifactPath + ') — falling back to Claude')
|
|
1859
1877
|
const fallbackOpts = {}
|
|
1860
|
-
|
|
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
|
|
1861
1884
|
}
|
|
1862
1885
|
|
|
1863
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' } } }
|
|
@@ -1879,7 +1902,7 @@ const ADR_FITNESS_CHECKLIST = 'ADR fitness checklist for Step 8: read every ' +
|
|
|
1879
1902
|
// judgment cannot carry (444 tests stayed green around undefended protections, including the exact MEASURED
|
|
1880
1903
|
// exploit string). Advisory, exactly like the discrimination gate: findings are HIGH gaps, never an auto-abort,
|
|
1881
1904
|
// and the pipeline never blocks on the tool.
|
|
1882
|
-
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 `
|
|
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.'
|
|
1883
1906
|
// no-stubs (backlog 0b403a0106103901, Karpathy-Michaels rule XI): an unfinished stub left in the
|
|
1884
1907
|
// run's own touched files means the task shipped incomplete — a deterministic grep is layer 1 on the
|
|
1885
1908
|
// cost-of-detection ladder; reviewer judgment is layer 4. Marker strings are ASSEMBLED so this
|
|
@@ -1888,18 +1911,38 @@ const MUTATION_GATE = 'MUTATION GATE (feature ha-mutation-gate — run alongside
|
|
|
1888
1911
|
const STUB_RX = '(^|[^A-Za-z0-9_])(' + ['TO' + 'DO', 'FIX' + 'ME', 'HA' + 'CK', 'XX' + 'X', 'PLACE' + 'HOLDER'].join('|') + ')([^A-Za-z0-9_]|$)'
|
|
1889
1912
|
const STUB_PHRASE = 'imple' + 'ment later'
|
|
1890
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.'
|
|
1891
|
-
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 `
|
|
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.'
|
|
1892
1915
|
// P2 (amendment-confirmation-discipline, fa-improvements 2026-07-18): amendments are where the SHARPEST design
|
|
1893
1916
|
// corrections land (challenge-panel/QCSD) and were the least-tested — prose deltas with no proving test. Every
|
|
1894
1917
|
// amendment is a mini-ADR: it carries a one-line Confirmation naming the test that falsifies it. Machine-checkable
|
|
1895
1918
|
// shape (a linter can assert the `→ test ` token); Step-8 verifies existence + non-vacuity via the SAME
|
|
1896
1919
|
// dz discrimination-check that guards the ADR property (cost-of-detection ladder: judgment → step gate).
|
|
1897
1920
|
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).'
|
|
1898
|
-
const AMENDMENT_GATE = 'AMENDMENT GATE (P2):
|
|
1921
|
+
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. ' +
|
|
1899
1922
|
'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.'
|
|
1900
1923
|
|
|
1901
1924
|
// Step 0: Router + MANDATORY self-learning recall
|
|
1902
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
|
+
}
|
|
1903
1946
|
await loadCheckpoints('Router')
|
|
1904
1947
|
await usageProbe('Router')
|
|
1905
1948
|
const routerTierDirective = A.tier
|
|
@@ -1960,7 +2003,7 @@ if (autoCostStages.length > 0) {
|
|
|
1960
2003
|
// GUARANTEED fa-panel write (the router, being low-effort + multi-job, tends to skip the fa-record
|
|
1961
2004
|
// Bash call). A dedicated single-command agent reliably lights up the live /feature-adr panel at the
|
|
1962
2005
|
// most visible moment. Uses the workspace bin (PATH-independent). Best-effort — never blocks.
|
|
1963
|
-
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
|
|
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' })
|
|
1964
2007
|
|
|
1965
2008
|
// R1 product-architecture-lens (ADR-001 Decision 3): forward-looking сверка of THIS feature vs the LIVE
|
|
1966
2009
|
// product map + vision. NON-BLOCKING/soft by design — it LOGS {signal,confidence} so a real command
|
|
@@ -2014,8 +2057,8 @@ designThunks.push(() => subCheckpoint('requirements', reqPromptText, reqOpts, ['
|
|
|
2014
2057
|
// the requirements prompt is where the Step-0 recalled lessons are INJECTED — recorded per pair
|
|
2015
2058
|
designPairMeta.push({ role: 'design:requirements', prompt: reqPromptText, model: modelLabel(reqOpts), lessons: (LEARNED && LEARNED !== 'none recalled') ? [String(LEARNED)] : [] })
|
|
2016
2059
|
if (isMplus) {
|
|
2017
|
-
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-
|
|
2018
|
-
designThunks.push(() => subCheckpoint('adr', adrPromptText, adrOpts, ['03_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')))
|
|
2019
2062
|
designPairMeta.push({ role: 'design:adr', prompt: adrPromptText, model: modelLabel(adrOpts), lessons: [] })
|
|
2020
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
|
|
2021
2064
|
designThunks.push(() => subCheckpoint('qcsd', qcsdPromptText, qcsdOpts, ['03.5_ideation_report.md'], () => designStage(qcsdPromptText, qcsdOpts, FDIR + '/03.5_ideation_report.md', 'qcsd')))
|
|
@@ -2033,7 +2076,7 @@ if (isMplus) {
|
|
|
2033
2076
|
// (Codex QE #2 — a one-file probe accepted a design missing its ADR/architecture).
|
|
2034
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])
|
|
2035
2078
|
const designArtifacts = ['01_requirements.md']
|
|
2036
|
-
if (isMplus) designArtifacts.push('03_adr/
|
|
2079
|
+
if (isMplus) designArtifacts.push('03_adr/', '03.5_ideation_report.md', '05_architecture.md')
|
|
2037
2080
|
if (isLplus) designArtifacts.push('02_research.md', '04_domain_model.md')
|
|
2038
2081
|
/**
|
|
2039
2082
|
* PER-SIBLING checkpoint (SP-2, no-amplification). Before 2026-08-20 the whole fan shared ONE
|
|
@@ -2110,7 +2153,7 @@ const designFan = await parallel(designThunks)
|
|
|
2110
2153
|
// line, so it can no longer forge one — and `-f` on an exact path cannot be satisfied by a name that
|
|
2111
2154
|
// merely renders like it. MEASURED red/green below in a real shell against a newline-bearing filename.
|
|
2112
2155
|
let fanChecks = ''
|
|
2113
|
-
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) + '; ')
|
|
2114
2157
|
const fanLsCmd = 'cd ' + shq(FDIR) + ' 2>/dev/null && { ' + fanChecks + 'echo ' + shq(FAN_LS_SENTINEL) + '; } || true'
|
|
2115
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' })
|
|
2116
2159
|
// The transcript is validated STRICTLY, not scanned. Round 7 measured the difference: an agent that
|
|
@@ -2172,7 +2215,7 @@ if (!fanVerdict.complete) {
|
|
|
2172
2215
|
// invisible to cost analysis, which is how a stage that keeps dying stays cheap-looking.
|
|
2173
2216
|
// (coderUsed/qe are the outer bindings, both still null here, so the row reports null honestly.)
|
|
2174
2217
|
await appendRunCostRow('design-gate', 'Design')
|
|
2175
|
-
return { tier: tier, phase: 'design-incomplete', slug: SLUG, artifactsDir: FDIR, missingSubstages: fanVerdict.missingSubstages, missingArtifacts: fanVerdict.missingArtifacts, reason: fanVerdict.reason, modelsUsed: modelsUsed, gates: { design: fanVerdict.reason === 'probe-not-established' ? 'not-established' : 'incomplete', plan: 'not-run', planCompleteness: 'not-run', challengePanel: 'not-run', code: 'not-run', qe: 'not-run' }, resumedStages: resumedStages, checkpointing: CHECKPOINTS_ON ? RESUME_MODE : 'off', trainingPairs: CAPTURE_PAIRS ? TP_DIR : 'off', captureFailures: captureFailures, usageEvents: usageEvents, usageThreshold: USAGE_THRESHOLD, polymorphism: POLY.hasManifest ? POLY.report : null, note: 'REFUSED at the Step-5/6 boundary: ' + what + ', so the design is incomplete and Step 6 was NOT dispatched. Planning off a partial design produces a plan with no ADR behind it. ' + repair + ' If a sibling died on a Claude limit, add usage-adaptive routing or route that stage to Codex first (args.models). To rebuild the whole design from scratch instead, re-invoke with args.resume=\'never\'.' }
|
|
2218
|
+
return { tier: tier, phase: 'design-incomplete', slug: SLUG, artifactsDir: FDIR, missingSubstages: fanVerdict.missingSubstages, missingArtifacts: fanVerdict.missingArtifacts, reason: fanVerdict.reason, modelsUsed: modelsUsed, gates: { design: fanVerdict.reason === 'probe-not-established' ? 'not-established' : 'incomplete', plan: 'not-run', planCompleteness: 'not-run', challengePanel: 'not-run', code: 'not-run', qe: 'not-run' }, resumedStages: resumedStages, checkpointing: CHECKPOINTS_ON ? RESUME_MODE : 'off', trainingPairs: CAPTURE_PAIRS ? TP_DIR : 'off', captureFailures: captureFailures, recordFailures: recordFailures, usageEvents: usageEvents, usageThreshold: USAGE_THRESHOLD, polymorphism: POLY.hasManifest ? POLY.report : null, note: 'REFUSED at the Step-5/6 boundary: ' + what + ', so the design is incomplete and Step 6 was NOT dispatched. Planning off a partial design produces a plan with no ADR behind it. ' + repair + ' If a sibling died on a Claude limit, add usage-adaptive routing or route that stage to Codex first (args.models). To rebuild the whole design from scratch instead, re-invoke with args.resume=\'never\'.' }
|
|
2176
2219
|
}
|
|
2177
2220
|
|
|
2178
2221
|
// Step 6: Plan — optionally routed to Codex's top model (opt-in via args.planner='codex').
|
|
@@ -2320,7 +2363,7 @@ if (planGate.verdict !== 'pass') {
|
|
|
2320
2363
|
// NAMED REFUSAL — the run stops here and the coder is never dispatched. This precedes the L/XL
|
|
2321
2364
|
// checkpoint deliberately: an incomplete plan is not something to steer, it is something to fix.
|
|
2322
2365
|
await appendRunCostRow('plan-gate', 'Plan')
|
|
2323
|
-
return { tier: tier, phase: 'plan-gate-failed', slug: SLUG, artifactsDir: FDIR, planner: (plan ? plan.planner : null), plan: (plan ? plan.summary : null), modelsUsed: modelsUsed, planGate: planGate, gates: { plan: (plan ? 'produced' : 'missing'), planCompleteness: planGate.verdict, challengePanel: 'not-run', code: 'not-run', qe: 'not-run' }, resumedStages: resumedStages, checkpointing: CHECKPOINTS_ON ? RESUME_MODE : 'off', trainingPairs: CAPTURE_PAIRS ? TP_DIR : 'off', captureFailures: captureFailures, usageEvents: usageEvents, usageThreshold: USAGE_THRESHOLD, polymorphism: POLY.hasManifest ? POLY.report : null, note: refusalNoteFor(planGate, SLUG) }
|
|
2366
|
+
return { tier: tier, phase: 'plan-gate-failed', slug: SLUG, artifactsDir: FDIR, planner: (plan ? plan.planner : null), plan: (plan ? plan.summary : null), modelsUsed: modelsUsed, planGate: planGate, gates: { plan: (plan ? 'produced' : 'missing'), planCompleteness: planGate.verdict, challengePanel: 'not-run', code: 'not-run', qe: 'not-run' }, resumedStages: resumedStages, checkpointing: CHECKPOINTS_ON ? RESUME_MODE : 'off', trainingPairs: CAPTURE_PAIRS ? TP_DIR : 'off', captureFailures: captureFailures, recordFailures: recordFailures, usageEvents: usageEvents, usageThreshold: USAGE_THRESHOLD, polymorphism: POLY.hasManifest ? POLY.report : null, note: refusalNoteFor(planGate, SLUG) }
|
|
2324
2367
|
}
|
|
2325
2368
|
|
|
2326
2369
|
// Hybrid checkpoint for L/XL
|
|
@@ -2337,11 +2380,29 @@ if (stopHere) {
|
|
|
2337
2380
|
let challengeVerdict = null
|
|
2338
2381
|
try { challengeVerdict = plan ? await runChallengePanel('features/' + SLUG + '/06_implementation_plan.md', plan.planner) : null }
|
|
2339
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))) }
|
|
2340
2401
|
// P4 (checkpoint-gate-line): a DERIVED gates map — each entry comes from machine state (artifact/verdict
|
|
2341
2402
|
// presence), never from prose, so a skipped gate shows as 'not-run' instead of being silently forgotten.
|
|
2342
2403
|
const planGates = { plan: (plan ? 'produced' : 'missing'), planCompleteness: planGate.verdict, challengePanel: (challengeVerdict ? 'ran' : 'not-run'), code: 'not-run', qe: 'not-run' }
|
|
2343
2404
|
await appendRunCostRow('plan', 'Plan')
|
|
2344
|
-
return { tier: tier, phase: 'checkpoint-after-plan', artifactsDir: FDIR, planner: (plan ? plan.planner : null), plan: (plan ? plan.summary : null), modelsUsed: plannedModels, challengeVerdict: challengeVerdict, gates: planGates, resumedStages: resumedStages, checkpointing: CHECKPOINTS_ON ? RESUME_MODE : 'off', trainingPairs: CAPTURE_PAIRS ? TP_DIR : 'off', captureFailures: captureFailures, usageEvents: usageEvents, usageThreshold: USAGE_THRESHOLD, polymorphism: POLY.hasManifest ? POLY.report : null, note: 'L/XL checkpoint - review the ADR + plan (+ the planned code/qe/fleet models) + the challenge panel verdict (advisory) + the gates line, then re-invoke with args.stopAfter="none" to implement + QE (durable checkpoints make the re-invoke resume router+design+plan instead of re-running them). Present the gates map as a `🚦 Gates:` line in the checkpoint banner, rendering the planCompleteness entry as `K2 plan-completeness ✓` (pass) / `✗` (fail) / `inconclusive`.' }
|
|
2405
|
+
return { tier: tier, phase: 'checkpoint-after-plan', artifactsDir: FDIR, planner: (plan ? plan.planner : null), plan: (plan ? plan.summary : null), modelsUsed: plannedModels, challengeVerdict: challengeVerdict, gates: planGates, resumedStages: resumedStages, checkpointing: CHECKPOINTS_ON ? RESUME_MODE : 'off', trainingPairs: CAPTURE_PAIRS ? TP_DIR : 'off', captureFailures: captureFailures, recordFailures: recordFailures, usageEvents: usageEvents, usageThreshold: USAGE_THRESHOLD, polymorphism: POLY.hasManifest ? POLY.report : null, note: 'L/XL checkpoint - review the ADR + plan (+ the planned code/qe/fleet models) + the challenge panel verdict (advisory) + the gates line, then re-invoke with args.stopAfter="none" to implement + QE (durable checkpoints make the re-invoke resume router+design+plan instead of re-running them). Present the gates map as a `🚦 Gates:` line in the checkpoint banner, rendering the planCompleteness entry as `K2 plan-completeness ✓` (pass) / `✗` (fail) / `inconclusive`.' }
|
|
2345
2406
|
}
|
|
2346
2407
|
|
|
2347
2408
|
// Step 7: Code (optional Codex fallback on Claude-limit exhaustion)
|
|
@@ -2370,7 +2431,7 @@ if (QE_SCOPE === 'uncommitted') {
|
|
|
2370
2431
|
|
|
2371
2432
|
phase('Code')
|
|
2372
2433
|
await usageProbe('Code')
|
|
2373
|
-
const codePrompt = 'Step 7 (Code) of /feature-adr for "' + DESC + '" (' + SLUG + ').
|
|
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')
|
|
2374
2435
|
// Resolve the coder model. args.models.code wins (a direct 'codex' spec = codex-first); else the legacy
|
|
2375
2436
|
// CODER knob drives it (with its codex-fallback null-guard). resolveStageModel('code') folds both via the
|
|
2376
2437
|
// code:null sentinel → resolveCoderSpec(). A Claude resolution merges {model} onto the Claude branch;
|
|
@@ -2461,7 +2522,7 @@ if (needsCodeLandedBarrier(coderUsed)) {
|
|
|
2461
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)')
|
|
2462
2523
|
const scrapeLine = scrapeDiagnostic.length ? '\ncodex-self-declared (diagnostic, not matched): ' + scrapeDiagnostic.join(', ') : ''
|
|
2463
2524
|
if (landingStatus === 'landed') {
|
|
2464
|
-
landedNote = '\n\nCODEX-CODED (out-of-band): Step 7.5 landing barrier verdict LANDED (mode=' + barrierPlan.mode + ', window=' + barrierPlan.pollWindowSeconds + 's).
|
|
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
|
|
2465
2526
|
} else if (landingStatus === 'genuinely-not-landed') {
|
|
2466
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
|
|
2467
2528
|
} else {
|
|
@@ -2490,8 +2551,48 @@ if (resumedStages.indexOf('code') !== -1 && landedNote !== '') {
|
|
|
2490
2551
|
|
|
2491
2552
|
// Step 8: QE (brutal-honesty, agentic-qe) + MANDATORY teach
|
|
2492
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.'
|
|
2493
2594
|
await usageProbe('QE')
|
|
2494
|
-
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
|
|
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')
|
|
2495
2596
|
// CROSS-MODEL QE (load-bearing): resolveStageModel('qe') derives the OTHER family than the resolved
|
|
2496
2597
|
// coder when args.models.qe is unset (coder-codex ⇒ opus; coder-Claude ⇒ codex, or opus if codex absent).
|
|
2497
2598
|
// An explicit args.models.qe wins. A Claude qe spec is merged onto the qe-code-reviewer base (role
|
|
@@ -2632,7 +2733,7 @@ if (qe === null && (qeIsCodex || QE_REVIEWER === 'codex-fallback')) {
|
|
|
2632
2733
|
// with no 08_qe_report.md at all. Named by cross-family review of b6973199. The verdict itself
|
|
2633
2734
|
// is real (Codex produced it), so a failed transcription DEGRADES the run rather than voiding
|
|
2634
2735
|
// it — but it must be visible, and it must never read as a clean QE.
|
|
2635
|
-
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
|
|
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
|
|
2636
2737
|
// WITNESS THE REWRITE, not the existence. On a re-QE or a resume with the same slug an OLD
|
|
2637
2738
|
// 08_qe_report.md is already sitting there, and an existence probe reports that stale file as
|
|
2638
2739
|
// landed — so a scribe that wrote nothing still marked the new verdict recorded, and the stage
|
|
@@ -2948,14 +3049,29 @@ if (DELIVERY_ON) {
|
|
|
2948
3049
|
// L/XL checkpoint-after-plan return above — no code has landed there yet.)
|
|
2949
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' })
|
|
2950
3051
|
|
|
2951
|
-
|
|
2952
|
-
|
|
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))
|
|
2953
3068
|
// QE-D#3/#4: the promise tag asserts the gate RAN AND its review doc LANDED — an errored gate or a missing
|
|
2954
3069
|
// artifact must not claim it (a promise about a file that does not exist is exactly a fabricated completeness).
|
|
2955
3070
|
if (delivery && delivery.artifact === 'written' && delivery.handoff !== 'errored') tags.push('FEATURE_ADR_DELIVERY_GATED')
|
|
2956
3071
|
await appendRunCostRow('full', (isLplus ? 'FleetQE' : 'QE'))
|
|
2957
3072
|
return {
|
|
2958
3073
|
slug: SLUG, tier: tier, mode: MODE, artifactsDir: FDIR,
|
|
3074
|
+
repoRootCheck: repoRootCheck,
|
|
2959
3075
|
design: design.filter(Boolean).map((d) => d.wrote).flat(),
|
|
2960
3076
|
codeWrote: code ? code.wrote : [],
|
|
2961
3077
|
qeGrade: qe ? qe.grade : null,
|
|
@@ -2974,7 +3090,7 @@ return {
|
|
|
2974
3090
|
resumedStages: resumedStages,
|
|
2975
3091
|
checkpointing: CHECKPOINTS_ON ? RESUME_MODE : 'off',
|
|
2976
3092
|
trainingPairs: CAPTURE_PAIRS ? TP_DIR : 'off',
|
|
2977
|
-
captureFailures: captureFailures,
|
|
3093
|
+
captureFailures: captureFailures, recordFailures: recordFailures,
|
|
2978
3094
|
reqeDue: reqeDue,
|
|
2979
3095
|
brain: BRAIN,
|
|
2980
3096
|
polymorphism: POLY.hasManifest ? POLY.report : null,
|