@dzhechkov/skills-feature-adr 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.dz-manifest.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "files": [
6
6
  {
7
7
  "path": "CHANGELOG.md",
8
- "sha256": "bbb43101a686369a8662f47fe7b7aa75073b614a188c2752cd81d9570620bb3d"
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": "469873d967c4438afd0b25ec8b013347841e07328f508f6786f90cc6aa2f09b9"
16
+ "sha256": "47f9402d4c3954d6d5a75ad906ec7b592010fdfd80f347d4a87dd14dd9e300ce"
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": "c1e4d257167fd2462e0c8e09f248fc1aef332aa34fb09dddd5940bdcc30ea34b"
28
+ "sha256": "3fbe9ef86c9f6fa7c86bbe7760729094e80edb67577e397678b2d98dca1c2034"
29
29
  },
30
30
  {
31
31
  "path": "scripts/sync-templates.js",
@@ -157,7 +157,7 @@
157
157
  },
158
158
  {
159
159
  "path": "templates/.claude/skills/feature-adr/modules/06-implementation-plan.md",
160
- "sha256": "c8fdd1877d8d9515ab29f52c3db49ddd9612b5797bc071e6208def860c02e6f2"
160
+ "sha256": "3903e2694ad47782914b35869bbf19c5f0803b6dafdcfb629df343a5ff00d1b9"
161
161
  },
162
162
  {
163
163
  "path": "templates/.claude/skills/feature-adr/modules/07-code.md",
@@ -165,7 +165,7 @@
165
165
  },
166
166
  {
167
167
  "path": "templates/.claude/skills/feature-adr/modules/08-qe.md",
168
- "sha256": "97e9a7bc54f196b381c4ef71258cddcb2fb48f216df1ba015927c176bad57084"
168
+ "sha256": "bd5b5809142c12fa1378de446b68027a0c342a7a91ad17e4fb3395ca8f6a8508"
169
169
  },
170
170
  {
171
171
  "path": "templates/.claude/skills/feature-adr/modules/09-fleet-qe.md",
@@ -257,7 +257,7 @@
257
257
  },
258
258
  {
259
259
  "path": "templates/.claude/skills/feature-adr/scripts/check-plan-completeness.mjs",
260
- "sha256": "41d85b012a9d6cc172eaa5d7777294f2c01fb54b752ed61c7cbe2d2cd7b79b67"
260
+ "sha256": "2e1afeb8cbc08ce872fc8280a0809e7712ee76879d08b48caa5a0d4082cb8647"
261
261
  },
262
262
  {
263
263
  "path": "templates/.claude/skills/frontend-design/LICENSE.txt",
@@ -321,7 +321,7 @@
321
321
  },
322
322
  {
323
323
  "path": "templates/.claude/workflows/feature-adr.js",
324
- "sha256": "1be239d2f02612bc33027767d9930eae8a2d6d5afd427d1485198bbdd821a3c7"
324
+ "sha256": "0c74e8995d82bb986ef3233e91beebfa0e761aae4434f24554077bb05d2199e9"
325
325
  },
326
326
  {
327
327
  "path": "templates/lib/memory-protocol.md",
@@ -349,5 +349,5 @@
349
349
  }
350
350
  ]
351
351
  },
