@dzhechkov/skills-feature-adr 1.5.2 → 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 +6 -6
- package/README.md +34 -1
- package/package.json +1 -1
- package/sbom.json +5 -5
- package/templates/.claude/skills/feature-adr/modules/06-implementation-plan.md +5 -0
- package/templates/.claude/skills/feature-adr/scripts/check-plan-completeness.mjs +30 -7
- package/templates/.claude/workflows/feature-adr.js +73 -5
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,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"path": "package.json",
|
|
28
|
-
"sha256": "
|
|
28
|
+
"sha256": "2224692687da750844ef25096d1fc41fc86d7e79743ac7a8f42bf51ba9018750"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"path": "src/cli.js",
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
"path": "templates/.claude/skills/feature-adr/modules/06-implementation-plan.md",
|
|
152
|
-
"sha256": "
|
|
152
|
+
"sha256": "b3efdfa1d44f4c51ff46551b5827066955c1f0bfee212d371ac1e2fb1c99980f"
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
"path": "templates/.claude/skills/feature-adr/modules/07-code.md",
|
|
@@ -249,7 +249,7 @@
|
|
|
249
249
|
},
|
|
250
250
|
{
|
|
251
251
|
"path": "templates/.claude/skills/feature-adr/scripts/check-plan-completeness.mjs",
|
|
252
|
-
"sha256": "
|
|
252
|
+
"sha256": "eddf116620050e80b88497ace026fb8e0a12986f94f216181480c7daa0e08be4"
|
|
253
253
|
},
|
|
254
254
|
{
|
|
255
255
|
"path": "templates/.claude/skills/frontend-design/LICENSE.txt",
|
|
@@ -313,7 +313,7 @@
|
|
|
313
313
|
},
|
|
314
314
|
{
|
|
315
315
|
"path": "templates/.claude/workflows/feature-adr.js",
|
|
316
|
-
"sha256": "
|
|
316
|
+
"sha256": "a4e277040b42646d9bc1b06f78e32aa11f118a924f1626be67640f87ede8b292"
|
|
317
317
|
},
|
|
318
318
|
{
|
|
319
319
|
"path": "templates/lib/memory-protocol.md",
|
|
@@ -325,5 +325,5 @@
|
|
|
325
325
|
}
|
|
326
326
|
]
|
|
327
327
|
},
|
|
328
|
-
"signature": "
|
|
328
|
+
"signature": "uUN7yuKgSOed/hE0xooh1K788NcAFhnvsh92AHcu4oHn/uMZurn0VnktfhsUDHUv3jR8F6bMoKgrb/F6dHrvCQ=="
|
|
329
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,7 +65,7 @@
|
|
|
65
65
|
"hashes": [
|
|
66
66
|
{
|
|
67
67
|
"alg": "SHA-256",
|
|
68
|
-
"content": "
|
|
68
|
+
"content": "2224692687da750844ef25096d1fc41fc86d7e79743ac7a8f42bf51ba9018750"
|
|
69
69
|
}
|
|
70
70
|
]
|
|
71
71
|
},
|
|
@@ -375,7 +375,7 @@
|
|
|
375
375
|
"hashes": [
|
|
376
376
|
{
|
|
377
377
|
"alg": "SHA-256",
|
|
378
|
-
"content": "
|
|
378
|
+
"content": "b3efdfa1d44f4c51ff46551b5827066955c1f0bfee212d371ac1e2fb1c99980f"
|
|
379
379
|
}
|
|
380
380
|
]
|
|
381
381
|
},
|
|
@@ -625,7 +625,7 @@
|
|
|
625
625
|
"hashes": [
|
|
626
626
|
{
|
|
627
627
|
"alg": "SHA-256",
|
|
628
|
-
"content": "
|
|
628
|
+
"content": "eddf116620050e80b88497ace026fb8e0a12986f94f216181480c7daa0e08be4"
|
|
629
629
|
}
|
|
630
630
|
]
|
|
631
631
|
},
|
|
@@ -785,7 +785,7 @@
|
|
|
785
785
|
"hashes": [
|
|
786
786
|
{
|
|
787
787
|
"alg": "SHA-256",
|
|
788
|
-
"content": "
|
|
788
|
+
"content": "a4e277040b42646d9bc1b06f78e32aa11f118a924f1626be67640f87ede8b292"
|
|
789
789
|
}
|
|
790
790
|
]
|
|
791
791
|
},
|
|
@@ -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
|
|
|
@@ -327,13 +327,36 @@ else for (const t of acidTokens) if (!new RegExp(`\\b${t.replace(/[.*+?^${}()|[\
|
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
329
|
if (sectionStart >= 0) {
|
|
330
|
-
for
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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`);
|
|
337
360
|
}
|
|
338
361
|
}
|
|
339
362
|
}
|
|
@@ -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
|
|
@@ -1747,7 +1752,12 @@ function planCompletenessGateCmd(repo, featureDir, tier, opts) {
|
|
|
1747
1752
|
// and the tried paths live OUTSIDE the verdict line so no path can smuggle a second verdict word
|
|
1748
1753
|
// into it.
|
|
1749
1754
|
'echo "K2_GATE_SCRIPT=${GS:-none}"',
|
|
1750
|
-
'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"',
|
|
1751
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',
|
|
1752
1762
|
].join('\n')
|
|
1753
1763
|
}
|
|
@@ -1834,6 +1844,57 @@ function decideModeBScope(o) {
|
|
|
1834
1844
|
return { ok: true, files: files, dropped: dropped }
|
|
1835
1845
|
}
|
|
1836
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
|
+
|
|
1837
1898
|
function crossFamilyQe(o) {
|
|
1838
1899
|
// NORMALISE first — raw-string comparison let 'Claude' vs 'claude' report a cross-family review
|
|
1839
1900
|
// that never happened (caught by codex review --commit on the TS-only fix: the exported helper
|
|
@@ -1917,7 +1978,7 @@ const DISCRIMINATION_GATE = '\u00a742 TEST-DISCRIMINATION GATE (run right after
|
|
|
1917
1978
|
// amendment is a mini-ADR: it carries a one-line Confirmation naming the test that falsifies it. Machine-checkable
|
|
1918
1979
|
// shape (a linter can assert the `→ test ` token); Step-8 verifies existence + non-vacuity via the SAME
|
|
1919
1980
|
// dz discrimination-check that guards the ADR property (cost-of-detection ladder: judgment → step gate).
|
|
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).'
|
|
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).'
|
|
1921
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. ' +
|
|
1922
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.'
|
|
1923
1984
|
|
|
@@ -2295,7 +2356,7 @@ async function runChallengePanel(planRel, plannerName) {
|
|
|
2295
2356
|
if (!authorIsCodex) {
|
|
2296
2357
|
// author=Claude → Codex adversary (cross-family). Compact prompt: Codex reads the files itself (no 24k
|
|
2297
2358
|
// brief inlined). safeCodexAgent is the honest exec path; null/invalid ⇒ loud Claude fallback below.
|
|
2298
|
-
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' })
|
|
2299
2360
|
if (cx) { try { verdict = sanitizeChallengeVerdict(JSON.parse(String(cx).replace(/^[^{]*/, '').replace(/[^}]*$/, ''))) } catch { verdict = null } }
|
|
2300
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' }
|
|
2301
2362
|
}
|
|
@@ -2355,7 +2416,7 @@ if (plan) {
|
|
|
2355
2416
|
// plan straight into Step 7. The gate is forced NOT-ESTABLISHED without probing the tree at all.
|
|
2356
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.' }
|
|
2357
2418
|
if (plan) {
|
|
2358
|
-
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' })
|
|
2359
2420
|
planGate = parsePlanGateVerdict(planGateOut)
|
|
2360
2421
|
}
|
|
2361
2422
|
log('K2 plan-completeness gate: ' + planGate.verdict + ' (exit=' + (planGate.exit === null ? 'unknown' : planGate.exit) + ', reason=' + planGate.reason + ')')
|
|
@@ -2424,7 +2485,14 @@ if (QE_SCOPE === 'uncommitted') {
|
|
|
2424
2485
|
const baseCmd = changeSetProbeCmd({ scope: 'uncommitted', paths: preCodeTargets, quote: shq })
|
|
2425
2486
|
if (baseCmd) {
|
|
2426
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' })
|
|
2427
|
-
|
|
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)
|
|
2428
2496
|
}
|
|
2429
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')
|
|
2430
2498
|
}
|