@dzhechkov/skills-feature-adr 1.3.52 → 1.3.53

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/skills-feature-adr",
3
- "version": "1.3.52",
3
+ "version": "1.3.53",
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"
@@ -570,6 +570,36 @@ const isMplus = tier === 'M' || tier === 'L' || tier === 'XL'
570
570
  const isLplus = tier === 'L' || tier === 'XL'
571
571
  log('Router: tier ' + tier)
572
572
 
573
+ // ── AUTO-COST pre-resolution (feature learned-cost-routing) ──
574
+ // A stage whose spec is 'auto-cost' is resolved HERE (tier is now known) to a concrete model via
575
+ // `dz routing --select` — the workflow is sandboxed (no fs), so selection I/O shells out to dz. Byte-identical
576
+ // no-op (ZERO agent calls) when no stage is 'auto-cost'. Order matters: resolve `code` FIRST so `qe` can be
577
+ // forced to the CROSS-family of the coder (the named cross-model-QE guard). Escalate-on-fail across runs is
578
+ // automatic: a gate-FAIL recorded below down-ranks the model so the NEXT run's select picks the next rung.
579
+ const AUTOCOST = {}
580
+ function acFamOf(spec) { return /codex|gpt|openai/i.test(String(spec)) ? 'openai' : 'claude' }
581
+ function acConcrete(model) { return CLAUDE_NAMES[model] ? model : ('codex:' + model + ':high') }
582
+ function acBareId(spec) { var s = String(spec || ''); return s.indexOf('codex:') === 0 ? (s.split(':')[1] || s) : s }
583
+ async function resolveAutoCost(stage, familyArg) {
584
+ const famFlag = familyArg ? (' --family ' + familyArg) : ''
585
+ const out = await agent('Run EXACTLY this via Bash and reply with ONLY its stdout (a single JSON line), nothing else: ' + DZ + ' routing --select --stage ' + stage + ' --tier ' + tier + famFlag, { label: 'auto-cost:select:' + stage, phase: 'Route', effort: 'low' })
586
+ let pick = null
587
+ try { pick = JSON.parse(String(out).replace(/^[^{]*/, '').replace(/[^}]*$/, '')) } catch { pick = null }
588
+ if (!pick || !pick.model) { log('auto-cost ' + stage + ': no candidate model — leaving session-inherited'); MODELS[stage] = undefined; return }
589
+ MODELS[stage] = acConcrete(pick.model)
590
+ AUTOCOST[stage] = { chain: (pick.chain || []), tier: tier, evidence: pick.evidence || '' }
591
+ log('auto-cost ' + stage + ' → ' + MODELS[stage] + ' [' + (pick.metBar ? 'learned' : 'cold-start') + ']')
592
+ }
593
+ const autoCostStages = Object.keys(MODELS).filter(function (s) { return MODELS[s] === 'auto-cost' })
594
+ if (autoCostStages.length > 0) {
595
+ for (const s of autoCostStages) { if (s !== 'qe') await resolveAutoCost(s, null) }
596
+ if (MODELS.qe === 'auto-cost') {
597
+ // cross-family of the resolved coder (guard): coder codex → qe claude; coder claude → qe openai.
598
+ const coderSpec = (MODELS.code !== undefined && MODELS.code !== null && MODELS.code !== 'auto-cost') ? MODELS.code : ((CODER === 'codex' || CODER === 'codex-fallback') ? 'codex' : 'opus')
599
+ await resolveAutoCost('qe', acFamOf(coderSpec) === 'openai' ? 'claude' : 'openai')
600
+ }
601
+ }
602
+
573
603
  // GUARANTEED fa-panel write (the router, being low-effort + multi-job, tends to skip the fa-record
574
604
  // Bash call). A dedicated single-command agent reliably lights up the live /feature-adr panel at the
575
605
  // most visible moment. Uses the workspace bin (PATH-independent). Best-effort — never blocks.
