@dzhechkov/skills-feature-adr 1.3.42 → 1.3.44
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/README.md +16 -0
- package/package.json +1 -1
- package/templates/.claude/workflows/feature-adr.js +166 -32
package/README.md
CHANGED
|
@@ -124,6 +124,15 @@ writing code. Without this the run stalls; with `coder: 'codex-fallback'` the pi
|
|
|
124
124
|
unavailable (limit?) — falling back to Codex auto"* and finishes the code + tests on Codex, no
|
|
125
125
|
restart and no lost work.
|
|
126
126
|
|
|
127
|
+
**Codex writes are out-of-band — the Step-7.5 landed barrier waits for them.** Codex applies edits via
|
|
128
|
+
its own runtime, so a naive pipeline runs QE before the async write flushes and false-grades *"Step 7
|
|
129
|
+
never ran"* on real code. For a Codex-coded run the pipeline polls a **bounded 120s backing-off window**
|
|
130
|
+
(`1,2,2,5,5,10,10,15,20,25,25`s), preferring the code stage's *declared expected files* when known, and
|
|
131
|
+
emits an explicit `changed=0 after 120s — genuinely not landed` only when the window truly expires — so
|
|
132
|
+
QE distinguishes "not implemented" from "not yet flushed". A Claude-coded run is synchronous and skips
|
|
133
|
+
the barrier entirely (zero added wait). Note: if Codex flushes slower than 120s, re-verify after the run
|
|
134
|
+
rather than trusting the end-of-run grade.
|
|
135
|
+
|
|
127
136
|
```bash
|
|
128
137
|
# Headless login on a VPS (no browser):
|
|
129
138
|
codex login --device-auth # prints a code + URL you approve on another device
|
|
@@ -141,6 +150,13 @@ Workflow({ scriptPath: '.claude/workflows/feature-adr.js',
|
|
|
141
150
|
Omit the Codex knobs entirely for today's all-Claude behavior. The run result reports
|
|
142
151
|
`plannerUsed` / `coderUsed` / `qeReviewerUsed` / `codexModel` / `modelsUsed` so you can see who did what.
|
|
143
152
|
|
|
153
|
+
**Live model visibility in `/workflows`:** each stage's agent label is decorated with its *resolved*
|
|
154
|
+
model — e.g. `adr · codex:gpt-5.5:xhigh`, `plan · opus` — so you see which model actually ran a stage in
|
|
155
|
+
the live progress tree, not just in the final report. This matters because a Codex stage's auto
|
|
156
|
+
model-badge shows the `codex:codex-rescue` Claude wrapper (the session model), never `codex`; the label
|
|
157
|
+
text is the honest signal. A Claude fallback appears as its own distinct node (never mislabeled as Codex),
|
|
158
|
+
and a routing-off run adds no suffix (byte-identical to today).
|
|
159
|
+
|
|
144
160
|
### Per-stage model routing — `args.models`
|
|
145
161
|
|
|
146
162
|
The three Codex knobs above are shortcuts. `args.models` is the **general dial**: an optional map that
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/skills-feature-adr",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.44",
|
|
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"
|
|
@@ -226,12 +226,130 @@ function modelLabel(opts) {
|
|
|
226
226
|
return 'session'
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
// stageLabel: make resolved model participation visible in LIVE /workflows labels.
|
|
230
|
+
// A session-inherited resolution is the routing-off BC path, so it stays silent.
|
|
231
|
+
function stageLabel(baseLabel, opts) {
|
|
232
|
+
const m = modelLabel(opts)
|
|
233
|
+
if (!m || m === 'session') return baseLabel
|
|
234
|
+
return baseLabel + ' · ' + m
|
|
235
|
+
}
|
|
236
|
+
|
|
229
237
|
// needsLandedBarrier — mirror of harness-core's pure gate (feature-adr-routing.ts). TRUE only when a
|
|
230
238
|
// stage resolved to Codex. A Claude stage is synchronous (artifact on disk when agent() returns), so
|
|
231
239
|
// this is FALSE and the barrier below is never constructed → an all-Claude run, and a Claude-design +
|
|
232
240
|
// Codex-QE run, do ZERO extra work (byte-identical to today).
|
|
233
241
|
function needsLandedBarrier(opts) { return !!(opts && opts.agentType === 'codex:codex-rescue') }
|
|
234
242
|
|
|
243
|
+
const DEFAULT_CODE_LANDING_MAX_WAIT_MS = 120000
|
|
244
|
+
const DEFAULT_CODE_LANDING_BACKOFF_MS = [1000, 2000, 2000, 5000, 5000, 10000, 10000, 15000, 20000, 25000, 25000]
|
|
245
|
+
const CODE_LANDED_BARRIER_SLEEPS_SECONDS = DEFAULT_CODE_LANDING_BACKOFF_MS.map((ms) => ms / 1000)
|
|
246
|
+
const CODE_LANDING_PIPELINE_PREFIXES = ['features/', '.dz/', '.agentic-qe/', 'roam/']
|
|
247
|
+
|
|
248
|
+
function codeLandingEmptySignal(seconds) {
|
|
249
|
+
return 'changed=0 after ' + seconds + 's — genuinely not landed'
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function needsCodeLandedBarrier(coderUsed) {
|
|
253
|
+
return coderUsed === 'codex' || coderUsed === 'codex-fallback'
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function normalizeCodeLandingPath(path) {
|
|
257
|
+
let p = String(path || '').trim().replace(/\\/g, '/')
|
|
258
|
+
while (p.indexOf('./') === 0) p = p.slice(2)
|
|
259
|
+
p = p.replace(/\/+/g, '/')
|
|
260
|
+
if (!p) return ''
|
|
261
|
+
if (p[0] === '/') return ''
|
|
262
|
+
if (p === '..' || p.indexOf('../') === 0 || p.indexOf('/../') >= 0 || p.endsWith('/..')) return ''
|
|
263
|
+
if (/[\0\r\n\t "'\x60$;&|<>*?()[\]{}!]/.test(p)) return ''
|
|
264
|
+
if (p.endsWith('/')) return ''
|
|
265
|
+
for (let i = 0; i < CODE_LANDING_PIPELINE_PREFIXES.length; i++) {
|
|
266
|
+
const prefix = CODE_LANDING_PIPELINE_PREFIXES[i]
|
|
267
|
+
const bare = prefix.slice(0, -1)
|
|
268
|
+
if (p === bare || p.indexOf(prefix) === 0) return ''
|
|
269
|
+
}
|
|
270
|
+
return p
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function filterPollableCodePaths(paths) {
|
|
274
|
+
const out = []
|
|
275
|
+
const seen = {}
|
|
276
|
+
for (let i = 0; i < (paths || []).length; i++) {
|
|
277
|
+
const normalized = normalizeCodeLandingPath(paths[i])
|
|
278
|
+
if (!normalized || seen[normalized]) continue
|
|
279
|
+
seen[normalized] = 1
|
|
280
|
+
out.push(normalized)
|
|
281
|
+
}
|
|
282
|
+
return out
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function codeLandedBarrierPlan(coderUsed, expectedPaths) {
|
|
286
|
+
const enabled = needsCodeLandedBarrier(coderUsed)
|
|
287
|
+
const pollWindowSeconds = DEFAULT_CODE_LANDING_MAX_WAIT_MS / 1000
|
|
288
|
+
if (!enabled) {
|
|
289
|
+
return { enabled: false, mode: 'any-code-change', sleepsMs: [], sleepsSeconds: [], pollWindowMs: 0, pollWindowSeconds: 0, expectedPaths: [], emptySignal: '' }
|
|
290
|
+
}
|
|
291
|
+
const filteredExpectedPaths = filterPollableCodePaths(expectedPaths || [])
|
|
292
|
+
return {
|
|
293
|
+
enabled: true,
|
|
294
|
+
mode: filteredExpectedPaths.length > 0 ? 'expected-files' : 'any-code-change',
|
|
295
|
+
sleepsMs: DEFAULT_CODE_LANDING_BACKOFF_MS,
|
|
296
|
+
sleepsSeconds: CODE_LANDED_BARRIER_SLEEPS_SECONDS,
|
|
297
|
+
pollWindowMs: DEFAULT_CODE_LANDING_MAX_WAIT_MS,
|
|
298
|
+
pollWindowSeconds: pollWindowSeconds,
|
|
299
|
+
expectedPaths: filteredExpectedPaths,
|
|
300
|
+
emptySignal: codeLandingEmptySignal(pollWindowSeconds),
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function addExpectedCodeTarget(value, out) {
|
|
305
|
+
if (value === null || value === undefined) return
|
|
306
|
+
if (Array.isArray(value)) {
|
|
307
|
+
for (let i = 0; i < value.length; i++) addExpectedCodeTarget(value[i], out)
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
if (typeof value === 'object') {
|
|
311
|
+
if (Array.isArray(value.wrote)) addExpectedCodeTarget(value.wrote, out)
|
|
312
|
+
if (Array.isArray(value.paths)) addExpectedCodeTarget(value.paths, out)
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
const lines = String(value).split(/\r?\n/)
|
|
316
|
+
for (let i = 0; i < lines.length; i++) {
|
|
317
|
+
const candidate = lines[i].replace(/^[-*]\s+/, '').replace(/^\x60+|\x60+$/g, '').trim()
|
|
318
|
+
if (candidate) out.push(candidate)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function extractExpectedCodeTargetsFromText(text) {
|
|
323
|
+
const out = []
|
|
324
|
+
const lines = String(text || '').split(/\r?\n/)
|
|
325
|
+
let inBlock = false
|
|
326
|
+
for (let i = 0; i < lines.length; i++) {
|
|
327
|
+
const trimmed = lines[i].trim()
|
|
328
|
+
if (/^EXPECTED_CODE_TARGETS:\s*$/i.test(trimmed)) { inBlock = true; continue }
|
|
329
|
+
if (!inBlock) continue
|
|
330
|
+
if (!trimmed) continue
|
|
331
|
+
if (/^[A-Z][A-Z0-9_ -]*:\s*$/.test(trimmed)) break
|
|
332
|
+
out.push(trimmed.replace(/^[-*]\s+/, '').replace(/^\x60+|\x60+$/g, '').trim())
|
|
333
|
+
}
|
|
334
|
+
return out
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function extractExpectedCodeTargets(argTargets, codexText) {
|
|
338
|
+
const out = []
|
|
339
|
+
addExpectedCodeTarget(argTargets, out)
|
|
340
|
+
addExpectedCodeTarget(extractExpectedCodeTargetsFromText(codexText), out)
|
|
341
|
+
return filterPollableCodePaths(out)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function codeLandingShellQuote(value) {
|
|
345
|
+
return "'" + String(value).replace(/'/g, "'\"'\"'") + "'"
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function codeLandingProbeCmd(repo, plan) {
|
|
349
|
+
const expectedList = plan.expectedPaths.length > 0 ? plan.expectedPaths.map(codeLandingShellQuote).join(' ') : "''"
|
|
350
|
+
return 'repo=' + codeLandingShellQuote(repo) + '; sleeps="' + plan.sleepsSeconds.join(' ') + '"; expected_count=' + plan.expectedPaths.length + '; elapsed=0; poll(){ paths=$(git -C "$repo" status --porcelain 2>/dev/null | sed -E "s/^...//" | sed -E "s/.* -> //" | grep -vE "^(features/|[.]dz/|[.]agentic-qe/|roam/)" | sed "/^$/d" | head -200); n=$(printf "%s\\n" "$paths" | sed "/^$/d" | wc -l | tr -d " "); if [ "$expected_count" -gt 0 ]; then matched=""; for p in ' + expectedList + '; do [ -z "$p" ] && continue; if printf "%s\\n" "$paths" | grep -Fx -- "$p" >/dev/null; then matched="$p"; break; fi; done; if [ -n "$matched" ]; then echo "CODEX-LANDING-SIGNAL status=landed changed=1 after=${elapsed}s predicate=expected-path"; echo "matched=$matched"; echo "files:"; printf "%s\\n" "$paths" | head -40; exit 0; fi; else if [ "$n" -gt 0 ]; then echo "CODEX-LANDING-SIGNAL status=landed changed=$n after=${elapsed}s predicate=any-code-change"; echo "files:"; printf "%s\\n" "$paths" | head -40; exit 0; fi; fi; }; poll; for wait in $sleeps; do sleep "$wait"; elapsed=$((elapsed + wait)); poll; done; echo "CODEX-LANDING-SIGNAL status=genuinely-not-landed ' + plan.emptySignal + '"; if [ "$expected_count" -gt 0 ]; then echo "predicate=expected-path observed=$n"; else echo "predicate=any-code-change"; fi; echo "files:"; if [ -n "$paths" ]; then printf "%s\\n" "$paths" | head -40; else echo "(none)"; fi'
|
|
351
|
+
}
|
|
352
|
+
|
|
235
353
|
// A Bash one-liner that waits for a Codex OUT-OF-BAND artifact write to LAND: polls up to ~40s until
|
|
236
354
|
// the file exists, is non-empty, AND its size is stable across two reads (write finished). Assumes a
|
|
237
355
|
// fresh feature slug (no stale same-path artifact) — true for a normal /feature-adr run.
|
|
@@ -245,7 +363,7 @@ function landedProbeCmd(f) {
|
|
|
245
363
|
// codex-rescue returns finalMessage text, not StructuredOutput; success is proven by the file landing —
|
|
246
364
|
// (b) add a FOREGROUND hint so the codex runtime blocks until the write completes, (c) poll the
|
|
247
365
|
// artifact, and (d) fall back to a Claude agent if it never lands (never blocks the pipeline).
|
|
248
|
-
async function designStage(promptText, opts, artifactPath) {
|
|
366
|
+
async function designStage(promptText, opts, artifactPath, baseLabel) {
|
|
249
367
|
if (!needsLandedBarrier(opts)) return await agent(promptText, opts)
|
|
250
368
|
const codexOpts = {}
|
|
251
369
|
for (const k in opts) if (k !== 'schema') codexOpts[k] = opts[k]
|
|
@@ -253,7 +371,8 @@ async function designStage(promptText, opts, artifactPath) {
|
|
|
253
371
|
const probe = await agent('Confirm a Codex OUT-OF-BAND artifact write has LANDED before the next stage reads it. Run EXACTLY this via Bash and return its stdout verbatim, nothing else:\n' + landedProbeCmd(artifactPath), { label: 'design:confirm-landed', phase: 'Design', effort: 'low' })
|
|
254
372
|
if (res && probe && /landed=/.test(String(probe))) return { wrote: [artifactPath], summary: String(res).slice(0, 300) }
|
|
255
373
|
log('design artifact did not land on codex (' + artifactPath + ') — falling back to Claude')
|
|
256
|
-
|
|
374
|
+
const fallbackOpts = {}
|
|
375
|
+
return await agent(promptText, mergeOpts({ label: stageLabel((baseLabel || 'design') + ':claude-fb', fallbackOpts), phase: 'Design', schema: ARTIFACT }, fallbackOpts))
|
|
257
376
|
}
|
|
258
377
|
|
|
259
378
|
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' } } }
|
|
@@ -266,7 +385,8 @@ const ADR_FITNESS_CHECKLIST = 'ADR fitness checklist for Step 8: read every ' +
|
|
|
266
385
|
phase('Router')
|
|
267
386
|
await usageProbe('Router')
|
|
268
387
|
const routerPrompt = 'You are Step 0 (Complexity Router) of the /feature-adr pipeline. TWO jobs. (1) MANDATORY SELF-LEARNING RECALL (never skip — run BOTH Bash commands VERBATIM, do not summarize instead of running them): the learned patterns live in the CANONICAL BRAIN store at `' + BRAIN + '` — pin every recall to it. Via your Bash tool run EXACTLY `' + DZ_RECALL('<the key domain terms of this feature>') + '` (and `' + DZ_RECALL('<the key domain terms of this feature>') + ' --all` if narrow) to load relevant LEARNED PATTERNS from the brain. Preserve recalled pattern TEXT, reward, domain, and any visible id in the rationale as a concrete list so Step 8 can compare candidate lessons against it. Then run `dz statusline --fa-record --slug ' + SLUG + ' --step "Step 0 recall" --recalled <count> --mode ' + MODE + ' --project ' + REPO + '`. Summarize the top 3 applicable patterns in the rationale. (2) Classify S/M/L/XL + active steps. Feature: "' + DESC + '". Code: ' + CODE_HINT + '. S=1-3 files (0,1,6,7,8; if an ADR is explicitly forced, use Nygard as the lightweight fallback); M=4-10 (0,1,3,3.5,5,6,7,8; Nygard/ITD-light ADR); L=11-30 (all+9; MADR+Confirmation ADRs); XL=30+ (full+9; MADR+Confirmation ADRs). ADR template-weight rule: S/M -> Nygard/ITD-light; L/XL -> MADR + NHS Wales Confirmation, while every generated ADR still carries the invariant core. Return {tier, activeSteps, rationale} with the recalled patterns folded into rationale.'
|
|
269
|
-
const
|
|
388
|
+
const routerModel = resolveStageModel('router')
|
|
389
|
+
const routerOpts = mergeOpts({ label: stageLabel('router+recall', routerModel), phase: 'Router', schema: ROUTER, effort: 'low' }, routerModel)
|
|
270
390
|
modelsUsed.router = modelLabel(routerOpts)
|
|
271
391
|
const router = await agent(routerPrompt, routerOpts)
|
|
272
392
|
let tier = A.tier || (router ? router.tier : 'M')
|
|
@@ -287,22 +407,26 @@ const designThunks = []
|
|
|
287
407
|
const reqExtra = isLplus ? ' Also write ' + FDIR + '/02_research.md (codebase patterns + external analogues; read the repo for the closest existing implementation to mirror).' : ''
|
|
288
408
|
// Resolve per-stage model opts up-front (parser-safe: no inline resolveStageModel inside the thunk arrays).
|
|
289
409
|
// research folds into requirements, ddd folds into architecture (single shared call) — recorded for reporting.
|
|
290
|
-
const
|
|
291
|
-
const
|
|
292
|
-
const
|
|
293
|
-
const
|
|
410
|
+
const reqModel = resolveStageModel('requirements')
|
|
411
|
+
const adrModel = resolveStageModel('adr')
|
|
412
|
+
const qcsdModel = resolveStageModel('ideation')
|
|
413
|
+
const archModel = resolveStageModel('architecture')
|
|
414
|
+
const reqOpts = mergeOpts({ label: stageLabel('requirements', reqModel), phase: 'Design', schema: ARTIFACT }, reqModel)
|
|
415
|
+
const adrOpts = mergeOpts({ label: stageLabel('adr', adrModel), phase: 'Design', schema: ARTIFACT }, adrModel)
|
|
416
|
+
const qcsdOpts = mergeOpts({ label: stageLabel('qcsd', qcsdModel), phase: 'Design', schema: ARTIFACT }, qcsdModel)
|
|
417
|
+
const archOpts = mergeOpts({ label: stageLabel('architecture', archModel), phase: 'Design', schema: ARTIFACT }, archModel)
|
|
294
418
|
modelsUsed.requirements = modelLabel(reqOpts)
|
|
295
419
|
modelsUsed.research = modelLabel(reqOpts)
|
|
296
420
|
modelsUsed.adr = modelLabel(adrOpts)
|
|
297
421
|
modelsUsed.ideation = modelLabel(qcsdOpts)
|
|
298
422
|
modelsUsed.architecture = modelLabel(archOpts)
|
|
299
423
|
modelsUsed.ddd = modelLabel(archOpts)
|
|
300
|
-
designThunks.push(() => designStage('Step 1 (Requirements)' + (isLplus ? ' + Step 2 (Research)' : '') + ' of /feature-adr for "' + DESC + '" (tier ' + tier + ', slug ' + SLUG + '). Code: ' + CODE_HINT + '. APPLY these Step-0 recalled LEARNED PATTERNS (fold the applicable ones into requirements/constraints - the loop paying off): ' + LEARNED + '. Write ' + FDIR + '/01_requirements.md (functional + non-functional requirements, acceptance criteria, constraints, and an "Applied learned patterns" note).' + reqExtra + ' Return wrote[] + a 1-line summary.', reqOpts, FDIR + '/01_requirements.md'))
|
|
424
|
+
designThunks.push(() => designStage('Step 1 (Requirements)' + (isLplus ? ' + Step 2 (Research)' : '') + ' of /feature-adr for "' + DESC + '" (tier ' + tier + ', slug ' + SLUG + '). Code: ' + CODE_HINT + '. APPLY these Step-0 recalled LEARNED PATTERNS (fold the applicable ones into requirements/constraints - the loop paying off): ' + LEARNED + '. Write ' + FDIR + '/01_requirements.md (functional + non-functional requirements, acceptance criteria, constraints, and an "Applied learned patterns" note).' + reqExtra + ' Return wrote[] + a 1-line summary.', reqOpts, FDIR + '/01_requirements.md', 'requirements'))
|
|
301
425
|
if (isMplus) {
|
|
302
|
-
designThunks.push(() => designStage('Step 3 (ADR + shift-left testability) of /feature-adr for "' + DESC + '" (' + SLUG + '). READ the actual code (' + CODE_HINT + ') to ground it. ' + ADR_TEMPLATE_GUIDE + ' Write ' + FDIR + '/03_adr/001-' + SLUG + '.md as a MADR-structured ADR that PASSES the Step-8 ADR fitness checklist (do NOT emit the legacy shape). Emit ALL of these sections, in order: a decision-shaped # Title (present-tense imperative verb — the auto-filename tracks the feature slug, so the IMPERATIVE signal lives in the title); ## Status (proposed/accepted/rejected/deprecated/superseded + a reversibility/revisit clause); ## Context (neutral, problem-first, BEFORE the Decision); ## Decision Drivers (ranked/weighted D1, D2, …); ## Considered Options (frame the CHOSEN approach as one option ALONGSIDE the rejected ones, each with symmetric Pros:/Cons:); ## Decision (concrete/testable — exact names, versions, paths, commands); ## Rationale (map each point to a driver Dn + why the losers lost); ## Consequences (Positive + Negative/Accepted Downsides + Follow-up ADRs + After-action Review with owner + date); a REQUIRED ## Confirmation stanza with Method:, Monitoring:, Success metric:, Owner:, Load-bearing property:, and Required automated check: `<test file>` NAMING the load-bearing property that MUST have a Step-8 test (the recurring lesson: the key safety property is often the untested one); and a ## Links traceability block (requirements, driving use case, related ADRs). Add a one-line provenance note (model-generated, edited for clarity) and, for a long ADR, a top-of-file table of contents. Do NOT use an "Alternatives considered" or "Testability/shift-left" heading in place of Considered Options / Confirmation. When creating ADDITIONAL ADRs, name them 03_adr/NNN-{decision-slug}.md with a lowercase-kebab, present-tense imperative, dateless, ticketless slug. Return wrote[] + summary.', adrOpts, FDIR + '/03_adr/001-' + SLUG + '.md'))
|
|
303
|
-
designThunks.push(() => designStage('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. Return wrote[] + summary.', qcsdOpts, FDIR + '/03.5_ideation_report.md'))
|
|
426
|
+
designThunks.push(() => designStage('Step 3 (ADR + shift-left testability) of /feature-adr for "' + DESC + '" (' + SLUG + '). READ the actual code (' + CODE_HINT + ') to ground it. ' + ADR_TEMPLATE_GUIDE + ' Write ' + FDIR + '/03_adr/001-' + SLUG + '.md as a MADR-structured ADR that PASSES the Step-8 ADR fitness checklist (do NOT emit the legacy shape). Emit ALL of these sections, in order: a decision-shaped # Title (present-tense imperative verb — the auto-filename tracks the feature slug, so the IMPERATIVE signal lives in the title); ## Status (proposed/accepted/rejected/deprecated/superseded + a reversibility/revisit clause); ## Context (neutral, problem-first, BEFORE the Decision); ## Decision Drivers (ranked/weighted D1, D2, …); ## Considered Options (frame the CHOSEN approach as one option ALONGSIDE the rejected ones, each with symmetric Pros:/Cons:); ## Decision (concrete/testable — exact names, versions, paths, commands); ## Rationale (map each point to a driver Dn + why the losers lost); ## Consequences (Positive + Negative/Accepted Downsides + Follow-up ADRs + After-action Review with owner + date); a REQUIRED ## Confirmation stanza with Method:, Monitoring:, Success metric:, Owner:, Load-bearing property:, and Required automated check: `<test file>` NAMING the load-bearing property that MUST have a Step-8 test (the recurring lesson: the key safety property is often the untested one); and a ## Links traceability block (requirements, driving use case, related ADRs). Add a one-line provenance note (model-generated, edited for clarity) and, for a long ADR, a top-of-file table of contents. Do NOT use an "Alternatives considered" or "Testability/shift-left" heading in place of Considered Options / Confirmation. When creating ADDITIONAL ADRs, name them 03_adr/NNN-{decision-slug}.md with a lowercase-kebab, present-tense imperative, dateless, ticketless slug. Return wrote[] + summary.', adrOpts, FDIR + '/03_adr/001-' + SLUG + '.md', 'adr'))
|
|
427
|
+
designThunks.push(() => designStage('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. Return wrote[] + summary.', qcsdOpts, FDIR + '/03.5_ideation_report.md', 'qcsd'))
|
|
304
428
|
const archExtra = isLplus ? ' Also ' + FDIR + '/04_domain_model.md (DDD).' : ''
|
|
305
|
-
designThunks.push(() => designStage((isLplus ? 'Step 4 (DDD) + ' : '') + 'Step 5 (Architecture) of /feature-adr for "' + DESC + '" (' + SLUG + '). READ the code. Write ' + FDIR + '/05_architecture.md (components, data flow, integration points, the emit/merge/wiring shape).' + archExtra + ' Return wrote[] + summary.', archOpts, FDIR + '/05_architecture.md'))
|
|
429
|
+
designThunks.push(() => designStage((isLplus ? 'Step 4 (DDD) + ' : '') + 'Step 5 (Architecture) of /feature-adr for "' + DESC + '" (' + SLUG + '). READ the code. Write ' + FDIR + '/05_architecture.md (components, data flow, integration points, the emit/merge/wiring shape).' + archExtra + ' Return wrote[] + summary.', archOpts, FDIR + '/05_architecture.md', 'architecture'))
|
|
306
430
|
}
|
|
307
431
|
const design = await parallel(designThunks)
|
|
308
432
|
|
|
@@ -319,8 +443,9 @@ const planModel = resolveStageModel('plan')
|
|
|
319
443
|
const planIsCodex = (planModel.agentType === 'codex:codex-rescue') || (MODELS.plan === undefined && PLANNER === 'codex')
|
|
320
444
|
let plan = null
|
|
321
445
|
if (planIsCodex) {
|
|
322
|
-
|
|
323
|
-
|
|
446
|
+
const planCodexLabelOpts = (planModel.agentType === 'codex:codex-rescue') ? planModel : specToOpts('codex:' + CODEX_MODEL + ':high')
|
|
447
|
+
modelsUsed.plan = modelLabel(planCodexLabelOpts)
|
|
448
|
+
const codexPlanOpts = mergeOpts({ label: stageLabel('plan:codex', planCodexLabelOpts), phase: 'Plan', agentType: 'codex:codex-rescue' }, planModel.agentType ? planModel : {})
|
|
324
449
|
const codexPlan = await agent(planPrompt + ' IMPORTANT: run the Codex task in FOREGROUND (synchronous — do NOT pass --background) so this call blocks until 06_implementation_plan.md is fully written to disk.', codexPlanOpts)
|
|
325
450
|
// Codex-landed barrier for the plan artifact: a stub return is NOT proof the file was written
|
|
326
451
|
// (codex writes out-of-band). Require the artifact to LAND; otherwise fall through to the Claude planner.
|
|
@@ -334,7 +459,8 @@ if (planIsCodex) {
|
|
|
334
459
|
}
|
|
335
460
|
if (plan === null && planIsCodex) reactiveBelt('Plan')
|
|
336
461
|
if (plan === null) {
|
|
337
|
-
const
|
|
462
|
+
const claudePlanModel = planIsCodex ? {} : planModel
|
|
463
|
+
const claudePlanOpts = mergeOpts({ label: stageLabel(planIsCodex ? 'plan:claude-fb' : 'plan', claudePlanModel), phase: 'Plan', schema: ARTIFACT }, claudePlanModel)
|
|
338
464
|
modelsUsed.plan = planIsCodex ? 'claude-fallback' : modelLabel(claudePlanOpts)
|
|
339
465
|
const claudePlan = await agent(planPrompt, claudePlanOpts)
|
|
340
466
|
plan = claudePlan ? { wrote: claudePlan.wrote, summary: claudePlan.summary, planner: planIsCodex ? 'claude-fallback' : 'claude' } : null
|
|
@@ -363,9 +489,11 @@ const codePrompt = 'Step 7 (Code) of /feature-adr for "' + DESC + '" (' + SLUG +
|
|
|
363
489
|
// under the BC omit-path it is {} (byte-identical).
|
|
364
490
|
const codeModel = resolveStageModel('code')
|
|
365
491
|
const codeIsCodexFirst = (MODELS.code !== undefined) ? (codeModel.agentType === 'codex:codex-rescue') : (CODER === 'codex')
|
|
366
|
-
const
|
|
492
|
+
const codeClaudeModel = codeIsCodexFirst ? {} : (codeModel.agentType ? {} : codeModel)
|
|
493
|
+
const codeClaudeOpts = mergeOpts({ label: stageLabel('code', codeClaudeModel), phase: 'Code', schema: ARTIFACT, effort: 'high' }, codeClaudeModel)
|
|
367
494
|
let code = null
|
|
368
495
|
let coderUsed = 'claude'
|
|
496
|
+
let codexCodeText = ''
|
|
369
497
|
if (!codeIsCodexFirst) {
|
|
370
498
|
code = await agent(codePrompt, codeClaudeOpts)
|
|
371
499
|
if (code) { coderUsed = 'claude'; modelsUsed.code = modelLabel(codeClaudeOpts) }
|
|
@@ -373,21 +501,24 @@ if (!codeIsCodexFirst) {
|
|
|
373
501
|
if (code === null && !codeIsCodexFirst) reactiveBelt('Code')
|
|
374
502
|
if (code === null && (codeIsCodexFirst || CODER === 'codex-fallback')) {
|
|
375
503
|
if (CODER === 'codex-fallback' && !codeIsCodexFirst) log('Code: Claude unavailable (limit?) — falling back to Codex ' + CODEX_MODEL)
|
|
376
|
-
const
|
|
377
|
-
const
|
|
378
|
-
|
|
504
|
+
const codeCodexLabelOpts = codeModel.agentType ? codeModel : specToOpts('codex:' + CODEX_MODEL + ':high')
|
|
505
|
+
const codeCodexOpts = mergeOpts({ label: stageLabel('code:codex', codeCodexLabelOpts), phase: 'Code', agentType: 'codex:codex-rescue' }, codeModel.agentType ? codeModel : {})
|
|
506
|
+
const codexExpectedTargetsHint = '\n\nBecause this is running on Codex, include a final EXPECTED_CODE_TARGETS: block listing the repo-relative production/test files you expect to create or modify. List only real code/test/config/docs targets outside features/, .dz/, .agentic-qe/, and roam/. Example:\nEXPECTED_CODE_TARGETS:\n- packages/example/src/file.ts\n- packages/example/test/file.test.ts'
|
|
507
|
+
const codexCode = await agent(codePrompt + CODEX_HINT + codexExpectedTargetsHint, codeCodexOpts)
|
|
508
|
+
if (codexCode) { codexCodeText = String(codexCode); code = { wrote: [FDIR + '/07_code_changes/change_manifest.md'], summary: codexCodeText.slice(0, 500) }; coderUsed = codeIsCodexFirst ? 'codex' : 'codex-fallback'; modelsUsed.code = modelLabel(codeCodexLabelOpts) }
|
|
379
509
|
}
|
|
380
510
|
|
|
381
511
|
// Step 7.5: Codex-landed barrier. Codex applies edits OUT-OF-BAND via its own runtime; without this,
|
|
382
|
-
// Step-8 QE
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
// reviews the ACTUAL landed changes. Claude-coded runs are synchronous → this barrier is skipped.
|
|
512
|
+
// Step-8 QE can read the tree before the async write flushes and false-grade "Step 7 never ran" on real
|
|
513
|
+
// completed code. Poll a bounded 120s backing-off window, preferring declared expected code targets when
|
|
514
|
+
// known. Claude-coded runs are synchronous → this barrier is skipped with zero target parsing/probe work.
|
|
386
515
|
let landedNote = ''
|
|
387
|
-
if (coderUsed
|
|
388
|
-
const
|
|
389
|
-
const
|
|
390
|
-
|
|
516
|
+
if (needsCodeLandedBarrier(coderUsed)) {
|
|
517
|
+
const expectedCodeTargets = extractExpectedCodeTargets(A.expectedCodeTargets, codexCodeText)
|
|
518
|
+
const barrierPlan = codeLandedBarrierPlan(coderUsed, expectedCodeTargets)
|
|
519
|
+
const barrierCmd = codeLandingProbeCmd(REPO, barrierPlan)
|
|
520
|
+
const probe = await agent('Confirm the Codex Step-7 edits have LANDED in the working tree BEFORE QE runs (Codex writes out-of-band). Expected-file mode must be satisfied by one of the declared expected paths; unrelated dirty files do not count in that mode. Run EXACTLY this via Bash and return its stdout verbatim, nothing else:\n' + barrierCmd, { label: 'code:confirm-landed', phase: 'Code' })
|
|
521
|
+
landedNote = '\n\nCODEX-CODED (out-of-band): Step 7.5 landing barrier used mode=' + barrierPlan.mode + ', window=' + barrierPlan.pollWindowSeconds + 's. Review the signal below. If status=landed, read the listed files and do NOT report "Step 7 never ran". Only status=genuinely-not-landed with "' + barrierPlan.emptySignal + '" means the bounded barrier found no intended code after the full window.\nExpected code targets: ' + (barrierPlan.expectedPaths.length ? barrierPlan.expectedPaths.join(', ') : '(none declared; fallback accepts any non-pipeline code change)') + '\n' + String(probe || '(landed-probe failed)').slice(0, 1500)
|
|
391
522
|
}
|
|
392
523
|
|
|
393
524
|
// Step 8: QE (brutal-honesty, agentic-qe) + MANDATORY teach
|
|
@@ -404,7 +535,8 @@ const qeModel = resolveStageModel('qe')
|
|
|
404
535
|
// the legacy qeReviewer='codex' knob used to re-route QE back to codex even when the CODER was codex.
|
|
405
536
|
if (MODELS.qe === undefined && QE_REVIEWER === 'codex' && coderIsCodex()) log('QE: coder is codex — enforcing cross-model Claude QE (ignoring qeReviewer=codex to avoid self-review)')
|
|
406
537
|
const qeIsCodex = qeShouldUseCodex()
|
|
407
|
-
const
|
|
538
|
+
const qeClaudeModel = qeIsCodex ? {} : qeModel
|
|
539
|
+
const qeClaudeOpts = mergeOpts({ label: stageLabel('qe:brutal', qeClaudeModel), phase: 'QE', agentType: 'qe-code-reviewer', schema: QE }, qeClaudeModel)
|
|
408
540
|
let qe = null
|
|
409
541
|
let qeReviewerUsed = 'claude'
|
|
410
542
|
if (!qeIsCodex) {
|
|
@@ -414,14 +546,16 @@ if (!qeIsCodex) {
|
|
|
414
546
|
if (qe === null && !qeIsCodex) reactiveBelt('QE')
|
|
415
547
|
if (qe === null && (qeIsCodex || QE_REVIEWER === 'codex-fallback')) {
|
|
416
548
|
if (QE_REVIEWER === 'codex-fallback' && !qeIsCodex) log('QE: Claude unavailable (limit?) — falling back to Codex ' + CODEX_MODEL)
|
|
417
|
-
const
|
|
549
|
+
const qeCodexLabelOpts = qeModel.agentType ? qeModel : specToOpts('codex:' + CODEX_MODEL + ':high')
|
|
550
|
+
const qeCodexOpts = mergeOpts({ label: stageLabel('qe:codex', qeCodexLabelOpts), phase: 'QE', agentType: 'codex:codex-rescue' }, qeModel.agentType ? qeModel : {})
|
|
418
551
|
const codexQe = await agent(qePrompt + CODEX_HINT, qeCodexOpts)
|
|
419
|
-
if (codexQe) { qe = { grade: 'codex-review', gaps: [], codeTestsAdequate: null, docTestsPresent: null, summary: String(codexQe).slice(0, 500) }; qeReviewerUsed = qeIsCodex ? 'codex' : 'codex-fallback'; modelsUsed.qe = modelLabel(
|
|
552
|
+
if (codexQe) { qe = { grade: 'codex-review', gaps: [], codeTestsAdequate: null, docTestsPresent: null, summary: String(codexQe).slice(0, 500) }; qeReviewerUsed = qeIsCodex ? 'codex' : 'codex-fallback'; modelsUsed.qe = modelLabel(qeCodexLabelOpts) }
|
|
420
553
|
}
|
|
421
554
|
// Belt: if a codex-first QE returned null (codex unavailable), fall back to a Claude reviewer — never block.
|
|
422
555
|
if (qe === null && qeIsCodex) {
|
|
423
556
|
log('QE: Codex unavailable — falling back to a Claude reviewer (cross-model belt)')
|
|
424
|
-
const
|
|
557
|
+
const qeBeltModel = routingRequested ? { model: 'opus' } : {}
|
|
558
|
+
const qeBeltOpts = mergeOpts({ label: stageLabel('qe:brutal:claude-fb', qeBeltModel), phase: 'QE', agentType: 'qe-code-reviewer', schema: QE }, qeBeltModel)
|
|
425
559
|
qe = await agent(qePrompt, qeBeltOpts)
|
|
426
560
|
if (qe) { qeReviewerUsed = 'claude'; modelsUsed.qe = modelLabel(qeBeltOpts) }
|
|
427
561
|
}
|
|
@@ -433,8 +567,8 @@ if (isLplus) {
|
|
|
433
567
|
await usageProbe('FleetQE')
|
|
434
568
|
const fleetModel = resolveStageModel('fleet')
|
|
435
569
|
modelsUsed.fleet = modelLabel(mergeOpts({}, fleetModel))
|
|
436
|
-
const fleetTraceOpts = mergeOpts({ label: 'fleet:trace', phase: 'FleetQE', agentType: 'qe-requirements-validator' }, fleetModel)
|
|
437
|
-
const fleetCovOpts = mergeOpts({ label: 'fleet:cov', phase: 'FleetQE', agentType: 'qe-coverage-specialist' }, fleetModel)
|
|
570
|
+
const fleetTraceOpts = mergeOpts({ label: stageLabel('fleet:trace', fleetModel), phase: 'FleetQE', agentType: 'qe-requirements-validator' }, fleetModel)
|
|
571
|
+
const fleetCovOpts = mergeOpts({ label: stageLabel('fleet:cov', fleetModel), phase: 'FleetQE', agentType: 'qe-coverage-specialist' }, fleetModel)
|
|
438
572
|
const fleetThunks = [
|
|
439
573
|
() => agent('Step 9 fleet-QE (requirements traceability + risk) for ' + SLUG + ': map ADR decisions to code to tests; flag orphans + high risk. Write ' + FDIR + '/09_fleet_qe_assessment.md.', fleetTraceOpts),
|
|
440
574
|
() => agent('Step 9 fleet-QE (coverage + regression) for ' + SLUG + ': risk-weighted coverage gaps + regression selection for the changed files. Append to ' + FDIR + '/09_fleet_qe_assessment.md.', fleetCovOpts),
|