352
- "signature": "gixeRY/3+gjSSS3a+DfVwn40SVlD9Gr2sCh3H8afGWd1+Dzo8R8CmD61qG0cLeDdHn2NgzRWwvICHe+0HFlYCA=="
352
+ "signature": "9WVoqK2D8eKSnmhhMqHPt31NedxwZCHiHyxJ5VLMxSVMecMqyXgrCJ2GQ3OOfIeiquB2D01bh7QjZW/EMVM9Aw=="
353
353
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,145 @@
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
+
21
+ ## [1.5.0] - 2026-08-21
22
+
23
+ ### Changed — feature `qe-scoped-review`
24
+
25
+ ### Changed — Step-8 Codex QE is SCOPED, and every fallback names its cause
26
+
27
+ Not published. Behaviour change to the Step-8 QE dispatch only; routing, the Claude belt, checkpoints
28
+ and training-pair capture are untouched.
29
+
30
+ - **The measurement that forced it** (same question, same model `gpt-5.6-sol` at effort `high`,
31
+ 2026-08-21): an UNSCOPED `codex exec` QE prompt of 19 038 chars spent 280 s, exited 124 and produced
32
+ 416 KB of exploration with NO verdict — and did it again under a 1500 s ceiling. The same question
33
+ scoped to two named files answered in 41 s with `Grade: B`; `codex review --commit` answered in
34
+ 146 s with the scope derived from the diff. The budget went on RECONNAISSANCE, so raising the
35
+ timeout buys more of it, and the prompt ceiling was never the binding constraint (19 038 < 24 000,
36
+ with ~5 000 chars of headroom). The harm was not the minutes: on timeout the dispatch returns null,
37
+ the belt runs a Claude reviewer, and cross-family QE is lost SILENTLY on exactly the large features
38
+ that need it most.
39
+ - **Mode A — `codex review`** is now the primary Step-8 Codex pass; the diff defines the scope.
40
+ Default `--uncommitted`, overridable with `args.qeScope` / `args.qeScopeRef`. The builder can never
41
+ emit `-m` and never appends a positional prompt: MEASURED, `codex review` rejects `-m` with exit 2,
42
+ and EVERY scope flag (`--commit`, `--base`, `--uncommitted`) rejects `[PROMPT]` with exit 2. Either
43
+ mistake would present as a review that silently did not happen.
44
+ - **Mode B — a NARROWED `codex exec`** carries our own questions over at most 3 named files and ends
45
+ with `Grade: <A|B|C|D>`. An unscoped mode-B dispatch is not constructible: an empty file list yields
46
+ `''`, and `codexExecPlan` now refuses an unscoped `qe` prompt outright.
47
+ - **A locked decline taxonomy** — `timeout | no-verdict | tool-error | unusable-output | unavailable |
48
+ over-ceiling` — replaces the single generic reason. A timeout ("narrow the scope") and a broken
49
+ invocation ("fix the command") can no longer render the same string inside
50
+ `opus (cross-family QE DID NOT happen — …)`. An unknown kind throws rather than rendering something
51
+ plausible.
52
+ - **A machine sentinel** (`CODEX-QE-SIGNAL exit=… elapsed=…s bytes=…`, the Step-7.5 landing-signal
53
+ grammar) carries the exit code past the shell agent, so a timeout is knowable without asking a model
54
+ to self-report its own failure. A MISSING sentinel is classified `tool-error` on the pipeline path —
55
+ never a pass.
56
+ - **Zero findings is never an `A`.** Mode A cannot be asked for a letter, so its grade is derived from
57
+ the severities the reviewer reported and labelled `gradeSource: 'derived-from-findings'`; an empty or
58
+ unparseable finding set yields `null` and declines. MEASURED: `codex review` on a clean tree exits 0
59
+ with a well-formed, entirely empty review — the exact input that a default letter would have turned
60
+ into a clean bill of health.
61
+ - **`qe` now returns `gaps` from the reviewer's actual findings**, plus `gradeSource` and
62
+ `qeScope {mode, ref, files}`; the review scope enters the `qe` checkpoint hash so a resume cannot
63
+ present a mode-B verdict as a mode-A one.
64
+ - **`CODEX_EXEC_PROMPT_CEILING_CHARS` is retained but DEMOTED** — it is a sanity bound on an absurd
65
+ payload, and is no longer documented as the thing that prevents a stall. The defence is scope.
66
+ - **Unchanged on purpose:** `resolveQeSpec` / `coderIsCodex` / `qeShouldUseCodex` (verified correct by
67
+ direct call), the `crossFamilyQe` label format, and the Claude belt — the pipeline still never blocks
68
+ on Codex.
69
+
70
+ ### Fixed — the K2 plan gate could not complete a run on a non-JS repository
71
+
72
+ - **C2 recognises test paths in every ecosystem.** The gate demanded a `.test.(ts|mjs|js)` suffix, so
73
+ a Python/Go/Rust/JVM/.NET feature could never satisfy it (measured: 4/4 JS forms matched, 0/11
74
+ non-JS). Replaced by a two-stage predicate — a language-neutral candidate-path extractor plus one
75
+ `$`-anchored rule per ecosystem (JS/TS, pytest `test_*.py` + `*_test.py`, Go, Rust `tests/*.rs` +
76
+ `tests.rs`, JVM, .NET). Widening it to "contains the word test" was rejected: `docs/testing.md`,
77
+ `src/latest.rs` and prose are still refused. An unknown ecosystem stays a FAIL, never a WARN.
78
+ - **New opt-in `testPathRules`** in the existing `architecture/project-skills.json`: additive rules,
79
+ each anchored by wrapping so an alternation cannot leak, capped at 200 characters, and FATAL when
80
+ malformed (`NOT-ESTABLISHED`, exit 3) rather than silently falling back to the built-ins.
81
+ - **The gate script is resolved from the WORKSPACE first.** The command `cd`s into the target repo,
82
+ so the repo-relative path died with `Cannot find module` on any repo without its own feature-adr
83
+ install. Order: `args.gateScript` (new, absolute, validated) → workspace copy → repo copy, with
84
+ `K2_GATE_SCRIPT=` / `K2_GATE_TRIED=` echoed for audit. Nothing found ⇒ reason `tooling-missing`
85
+ (a NOT-ESTABLISHED, never a skip), and the operator note now says the gate could not be RUN and
86
+ that this is not a plan defect — it no longer tells you to fix a plan that is fine.
87
+ - **A relative `args.dzBin` is pinned to the workspace root once**, before any `cd`, so the six
88
+ commands that splice it stop resolving against three different bases (a null usage probe was being
89
+ read upstream as "the Claude limit was hit").
90
+
91
+ ### Changed — `EXPECTED_CODE_TARGETS` admissibility (C3)
92
+
93
+ - Dotfile targets (`.claude/…`, `.github/workflows/ci.yml`, `.gitignore`, `.env.example`) are
94
+ admissible — previously feature-adr could not name its own files or any CI config, in any language.
95
+ - Directory-shaped targets are now REJECTED (`trailing slash — names a directory, not a file`): a bare
96
+ directory gives plan-vs-diff matching nothing concrete to verify.
97
+ - Every refusal names its own defect instead of one catch-all: `path traversal ('..' segment)`,
98
+ `degenerate path segment`, `empty path segment`, `path segment ends with '.'`,
99
+ `illegal character '<c>'`, `empty stem after the leading dot`. The traversal check is a SEGMENT
100
+ test now, so the ordinary filename `foo..bar.ts` is no longer called a path traversal.
101
+
102
+ ### Requires
103
+
104
+ - Nothing new at runtime: the workflow INLINES the gate command builder and its parser (the sandbox
105
+ cannot import), so this release is self-contained. The pure halves it mirrors live in
106
+ `@dzhechkov/harness-core` (`planCompletenessGateCmd` gained an optional 4th argument;
107
+ `refusalNoteFor` and `normalizeDzBin` are new) and a drift test pins the two copies together.
108
+
109
+
110
+ ### Fixed — Step 0 wrote nothing down, so the tier was unreadable and the acid check had no input
111
+
112
+ - **Step 0 now WRITES `00_complexity_assessment.md`** before returning: the tier with its decisive
113
+ criterion, the active steps, the recalled patterns, and an acid-case table whose rows are pinned to
114
+ the exact `| A<n> | <bad input> | <what must happen> |` shape the K2 gate parses. MEASURED
115
+ 2026-08-21: 66 of 199 features carried that file and the last four in a row did not. Two silent
116
+ consequences — the tier was recorded NOWHERE while a run was alive (the checkpoint lands at phase
117
+ end, the result object only at the very end), and the C4 acid check quietly switched itself off,
118
+ including for the features that introduced it. An honest no-acid-cases run still writes prose and
119
+ no table; the gate now tells an ABSENT file from a deliberate skip.
120
+ - **A caller-forced tier is the tier of record.** `tier = args.tier || router.tier` means the run
121
+ executes the override while Step 0 wrote down its own classification. The artifact now carries
122
+ `Effective tier: <forced> (forced by the caller)` alongside the router's own recommendation, and
123
+ sizes the acid table for the effective tier. With no override the prompt is byte-identical.
124
+ - **A pre-contract router checkpoint no longer resumes into the new contract** — the resume gate only
125
+ asks whether the artifact is PRESENT, and any of the 66 features with a tableless file satisfied it.
126
+
127
+ ### Fixed — the Step-8 QE stage measured the wrong thing in four different ways
128
+
129
+ - the change set is the run's DELTA (pre-code baseline vs after), not the working tree's current dirt;
130
+ - a finding whose location cannot be parsed is `unlocatable` and stays in the graded set — it used to
131
+ be filed as someone else's dirt, i.e. unknown counted as clean;
132
+ - mode B refuses a scope built from unlanded code, an unmeasured change set or an empty intersection;
133
+ - the QE scribe is WITNESSED: the report is re-hashed before and after, so an existing file from an
134
+ earlier run can no longer stand in for one this run never wrote.
135
+
136
+ ### Fixed — durable state is written by a command, not by hand
137
+
138
+ - checkpoint lines now go through `dz feature-adr-checkpoint`. The subagent RUNS a command instead of
139
+ hand-writing state into a file: a security classifier blocked NINE consecutive checkpoint writes in
140
+ one run, `.fa-state/checkpoints.jsonl` was never created, resume was silently dead, and the run
141
+ reported success.
142
+
3
143
  ## [1.4.0] - 2026-08-20
