@dzhechkov/harness-cli 0.3.257 → 0.3.258

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
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "path": "README.md",
12
- "sha256": "7333dd44bcbeb6536e8ee0f0490a57fefca211e706535da7f44cea7012388213"
12
+ "sha256": "a38e5ef6a741aec138af0de7bd3616d4356d613ba1616bd8b5417194c1ee5f03"
13
13
  },
14
14
  {
15
15
  "path": "coverage/coverage-final.json",
@@ -41,15 +41,15 @@
41
41
  },
42
42
  {
43
43
  "path": "dist/cli.d.ts.map",
44
- "sha256": "818293a250cdf1bd10b5d69267b714d9a8ab90017c1fa18de29fc24e28ad8a55"
44
+ "sha256": "f954d1e2e3d9f5941481a2b2ccbf553bab892dfa9ba1e7aefb4bf7d1800e9203"
45
45
  },
46
46
  {
47
47
  "path": "dist/cli.js",
48
- "sha256": "9c37499ed55b571232b5718c863b83b31f7a79faab46a9dcbe3283b993b1862a"
48
+ "sha256": "5fb105be335591fb20401a3b23bac9829777e7ba96c2a2480c29970f4ffa2774"
49
49
  },
50
50
  {
51
51
  "path": "dist/cli.js.map",
52
- "sha256": "6cf537925ccb4b1ffff84c8fd255b487438b78b56a46f9d91d33b25500ae520d"
52
+ "sha256": "a4be3aac6dadec03c1fc85fc5354d9a286ac0a21b4983349c4e511cae49dec79"
53
53
  },
54
54
  {
55
55
  "path": "dist/index.d.ts",
@@ -73,7 +73,7 @@
73
73
  },
74
74
  {
75
75
  "path": "package.json",
76
- "sha256": "7abb07cdda09bea2b8e948cecdbaed4579371e8d0e8cee4436e7fcb20f430cdd"
76
+ "sha256": "b502ba368d29e25413ce35e8853bc5650c06f686202e51f997f67db0fac03224"
77
77
  },
78
78
  {
79
79
  "path": "src/bin.ts",
@@ -81,7 +81,7 @@
81
81
  },
82
82
  {
83
83
  "path": "src/cli.ts",
84
- "sha256": "d64af623372238bb5f2ead34e733130d7b013c49058a26f4795f09586b2b5dda"
84
+ "sha256": "a369561502f08d9dd4993a294adcb3a38349f543959fa8de31cd1580778062b0"
85
85
  },
86
86
  {
87
87
  "path": "src/index.ts",
@@ -109,5 +109,5 @@
109
109
  }
110
110
  ]
111
111
  },
112
- "signature": "SGosjJne1+J5lgK/bAptsnGAciI95Bi4z2zHjB1QC8yWic0SGvKE3rj2mZqvjBfwmwzxvpmYBv/laFmWIQEtAw=="
112
+ "signature": "CXaYCYQIKEVpAB29MZ5rG0/VUWtihZaeJh86r6x8uFpzKrgzHnrzmg/MmTUW3R/rqeA5w12hcfyfr4kl85ZxBA=="
113
113
  }
package/README.md CHANGED
@@ -632,6 +632,93 @@ a guess dressed as a number. The authoritative calibration is `--calibrate` with
632
632
  shown on claude.ai/settings/usage; absent that, limits set from your own observed peak mean "unusually
633
633
  heavy **for you**", which is exactly what a pre-emptive routing switch needs.
634
634
 
