@dzhechkov/skills-feature-adr 1.3.42 → 1.3.43

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 CHANGED
@@ -141,6 +141,13 @@ Workflow({ scriptPath: '.claude/workflows/feature-adr.js',
141
141
  Omit the Codex knobs entirely for today's all-Claude behavior. The run result reports
142
142
  `plannerUsed` / `coderUsed` / `qeReviewerUsed` / `codexModel` / `modelsUsed` so you can see who did what.
143
143
 
144
+ **Live model visibility in `/workflows`:** each stage's agent label is decorated with its *resolved*
145
+ model — e.g. `adr · codex:gpt-5.5:xhigh`, `plan · opus` — so you see which model actually ran a stage in
146
+ the live progress tree, not just in the final report. This matters because a Codex stage's auto
147
+ model-badge shows the `codex:codex-rescue` Claude wrapper (the session model), never `codex`; the label
148
+ text is the honest signal. A Claude fallback appears as its own distinct node (never mislabeled as Codex),
149
+ and a routing-off run adds no suffix (byte-identical to today).
150
+
144
151
  ### Per-stage model routing — `args.models`
145
152
 
146
153
  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.42",
3
+ "version": "1.3.43",
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,6 +226,14 @@ 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 +
@@ -245,7 +253,7 @@ function landedProbeCmd(f) {
245
253
  // codex-rescue returns finalMessage text, not StructuredOutput; success is proven by the file landing —
246
254
  // (b) add a FOREGROUND hint so the codex runtime blocks until the write completes, (c) poll the
247
255
  // artifact, and (d) fall back to a Claude agent if it never lands (never blocks the pipeline).
248
- async function designStage(promptText, opts, artifactPath) {
256
+ async function designStage(promptText, opts, artifactPath, baseLabel) {
249
257
  if (!needsLandedBarrier(opts)) return await agent(promptText, opts)
250
258
  const codexOpts = {}
251
259
  for (const k in opts) if (k !== 'schema') codexOpts[k] = opts[k]
@@ -253,7 +261,8 @@ async function designStage(promptText, opts, artifactPath) {
253
261
  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
262
  if (res && probe && /landed=/.test(String(probe))) return { wrote: [artifactPath], summary: String(res).slice(0, 300) }
255
263
  log('design artifact did not land on codex (' + artifactPath + ') — falling back to Claude')
256
- return await agent(promptText, mergeOpts({ label: (opts.label || 'design') + ':claude-fb', phase: 'Design', schema: ARTIFACT }, {}))
264
+ const fallbackOpts = {}
265
+ return await agent(promptText, mergeOpts({ label: stageLabel((baseLabel || 'design') + ':claude-fb', fallbackOpts), phase: 'Design', schema: ARTIFACT }, fallbackOpts))
257
266
  }
258
267
 
259
268
  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 +275,8 @@ const ADR_FITNESS_CHECKLIST = 'ADR fitness checklist for Step 8: read every ' +
266
275
  phase('Router')
267
276
  await usageProbe('Router')
268
277
  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 routerOpts = mergeOpts({ label: 'router+recall', phase: 'Router', schema: ROUTER, effort: 'low' }, resolveStageModel('router'))
278
+ const routerModel = resolveStageModel('router')
279
+ const routerOpts = mergeOpts({ label: stageLabel('router+recall', routerModel), phase: 'Router', schema: ROUTER, effort: 'low' }, routerModel)
270
280
  modelsUsed.router = modelLabel(routerOpts)
271
281
  const router = await agent(routerPrompt, routerOpts)
272
282
  let tier = A.tier || (router ? router.tier : 'M')
@@ -287,22 +297,26 @@ const designThunks = []
287
297
  const reqExtra = isLplus ? ' Also write ' + FDIR + '/02_research.md (codebase patterns + external analogues; read the repo for the closest existing implementation to mirror).' : ''
288
298
  // Resolve per-stage model opts up-front (parser-safe: no inline resolveStageModel inside the thunk arrays).
289
299
  // research folds into requirements, ddd folds into architecture (single shared call) — recorded for reporting.
290
- const reqOpts = mergeOpts({ label: 'requirements', phase: 'Design', schema: ARTIFACT }, resolveStageModel('requirements'))
291
- const adrOpts = mergeOpts({ label: 'adr', phase: 'Design', schema: ARTIFACT }, resolveStageModel('adr'))
292
- const qcsdOpts = mergeOpts({ label: 'qcsd', phase: 'Design', schema: ARTIFACT }, resolveStageModel('ideation'))
293
- const archOpts = mergeOpts({ label: 'architecture', phase: 'Design', schema: ARTIFACT }, resolveStageModel('architecture'))
300
+ const reqModel = resolveStageModel('requirements')
301
+ const adrModel = resolveStageModel('adr')
302
+ const qcsdModel = resolveStageModel('ideation')
303
+ const archModel = resolveStageModel('architecture')
304
+ const reqOpts = mergeOpts({ label: stageLabel('requirements', reqModel), phase: 'Design', schema: ARTIFACT }, reqModel)
305
+ const adrOpts = mergeOpts({ label: stageLabel('adr', adrModel), phase: 'Design', schema: ARTIFACT }, adrModel)
306
+ const qcsdOpts = mergeOpts({ label: stageLabel('qcsd', qcsdModel), phase: 'Design', schema: ARTIFACT }, qcsdModel)
307
+ const archOpts = mergeOpts({ label: stageLabel('architecture', archModel), phase: 'Design', schema: ARTIFACT }, archModel)
294
308
  modelsUsed.requirements = modelLabel(reqOpts)
295
309
  modelsUsed.research = modelLabel(reqOpts)
296
310
  modelsUsed.adr = modelLabel(adrOpts)
297
311
  modelsUsed.ideation = modelLabel(qcsdOpts)
298
312
  modelsUsed.architecture = modelLabel(archOpts)
299
313
  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'))
314
+ 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
315
  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'))
316
+ 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'))
317
+ 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
318
  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'))
319
+ 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
320
  }
307
321
  const design = await parallel(designThunks)
308
322
 
@@ -319,8 +333,9 @@ const planModel = resolveStageModel('plan')
319
333
  const planIsCodex = (planModel.agentType === 'codex:codex-rescue') || (MODELS.plan === undefined && PLANNER === 'codex')
320
334
  let plan = null
321
335
  if (planIsCodex) {
322
- modelsUsed.plan = (planModel.agentType === 'codex:codex-rescue') ? modelLabel(planModel) : ('codex:' + CODEX_MODEL + ':high')
323
- const codexPlanOpts = mergeOpts({ label: 'plan:codex', phase: 'Plan', agentType: 'codex:codex-rescue' }, planModel.agentType ? planModel : {})
336
+ const planCodexLabelOpts = (planModel.agentType === 'codex:codex-rescue') ? planModel : specToOpts('codex:' + CODEX_MODEL + ':high')
337
+ modelsUsed.plan = modelLabel(planCodexLabelOpts)
338
+ const codexPlanOpts = mergeOpts({ label: stageLabel('plan:codex', planCodexLabelOpts), phase: 'Plan', agentType: 'codex:codex-rescue' }, planModel.agentType ? planModel : {})
324
339
  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
340
  // Codex-landed barrier for the plan artifact: a stub return is NOT proof the file was written
326
341
  // (codex writes out-of-band). Require the artifact to LAND; otherwise fall through to the Claude planner.
@@ -334,7 +349,8 @@ if (planIsCodex) {
334
349
  }
335
350
  if (plan === null && planIsCodex) reactiveBelt('Plan')
336
351
  if (plan === null) {
337
- const claudePlanOpts = mergeOpts({ label: 'plan', phase: 'Plan', schema: ARTIFACT }, planIsCodex ? {} : planModel)
352
+ const claudePlanModel = planIsCodex ? {} : planModel
353
+ const claudePlanOpts = mergeOpts({ label: stageLabel(planIsCodex ? 'plan:claude-fb' : 'plan', claudePlanModel), phase: 'Plan', schema: ARTIFACT }, claudePlanModel)
338
354
  modelsUsed.plan = planIsCodex ? 'claude-fallback' : modelLabel(claudePlanOpts)
339
355
  const claudePlan = await agent(planPrompt, claudePlanOpts)
340
356
  plan = claudePlan ? { wrote: claudePlan.wrote, summary: claudePlan.summary, planner: planIsCodex ? 'claude-fallback' : 'claude' } : null
@@ -363,7 +379,8 @@ const codePrompt = 'Step 7 (Code) of /feature-adr for "' + DESC + '" (' + SLUG +
363
379
  // under the BC omit-path it is {} (byte-identical).
364
380
  const codeModel = resolveStageModel('code')
365
381
  const codeIsCodexFirst = (MODELS.code !== undefined) ? (codeModel.agentType === 'codex:codex-rescue') : (CODER === 'codex')
366
- const codeClaudeOpts = mergeOpts({ label: 'code', phase: 'Code', schema: ARTIFACT, effort: 'high' }, codeIsCodexFirst ? {} : (codeModel.agentType ? {} : codeModel))
382
+ const codeClaudeModel = codeIsCodexFirst ? {} : (codeModel.agentType ? {} : codeModel)
383
+ const codeClaudeOpts = mergeOpts({ label: stageLabel('code', codeClaudeModel), phase: 'Code', schema: ARTIFACT, effort: 'high' }, codeClaudeModel)
367
384
  let code = null
368
385
  let coderUsed = 'claude'
369
386
  if (!codeIsCodexFirst) {
@@ -373,9 +390,10 @@ if (!codeIsCodexFirst) {
373
390
  if (code === null && !codeIsCodexFirst) reactiveBelt('Code')
374
391
  if (code === null && (codeIsCodexFirst || CODER === 'codex-fallback')) {
375
392
  if (CODER === 'codex-fallback' && !codeIsCodexFirst) log('Code: Claude unavailable (limit?) — falling back to Codex ' + CODEX_MODEL)
376
- const codeCodexOpts = mergeOpts({ label: 'code:codex', phase: 'Code', agentType: 'codex:codex-rescue' }, codeModel.agentType ? codeModel : {})
393
+ const codeCodexLabelOpts = codeModel.agentType ? codeModel : specToOpts('codex:' + CODEX_MODEL + ':high')
394
+ const codeCodexOpts = mergeOpts({ label: stageLabel('code:codex', codeCodexLabelOpts), phase: 'Code', agentType: 'codex:codex-rescue' }, codeModel.agentType ? codeModel : {})
377
395
  const codexCode = await agent(codePrompt + CODEX_HINT, codeCodexOpts)
378
- if (codexCode) { code = { wrote: [FDIR + '/07_code_changes/change_manifest.md'], summary: String(codexCode).slice(0, 500) }; coderUsed = codeIsCodexFirst ? 'codex' : 'codex-fallback'; modelsUsed.code = modelLabel(codeCodexOpts) }
396
+ if (codexCode) { code = { wrote: [FDIR + '/07_code_changes/change_manifest.md'], summary: String(codexCode).slice(0, 500) }; coderUsed = codeIsCodexFirst ? 'codex' : 'codex-fallback'; modelsUsed.code = modelLabel(codeCodexLabelOpts) }
379
397
  }
380
398
 
381
399
  // Step 7.5: Codex-landed barrier. Codex applies edits OUT-OF-BAND via its own runtime; without this,
@@ -404,7 +422,8 @@ const qeModel = resolveStageModel('qe')
404
422
  // the legacy qeReviewer='codex' knob used to re-route QE back to codex even when the CODER was codex.
405
423
  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
424
  const qeIsCodex = qeShouldUseCodex()
407
- const qeClaudeOpts = mergeOpts({ label: 'qe:brutal', phase: 'QE', agentType: 'qe-code-reviewer', schema: QE }, qeIsCodex ? {} : qeModel)
425
+ const qeClaudeModel = qeIsCodex ? {} : qeModel
426
+ const qeClaudeOpts = mergeOpts({ label: stageLabel('qe:brutal', qeClaudeModel), phase: 'QE', agentType: 'qe-code-reviewer', schema: QE }, qeClaudeModel)
408
427
  let qe = null
409
428
  let qeReviewerUsed = 'claude'
410
429
  if (!qeIsCodex) {
@@ -414,14 +433,16 @@ if (!qeIsCodex) {
414
433
  if (qe === null && !qeIsCodex) reactiveBelt('QE')
415
434
  if (qe === null && (qeIsCodex || QE_REVIEWER === 'codex-fallback')) {
416
435
  if (QE_REVIEWER === 'codex-fallback' && !qeIsCodex) log('QE: Claude unavailable (limit?) — falling back to Codex ' + CODEX_MODEL)
417
- const qeCodexOpts = mergeOpts({ label: 'qe:codex', phase: 'QE', agentType: 'codex:codex-rescue' }, qeModel.agentType ? qeModel : {})
436
+ const qeCodexLabelOpts = qeModel.agentType ? qeModel : specToOpts('codex:' + CODEX_MODEL + ':high')
437
+ const qeCodexOpts = mergeOpts({ label: stageLabel('qe:codex', qeCodexLabelOpts), phase: 'QE', agentType: 'codex:codex-rescue' }, qeModel.agentType ? qeModel : {})
418
438
  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(qeCodexOpts) }
439
+ 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
440
  }
421
441
  // Belt: if a codex-first QE returned null (codex unavailable), fall back to a Claude reviewer — never block.
422
442
  if (qe === null && qeIsCodex) {
423
443
  log('QE: Codex unavailable — falling back to a Claude reviewer (cross-model belt)')
424
- const qeBeltOpts = mergeOpts({ label: 'qe:brutal', phase: 'QE', agentType: 'qe-code-reviewer', schema: QE }, routingRequested ? { model: 'opus' } : {})
444
+ const qeBeltModel = routingRequested ? { model: 'opus' } : {}
445
+ const qeBeltOpts = mergeOpts({ label: stageLabel('qe:brutal:claude-fb', qeBeltModel), phase: 'QE', agentType: 'qe-code-reviewer', schema: QE }, qeBeltModel)
425
446
  qe = await agent(qePrompt, qeBeltOpts)
426
447
  if (qe) { qeReviewerUsed = 'claude'; modelsUsed.qe = modelLabel(qeBeltOpts) }
427
448
  }
@@ -433,8 +454,8 @@ if (isLplus) {
433
454
  await usageProbe('FleetQE')
434
455
  const fleetModel = resolveStageModel('fleet')
435
456
  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)
457
+ const fleetTraceOpts = mergeOpts({ label: stageLabel('fleet:trace', fleetModel), phase: 'FleetQE', agentType: 'qe-requirements-validator' }, fleetModel)
458
+ const fleetCovOpts = mergeOpts({ label: stageLabel('fleet:cov', fleetModel), phase: 'FleetQE', agentType: 'qe-coverage-specialist' }, fleetModel)
438
459
  const fleetThunks = [
439
460
  () => 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
461
  () => 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),