4
144
 
5
145
  ### Changed — the composite design checkpoint
package/README.md CHANGED
@@ -106,6 +106,127 @@ ARCHITECTURE → IMPLEMENTATION → CODE → QE → FLEET QE
106
106
  # Full protocols + 6 extra skills, up to 7 fleet QE agents
107
107
  ```
108
108
 
109
+ ### The K2 plan gate runs on ANY repo, not just JS/TS ones (v1.5.0)
110
+
111
+ Between Step 6 and Step 7 the pipeline runs a machine plan-completeness gate (K2). Until v1.5.0 it
112
+ could not finish a run on a non-JS repository — three separate defects, all closed here.
113
+
114
+ **1. Test paths in every ecosystem.** C2 checks that each ADR's Confirmation names a test file and
115
+ that the plan names the same path. It used to demand a `.test.(ts|mjs|js)` suffix, so on a Python,
116
+ Go, Rust, JVM or .NET repo it found nothing and failed the plan (measured: 4/4 JS forms matched,
117
+ 0/11 non-JS forms did). It now uses a two-stage predicate — candidate paths are extracted from the
118
+ Confirmation text, then matched against one anchored rule per ecosystem:
119
+
120
+ | Ecosystem | Recognised |
121
+ |---|---|
122
+ | JS/TS | `*.test.*` / `*.spec.*` (`ts tsx mts cts mjs cjs js jsx`) |
123
+ | pytest | `test_*.py` **and** `*_test.py` |
124
+ | Go | `*_test.go` |
125
+ | Rust | `tests/*.rs` **and** `tests.rs` |
126
+ | JVM | `*Test|Tests|IT|Spec.(java kt kts scala groovy)` |
127
+ | .NET | `*Test|Tests.(cs fs vb)` |
128
+
129
+ Deliberately NOT "any path containing the word test": `docs/testing.md`, `src/latest.rs`,
130
+ `contests/results.py` and a sentence like *"the staleness test will verify this"* are all refused. A
131
+ gate that passes prose is worse than one that fails Python. An unknown ecosystem is still a FAIL,
132
+ never a warning — the message names the recognised set and points at the override below.
133
+
134
+ **Extending the vocabulary — `testPathRules`.** Add rules to the manifest the pipeline already
135
+ reads, `architecture/project-skills.json` (no second dotfile):
136
+
137
+ ```json
138
+ {
139
+ "testPathRules": [
140
+ { "ecosystem": "elixir", "pattern": "(?:^|/)test/[^/]+_test\\.exs" }
141
+ ]
142
+ }
143
+ ```
144
+
145
+ Rules are ADDED to the built-ins, never replace them, and each pattern is anchored by wrapping
146
+ (`(?:<pattern>)$`) so every branch of an alternation is anchored. A malformed `testPathRules`
147
+ (bad JSON, not an array, missing `ecosystem`/`pattern`, an invalid regex, over 200 characters, or a
148
+ nested quantifier) is **NOT-ESTABLISHED, exit 3** — never a quiet fall-back to the built-ins.
149
+
150
+ **2. The gate script is found where it is installed — `args.gateScript`.** The skill lives in your
151
+ WORKSPACE; the gate command `cd`s into the TARGET repo, so a repo-relative lookup died with
152
+ `Cannot find module` on every repo that is not itself a feature-adr install. The resolution order is
153
+ now:
154
+
155
+ 1. `args.gateScript` — an explicit ABSOLUTE path (validated: absolute, no `..`, or the run fails loudly);
156
+ 2. the WORKSPACE copy (`$WS/.claude/skills/feature-adr/scripts/check-plan-completeness.mjs`);
157
+ 3. the target-repo copy.
158
+
159
+ Workspace before repo on purpose: the verdict contract is defined by the parser inside the running
160
+ workflow, so only that installation's own copy is known to speak it. Every run echoes
161
+ `K2_GATE_SCRIPT=` (which copy ran) and `K2_GATE_TRIED=` (all candidates) for audit. If none exists
162
+ the run refuses with reason `tooling-missing` — a NOT-ESTABLISHED, never a skip — and the operator
163
+ message says the gate could not be RUN and that this is **not** a plan defect.
164
+
165
+ **3. Dotfile targets are admissible; directory-shaped ones are not.** `EXPECTED_CODE_TARGETS` may
166
+ now name `.claude/…`, `.github/workflows/ci.yml`, `.gitignore`, `.env.example`. What is refused is
167
+ refused BY NAME: `path traversal ('..' segment)`, `degenerate path segment`, `empty path segment`,
168
+ `trailing slash — names a directory, not a file`, `path segment ends with '.'`,
169
+ `illegal character '<c>'`, `empty stem after the leading dot`. The old substring traversal test also
170
+ rejected the ordinary filename `foo..bar.ts`; it is a segment test now.
171
+
172
+ ### The Step-8 amendment gate runs a command instead of judging (v1.5.1)
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
+
191
+ ### Step 0 writes the assessment down, and the acid check gets its input back (v1.5.0)
192
+
193
+ Step 0 classifies the feature and now **writes `00_complexity_assessment.md` before it returns** — the
194
+ tier with the criterion that decided it, the active steps, the recalled patterns, and an acid-case
195
+ table. MEASURED 2026-08-21: 66 of 199 features carried that file and the last four in a row did not,
196
+ with two silent consequences. The tier was recorded NOWHERE while a run was alive (the router
197
+ checkpoint lands at phase end and the result object only at the very end), so mid-run it had to be
198
+ guessed from which artifacts happened to exist. And the K2 gate reads its acid corpus from that file,
199
+ so the C4 check quietly switched itself off — including for the features that introduced it.
200
+
201
+ Acid rows are pinned to the exact shape the gate parses, `| A<n> | <the bad input> | <what must
202
+ happen> |`; a loose shape disables the check just as silently as a missing file. A feature with no
203
+ acid cases still says so in prose and writes no table — the gate now tells an ABSENT file (a warning
204
+ that names the missing artifact) from a deliberate skip.
205
+
206
+ Two ways the tier could still go unrecorded, both closed here:
207
+
208
+ - a **pre-contract router checkpoint** used to resume into the new contract. The resume gate only asks
209
+ whether the artifact is PRESENT, and any of the 66 features carrying a tableless file satisfied it,
210
+ so Step 0 never re-ran. A router-scoped hash token now makes those entries re-run — once, and only
211
+ the router, rather than every in-flight stage.
212
+ - a **caller-forced `args.tier`** is now the tier of record. The run executes the override, so the
213
+ file carries `Effective tier: <forced> (forced by the caller)` alongside the router's own
214
+ recommendation, and sizes the acid table for the effective tier. With no override the prompt is
215
+ byte-identical to before.
216
+
217
+ Requires `@dzhechkov/harness-core >= 0.6.0`.
218
+
219
+ ### The Step-8 QE stage measures the run, not the room (v1.5.0)
220
+
221
+ Four separate ways the QE stage graded the wrong thing, all closed: the change set is this run's DELTA
222
+ (a pre-code baseline against the state after) instead of the working tree's current dirt, so an
223
+ unrelated dirty file can no longer grade your feature; a finding whose location cannot be parsed is
224
+ `unlocatable` and stays in the graded set, where it used to be filed as someone else's dirt — unknown
225
+ counted as clean, inside the function whose job is honest attribution; mode B refuses a scope built
226
+ from unlanded code, an unmeasured change set, or an empty intersection; and the QE scribe is
227
+ WITNESSED — the report is re-hashed before and after, so a file left by an earlier run can no longer
228
+ stand in for one this run never wrote.
229
+
109
230
  ### The design fan resumes per sibling, and an incomplete design is REFUSED (v1.4.0)
110
231
 
111
232
  Step 1–5 run as one parallel fan — requirements, ADR, QCSD, architecture. Until v1.4.0 the whole fan
@@ -237,6 +358,49 @@ family than the coder** — a model that writes code must not also self-QE; inde
237
358
  catches what self-review misses. coder=Codex ⇒ QE=Claude (`opus`); coder=Claude ⇒ QE=Codex
238
359
  (`codex:<top>:high`), or a Claude reviewer if Codex is unavailable (never blocks).
239
360
 
361
+ ### Step 8 on Codex is SCOPED — two modes, and every fallback names its cause
362
+
363
+ When QE routes to Codex, Step 8 no longer hands it one unscoped `codex exec` prompt. Why, MEASURED
364
+ 2026-08-21 — same question, same model (`gpt-5.6-sol`, effort `high`), three dispatches:
365
+
366
+ | dispatch | prompt | wall time | exit | verdict? |
367
+ |---|---|---|---|---|
368
+ | `codex exec`, unscoped | 19 038 chars | 280 s (and again under a 1500 s ceiling) | 124 | **none** — 416 KB of exploration |
369
+ | `codex exec`, scoped (*"read ONLY these two files"*) | 1 461 chars | 41 s | 0 | `Grade: B` + findings |
370
+ | `codex review --commit <SHA>` | scope from the diff | 146 s | 0 | verdict + findings |
371
+
372
+ The unscoped run spent its budget on **reconnaissance of the tree**, not on reasoning about the change.
373
+ So raising the timeout does not help, and the prompt-length ceiling was never the binding constraint
374
+ (19 038 sat under `CODEX_EXEC_PROMPT_CEILING_CHARS` = 24 000 with ~5 000 to spare). The cost is not the
375
+ wasted minutes: on timeout the dispatch returns `null`, the belt runs a Claude reviewer, and
376
+ cross-family QE is silently lost — on exactly the big features that need it most.
377
+
378
+ - **Mode A (primary): `codex review`.** The scope comes from the DIFF, so Codex computes for free the
379
+ thing we were paying a model to do badly. Default scope `--uncommitted`; override with
380
+ `args.qeScope: 'commit' | 'base'` + `args.qeScopeRef`. Two CLI facts are encoded as refusals, both
381
+ measured: `codex review` rejects `-m` (exit 2 — the model goes through `-c model=`), and **every**
382
+ scope flag rejects a positional prompt (`--commit`, `--base` and `--uncommitted` alike, exit 2). So
383
+ mode A runs on Codex's built-in review instructions and cannot be asked our questions.
384
+ - **Mode B (follow-up): a NARROWED `codex exec`.** Carries our own questions over at most 3 named
385
+ files with the load-bearing clause *"read ONLY these files, do not explore the repository"*, and
386
+ ends with `Grade: <A|B|C|D>`. An unscoped mode-B prompt is not constructible: an empty file list
387
+ returns `''` and `codexExecPlan` refuses an unscoped `qe` prompt outright.
388
+ - **The verdict is parsed, never synthesised.** A `Grade: D` is a SUCCESSFUL cross-family review; a
389
+ review with no grade is a FAILED one. Mode A cannot state a letter, so its grade is DERIVED from the
390
+ severities it reported and labelled `gradeSource: 'derived-from-findings'` — and **zero findings
391
+ yields `null`, never `A`** (measured: `codex review` on a clean tree exits 0 with a polite, entirely
392
+ empty review; calling that an `A` would be a review of nothing reported as a pass).
393
+ - **Every fallback names its cause.** The reason carried into
394
+ `opus (cross-family QE DID NOT happen — …)` comes from a locked taxonomy —
395
+ `timeout` (narrow the scope) · `no-verdict` · `tool-error` (fix the invocation) · `unusable-output` ·
396
+ `unavailable` (fix the account/model) · `over-ceiling`. A timeout and an unusable output can never
397
+ render the same string, because the operator's next move differs.
398
+ - **The pipeline still never blocks on Codex.** Both modes fail into the same Claude belt as before.
399
+
400
+ After a Codex verdict a cheap Claude agent transcribes it into `08_qe_report.md` (mode A takes no
401
+ prompt, so the reviewer cannot be asked to write anything). It is a scribe, not a second reviewer: the
402
+ grade is Codex's and is stated as final.
403
+
240
404
  **Precedence:** `args.models[stage]` wins; the legacy `planner`/`coder`/`qeReviewer`/`codexModel` knobs
241
405
  fill only unspecified stages. `codexModel` seeds the id for a bare `'codex'` spec. gpt-5.6-ready: a new
242
406
  Codex id is a data-only allowlist edit.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/skills-feature-adr",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
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": "bbb43101a686369a8662f47fe7b7aa75073b614a188c2752cd81d9570620bb3d"
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": "469873d967c4438afd0b25ec8b013347841e07328f508f6786f90cc6aa2f09b9"
38
+ "content": "47f9402d4c3954d6d5a75ad906ec7b592010fdfd80f347d4a87dd14dd9e300ce"
39
39
  }
40
40
  ]
41
41
  },
@@ -65,7 +65,7 @@
65
65
  "hashes": [
66
66
  {
67
67
  "alg": "SHA-256",
68
- "content": "c1e4d257167fd2462e0c8e09f248fc1aef332aa34fb09dddd5940bdcc30ea34b"
68
+ "content": "3fbe9ef86c9f6fa7c86bbe7760729094e80edb67577e397678b2d98dca1c2034"
69
69
  }
70
70
  ]
71
71
  },
@@ -395,7 +395,7 @@
395
395
  "hashes": [
396
396
  {
397
397
  "alg": "SHA-256",
398
- "content": "c8fdd1877d8d9515ab29f52c3db49ddd9612b5797bc071e6208def860c02e6f2"
398
+ "content": "3903e2694ad47782914b35869bbf19c5f0803b6dafdcfb629df343a5ff00d1b9"
399
399
  }
400
400
  ]
401
401
  },
@@ -415,7 +415,7 @@
415
415
  "hashes": [
416
416
  {
417
417
  "alg": "SHA-256",
418
- "content": "97e9a7bc54f196b381c4ef71258cddcb2fb48f216df1ba015927c176bad57084"
418
+ "content": "bd5b5809142c12fa1378de446b68027a0c342a7a91ad17e4fb3395ca8f6a8508"
419
419
  }
420
420
  ]
421
421
  },
@@ -645,7 +645,7 @@
645
645
  "hashes": [
646
646
  {
647
647
  "alg": "SHA-256",
648
- "content": "41d85b012a9d6cc172eaa5d7777294f2c01fb54b752ed61c7cbe2d2cd7b79b67"
648
+ "content": "2e1afeb8cbc08ce872fc8280a0809e7712ee76879d08b48caa5a0d4082cb8647"
649
649
  }
650
650
  ]
651
651
  },
@@ -805,7 +805,7 @@
805
805
  "hashes": [
806
806
  {
807
807
  "alg": "SHA-256",
808
- "content": "1be239d2f02612bc33027767d9930eae8a2d6d5afd427d1485198bbdd821a3c7"
808
+ "content": "0c74e8995d82bb986ef3233e91beebfa0e761aae4434f24554077bb05d2199e9"
809
809
  }
810
810
  ]
811
811
  },
@@ -198,6 +198,13 @@ After the plan is written, run the gate. It is a script, not a judgement call:
198
198
  node .claude/skills/feature-adr/scripts/check-plan-completeness.mjs features/<slug>
199
199
  ```
200
200
 
201
+ The path above is relative to the workspace the skill is INSTALLED in. Working inside a target repo
202
+ that has no feature-adr install of its own? Pass the absolute path to the script instead — the
203
+ ultracode workflow does this for you (it tries `args.gateScript`, then the workspace copy, then the
204
+ target-repo copy, and refuses with `tooling-missing` if none exists). The gate is language-neutral:
205
+ C2 recognises JS/TS, pytest, Go, Rust, JVM and .NET test paths, extensible per project with
206
+ `testPathRules` in `architecture/project-skills.json`.
207
+
201
208
  | Exit | Verdict | What you do |
202
209
  |------|---------|-------------|
203
210
  | `0` | PASS | proceed to Step 7 |
@@ -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
- Read the `## Amendments` sections of `03.5_ideation_report.md` and `06_implementation_plan.md` (skip
164
- silently if absent). For EVERY `AM-N` row verify:
165
- 1. the row contains a `→ test ` token naming a real test (the fixed shape);
166
- 2. the named test **exists** in the suite;
167
- 3. it is **non-vacuous** — include the amendment test files in the SAME
168
- `dz discrimination-check --test <f1,f2,...> --base HEAD --json` run as the ADR property test. An
169
- amendment test that stays green at pre-feature base proves nothing about its amendment;
170
- 4. a **safeguard** amendment's test proves the safeguard FIRES on a real input (a structurally-dead
171
- safeguard passes an existence test and never fires in production).
172
-
173
- A missing, unnamed, or vacuous amendment test is a HIGH gap. This closes the distance between "we caught
174
- the design flaw" (challenge-panel/QCSD) and "we proved the fix works".
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