635
+ ### Where did the run's budget actually go? — `dz usage --by-stage`
636
+
637
+ **The itemized receipt.** A `/feature-adr` run reports ONE number. The recorded run
638
+ `wf_0576bd7d-797` spent `623290` tokens (MEASURED — reproducer:
639
+ `dz usage --by-stage --run wf_0576bd7d-797`, `totalTokens` field of the run record) — a restaurant
640
+ bill with no line items. `dz usage --by-stage` turns it into a receipt keyed by the workflow's own
641
+ stage labels, so *"where the budget burns"* stops being a feeling and becomes a number you can sort.
642
+
643
+ Why it matters, in four points:
644
+
645
+ 1. **Visibility → control.** Per-stage rows (`code` · `qe:brutal` · `fleet:cov` · `delivery:*`),
646
+ each with its model, its weighted tokens, its call count and a USD estimate — sorted by spend, so
647
+ the expensive stage is the first line you read.
648
+ 2. **Real numbers for auto-cost routing.** `args.models.<stage> = 'auto-cost'` picks models from a
649
+ STATIC assumptions table. The ledger exposes MEASURED per-stage aggregates
650
+ (`{stage, model, avgTokens, runs}`) — the missing sense organ for a system meant to optimize its
651
+ own cost. *Wiring into routing is deliberately out of scope for now: the reader exists, nothing
652
+ consumes it yet.*
653
+ 3. **The reconciliation invariant guards the bookkeeping itself.** Per-stage sums MUST reconcile
654
+ with the run total: `accounted + unaccounted = run total`, as raw integer equality. A mismatch is
655
+ a **NAMED defect** (`Unaccounted` / `DoubleAttributed` / `ForeignSample` /
656
+ `MissingStageTranscript` / `MalformedRecord`), never a rounding remainder — the same discipline as
657
+ the event-chain ledger. Without it a by-stage table can quietly lie: it already caught a real run
658
+ where 12 agent transcripts had no stage entry at all.
659
+ 4. **The honest limit, stated next to the benefits.** Totals are LOCAL TRANSCRIPT ESTIMATES — no
660
+ billing API is consulted. So the invariant catches **attribution** errors (a double-counted stage,
661
+ a missing one), **not pricing** errors. There is no "accurate to the cent" promise here, and the
662
+ USD column marks with `*` every row priced by the sonnet-class fallback.
663
+
664
+ #### Зачем это
665
+
666
+ Прогон `/feature-adr` отчитывается **одним** числом. У записанного прогона `wf_0576bd7d-797` это
667
+ `623290` токенов (MEASURED — воспроизводится: `dz usage --by-stage --run wf_0576bd7d-797`) — счёт из
668
+ ресторана без позиций. `dz usage --by-stage` превращает его в **детализированный счёт** по тем самым
669
+ меткам стадий, которые пайплайн уже проставляет, — и «где горит бюджет» перестаёт быть ощущением и
670
+ становится числом.
671
+
672
+ 1. **Видимость → управляемость.** Строка на стадию (`code` · `qe:brutal` · `fleet:cov` ·
673
+ `delivery:*`) с моделью, взвешенными токенами, числом вызовов и оценкой в долларах, отсортированные
674
+ по расходу: дорогая стадия — первая строка, которую вы читаете.
675
+ 2. **Живые данные для auto-cost роутинга.** Сейчас `args.models.<stage> = 'auto-cost'` выбирает
676
+ модель по СТАТИЧЕСКОЙ таблице предположений. Реестр отдаёт ИЗМЕРЕННЫЕ агрегаты по стадиям
677
+ (`{stage, model, avgTokens, runs}`) — недостающий орган чувств для системы, которая должна
678
+ оптимизировать собственную стоимость. *Подключение к роутингу сознательно вынесено за рамки:
679
+ читатель есть, потребителя пока нет.*
680
+ 3. **Инвариант сверки страхует саму бухгалтерию.** Сумма по стадиям ОБЯЗАНА сходиться с итогом
681
+ прогона: `учтено + неучтённое = итог`, точное целочисленное равенство. Расхождение — это
682
+ **именованный дефект**, а не «остаток от округления»: та же дисциплина, что у event-chain-реестра.
683
+ Без него таблица по стадиям может тихо врать — и она уже поймала реальный прогон, где 12
684
+ транскриптов агентов не имели записи ни об одной стадии.
685
+ 4. **Честная граница — рядом с пользой.** Итоги считаются по ЛОКАЛЬНЫМ транскриптам (никакого
686
+ биллингового API), поэтому инвариант ловит ошибки **атрибуции** (двойной счёт, потерянную стадию),
687
+ а НЕ ошибки **цены**. Обещания «до цента» здесь нет, а строки, посчитанные по резервному
688
+ sonnet-тарифу, помечены `*`.
689
+
690
+ ```bash
691
+ dz usage --by-stage # the most recent workflow run
692
+ dz usage --by-stage --slug portable-gates # by feature slug
693
+ dz usage --by-stage --run wf_0576bd7d-797 # by run id
694
+ dz usage --by-stage --json # rows + reconciliation, machine-readable
695
+ dz usage --by-stage --write features/x/09_cost_ledger.jsonl # materialize the derived report
696
+ ```
697
+
698
+ Real output (MEASURED — reproducer: the `--run wf_0576bd7d-797` command above, abridged):
699
+
700
+ ```
701
+ usage --by-stage: run wf_0576bd7d-797 · slug recall-usage-instrumentation · feature-adr · completed
702
+ stage model weighted calls ~USD
703
+ adr:claude-fb claude-opus-4-8[1m] 253,649 11 $1.27
704
+ design:confirm-landed claude-opus-4-8[1m] 196,013 7 $0.9801
705
+ qcsd · codex:gpt-5.6:xhigh (usage-switched) claude-sonnet-5 109,122 7 $0.3274
706
+ usage:probe claude-haiku-4-5-20251001 76,441 6 $0.0764
707
+ reconciliation: accounted 1,112,167 + unaccounted 0 = run total 1,112,167 (epsilon 0.00%)
708
+ identity: holds (raw integer equality)
709
+ verdict: BALANCED
710
+ scope: local transcript ESTIMATES, not billed amounts — the reconciliation invariant catches
711
+ ATTRIBUTION errors (double-counted or missing stages), NOT pricing errors
712
+ ```
713
+
714
+ **Three verdicts, and `INSUFFICIENT_DATA` is not one of the good ones.** `BALANCED` means measured
715
+ and reconciled; `DEFECT` names what is wrong; `INSUFFICIENT_DATA` means nothing was measured — never
716
+ read `!== 'DEFECT'` as success. Nothing is written unless you pass `--write`, and what it writes is a
717
+ regenerable report, not a store.
718
+
719
+ **When to use:** after an expensive run, before deciding which stage to re-route or downgrade; when
720
+ a run's cost surprises you; and any time a by-stage number is about to be quoted to someone.
721
+
635
722
  ### Do your skills actually register? — `dz skills-verify`