@@ -845,6 +875,55 @@ if (qe === null && qeIsCodex) {
845
875
  const claimGate = step8ClaimGate(qe && qe.claimCheck ? qe.claimCheck : null)
846
876
  log(claimGate.note)
847
877
 
878
+ // ── AUTO-COST outcome recording (feature learned-cost-routing) ──
879
+ // The two-phase label lands here: every auto-cost stage that produced an artifact records a PROVISIONAL (i);
880
+ // the CODE stage's authoritative gate (ii) is the Step-8 QE grade (A/B = pass) — a produced-but-gate-FAILED
881
+ // run is recorded as a FAILURE, down-ranking that model for the next run (ADR §2). Byte-identical no-op when
882
+ // nothing was auto-cost. Gate-less stages get weak provisional credit; richer per-stage gates are Phase-2.
883
+ if (Object.keys(AUTOCOST).length > 0) {
884
+ const recPhase = isLplus ? 'FleetQE' : 'QE'
885
+ // Record ONLY stages that ACTUALLY RAN and produced an artifact (QE finding: a blanket loop over configured
886
+ // auto-cost stages credited skipped/failed/fallback stages). `code`/`plan` are guarded by their result var.
887
+ // ATTRIBUTION guard (QE #2): the auto-cost pick's outcome must be attributed to the PICK's model — but a
888
+ // codex-FALLBACK produces the code on a DIFFERENT model when the picked model returned null. So:
889
+ // • the picked model ran & produced → provisional + finalize by the QE gate.
890
+ // • a fallback fired (pick returned null) → the PICK FAILED to deliver → finalize(pick, false), NOT the
891
+ // fallback model (which was not auto-cost-selected). This down-ranks the pick honestly.
892
+ const codeMid = acBareId(MODELS.code)
893
+ if (AUTOCOST.code && codeMid) {
894
+ const pickIsCodex = /codex|gpt/i.test(String(MODELS.code))
895
+ const pickRan = pickIsCodex ? (coderUsed === 'codex') : (coderUsed === 'claude')
896
+ if (pickRan && code) {
897
+ const codePassed = !!(qe && /^[AB]$/i.test(String(qe.grade || '').trim()))
898
+ await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + DZ + ' routing --record-provisional --stage code --tier ' + AUTOCOST.code.tier + ' --model ' + codeMid, { label: 'auto-cost:record:code', phase: recPhase, effort: 'low' })
899
+ await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + DZ + ' routing --finalize --stage code --tier ' + AUTOCOST.code.tier + ' --model ' + codeMid + ' --success ' + (codePassed ? 'true' : 'false'), { label: 'auto-cost:finalize:code', phase: recPhase, effort: 'low' })
900
+ } else {
901
+ // the picked model did not deliver (fallback fired or produced nothing) → record it as a failure.
902
+ log('auto-cost code: picked model ' + codeMid + ' did not deliver (coderUsed=' + coderUsed + ') — recording a failure')
903
+ await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + DZ + ' routing --finalize --stage code --tier ' + AUTOCOST.code.tier + ' --model ' + codeMid + ' --success false', { label: 'auto-cost:finalize:code-fail', phase: recPhase, effort: 'low' })
904
+ }
905
+ }
906
+ // PLAN: provisional only (its gate is landing, already enforced upstream); guarded by the plan result var.
907
+ const planMid = acBareId(MODELS.plan)
908
+ if (AUTOCOST.plan && plan && planMid) {
909
+ await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + DZ + ' routing --record-provisional --stage plan --tier ' + AUTOCOST.plan.tier + ' --model ' + planMid, { label: 'auto-cost:record:plan', phase: recPhase, effort: 'low' })
910
+ }
911
+ // QE: gate (ii) = produced a PARSEABLE verdict with a grade (qe non-null). A qe that named no grade fell back
912
+ // and is not the auto-cost pick, so record only when the picked reviewer actually delivered a verdict. Its
913
+ // model ran iff qeReviewerUsed matches the pick's family (else a belt/fallback reviewer ran).
914
+ const qeMid = acBareId(MODELS.qe)
915
+ if (AUTOCOST.qe && qeMid) {
916
+ const qePickIsCodex = /codex|gpt/i.test(String(MODELS.qe))
917
+ const qePickRan = qePickIsCodex ? (qeReviewerUsed === 'codex') : (qeReviewerUsed === 'claude')
918
+ if (qePickRan && qe && String(qe.grade || '').trim() !== '') {
919
+ await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + DZ + ' routing --record-provisional --stage qe --tier ' + AUTOCOST.qe.tier + ' --model ' + qeMid, { label: 'auto-cost:record:qe', phase: recPhase, effort: 'low' })
920
+ await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + DZ + ' routing --finalize --stage qe --tier ' + AUTOCOST.qe.tier + ' --model ' + qeMid + ' --success true', { label: 'auto-cost:finalize:qe', phase: recPhase, effort: 'low' })
921
+ } else {
922
+ await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + DZ + ' routing --finalize --stage qe --tier ' + AUTOCOST.qe.tier + ' --model ' + qeMid + ' --success false', { label: 'auto-cost:finalize:qe-fail', phase: recPhase, effort: 'low' })
923
+ }
924
+ }
925
+ }
926
+
848
927
  // Step 9: Fleet QE (L/XL)
849
928
  let fleet = 'skipped (S/M)'
850
929
  if (isLplus) {
@@ -889,5 +968,6 @@ return {
889
968
  brain: BRAIN,
890
969
  polymorphism: POLY.hasManifest ? POLY.report : null,
891
970
  claimGate: claimGate,
971
+ autoCost: Object.keys(AUTOCOST).length ? AUTOCOST : null,
892
972
  promiseTags: tags,
893
973
  }