636
723
 
637
724
  Shipping a skill pack is not the same as a skill **registering**. A layout test that asserts
@@ -888,6 +975,7 @@ dz vector harmonize [--apply] [--threshold <0..1>] [--json] # SEMANTIC me
888
975
  dz teach --harmonize [--apply] [--threshold <0..1>] # alias of `dz vector harmonize`
889
976
  dz statusline [--json] [--install] # compact Claude Code statusline: live self-learning pattern count + brain sources
890
977
  dz usage [--json] [--project <dir>] | --calibrate --session <pct> --weekly <pct> [--model fable=<pct>] # ESTIMATE Claude usage from fixed reset windows; optional per-model weekly binding; exit 0 ALWAYS
978
+ --by-stage [--run <id> | --slug <s>] [--epsilon <0..1>] [--write <file.jsonl>] [--json] # per-stage cost ledger for ONE feature-adr run + reconciliation invariant (BALANCED | DEFECT | INSUFFICIENT_DATA)
891
979
  dz claim-check [paths...] [--json] [--fail-on high|medium|none] [--project <dir>] # enforce the Integrity Rule: flag untagged/overstated accuracy claims; default scan = READMEs + features' 08_qe_report.md; exit 1 only at/above --fail-on (default high)
892
980
  dz brain list [--json] # the durable cross-project knowledge brain
893
981
  dz brain query "<q>" [--source <slug>] [--limit <N>] [--any] [--rerank] [--json] # cross-source recall (--any = OR match; --rerank reorders on-point first). Auto-broadens: if strict all-terms match yields 0 hits, it retries once as OR and labels the result "broadened" (text note + broadened:true in --json) instead of returning empty. Explicit --any is OR from the start (never labeled broadened).
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA6WH,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;CAC5C;AAED,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KACvD;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAyjO9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA4J5E"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAmXH,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;CAC5C;AAED,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KACvD;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAonO9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA4J5E"}
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url';
9
9
  import { execFileSync, execSync, spawn } from 'node:child_process';
10
10
  import { homedir, tmpdir } from 'node:os';
11
11
  import { createRequire } from 'node:module';
12
- import { createSkill, getSkillInfo, getWorkflow, isTargetName, listSkills, runDoctor, runInit, benchmarkSkill, benchmarkSkills, scanMcp, reconcileCapabilities, RECONCILE_BANNER, buildRegistry, discoverSkillPackDirs, checkUpstream, compareSkills, checkAllUpstream, sweepSkillDrift, syncCanonicalSkill, checkUpgrades, discoverPackages, discoverSourcePackages, fetchAllDownloads, filterByCategory, pretrain, recommend, generatePlugin, publishPackages, runSetup, runMigrate, searchRegistry, runSync, runVerify, runInitAgentsMd, runInitGeminiMd, TARGET_NAMES, buildParityMatrix, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_NAMES, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, claimCheck, summarize, queryBookKnowledge, loadStorePatternsSync, patternRecordId, loadStoreRecords, recordToPattern, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, RECALL_USAGE_LOG_RELATIVE, RECALL_USAGE_LOG_MAX_BYTES, parseRecallUsageLog, buildRecallUsageReport, EVENT_CHAIN_TAIL_BYTES, EMPTY_LOG_TAIL, readTailInfo, appendChainedLines, verifyEventChainText, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, parsePnpmLockImporters,
12
+ import { createSkill, getSkillInfo, getWorkflow, isTargetName, listSkills, runDoctor, runInit, benchmarkSkill, benchmarkSkills, scanMcp, reconcileCapabilities, RECONCILE_BANNER, buildRegistry, discoverSkillPackDirs, checkUpstream, compareSkills, checkAllUpstream, sweepSkillDrift, syncCanonicalSkill, checkUpgrades, discoverPackages, discoverSourcePackages, fetchAllDownloads, filterByCategory, pretrain, recommend, generatePlugin, publishPackages, runSetup, runMigrate, searchRegistry, runSync, runVerify, runInitAgentsMd, runInitGeminiMd, TARGET_NAMES, buildParityMatrix, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_NAMES, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveCostLedger, renderCostLedger, verifyCostLedgerReport, writeCostLedgerJsonl, COST_LEDGER_SCOPE, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, claimCheck, summarize, queryBookKnowledge, loadStorePatternsSync, patternRecordId, loadStoreRecords, recordToPattern, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, RECALL_USAGE_LOG_RELATIVE, RECALL_USAGE_LOG_MAX_BYTES, parseRecallUsageLog, buildRecallUsageReport, EVENT_CHAIN_TAIL_BYTES, EMPTY_LOG_TAIL, readTailInfo, appendChainedLines, verifyEventChainText, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, parsePnpmLockImporters,
13
13
  // guard-promotion (feature guard-promotion, scout idea #1)
14
14
  assembleCandidates, renderPromotionReport, renderPromotionAdr, normalizePromotionState, nextPromotionState, globMatch, promotionAdrRelPath, DEFAULT_WINDOW_DAYS, DEFAULT_PERIODS, MAX_CONTENT_FETCHES, BUILTIN_COVERAGE, decideProvenance, isInsideTree, signManifest, verifyManifest, listSignablePackFiles, assertKeyOutsideTree, decidePublishGate, collectPackageFacts, planReleaseGates, selectAffectedPackages, classifyGateExecutions, buildFailureIssue, buildReleaseNotes, releaseTagName, firstOutputLine, formatPublishError, MANIFEST_NAME, SBOM_NAME, buildArchitectureMap, renderMapHuman, findArchitectureDrift, renderDriftReport, scanWorkspacePackages, loadSubsystemManifest, loadProductVision, checkFeatureAgainstArchitecture, renderArchCheck, planProjectSkills, guidanceForStage, renderInjectionReport, analyzeCorpus, renderRakeReport, renderCriticSection, rakeAsLesson, rakeReward, DEFAULT_RAKE_THRESHOLDS, streamSessionEvents, findLatestTranscript, detectProcessRakes, buildRetro, renderRetro, retroLessonText, PROCESS_SIGNATURES, RETRO_DOMAIN, scanForSetup, buildSetupPlan, scaffoldFromSpec, renderScaffoldPreview, readExistingForScaffold, assembleChallengeContext, buildChallengeBrief, planDiscriminationCheck, classifyDiscrimination, pickAdversaryModel, CHALLENGE_QUESTIONS, loadOutcomes, renderOutcomes, statsForKey, selectAutoCost, recordProvisional, finalizeOutcome, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, scanSkillsLayout, parseInitFacts, verifyRegistration, buildContentProbePrompt, classifyContentProbe, renderContentProbe, findNonRegistrableSkillDirs, assembleCompoundingReport,
15
15
  // Cold-vs-warm EPOCH RUNNER (feature epoch-replay) — orchestrates + scores, never calls a model.
@@ -79,6 +79,7 @@ Usage:
79
79
  dz statusline [--json] [--install] [--project <dir>] (live self-learning panel for Claude Code's status bar; reads the CC JSON payload from STDIN)
80
80
  dz statusline --fa-record --slug <s> --step "<label>" [--recalled <n>] [--stored <n>] [--mode <m>] (feature-adr: record live per-run learning state → 📐 panel segment)
81
81
  dz usage [--json] [--project <dir>] | dz usage --calibrate --session <pct> --weekly <pct> [--model fable=<pct>] [--project <dir>] (ESTIMATE Claude usage from fixed reset windows; optional per-model weekly binding; exit 0 ALWAYS; pct=null when limits unconfigured)
82
+ dz usage --by-stage [--run <runId> | --slug <slug>] [--epsilon <0..1>] [--write <file.jsonl>] [--json] (per-stage cost ledger for ONE feature-adr run + the reconciliation invariant: accounted + unaccounted = run total; verdict BALANCED | DEFECT | INSUFFICIENT_DATA; local transcript ESTIMATES — catches ATTRIBUTION errors, not pricing errors)
82
83
  dz claim-check [paths...] [--json] [--fail-on high|medium|none] [--project <dir>] (enforce the Integrity Rule: flag untagged/overstated accuracy claims; default scan = root README.md + every discovered package's README.md + features/*/08_qe_report.md + docs/**/*.md (historical feature artifacts are NOT scanned — pass paths explicitly); exit 1 only at/above --fail-on, default high)
83
84
  dz pretrain [--project <dir>]
84
85
  dz recommend "<task description>"
@@ -1129,6 +1130,57 @@ function cmdUsageCalibrate(options, optionLists, cwd, write) {
1129
1130
  return 0;
1130
1131
  }
1131
1132
  }
1133
+ /**
1134
+ * `dz usage --by-stage` — the per-stage cost ledger for one feature-adr run (feature `cost-ledger`).
1135
+ *
1136
+ * A run reports ONE number; this turns it into an itemized receipt keyed by the workflow's OWN
1137
+ * `stageLabel()` strings, plus the reconciliation line that guards the bookkeeping itself. Derived
1138
+ * post-hoc from the transcripts already on disk — no workflow edit, killed runs included.
1139
+ *
1140
+ * Exit code is 0 ALWAYS, matching the rest of `dz usage`; the VERDICT (`BALANCED` / `DEFECT` /
1141
+ * `INSUFFICIENT_DATA`) is the signal, and `INSUFFICIENT_DATA` is not success.
1142
+ */
1143
+ function cmdUsageByStage(options, flags, write) {
1144
+ const runId = options.get('run');
1145
+ const slug = options.get('slug');
1146
+ const epsilonRaw = options.get('epsilon');
1147
+ const epsilon = epsilonRaw === undefined ? undefined : Number(epsilonRaw);
1148
+ const report = deriveCostLedger({
1149
+ ...(runId !== undefined ? { runId } : {}),
1150
+ ...(slug !== undefined ? { slug } : {}),
1151
+ ...(epsilon !== undefined && Number.isFinite(epsilon) ? { epsilon } : {}),
1152
+ });
1153
+ if (report === null) {
1154
+ // An ABSENT run is never a balanced empty report (ADR-003).
1155
+ const detail = runId !== undefined ? `run ${runId}` : slug !== undefined ? `slug ${slug}` : 'any workflow run';
1156
+ if (flags.has('json')) {
1157
+ write(JSON.stringify({ verdict: 'INSUFFICIENT_DATA', reason: `no workflow run record found for ${detail}`, rows: [], estimated: true, scope: COST_LEDGER_SCOPE }));
1158
+ }
1159
+ else {
1160
+ write(`usage --by-stage: INSUFFICIENT_DATA — no workflow run record found for ${detail}`);
1161
+ write(`usage --by-stage: scope: ${COST_LEDGER_SCOPE}`);
1162
+ }
1163
+ return 0;
1164
+ }
1165
+ // The verifier re-derives the identities from the EMITTED report — a builder bug must surface as
1166
+ // a finding, not as a plausible table.
1167
+ const verifyDefects = verifyCostLedgerReport(report);
1168
+ const outPath = options.get('write');
1169
+ let wrote = null;
1170
+ if (outPath !== undefined && outPath.length > 0)
1171
+ wrote = writeCostLedgerJsonl(resolve(outPath), report);
1172
+ if (flags.has('json')) {
1173
+ write(JSON.stringify({ ...report, verifyDefects, ...(wrote === null ? {} : { wrote, writePath: resolve(outPath ?? '') }) }));
1174
+ return 0;
1175
+ }
1176
+ write(renderCostLedger(report));
1177
+ for (const d of verifyDefects)
1178
+ write(` verifier: ${d.kind}: ${d.detail}`);
1179
+ if (wrote !== null) {
1180
+ write(` ${wrote ? 'wrote' : 'FAILED to write'} ${resolve(outPath ?? '')} (derived report — regenerable, never read back)`);
1181
+ }
1182
+ return 0;
1183
+ }
1132
1184
  /**
1133
1185
  * `dz usage` — print an ESTIMATE of Claude session + weekly usage from fixed reset windows,
1134
1186
  * aggregated READONLY from the local transcript store (see {@link computeUsage}). `--json` emits
@@ -1154,6 +1206,8 @@ function cmdUsage(options, optionLists, flags, cwd, write) {
1154
1206
  try {
1155
1207
  if (flags.has('calibrate'))
1156
1208
  return cmdUsageCalibrate(options, optionLists, cwd, write);
1209
+ if (flags.has('by-stage'))
1210
+ return cmdUsageByStage(options, flags, write);
1157
1211
  const u = computeUsage(projectRoot);
1158
1212
  const lim = readUsageLimits(projectRoot);
1159
1213
  const modelLimits = lim.weeklyTokenLimitByModel;