@dzhechkov/harness-cli 0.8.22 → 0.8.23

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
@@ -2,13 +2,16 @@
2
2
 
3
3
  The **`dz`** CLI — the main entry point to the DZ Harness Hub. Install AI skills for **Claude Code, Codex, OpenCode, Hermes, OpenClaude, GitHub Copilot** from a single command.
4
4
 
5
- ## Test execution
5
+ ## Install in 30 seconds
6
6
 
7
- `npx vitest run` uses two projects and returns one combined verdict: `parallel` runs the ordinary
8
- suites concurrently, while `serial` runs process-spawning and real-time suites one file at a time.
9
- The serial paths in `test/serial-suites.txt` are regenerated from
10
- `test/serial-suites-census.test.ts`, which scans test sources for synchronous and asynchronous
11
- process markers, including `execSync(` and `execFile(`, and fails when the list and census differ.
7
+ ```bash
8
+ npm install -g @dzhechkov/harness-cli
9
+ dz setup --target claude-code --preset devops
10
+ dz doctor
11
+ ```
12
+
13
+ You do NOT need to clone any repository to use dz. Source is for contributors — see
14
+ [From source (contributors only)](#from-source-contributors-only) at the end of this README.
12
15
 
13
16
  ## Why dz?
14
17
 
@@ -120,7 +123,7 @@ Re-running is safe (existing files are skipped unless `--force`). Targets: `clau
120
123
 
121
124
  Don't see your tool below? Two fallbacks: if it reads `CLAUDE.md`/`.claude/` use `--target claude-code`;
122
125
  otherwise `dz bundle --select <ids> --out <dir>` gives a portable tree of **raw `SKILL.md`** files you can
123
- point any tool at (a skill is just Markdown). Dedicated targets are on the [roadmap](https://github.com/djd1m/dz-harness-hub/blob/main/docs/target-expansion-research.md).
126
+ point any tool at (a skill is just Markdown). Dedicated targets are on the roadmap (contributors — see `docs/target-expansion-research.md` in the source repo).
124
127
 
125
128
  **✅ Shipped native targets** (each compiles to the tool's own layout):
126
129
 
@@ -287,7 +290,7 @@ you know exactly which file is at fault, and you know whose defect it is.
287
290
 
288
291
  ## User Journey — from install to mastery
289
292
 
290
- All 91 commands (MEASURED — reproducer: `node --input-type=module -e "import('./dist/index.js').then(m=>console.log(m.DZ_COMMANDS.length))"` from this package; rendered help documents 92 unique top-level names — the 91 plus the built-in `help` — pinned NAME-FOR-NAME by `test/command-inventory-parity.test.ts`) mapped to a real workflow:
293
+ All 92 commands (MEASURED — reproducer: `node --input-type=module -e "import('./dist/index.js').then(m=>console.log(m.DZ_COMMANDS.length))"` from this package; rendered help documents 93 unique top-level names — the 92 plus the built-in `help` — pinned NAME-FOR-NAME by `test/command-inventory-parity.test.ts`) mapped to a real workflow:
291
294
 
292
295
  ```
293
296
  DISCOVER → INSTALL → USE → CREATE → MAINTAIN → SHARE
@@ -1021,17 +1024,15 @@ extracts you scope are what leaves the machine. RU: мост в обратную
1021
1024
  ### Пересмотр после аварийного само-ревью — `dz reqe`
1022
1025
 
1023
1026
  The feature-adr pipeline's cross-model guard says *the model that writes code must not review it*.
1024
- Under limit pressure (usage-adaptive `>=70%` switch) that guard is CONSCIOUSLY suspended: coder AND
1025
- Step-8 QE both run on Codex (FR-2.9 a Claude reviewer is exactly the agent that dies at the
1026
- limit). The rule used to say "re-review manually after limits reset" an instruction nobody
1027
- remembers. `dz reqe` turns it into a **debt with a lifecycle**: the run records
1028
- `features/<slug>/.fa-state/reqe-due.json`, `dz usage` surfaces the count the moment you check your
1029
- freed-up limits, and settlement is FAIL-CLOSED.
1027
+ Historical usage-switched runs could suspend that guard under provider-limit pressure: coder AND
1028
+ Step-8 QE both ran on Codex. The rule used to say "re-review manually after limits reset" an
1029
+ instruction nobody remembers. `dz reqe` turns it into a **debt with a lifecycle**: the run records
1030
+ `features/<slug>/.fa-state/reqe-due.json`, `dz usage --json` carries the outstanding count as
1031
+ `reqeDue`, and settlement is FAIL-CLOSED.
1030
1032
 
1031
1033
  ```bash
1032
- $ dz usage
1033
- usage: session ~12% (resets 03:00) · week ~41% (resets 06:00) · estimated
1034
- re-QE due: 1 usage-switched run(s) kept same-family QE — run `dz reqe` for the cross-family pass
1034
+ $ dz usage --json
1035
+ {"sessionPct":null,"weeklyPct":null,"routing":"disabled-by-design","spend":{"days":[...],"total7d":{...},"byModel":{...}},"reqeDue":1}
1035
1036
 
1036
1037
  $ dz reqe # the ledger
1037
1038
  dz reqe — 1 unsettled re-QE debt(s):
@@ -1042,8 +1043,8 @@ $ dz reqe --slug add-x --done --report features/add-x/08b_reqe_report.md
1042
1043
  dz reqe: debt settled: re-QE grade C (report …) — settlement appended to features/add-x/08_qe_report.md
1043
1044
  ```
1044
1045
 
1045
- **When to use:** any time `dz usage` prints a `re-QE due` line, or a feature-adr result carried
1046
- `reqeDue: true`. **Fail-closed settlement:** the report must exist, be non-trivial, and name exactly
1046
+ **When to use:** any time `dz usage --json` reports a positive `reqeDue`, or a feature-adr result
1047
+ carried `reqeDue: true`. **Fail-closed settlement:** the report must exist, be non-trivial, and name exactly
1047
1048
  one line-anchored `GRADE` (the boilerplate phrase `GRADE A-F` does not count); the run's own
1048
1049
  `08_qe_report.md` can never settle its own debt (real-path AND inode compared — a hard link doesn't
1049
1050
  fool it); the settlement epilogue lands in `08_qe_report.md` and the due-file rotates to
@@ -1051,12 +1052,13 @@ fool it); the settlement epilogue lands in `08_qe_report.md` and the due-file ro
1051
1052
  nothing re-runs QE automatically, and the validator proves the settlement is procedurally sound —
1052
1053
  which model authored the report stays with the human running the brief. RU: гард «кодер не ревьюит
1053
1054
  сам себя» осознанно снимается под лимитом; `dz reqe` превращает инструкцию «перепроверь потом»
1054
- в долг на диске — виден в `dz usage`, гасится только настоящим кросс-семейным отчётом с грейдом.
1055
+ в долг на диске — виден как `reqeDue` в `dz usage --json`, гасится только настоящим кросс-семейным отчётом с грейдом.
1055
1056
 
1056
- ### Usage estimates you can act on — `dz usage`
1057
+ ### Spend you can inspect — `dz usage`
1057
1058
 
1058
- `dz usage` estimates how much of your Claude session (5h block) and week you have spent, from local
1059
- transcripts. It drives feature-adr's pre-emptive "switch to Codex before the limit" routing.
1059
+ `dz usage` reports cost-weighted token spend from local Claude Code and subagent transcripts. It does
1060
+ not predict provider limits: there is no provider usage API, weekly resets are per-account, and
1061
+ ad-hoc resets make a local denominator non-measurable. Feature routing is therefore disabled by design.
1060
1062
 
1061
1063
  Two things changed to make it mean something:
1062
1064
 
@@ -1066,20 +1068,44 @@ Two things changed to make it mean something:
1066
1068
  1-hour TTL write, cache-read 0.1x, output 5x).
1067
1069
  - **Subagent transcripts count.** `<session>/subagents/*.jsonl` carry real, non-duplicated usage and
1068
1070
  were silently excluded.
1071
+ - **Symlinked project directories are scanned by real path, each real directory once.** Broken links
1072
+ and symlinks to files are skipped.
1069
1073
  - **The walk is safe and bounded.** Only regular files are read, symlinked files and directory
1070
- components are skipped (a FIFO used to block it, a symlink used to be followed), and the file cap
1071
- keeps the NEWEST transcripts so a long history cannot push current usage out of view.
1074
+ components cannot create duplicate walks or loops, and the file cap keeps the NEWEST transcripts
1075
+ so a long history cannot push current usage out of view.
1072
1076
 
1073
1077
  ```bash
1074
- dz usage # session ~13% (resets 10:24) · week ~15% (resets 08:59) · estimated
1075
- dz usage --json # machine-readable; pct is null when limits are unconfigured
1076
- dz usage --calibrate --session 42 --weekly 61 # teach it YOUR real numbers from claude.ai
1077
- ```
1078
-
1079
- **`pct` is `null` until you configure limits that is deliberate**, an unconfigured estimate is not
1080
- a guess dressed as a number. The authoritative calibration is `--calibrate` with the percentages
1081
- shown on claude.ai/settings/usage; absent that, limits set from your own observed peak mean "unusually
1082
- heavy **for you**", which is exactly what a pre-emptive routing switch needs.
1078
+ dz usage # seven UTC days + seven-day total + model shares + today-by-model
1079
+ dz usage --json # sessionPct/weeklyPct stay null; spend carries days, total7d, byModel, daysByModel
1080
+ ```
1081
+
1082
+ The compatibility keys `sessionPct` and `weeklyPct` remain present and always `null`, so existing
1083
+ feature-adr probes keep parsing the payload without treating a guess as a routing signal.
1084
+
1085
+ **What `unknown` means, and which window each block covers.** The "by model" table is a
1086
+ **seven-day window total** it is the same seven days as the daily table above it, collapsed
1087
+ across models, never a per-day figure (a defect this project has hit before: a weekly figure read
1088
+ as a daily one). A model row named `unknown` is the aggregation's fallback bucket, literally
1089
+ `event.model ?? 'unknown'` in `spendReport` — it catches BOTH of the two ways an event can carry no
1090
+ usable model: an event with **no model field at all**, and one whose model string did not match
1091
+ any of the four known substrings (`fable`/`opus`/`sonnet`/`haiku`). (Fix-round-1, Codex review
1092
+ MEDIUM #3: an earlier revision of this paragraph said `unknown` was "not an event without a
1093
+ model" — the opposite of what the code does; corrected to name both causes.) MEASURED on this
1094
+ machine, the second cause dominates — almost entirely `<synthetic>` assistant-turn markers, not a
1095
+ parsing failure — and `unknown` is always shown, never hidden, because a hidden `unknown` row would
1096
+ make the visible rows undercount the real total. Both the "by model" table and the "today by model"
1097
+ block print a third column: the model's **share as a fraction 0..1** (two decimals), not a
1098
+ percentage — `by model` shares are of the **seven-day total**, `today by model` shares are of
1099
+ **today's own total**, and a complete breakdown's column sums to `1.00` in either block (never
1100
+ `100` — that would be the percentage form). The `--json` payload additionally carries
1101
+ `spend.daysByModel`: one entry per day (same dates as `spend.days`, same order) with that single
1102
+ day's `{model → weightedTokens}`, `unknown` included — read `daysByModel.at(-1)` for **today's**
1103
+ per-model spend, which the plain-text report prints as the `today (<date>) by model` block right
1104
+ after the seven-day "by model" table. The invariant this rests on — every model breakdown
1105
+ reconciles exactly with the totals it was sliced from — is `spendInvariantViolations()` in
1106
+ `harness-core` (relative tolerance `max(1e-6, 1e-9·max(|a|,|b|))`, and a `NaN`/`Infinity` sum is
1107
+ always a violation, never a silent pass), covered by a dedicated mutation-registry entry
1108
+ (`usage-days-by-model-invariant`).
1083
1109
 
1084
1110
  ### Where did the run's budget actually go? — `dz usage --by-stage`
1085
1111
 
@@ -1327,6 +1353,22 @@ Four rules the gate itself obeys — these are what distinguish it from a green-
1327
1353
  undefended property lives in this repo's test suite, and the gate MUST fail on it — a gate that
1328
1354
  cannot fail cannot pass.
1329
1355
 
1356
+ **Where the full output of a RED baseline/rebaseline line lives** (gate-stability, 2026-09-12,
1357
+ fix-round-1 2026-09-12): the bounded 3-line/20-line tail in the verdict is a teaser, and under a
1358
+ multi-entry `--only a,b` run it was measured to hand back an unrelated neighbour's stderr —
1359
+ undiagnosable. When the initial baseline or a per-entry/final rebaseline line comes back non-zero,
1360
+ the executor saves the FULL stdout+stderr to
1361
+ `<os.tmpdir()>/dz-mutgate-output/dz-mutgate-<entryId|baseline>-<phase>-<ISO-timestamp>.log` and the
1362
+ verdict text names the path (`; full output: <path>`); a green line writes nothing. Files older
1363
+ than 7 days in that directory are rotated away on the next write, and rotation only ever touches a
1364
+ name carrying BOTH our own `dz-mutgate-` prefix AND our exact ISO-timestamp shape — a foreign file
1365
+ (even one shaped like `service-baseline-backup.log`, which the pre-fix-round-1 pattern would have
1366
+ matched and deleted) is never touched. If the save itself fails (EACCES/ENOSPC/EROFS/a blocked
1367
+ output directory), the verdict says so instead of silently losing the diagnostic artifact
1368
+ (`; full output NOT saved: <error>`) — the gate's own exit code is never changed by a logging
1369
+ failure. Override the directory with `DZ_MUTGATE_OUTPUT_DIR` (used by this package's own tests to
1370
+ avoid touching the real tmpdir).
1371
+
1330
1372
  When to reach for it: after a QE round names safety properties (seed the registry so they STAY
1331
1373
  defended); in CI for a package whose protections have burned you before (`npm run test:mutation`);
1332
1374
  and in feature-adr Step 8, where the QE reviewer runs it whenever the touched package has a
@@ -1449,13 +1491,13 @@ Each pack is an npm package — click through for the **full per-skill documenta
1449
1491
  | [@dzhechkov/skills-academic](https://www.npmjs.com/package/@dzhechkov/skills-academic) | 5 | Thesis-defense toolkit — dissertation review, questions, doc-check, defense eval |
1450
1492
  | [@dzhechkov/skills-news](https://www.npmjs.com/package/@dzhechkov/skills-news) | 3 | *dz-original* — news digests (`news-digest`) + delta watches (`news-monitor`) + bundled `goap-research-ed25519` verified-research backend (mandatory) |
1451
1493
  | [@dzhechkov/skills-demo-publisher](https://github.com/djd1m/dz-harness/tree/main/packages/%40dzhechkov/skills-demo-publisher) | 1 | *dz-original, staged* — scenario-driven product recording → budgeted static HTML5 video site with Russian captions and fail-closed Pages delivery checks |
1452
- | [@dzhechkov/skills-idea2prd](https://www.npmjs.com/package/@dzhechkov/skills-idea2prd) | 1 | *dz-original* — `idea2prd-manual`: idea/problem → PRD+ADR+DDD+C4+Pseudocode+Tests+Completion (9 checkpoints); bundles the analyst trio as a sources.json-tracked vendor ([ADR-0001](https://github.com/djd1m/dz-harness-hub/blob/main/docs/adr/0001-skill-canonicalization-and-dependency-model.md)) |
1453
- | [@dzhechkov/skills-reverse-engineering](https://www.npmjs.com/package/@dzhechkov/skills-reverse-engineering) | 1 | *dz-original* — `reverse-engineering-unicorn`: company → launch playbook (+CJM) via 6-module QUICK/DEEP/VERIFIED pipeline; canonical home that resolved the keysarium↔p-replicator drift ([ADR-0001](https://github.com/djd1m/dz-harness-hub/blob/main/docs/adr/0001-skill-canonicalization-and-dependency-model.md)) |
1454
- | [@dzhechkov/skills-presentation-storyteller](https://www.npmjs.com/package/@dzhechkov/skills-presentation-storyteller) | 1 | *dz-original* — `presentation-storyteller`: selling deck + verified sources + slide-by-slide speaker script; referenced (not vendored) by reverse-engineering-unicorn's Post-M6 step ([ADR-0001](https://github.com/djd1m/dz-harness-hub/blob/main/docs/adr/0001-skill-canonicalization-and-dependency-model.md)) |
1455
- | [@dzhechkov/skills-website-cloner](https://www.npmjs.com/package/@dzhechkov/skills-website-cloner) | 1 | *imported (MIT)* — `clone-website`: live site → pixel-perfect Next.js clone (recon → specs → parallel build → visual QA); needs a browser-MCP + Next.js scaffold; referenced by p-replicator's `/replicate` ([ADR-0001](https://github.com/djd1m/dz-harness-hub/blob/main/docs/adr/0001-skill-canonicalization-and-dependency-model.md)) |
1456
- | [@dzhechkov/skills-pm](https://www.npmjs.com/package/@dzhechkov/skills-pm) | 18 | *imported (MIT)* — product-management toolkit: OST, RICE/ICE prioritization, product-strategy, pricing, OKRs, NSM/metrics/A-B/cohort, outcome-roadmap, stakeholder-map, sprint-plan, strategy-red-team, GTM/growth/beachhead, market-sizing; curated from phuryn/pm-skills (`dz init --preset pm`) ([ADR-0002](https://github.com/djd1m/dz-harness-hub/blob/main/docs/adr/0002-product-and-design-expansion.md)) |
1457
- | [@dzhechkov/skills-taste](https://www.npmjs.com/package/@dzhechkov/skills-taste) | 1 | *imported (MIT)* — `design-taste-frontend`: anti-slop landing/portfolio/redesign framework (dials + pre-flight + GSAP skeletons); complements frontend-design (`dz init --select design-taste-frontend`) ([ADR-0002](https://github.com/djd1m/dz-harness-hub/blob/main/docs/adr/0002-product-and-design-expansion.md)) |
1458
- | [@dzhechkov/skills-book-digitizer](https://www.npmjs.com/package/@dzhechkov/skills-book-digitizer) | 8 | *dz-original* — book → installable methodology pack: `digitize-book` (orchestrator) + ingest/extract/distill/pack/kb-index + `book-brain-register` (CP6 promote → cross-project brain) + `source-brain-ingest` (repo sibling). Verified provenance, IP-safe, resumable (`dz init --select digitize-book`) ([ADR-001](https://github.com/djd1m/dz-harness-hub/blob/main/features/book-knowledge-digitizer/03_adr/001-book-to-skillpack-pipeline.md)) |
1494
+ | [@dzhechkov/skills-idea2prd](https://www.npmjs.com/package/@dzhechkov/skills-idea2prd) | 1 | *dz-original* — `idea2prd-manual`: idea/problem → PRD+ADR+DDD+C4+Pseudocode+Tests+Completion (9 checkpoints); bundles the analyst trio as a sources.json-tracked vendor (ADR-0001, contributors-only doc) |
1495
+ | [@dzhechkov/skills-reverse-engineering](https://www.npmjs.com/package/@dzhechkov/skills-reverse-engineering) | 1 | *dz-original* — `reverse-engineering-unicorn`: company → launch playbook (+CJM) via 6-module QUICK/DEEP/VERIFIED pipeline; canonical home that resolved the keysarium↔p-replicator drift (ADR-0001, contributors-only doc) |
1496
+ | [@dzhechkov/skills-presentation-storyteller](https://www.npmjs.com/package/@dzhechkov/skills-presentation-storyteller) | 1 | *dz-original* — `presentation-storyteller`: selling deck + verified sources + slide-by-slide speaker script; referenced (not vendored) by reverse-engineering-unicorn's Post-M6 step (ADR-0001, contributors-only doc) |
1497
+ | [@dzhechkov/skills-website-cloner](https://www.npmjs.com/package/@dzhechkov/skills-website-cloner) | 1 | *imported (MIT)* — `clone-website`: live site → pixel-perfect Next.js clone (recon → specs → parallel build → visual QA); needs a browser-MCP + Next.js scaffold; referenced by p-replicator's `/replicate` (ADR-0001, contributors-only doc) |
1498
+ | [@dzhechkov/skills-pm](https://www.npmjs.com/package/@dzhechkov/skills-pm) | 18 | *imported (MIT)* — product-management toolkit: OST, RICE/ICE prioritization, product-strategy, pricing, OKRs, NSM/metrics/A-B/cohort, outcome-roadmap, stakeholder-map, sprint-plan, strategy-red-team, GTM/growth/beachhead, market-sizing; curated from phuryn/pm-skills (`dz init --preset pm`) (ADR-0002, contributors-only doc) |
1499
+ | [@dzhechkov/skills-taste](https://www.npmjs.com/package/@dzhechkov/skills-taste) | 1 | *imported (MIT)* — `design-taste-frontend`: anti-slop landing/portfolio/redesign framework (dials + pre-flight + GSAP skeletons); complements frontend-design (`dz init --select design-taste-frontend`) (ADR-0002, contributors-only doc) |
1500
+ | [@dzhechkov/skills-book-digitizer](https://www.npmjs.com/package/@dzhechkov/skills-book-digitizer) | 8 | *dz-original* — book → installable methodology pack: `digitize-book` (orchestrator) + ingest/extract/distill/pack/kb-index + `book-brain-register` (CP6 promote → cross-project brain) + `source-brain-ingest` (repo sibling). Verified provenance, IP-safe, resumable (`dz init --select digitize-book`) (ADR-001, contributors-only doc) |
1459
1501
  | [@dzhechkov/skills-12factor](https://www.npmjs.com/package/@dzhechkov/skills-12factor) | 12 | *generated by the digitizer, CC BY 4.0* — The Twelve-Factor App distilled into 12 decision-moment skills (one per factor). The first PUBLIC digitized-book pack; paraphrased (shingling-gated), routing-gated (every factor carries triggers), attributed (`NOTICE`) (`dz init --select 12factor-config-in-environment,…`) |
1460
1502
  | [@dzhechkov/skills-book-ai-apps](https://www.npmjs.com/package/@dzhechkov/skills-book-ai-apps) | 17 | *generated by the digitizer, CP5-published* — «Building Applications with AI Agents» (Albada, рус. пер.) distilled into 17 decision-moment skills across the whole agent-building arc: agent-fit & model choice, single-vs-multi, orchestration, tool design, knowledge & memory, context engineering, evaluation, probabilistic behaviour checks, release gates, improvement loops, drift, human-in-the-loop, agent UX, governance, security. Ships our page-anchored Knowledge Units, NOT the book text — shingling-gated at 0 uncited verbatim runs >=8 words; publication is the recorded CP5 owner decision; `trust_tier 1` (routing-gated, not human-reviewed) Since 0.2.2 it also ships `brain/ai-apps.sqlite`, the 223-KU knowledge slice: `dz brain add --from-pack @dzhechkov/skills-book-ai-apps` loads it into your `~/.dz/brain`, then `dz brain query --source ai-apps` answers in any project (`dz install @dzhechkov/skills-book-ai-apps --target claude-code`) |
1461
1503
  | [@dzhechkov/skills-book-clean-code](https://www.npmjs.com/package/@dzhechkov/skills-book-clean-code) | 9 | *generated by the digitizer, CP5-published 2026-09-11* — «Чистый код» (Robert C. Martin, рус. пер.): intent/comment contract, source layout, function contracts, object/data ownership, error and boundary handling, test-suite feedback, legacy refactoring loop, architecture assembly, concurrency safety; ships `brain/clean-code.sqlite` (259 KU). Shingling IP gate 0 violations on the published bytes; trust tier 1. |
@@ -1972,10 +2014,10 @@ runs a command that MEASURES the declared artifacts itself. It refuses a null re
1972
2014
  never recorded as done), an absent or partially-present artifact set, and a stage that declares nothing
1973
2015
  to witness — so a stage that did not happen can no longer be recorded, which the old mechanism allowed.
1974
2016
 
1975
- ## All Commands (91)
2017
+ ## All Commands (92)
1976
2018
 
1977
- *(91 MEASURED from the bounded command inventory below; rendered `dz --help` exposes 92 unique
1978
- top-level names — the 91 plus the built-in `help`, which prints USAGE before the dispatch switch and
2019
+ *(92 MEASURED from the bounded command inventory below; rendered `dz --help` exposes 93 unique
2020
+ top-level names — the 92 plus the built-in `help`, which prints USAGE before the dispatch switch and
1979
2021
  is therefore documented without being a command. Both numbers are COMPUTED, never typed: the name
1980
2022
  sets of this section, the root README, the docs site and `DZ_COMMANDS` are pinned to each other by
1981
2023
  `test/command-inventory-parity.test.ts`.)*
@@ -1992,7 +2034,7 @@ dz teach --from-json <file> [--project <dir>] [--no-mirror] [--harmonize] # bu
1992
2034
  dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror]
1993
2035
  dz recall "<query>" [--limit <N>] [--domain <name>] [--semantic | --no-semantic] [--full] [--project <dir>] # hybrid lexical+vector when the vector tier is enabled; --domain BOOSTS same-domain lessons (never filters)
1994
2036
  dz recall --all [--json] [--stats] [--include-domain <name,…>] # export the learned store (held-out domains are withheld unless named), or inspect learning stats/top uses
1995
- dz recall --books "<query>" [--book <slug>] # digitized-book KUs; --book narrows to one book
2037
+ dz recall --books "<query>" [--book <slug>] # digitized-book KUs; --book narrows to one book. Reads via a read-only opener (ADR-001, store-readonly-reads): works from a read-only-mounted store directory (falls back to a temporary copy) and no longer writes `-wal`/`-shm` on every invocation
1996
2038
  dz vector status [--project <dir>] [--json] # semantic tier: engine availability, mirrored vs lexical counts, pending queue
1997
2039
  dz vector reindex [--project <dir>] [--json] # re-embed learned vectors with the CONFIGURED model + stamp the manifest (snapshot first; atomic — a mid-way failure restores the store). Warns about task types it does not own (e.g. book-knowledge → run `dz brain reindex`)
1998
2040
  dz vector export <path> [--project <dir>] # portable VECTOR form (.rvf checkpoint; needs the opt-in RVF engine)
@@ -2001,13 +2043,13 @@ dz vector harmonize [--apply] [--threshold <0..1>] [--json] # SEMANTIC me
2001
2043
  dz teach --harmonize [--apply] [--threshold <0..1>] # alias of `dz vector harmonize`
2002
2044
  dz statusline [--json] [--install] # compact Claude Code statusline: live self-learning pattern count + brain sources
2003
2045
  dz store-guard [--status|--reset] [--yes] [--project <dir>] # inspect the monotonic external high-water mark; --reset is the only lowering path and requires confirmation or --yes
2004
- 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
2046
+ dz usage [--json] [--project <dir>] # 7-day UTC spend from local Claude Code + subagent transcripts; provider-limit routing disabled by design
2005
2047
  --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)
2006
2048
  dz chain [--project <dir>] [--json] # verify EVERY hash-chained journal in one command; coverage is DERIVED from the CHAINED_JOURNALS registry, so a journal cannot be chained and checked by nobody; an ABSENT journal is NAMED, never omitted; exit 1 on broken/unreadable
2007
2049
  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)
2008
2050
  dz lint [paths...] [--json] [--config <file>] [--registry <file>] [--project <dir>] # advisory EN/RU prose-style lint; findings exit 0, incomplete input/policy exits 1, usage exits 2
2009
2051
  dz brain list [--json] # the durable cross-project knowledge brain
2010
- 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).
2052
+ 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). `dz brain query`/`dz brain ground` read via the same read-only opener as `dz recall --books` — see ADR-001, store-readonly-reads.
2011
2053
  dz brain add [--source <slug>] [--project <dir>] [--from-slice <f>|--from-pack <p>|--from-kus <f> --slug <s> --kind <k> --license <spdx> [--override]] [--json] # grow the brain
2012
2054
  dz brain ground "<prompt>" [--k <N>] [--source <slug>] [--text] [--budget <N>] [--full] # retrieve + emit grounding citations (hook entrypoint; silent if irrelevant); --budget N eager-inlines top-K KU content within ~N tokens (chars/4 approx); --full = --budget 8000
2013
2055
  dz brain expand <kuId> [--source <slug>] [--json] # full-content lookup by kuId — the command the grounding directive names; prints name/problem/pages/book + FULL content (untruncated)
@@ -2015,6 +2057,8 @@ dz brain init [--project <dir>] [--k <N>] # opt-in: w
2015
2057
  dz brain primer <slug> [--json] # print a source's capability card (histogram + top decision moments)
2016
2058
  dz brain export --source <slug> --out <file> # write a portable per-book KB slice (ships inside the pack, §8.1)
2017
2059
  dz brain update <slug> [--project <dir>] [--json] # non-destructive refresh: re-mirror a re-ingested source, evict stale corpus
2060
+ dz brain reindex [--json] # snapshot + re-embed the brain's book-KU vectors, stamp current model; ALSO rotates the brain's own pre-reindex snapshots on success (default keep 3)
2061
+ dz brain snapshots [--keep <N>] [--prune] [--json] [--project <dir>] # list (default) — or, with --prune, rotate — the brain's own `agentdb.db.pre-reindex-<ms>.bak` snapshot families; reaches the same rotation WITHOUT running a reindex
2018
2062
  dz pretrain [--project <dir>]
2019
2063
  dz recommend "<task description>" [--json] (RU/EN lexical topics; explicit task/project-stack/none provenance)
2020
2064
  dz compose <preset1+preset2+...> [--target <name>]
@@ -2041,7 +2085,86 @@ dz discrimination-check --test <f[,f]> [--base <ref>] [--name <filter>] [--runne
2041
2085
  dz amendment-check --slug <slug> | --feature-dir <dir> | --all [--json] (every AM-N / AM-CP-N amendment row must resolve to a test found INSIDE the file the row names; the PLAN is authoritative when it carries rows, and an ideation amendment the plan drops is a failure; --all is a census that always exits 0; exit 0 pass/skip, 1 fail, 3 NOT-ESTABLISHED — ZERO parsed rows may skip only when the whole first paragraph or heading suffix is exactly "None"/"N/A"/"нет", optionally with a full stop; an optional CommonMark closing # sequence is heading furniture, qualified text is refused, and AM-like content beside the declaration is NOT-ESTABLISHED)
2042
2086
  dz contract-check --slug <s> [--json] (read-only retrospective feature contract gate: canonical AC-N + ADR Confirmation → CC-N; every item needs one artifact-anchored met|unmet|not-testable verdict; A/B with unmet is refused; exit 0 pass / 1 readable violation / 2 invalid invocation or unreadable/not-established artifacts)
2043
2087
  dz journal add --kind decision|verdict|run|error|block "<text>" [--ref <trace>] [--at <ISO>] [--quote <file>] [--commit-quote]; show [--day|--week] [--at <date>] [--kind <kind>] [--json] # UTC day files, witnessed append; quotes local by default
2044
- dz feature-adr-record --kind ledger|training-pair --stage <s> [--slug <s>] [--row|--pair <json>] [--mark <n>] [--once] [--json] (the witnessed writer: payload as an ARGUMENT never as shell, refused before any write, timestamp stamped before serialising, append verified by re-reading the tail; exit 0 written|duplicate|skipped, 2 refused, 3 not-verified, never blocking)
2088
+ dz feature-adr-record --kind ledger|training-pair --stage <s> [--slug <s>] [--row|--pair <json>] [--run-id <id>] [--mark <n>] [--once] [--json] (the witnessed writer: payload as an ARGUMENT never as shell, refused before any write; for a ledger row `ts` is ALWAYS the real write instant (a payload `ts` survives as `payloadTs`, never lost); `--run-id` fills a MISSING `runId` — absent, null, empty, or non-string — and tags it `runIdSource:'cli-flag'`; `minutesSincePrev`/`minutesSource` on an auto ledger row measure against the previous row of the same run, reporting `unavailable` on no prior row OR a corrupt/non-object ledger line anywhere in between; new fields never reorder existing ones; append verified by re-reading the tail; exit 0 written|duplicate|skipped, 2 refused, 3 not-verified, never blocking)
2089
+ dz round open --slug <s> --round <n|auto> --topic "<topic>" [--run <id>] [--owner-pid <n> | --owner-run <runId>] [--force] | exec --slug <s> --round <n> --brief <file> [--log <file>] [--model gpt-5.6-sol] [--effort high] [--timeout-min 30] | close --slug <s> --round <n> --outcome shipped|refuted|blocked|abandoned (--lesson teach:<id>... | --no-new-knowledge "<reason>") [--no-cost] | status [--older-than <minutes>] -- every subcommand also takes [--state-root <dir>] (or DZ_ROUND_STATE_ROOT) # open records the parent process by default; --owner-pid records a process and --owner-run records a run-registry owner; live/stalled runs are live, finished/orphaned runs are dead, and unknown registry state stays unknown; open --force refuses a live or unknown owner
2090
+
2091
+ The round owner is the session shell (`process.ppid`) by default. Under `setsid`, or in a container
2092
+ where that parent is PID 1, pass `--owner-pid <pid>` for the real supervising process; otherwise PID 1
2093
+ can make an abandoned round appear permanently owned by a live process.
2094
+
2095
+ `--state-root <dir>` (or `DZ_ROUND_STATE_ROOT=<dir>`, flag wins when both are set) names where round
2096
+ state (`.dz/rounds/`) AND its ledger row live — every subcommand reads/writes under this root instead
2097
+ of cwd when it is given. **`--project` is unrelated and stays recall-only**: `open` only ever passes
2098
+ it to `dz recall` for lessons, round state itself has always lived in cwd (lesson `2ac30a70`), and
2099
+ `--state-root` is the flag that actually redirects that state. The value must be an absolute path —
2100
+ a relative one is refused with exit 2 before anything is written. Without the flag or the variable,
2101
+ behavior is unchanged (state in cwd, exactly as before this flag existed). `open` always prints the
2102
+ resolved root as `state root: <dir>` (text) / `stateRoot` (JSON), so a caller never has to assume it
2103
+ landed in cwd; `status` prints the same `stateRoot` in JSON always, and the `state root: <dir>` text
2104
+ line only when `--state-root`/`DZ_ROUND_STATE_ROOT` was actually given (its default-cwd text output
2105
+ is otherwise unchanged, byte-for-byte, from before this flag existed).
2106
+
2107
+ Every mutation of `.dz/rounds/<slug>-<round>.json` (`open`'s archive+write, `exec`'s owner-claim and
2108
+ owner-restore writes, `close`'s final reread+delete) runs under ONE named lock,
2109
+ `<stateRoot>/.dz/locks/round-state.lock` (`withNamedLockSync`, feature round-state-lock) — two `dz
2110
+ round` processes sharing a `--state-root` can no longer lose each other's update. Recall (`open`) and
2111
+ the run-cost ledger write (`close`) stay OUTSIDE the lock — they can be slow, and the lock must not be
2112
+ held across anything that isn't a short, synchronous read-modify-write. `open` rereads state again
2113
+ UNDER the lock right before writing: if recall ran for a while and a different process opened the
2114
+ same round in the meantime, `open` refuses instead of silently overwriting that process's state.
2115
+ `status` never takes the lock (read-only) and keeps working while it is held elsewhere. Two refusals,
2116
+ both observable, neither one leaves a state file half-written:
2117
+ - **`already-open`** — a different process's state appeared while `open` was recalling: exit 1,
2118
+ `круг уже открыт (pid …) — состояние не перезаписано`, `--json` carries `{"refused":"already-open",
2119
+ "pid": <n>}`. The ORIGINAL state (the other process's) is left on disk untouched.
2120
+ - **`lock-busy`** — the lock could not be acquired before its deadline (another process is still
2121
+ inside its own critical section): exit 1, `lock busy: …`, `--json` carries `{"refused":"lock-busy"}`.
2122
+ Nothing is written; for `close` specifically, the run-cost ledger row is written BEFORE this lock is
2123
+ ever attempted (see above), so a `close` that hits `lock-busy` on its final delete has already logged
2124
+ the outcome — only the round's own state file is left undeleted (retry `close` to finish it).
2125
+
2126
+ **Fix-round (identity beats pid, `stateId`).** Every state `open` writes carries a `stateId` (16
2127
+ random hex chars) — the token `exec`/`close` compare against, not pid: `process.ppid` coincides for
2128
+ two `dz` launched from the same shell, and every run-owned state carries pid 0, so pid alone could
2129
+ not tell a genuine race from an innocent coincidence.
2130
+ - `open`'s recheck-under-lock now refuses on ANY raw-byte change since the pre-recall snapshot
2131
+ (pid is diagnostic-only in the refusal text), except a vanished file — a round closed while `open`
2132
+ was recalling is not a race to refuse, it is the ordinary "no existing round" case, `--force` or not.
2133
+ - `exec`'s claim AND owner-restore sections both refuse — leaving the file untouched — when the state
2134
+ under the lock is gone (`круг закрыт во время exec, владелец не менялся`) or carries a DIFFERENT
2135
+ `stateId` (`состояние круга заменено (stateId …), возврат владельца пропущен`); neither section ever
2136
+ falls back to resurrecting or overwriting with a stale pre-lock snapshot. The owner-restore lock is
2137
+ retried up to 3 times at the same timeout before giving up; if it still cannot recover ownership, the
2138
+ round is left `ownerKind: 'exec'` and the command says so in prose (`владелец круга не восстановлен
2139
+ (ownerKind=exec остался): повторите dz round exec для этого круга, когда блокировка освободится`) —
2140
+ no new flag is added for this. `open` and `status` warn on their own when a round has been sitting at
2141
+ `ownerKind: 'exec'` for 10+ minutes, so a stuck claim is never silently left for someone to trip over.
2142
+ - `close`'s final delete deletes ONLY the state instance its own ledger row was written for (matched by
2143
+ `stateId`): a state that vanished under the lock is `круг уже закрыт (строка леджера записана)` (exit
2144
+ 0 — the same round reaching its own success postcondition a different way, not a failure); a state
2145
+ replaced by a different `stateId` is `состояние заменено, не удалено` (exit 1, never deleted). A
2146
+ `close` that hits `lock-busy` on the delete step now says exactly what already happened:
2147
+ `строка леджера записана, состояние круга осталось открытым — повторите close`, `--json` carries
2148
+ `{"refused":"lock-busy","ledgerWritten":true}` — and the retry is idempotent: if the ledger already
2149
+ carries this attempt's row (same slug/round/closedAt), the retry does not append a second one.
2150
+
2151
+ `feature-adr` is one outer round, not one round per stage. Step 0 opens it with `--round auto` and
2152
+ `--run fa:<slug>` plus `--owner-run <runId>` when the run-registry start receipt supplies a non-empty
2153
+ id, so the run registry—not the short-lived command process—owns its liveness. If that best-effort
2154
+ registry write fails, the workflow omits `--owner-run`, logs the fallback explicitly, and keeps round
2155
+ tracking under the command's ordinary explicit owner. Step 8 maps the QE grade to the outcome and
2156
+ closes with `--no-cost`. The existing stage ledger commands remain byte-stable and remain the only
2157
+ cost source, so cadence/usage readers do not count the same minutes or tokens twice. A refused close
2158
+ is logged and returned as `roundClosed:false` without failing the feature run.
2159
+
2160
+ In round rows, `tokens: null` means tokens were not measured; an explicit `--tokens 0` remains zero.
2161
+ Automatic feature-adr stage rows keep total `tokens` null and record the measurable partial cost as
2162
+ `tokensOut`, with `tokensOutSource` naming `budget.spent` or `unavailable`.
2163
+
2164
+ When `args.dzBin` is supplied, feature-adr uses it without a selection/version agent call and logs
2165
+ version `not probed (dzBin given)`. With no `dzBin`, binary selection costs no more than **3 additional
2166
+ agent calls**: up to 2 `probeSessionCwd` attempts when the workspace is not yet known, plus 1 combined
2167
+ probe that both selects the workspace build or PATH fallback and prints the selected binary's version.
2045
2168
  dz runs [--settle] [--stall-minutes N] [--json] [--project <dir>] (read the run registry: live / stalled / orphaned / inconclusive / finished)
2046
2169
  dz runs-record --event started|heartbeat|finished [--run-id <id>] [--kind <kind>] [--slug <slug>] [--pid <pid|host>] [--parent-run-id <id>] [--outcome <text>] [--project <dir>] [--json] (append one run event)
2047
2170
  dz runs-clean [--apply] [--retention-days N] [--project <dir>] [--json] (plan cleanup of old clean merged worktrees and dead/finished registry histories; apply explicitly)
@@ -2056,7 +2179,7 @@ dz epoch-replay --judge <filled-work-order.json> [--out <file>] [--json] # bli
2056
2179
  dz epoch-replay --score <judgments.json> --work-order <file> [--slice <name>] [--json] # un-blind against the VERIFIED pre-registered assignment; ONE paired binomial over DECISIVE pairs (ties excluded, reported) → SUPPORTED only when the lift interval (2p−1) lies entirely above zero; FALSIFIED only on harm or a passed non-superiority test (lift upper bound below the margin PRE-REGISTERED in the work order, default 0.05, at 10+ decisive pairs); else INCONCLUSIVE (min 5 decisive pairs). Refuses a forged work order, a --margin flag, or duplicate judgement ids; the verdict is data, not an exit code
2057
2180
  dz score --slug <feature> [--project <dir>] [--json] # process scorecard for ONE feature-adr run, from its artifacts: ADR confirmation, discrimination proof, cross-model QE grade, live verification, README-first, learning loop, amendments — DESCRIPTIVE-ONLY (a low score exits 0); evidence lines are shown so the reader judges the heuristics
2058
2181
  dz recap [--day|--week|--month] [--at <ISO date>] [--refresh-publishes] [--project <dir>] [--json] # what was done over a window, from records only. `--refresh-publishes` fills the third-party publish-times cache first (the ONLY place this command touches the network — 51 packages in ~7s, batches of 8); the report itself always reads the cache, prints its AGE, and names any package the registry did not answer for: those are MISSING from the numbers, not zero. Deliveries carry the grade an independent review STATED — a report naming two different grades is reported AMBIGUOUS, never guessed; registry publishes come from a cache (51 packages cost 18.3s over the network, MEASURED — never inside a report); gate verdicts and knowledge reuse come from the local stores. --quarter/--half-year/--year are RECOGNISED and REFUSED with the real span in days: there is exactly ONE complete quarter and the longest record is 174 days, so a quarter-over-quarter comparison is arithmetically impossible and a year would be fabrication. Every section carries its own data-start date, and "the source was not read" never prints as a zero. Contaminated measures are NOT computed and the report says so: commit count (this project mandates a commit per logical change), lines changed (352 of 1318 commits are docs), token spend (self-declared an estimate, once wrong sixfold), learning-event volume (the curve tracks when hooks were installed), inventory counts (monotonic — they can only flatter), lesson count (54% of the pool has never been read). exit 0 reported / 2 refused
2059
- dz cadence [--window day|week|month|quarter|halfyear|year] [--json] # the RHYTHM view of what shipped: graded-shipment counts bucketed by ISO week, npm-publish cadence (reads `dz recap`'s publish-times cache — run `dz recap --refresh-publishes` to warm it), guard repeat DECAY on the FIXED rule set (a rule joins only with pre-window history, so a newborn rule's zero is youth, not virtue — the no-stubs false-zero class excluded by construction), and recall reuse per week. A window deeper than 2× the record is REFUSED with the measured depth and the largest honest window named (a cadence from under two full windows is scale forgery). Sibling of `dz recap`: recap is the narrative what-was-done report over one window; cadence is the week-by-week rhythm across the window. exit 0 / 2 refused-window / 1 usage
2182
+ dz cadence [--window day|week|month|quarter|halfyear|year] [--json] # the RHYTHM view of what shipped: graded-shipment counts bucketed by ISO week, round/round-exec outcomes with every unfinished round named, packages/ commits in the window (`not measured` when git is unavailable), npm-publish cadence (reads `dz recap`'s publish-times cache — run `dz recap --refresh-publishes` to warm it), guard repeat DECAY on the FIXED rule set (a rule joins only with pre-window history, so a newborn rule's zero is youth, not virtue — the no-stubs false-zero class excluded by construction), and recall reuse per week. A window deeper than 2× the record is REFUSED with the measured depth and the largest honest window named (a cadence from under two full windows is scale forgery). Sibling of `dz recap`: recap is the narrative what-was-done report over one window; cadence is the week-by-week rhythm across the window. exit 0 / 2 refused-window / 1 usage
2060
2183
  dz profile init | show | set <field> <value> | sync [--target claude] # WHO is being talked to. Per-USER store ~/.dz/profile.json (0600, never under a project root), delivered as a marked block in ~/.claude/CLAUDE.md so it loads in EVERY project on the machine, dz installed or not. Two axes: default register (pro | pro-lite | plain; RU aliases профи / профи лайт / просто) and named domains that move it — `deep` = full pro no scaffolding, `weak` = one plain sentence EVERY time unprompted, MANDATORY there. Three fixed rules ride along and the register cannot override them: an explanation is SELF-CONTAINED (every term glossed at first use in THIS passage), the register governs dialogue and owner-facing surfaces but NEVER ADRs / commits / QE reports / npm READMEs, and it changes FORM not FACTS. `show` always prints the store path and the profile's age; an unknown register value is REFUSED naming the accepted set, never silently defaulted; a hand-edited block is reported as drift and `sync` repairs it with a timestamped backup, foreign content preserved byte-for-byte. Redacted from training-pair capture (.dz/fa-training/ records the full prompt and is deliberately not gitignored)
2061
2184
  dz qe-rounds (--slug <feature> | --feature-dir <abs>) [--ceiling <n>] [--project <dir>] [--json] # how many Step-8 review rounds has this feature ALREADY had? The rule "Max iterations: 3" lived only as a sentence in a prose module, so every restart of the agent forgot it — MEASURED, one real slug reached 38 graded rounds. Reads what `dz qe-bridge` already wrote (signoff-<runId>.json / failed-*.json under features/<slug>/.fa-state/qe-bridge) and writes NOTHING itself, so it answers for runs already past. A round is a runId, not a file; an attempt with no verdict is counted separately and never merged; ONE directory, never a union across checkouts. FAILS CLOSED: if any record cannot be counted the verdict is NOT ESTABLISHED, never a smaller number presented as the answer. exit 0 under the ceiling / 1 at-or-over — the owner decides, this never judges whether the rounds were warranted / 2 NOT ESTABLISHED, which is never "zero rounds"
2062
2185
  dz restart-advisor --slug <feature> [--threshold C|D] [--rounds <n>] [--json] # manual, read-only advice over this feature's QE history. Defaults: threshold D, rounds 2. Reads only features/<slug>/.fa-state/checkpoints.jsonl and .dz/fa-training/<slug>/qe.jsonl; when both carry QE rounds they must normalize identically and are never unioned. RESTART_CODE_STAGE is a recommendation for the operator, not an action: autoAction is always false, and every firing result includes an explicit decision-log line. exit 0 established recommendation/no-recommendation / 2 invalid or not established / 1 unexpected runtime failure
@@ -2071,7 +2194,7 @@ dz backlog <sub> add "<idea>" | list | show <id> | goals [--validate] | roul
2071
2194
  dz sign --init --out <path> | --pack <dir> --key <path> # --init: generate the Ed25519 keypair (private OUTSIDE the repo, prints the public key for keys/dz.pub); else sign a pack's manifest + CycloneDX SBOM
2072
2195
  dz sbom --pack <dir> [--out <file>] # emit the CycloneDX 1.5 SBOM for a pack standalone (file-level bill of materials); print to stdout or write to a file
2073
2196
  dz verify-pack --pack <dir> [--pubkey <path>] # signature check of a pack: fail-closed, and the trust root comes from the repo, never from the pack it is verifying
2074
- dz guard check --op <publish|teach|consolidate|reindex> [--text <s>] [--json] [--force <reason>] # declarative constraint layer before self-mutating ops: HARD violation → block (exit 1), SOFT → warn; zero-config defaults, .dz/guard.json to customise; dz guard --init | dz guard log (append-only audit). dz publish runs it automatically (--no-guard "<reason>" = logged escape hatch)
2197
+ dz guard check --op <publish|teach|consolidate|reindex> [--text <s>] [--json] [--force <reason>] # declarative constraint layer before self-mutating ops: HARD violation → block (exit 1), SOFT → warn; zero-config defaults, .dz/guard.json to customise; dz guard --init | dz guard log (append-only audit). The built-in SOFT `rounds-traced` rule warns at 10 packages/ commits since the latest round receipt (or the ledger's first row when no round exists); a null git fact is `not measured`, and `.dz/config.json` `rounds.traced:false` is a named skip. dz publish runs it automatically (--no-guard "<reason>" = logged escape hatch)
2075
2198
  dz guard promote [--dry-run | --apply] [--window-days <N>] [--periods <N>] [--json] # lesson → guard-rule promotion: ranks lessons by firings × cost, SHADOW-replays each candidate over real commits, and proposes a rule only after TWO consecutive wins AND two window-lengths of REAL elapsed time since first observation. Non-dry runs add bounded prospective funnel evidence to .dz/promotion-state.json without feeding the verdict; --dry-run remains write-free. --apply installs SOFT rules only; promotions/refusals remain under features/guard-promotion/promotions/
2076
2199
  dz feature-adr-setup --guards [--loc-cap <n>] [--apply] # P3: scaffold DETERMINISTIC guard tests into the project — guards.config.json + a zero-dependency check.mjs runner (LOC cap, secret scan, frozen-file sha256 pins, waivers-with-reasons); wire `node architecture/guards/check.mjs` into CI
2077
2200
  dz publish [--filter <name>] [--bump-only] [--claim-check <off|warn|error>] [--mirror-cmd <cmd>|--no-mirror] (dry-run by default; pass --yes/--confirm to go live; a configured mirror must return a live receipt or the published run exits 3; config: publish.mirrorCommand)
@@ -2101,7 +2224,7 @@ dz import-ecc [--local-path <dir>] [--select id,id,...] [--limit N] [--out
2101
2224
  ```
2102
2225
 
2103
2226
  Built-in, not a command: `dz help` prints this same USAGE and is handled before the dispatch switch,
2104
- so `dz --help` lists 92 names while the canonical inventory above holds 91 (ADR-001 of feature
2227
+ so `dz --help` lists 93 names while the canonical inventory above holds 92 (ADR-001 of feature
2105
2228
  `command-count-triad`).
2106
2229
 
2107
2230
  **Adding a command — the whole checklist, in order.** It replaces the seven-place folklore that used
@@ -2244,27 +2367,25 @@ echo "how do I handle replication lag?" | dz brain ground --budget 2000 # eage
2244
2367
 
2245
2368
  ### Live self-learning panel (`dz statusline`)
2246
2369
 
2247
- The owner's combined panel keeps the mirror indicator silent on equal lesson counts. On divergence
2248
- it shows `dz 🎓 N (mirror M ⚠)`. A mirror that EXISTS but cannot be read shows
2249
- `dz 🎓 N (mirror unreadable ⚠)`; a project with NO mirror at all stays silent — there is nothing to
2250
- compare, and lighting up there would make the indicator permanent noise.
2251
- The mirror figure counts LESSONS only — backlog ideas and book units are excluded, so the indicator
2252
- is comparable with the lexical count instead of lighting up permanently. It is read from
2253
- `vectorLessonRows`, never from `vectorRows`: that field keeps counting the whole mirror because the
2254
- store guard reads it as an integrity signal, and narrowing it would look like a store collapse.
2255
- The JSON payload exposes these warnings as `patternMirror`; parity omits that field. Both surfaces
2256
- carry the indicator — the combined panel helper and `dz statusline` itself.
2370
+ The panel always names the mirror: `mirror: N lessons (pending M)` when readable, or `mirror: absent` when it is not configured.
2371
+ Its JSON `mirror` section reports `{available, rows, lessons, pending, source:'agentdb'}`; `patternMirror` compares only mirrorable lexical lessons and reports class/noise exclusions.
2372
+
2373
+ A mirror that EXISTS but cannot be read remains a distinct degraded state (`mirror: unreadable ⚠`).
2374
+ Mirror lesson counts exclude backlog ideas and book units, while the lexical comparison also excludes
2375
+ class lessons and noise rejected by the writer. `vectorRows` remains the full physical count because
2376
+ the store guard consumes it as an integrity signal.
2257
2377
 
2258
2378
  `dz statusline` renders a compact Claude Code statusline that surfaces dz's learning at a glance:
2259
2379
 
2260
2380
  ```
2261
- 🎓 dz: 12 (9 active · 3 quarantined) (mirror 11 ) · 🧠 3 sources (223/315/60)
2381
+ 🎓 dz: 12 (9 active · 3 quarantined) · mirror: 11 lessons (pending 1) · 🧠 3 sources (223/315/60)
2262
2382
  ```
2263
2383
 
2264
- The parenthesized split is read only from the primary lexical store. A row is quarantined exactly
2384
+ The parenthesized split is read from the primary lexical store; its JSON source is `lexical+mirror`
2385
+ when the mirror is readable and `lexical` otherwise. A row is quarantined exactly
2265
2386
  when parsed metadata has `qStatus: "quarantined"`; historical fields do not count. The quarantine
2266
2387
  fragment is omitted at zero, `⚠` appears at one third of the pool, and `⚠ tiers ΔN` reports a
2267
- quarantine-label difference greater than five rows from the vector mirror. `M sources` is how many
2388
+ quarantine-label difference greater than five rows across the mirrorable populations. `M sources` is how many
2268
2389
  brain sources (book-KBs / grounding stores) are wired, and the parenthesised list after it gives the
2269
2390
  KU volume of EACH source in brain order — four sources of 300 units and four of three read
2270
2391
  identically without it. The list is omitted (bare source count) when the volumes cannot be
@@ -2426,75 +2547,36 @@ the exact `dz store-guard --reset --project '<dir>'` command. `--status` and gua
2426
2547
  selected lexical source; when both SQLite and JSONL exist they also name the ignored JSONL row count.
2427
2548
  The snapshot helper supports both lexical formats.
2428
2549
 
2429
- ### Usage estimate (`dz usage`)
2430
- > **Pin the weekly reset to an ABSOLUTE instant.** `weeklyResetAnchor: "Wed 08:59"` is
2431
- > server-timezone-relative — measured: the same moment lands a week apart under UTC vs `+03:00`, so
2432
- > after a real account reset the counter can keep showing the OLD week for hours while printing the
2433
- > "correct" clock time. Add your offset: `"Wed 08:59 +03:00"` in `.dz/config.json` — the boundary
2434
- > then never moves with the machine's timezone, and `dz usage` prints the full anchor
2435
- > (`resets Wed 08:59 +03:00`). Without an offset it warns on every run.
2436
-
2550
+ ### Usage spend (`dz usage`)
2437
2551
 
2438
- `dz usage` prints a READONLY, never-throw ESTIMATE of Claude SESSION and WEEKLY token usage,
2439
- aggregated from your local `~/.claude/projects/**/*.jsonl` transcripts. Weekly counts start at the
2440
- configured fixed local anchor (`memory.usage.weeklyResetAnchor`, for example `Wed 08:59`). Session
2441
- counts use the active fixed-length transcript block (`memory.usage.sessionBlockHours`, normally `5`).
2552
+ `dz usage` prints a READONLY, never-throw spend report over the last seven UTC calendar days,
2553
+ aggregated from local `~/.claude/projects/**/*.jsonl` Claude Code and subagent transcripts. Every day
2554
+ shows weighted tokens, raw input/output/cache-read/cache-write tokens, and event count; the report
2555
+ also carries a seven-day total and cost-weighted model shares.
2442
2556
 
2443
2557
  ```bash
2444
- dz usage # session ~74% (resets 19:00) · week ~76% fable-bound (resets 08:59) · estimated
2445
- dz usage --json # {"sessionPct":74,"weeklyPct":76,"sessionTokens":...,"weeklyTokens":...,"resetsAt":{...},"limits":{...},"estimated":true}
2446
- ```
2447
-
2448
- **Exit code is 0 ALWAYS** (even unconfigured/error → all-null JSON) — a probe must never distinguish
2449
- "usage unknown" from "command failed" via a non-zero exit. Percentages are **ESTIMATES** from local
2450
- transcripts; claude.ai/settings/usage is authoritative. They are `null` until you configure the
2451
- plan-dependent limits in `.dz/config.json`:
2452
-
2453
- ```json
2454
- {
2455
- "memory": {
2456
- "usage": {
2457
- "sessionTokenLimit": 200000000,
2458
- "weeklyTokenLimit": 1000000000,
2459
- "weeklyResetAnchor": "Wed 08:59",
2460
- "sessionBlockHours": 5,
2461
- "weeklyTokenLimitByModel": { "fable": 500000000 }
2462
- }
2463
- }
2464
- }
2558
+ dz usage
2559
+ dz usage --json
2465
2560
  ```
2466
2561
 
2467
- `sessionTokenLimit` and `weeklyTokenLimit` are optional; absent means that pct is `null` (unknown,
2468
- never `0`). `weeklyTokenLimit` is the all-model weekly limit. `weeklyTokenLimitByModel` is optional;
2469
- when it has at least one valid model limit (`fable`, `opus`, `sonnet`, or `haiku`), `weeklyPct` is the
2470
- binding per-model percentage and `--json` adds:
2562
+ The weighting is input `1x`, five-minute cache write `1.25x`, one-hour cache write `2x`, cache read
2563
+ `0.1x`, and output `5x`. The JSON contract is:
2471
2564
 
2472
2565
  ```json
2473
2566
  {
2474
- "weeklyByModel": { "fable": { "tokens": 380, "pct": 76 } },
2475
- "limits": { "session": 200000000, "weekly": 1000000000, "weeklyByModel": { "fable": 500 } }
2567
+ "sessionPct": null,
2568
+ "weeklyPct": null,
2569
+ "routing": "disabled-by-design",
2570
+ "spend": { "days": [], "total7d": {}, "byModel": {} },
2571
+ "reqeDue": 0
2476
2572
  }
2477
2573
  ```
2478
2574
 
2479
- When no per-model limits are configured, the JSON shape stays at the legacy fields:
2480
- `sessionPct`, `weeklyPct`, `sessionTokens`, `weeklyTokens`, `resetsAt`, `limits`, `estimated`.
2481
-
2482
- Calibrate from human-transcribed claude.ai percentages with one command:
2483
-
2484
- ```bash
2485
- dz usage --calibrate --session 20 --weekly 50 --model fable=76 --project .
2486
- ```
2487
-
2488
- It computes each limit as `currentTokens / (pct / 100)`, writes only `memory.usage` in
2489
- `.dz/config.json`, and records `calibratedAt` plus `source: "claude.ai/settings/usage"`. Invalid
2490
- percentages, unknown models, missing transcripts, and zero-token calibration requests are skipped with
2491
- exit code `0`.
2492
-
2493
- This feeds `/feature-adr`'s **usage-adaptive routing** — the pipeline probes `dz usage --json` at each
2494
- phase boundary and pre-emptively routes the remaining stages to Codex when `sessionPct` or the binding
2495
- `weeklyPct` crosses the threshold (default 70%). **Honest caveat:** at TRUE exhaustion even the Codex
2496
- dispatch dies (`codex:codex-rescue` is a Claude wrapper), so the switch must happen BEFORE — the
2497
- pre-emptive probe, not reactive detection, is the real defense.
2575
+ The percentage keys deliberately remain present and always `null` for the existing feature-adr probe.
2576
+ Provider limits are not measurable locally: there is no provider usage API, weekly resets are tied to
2577
+ an account, and ad-hoc resets invalidate a learned denominator. `--calibrate` was removed and returns
2578
+ exit `2` with that reason. Existing `.dz/config.json` limit fields are neither read for this report nor
2579
+ modified.
2498
2580
 
2499
2581
  ### Claim check (`dz claim-check`)
2500
2582
 
@@ -3416,6 +3498,102 @@ dz setup --target claude-code --preset devops --memory agentdb # AgentDB (vect
3416
3498
  | **MCP tools** | 0 | pattern, reflexion, causal, skill, hierarchy (whatever the pinned `agentdb` build exposes — `dz` hardcodes no count) |
3417
3499
  | **Dependencies** | None | agentdb (optional, via npx) |
3418
3500
 
3501
+ ### The apply leg — `dz setup --memory agentdb` installs the whole loop, not two of three
3502
+
3503
+ Self-learning is a three-leg loop: **collect** (session hooks write into the store above),
3504
+ **rank** (`dz teach` / `dz recall` / `dz consolidate` score and retrieve), and **apply** (a
3505
+ `UserPromptSubmit` hook injects the top-ranked lessons back into your next prompt, automatically).
3506
+ Before this feature `dz setup` shipped the first two legs only — the apply leg's files existed
3507
+ solely in this repo's own `.claude/helpers/`, so every OTHER project that ran `dz setup --memory
3508
+ agentdb` got collection and ranking, but never automatic recall (MEASURED: a clean install wrote no
3509
+ `UserPromptSubmit` entry at all, on 0.8.10 and 0.8.23 alike, with or without `--memory agentdb`).
3510
+
3511
+ `dz setup --target claude-code --memory agentdb` now installs all three, additively (a repeat run
3512
+ changes nothing; a foreign hook you wrote yourself is left exactly where it is):
3513
+
3514
+ | What | Where | Purpose |
3515
+ |---|---|---|
3516
+ | `recall-hook.cjs` | `.claude/helpers/` | `UserPromptSubmit` hook — asks the embed daemon for relevant lessons, injects them as prompt context, silent when nothing clears the relevance floor |
3517
+ | `dz-embed-daemon.mjs` | `.claude/helpers/` | Resident embedding daemon behind a unix socket — keeps the ~1.5 s model-load cost off every single prompt |
3518
+ | `UserPromptSubmit` entry | `.claude/settings.json` | Runs the recall hook (`node ".../recall-hook.cjs" 2>/dev/null \|\| true` — a broken hook never blocks a prompt) |
3519
+ | `SessionStart` entry | `.claude/settings.json` | Spawns the embed daemon detached (`sh -c 'nohup node ".../dz-embed-daemon.mjs" ... & exit 0'`) |
3520
+
3521
+ The hook resolves your harness-core installation by an ABSOLUTE PATH baked in at `dz setup` time
3522
+ (the installation actually running the command), first in its candidate list — no more guessing at
3523
+ `/usr/lib/node_modules/...`, which silently failed on any other npm prefix (nvm, `/usr/local`, a
3524
+ global install elsewhere). Project-local candidates (`node_modules/`, a monorepo checkout) stay as
3525
+ fallbacks after it.
3526
+
3527
+ **On the jsonl backend** (no `--memory agentdb`) the step reports `skipped` with the reason named —
3528
+ the embed daemon needs agentdb's transitive `@huggingface/transformers`/`@xenova/transformers`
3529
+ dependency, which jsonl mode never installs. This is an honest boundary, not a missing feature: run
3530
+ `dz setup --memory agentdb` when you want the automatic apply leg.
3531
+
3532
+ **Memory footprint and idle lifecycle.** OWNER-MEASURED: the resident embed daemon holds ~1.0 GB
3533
+ RSS with only 7 patterns loaded — the multilingual embedding model dominates that number, not the
3534
+ pattern count, so it does not grow materially with a larger store. The recommended mental model is
3535
+ "spawns on session start, exits after 30 minutes idle" (`DZ_EMBED_IDLE_MS`, default
3536
+ `30 * 60 * 1000`) — not "runs forever": a machine that cannot spare ~1 GB resident for a background
3537
+ process should stay on the jsonl backend rather than tune the idle window down to zero, since `0`
3538
+ (or a negative/non-numeric value) now DISABLES the idle exit entirely (issue #10 defect 7) rather
3539
+ than exiting immediately — the daemon is meant to be reaped by inactivity, not pinned forever by a
3540
+ misconfigured knob. A value above Node's 32-bit `setTimeout` ceiling (2 147 483 647 ms, ~24.9 days)
3541
+ is clamped to that ceiling with a logged line (`idle clamp: <value> -> 2147483647 ms`) instead of
3542
+ silently wrapping to ~1 ms — the pre-fix bug that made a "30 days" idle setting exit in about a
3543
+ second.
3544
+
3545
+ **`dz doctor` and `dz parity` measure this, they don't declare it.** Both read the SAME
3546
+ `applyLegStatus(root)` function — a plain filesystem check (do the two files exist? does
3547
+ `settings.json` reference them?) — so neither can say "installed" about a project that only has the
3548
+ memory backend configured:
3549
+
3550
+ ```bash
3551
+ dz setup --target claude-code --memory agentdb # installs all three legs
3552
+ dz doctor # apply-leg installed: ✓ (or a red row naming: dz setup --target claude-code --memory agentdb)
3553
+ dz parity --target claude-code # ✓ Self-learning: automatic apply-leg via UserPromptSubmit hook (auto recall)
3554
+ ```
3555
+
3556
+ Without the apply leg installed, `dz doctor` prints a RED `apply-leg installed` check with the
3557
+ exact fix command, and `dz parity`'s `learning-apply` row for `claude-code` reads `◐ manual` with
3558
+ `not installed — run dz setup --target claude-code --memory agentdb` — never a silent `✓` read off
3559
+ a capability table that has never looked at your project.
3560
+
3561
+ #### Post-install verification (acceptance check, dz-harness-hub issue #10)
3562
+
3563
+ A live end-to-end check for a fresh `--memory agentdb` install — every line below is something the
3564
+ install must produce, not a smoke test of the CLI parsing its own flags:
3565
+
3566
+ ```bash
3567
+ # 1. Install — all three legs, on a bare Node 20/22 host (no C toolchain required: AM-2 pins
3568
+ # better-sqlite3@^11, the range with a prebuilt binary for Node 20's ABI 115).
3569
+ dz setup --target claude-code --project . --memory agentdb
3570
+
3571
+ # 2. Both hook entries actually landed in settings.json.
3572
+ grep -c recall-hook.cjs .claude/settings.json # → 1
3573
+ grep -c dz-embed-daemon.mjs .claude/settings.json # → 1
3574
+
3575
+ # 3. The pinned dependency resolved to a version WITH a prebuilt binary.
3576
+ node -e "console.log(require('./node_modules/better-sqlite3/package.json').version)" # → 11.x
3577
+
3578
+ # 4. A lesson taught right after setup — before any session has ever ended — still mirrors
3579
+ # (AM-4: setup pre-creates an empty .dz/agentdb.db so this is never silent).
3580
+ dz teach "acceptance check lesson" --allow-cold-start # → "↳ mirrored to vector tier (agentdb)"
3581
+
3582
+ # 5. The embed daemon comes up (spawned by SessionStart; give it up to ~20s to load the model).
3583
+ timeout 20 sh -c 'until [ -S .dz/embed.sock ]; do sleep 1; done' && echo "daemon socket is up"
3584
+
3585
+ # 6. The recall hook actually injects context for a relevant prompt, and stays silent for one
3586
+ # that clears no relevance floor — never a hang, never a stray print on stdout.
3587
+ echo '{"prompt":"acceptance check lesson"}' | node .claude/helpers/recall-hook.cjs # → one line of JSON, hookSpecificOutput.additionalContext non-empty
3588
+ echo '{"prompt":"unrelated weather report"}' | node .claude/helpers/recall-hook.cjs # → empty stdout, exit 0
3589
+
3590
+ # 7. Doctor confirms the leg is alive, not merely installed.
3591
+ dz doctor # → "apply-leg alive (embed daemon): embed.sock present"
3592
+ ```
3593
+
3594
+ Every step above is a REPRODUCER, not a claim: run it on a scratch project before trusting a `dz
3595
+ setup --memory agentdb` install on a new machine.
3596
+
3419
3597
  ### AgentDB self-learning algorithms
3420
3598
 
3421
3599
  When using `--memory agentdb`, the following algorithms automatically tune search quality:
@@ -3756,8 +3934,8 @@ Learned routing outcomes (what `auto-cost` currently believes):
3756
3934
  Cold-start (no history) walks a cheapest-first chain and escalates on failure; once a model clears the bar it is
3757
3935
  chosen directly. Two invariants held hard: `auto-cost` on the **qe** stage only ever ranks the **cross-family of
3758
3936
  the coder** (a model never self-QEs), and the whole thing is **opt-in** — with no `auto-cost` spec anywhere the
3759
- pipeline is byte-identical and the store is never touched. Layered under the usage-adaptive override (a session
3760
- near its limit still switches to Codex first). Storage is a plain JSON file — zero native deps.
3937
+ pipeline is byte-identical and the store is never touched. Provider-limit overrides are disabled by design;
3938
+ `auto-cost` decisions use only their own gated outcome history. Storage is a plain JSON file — zero native deps.
3761
3939
 
3762
3940
  ### `dz bto-optimize` — when you want to improve a skill WITHOUT gaming its own benchmark
3763
3941
 
@@ -3891,7 +4069,7 @@ invariants: HARD rules **block** the operation, SOFT rules warn. Zero config nee
3891
4069
  cover the known rakes; `.dz/guard.json` (via `dz guard --init`) exists only if you want to tune a severity
3892
4070
  or disable a rule.
3893
4071
  ```bash
3894
- dz guard check --op publish # no-workspace-star · no-skill-drift · no-secrets · readme-consistency · readme-first · signature-fresh · skills-registrable · lockfile-in-sync · marketplace-parity · no-stubs · review-round · licence-hold
4072
+ dz guard check --op publish # no-workspace-star · no-skill-drift · no-secrets · readme-consistency · readme-first · signature-fresh · skills-registrable · lockfile-in-sync · marketplace-parity · no-stubs · review-round · rounds-closed · rounds-traced · licence-hold
3895
4073
  dz guard check --op teach --text "the fix: export sk-abc..." # → BLOCK (exit 1): looks like a credential
3896
4074
  dz guard log # append-only audit: every verdict + every forced override
3897
4075
  ```
@@ -4373,6 +4551,119 @@ stay in the previous embedding space until their own reindex runs) instead of le
4373
4551
  Only models listed in `KNOWN_EMBED_DIMS` are accepted — the store's `vectorDim` is 384, so a 768-dim
4374
4552
  model is rejected rather than silently writing mismatched vectors.
4375
4553
 
4554
+ #### Pre-reindex snapshots pile up — rotating them
4555
+
4556
+ Every `dz brain reindex` (and every `dz vector reindex`) copies the store to
4557
+ `<db>.pre-reindex-<ms>.bak` first, as an undo point. Nothing pruned those copies before this feature —
4558
+ 13 snapshots / 50 MB accumulated on one real brain home before anyone noticed. Two ways to clear them:
4559
+
4560
+ ```bash
4561
+ $ dz brain reindex
4562
+ dz brain reindex: re-embedded 812 KU vector(s) with Xenova/paraphrase-multilingual-MiniLM-L12-v2 (manifest v4)
4563
+ snapshot: /home/you/.dz/brain/agentdb.db.pre-reindex-1757754000000.bak
4564
+ ↳ snapshots: kept 6, removed 8 (41.2 MB)
4565
+ ```
4566
+
4567
+ `dz brain reindex` now rotates automatically after a SUCCESSFUL reindex, keeping the newest 3
4568
+ snapshot families by default (`opts.keepSnapshots`) — the family it just created is never removed,
4569
+ even at `keep=0`. A reindex that itself fails rotates nothing (the old snapshots may be the only
4570
+ working copy left).
4571
+
4572
+ When you cannot or do not want to run a reindex — e.g. a hub where reindexing itself is off-limits
4573
+ today, but old snapshots have piled up regardless — rotate directly:
4574
+
4575
+ ```bash
4576
+ $ dz brain snapshots
4577
+ dz brain snapshots — 9 family(-ies) @ /home/you/.dz/brain/agentdb.db
4578
+ 2026-09-01T08:00:00.000Z ms=1756713600000 2 file(s) 4.1 MB
4579
+ 2026-09-05T08:00:00.000Z ms=1757059200000 2 file(s) 4.1 MB
4580
+ ...
4581
+ (dry run — pass --prune to remove families older than --keep)
4582
+
4583
+ # The PROJECT store (.dz/agentdb.db, written by the vector-tier reindex) is addressed with --project:
4584
+ $ dz brain snapshots --project .
4585
+ dz brain snapshots — 13 family(-ies) @ /path/to/project/.dz/agentdb.db
4586
+
4587
+ $ dz brain snapshots --prune --keep 3
4588
+ dz brain snapshots: kept 6, removed 12 (37.1 MB)
4589
+ removed: agentdb.db.pre-reindex-1756713600000.bak, agentdb.db.pre-reindex-1756713600000.bak.embed-manifest.json, ...
4590
+ ```
4591
+
4592
+ `dz brain snapshots` never touches anything but the brain's own strictly-named
4593
+ `agentdb.db.pre-reindex-<ms>.bak` family (a symlink or a differently-named neighbour is left alone);
4594
+ without `--prune` it only lists, and a bad `--keep` — negative, non-numeric, empty, or whitespace-
4595
+ padded (`--keep ' 2'`) — is refused with exit 2 rather than silently treated as 0 or 2.
4596
+
4597
+ **Fix-round hardenings (2026-09-13, after a Codex review found the earlier version could delete
4598
+ every snapshot on a bad `--keep`):**
4599
+
4600
+ - **10-minute grace period.** A family younger than 10 minutes is never removed, even past `--keep`
4601
+ — there is no flag to shorten or disable this; it protects a snapshot a DIFFERENT, still-running
4602
+ process just wrote from looking "old" to a `--prune` invoked moments later.
4603
+ - **Removal order inside a family: siblings first, `.bak` last, only if every sibling actually
4604
+ unlinked.** If a sibling (the manifest, `-shm`, `-wal`) fails to unlink, the `.bak` — the one file
4605
+ that alone still proves the snapshot existed — is left in place, and the family's `⚠ .bak
4606
+ preserved after a sibling failure` line names it.
4607
+ - **A scan error blocks the whole rotation, not just that one file.** If the brain directory itself
4608
+ cannot be listed (or a file's status cannot be read) for a reason other than "it does not exist",
4609
+ the command prints `⚠ scan error(s), nothing removed this call` and removes NOTHING — an
4610
+ incomplete file list is never a safe basis for "these are the old ones".
4611
+
4612
+ ```bash
4613
+ $ dz brain snapshots --prune --keep 3
4614
+ dz brain snapshots: kept 6, removed 12 (37.1 MB)
4615
+ removed: agentdb.db.pre-reindex-1756713600000.bak, agentdb.db.pre-reindex-1756713600000.bak.embed-manifest.json, ...
4616
+ ⚠ .bak preserved after a sibling failure in famil(y/ies): 1756713600000 # only printed if a sibling unlink failed
4617
+ ⚠ scan error(s), nothing removed this call: readdir /home/you/.dz/brain: EACCES # only printed on a scan failure
4618
+ ```
4619
+
4620
+ **Snapshot lock (agentdb-snapshot-lock, 2026-09-13).** `--prune` now takes the SAME advisory lock a
4621
+ concurrent `dz brain reindex`/`reindexAgentdbRows` uses (`<store dir>/.dz/locks/agentdb-snapshot.lock`)
4622
+ before it removes anything — a `rotate --keep 0` racing a live reindex used to be able to delete the
4623
+ undo point the reindex was still relying on; now it waits for the lock, or reports the timeout
4624
+ explicitly instead of silently succeeding on an empty rotation:
4625
+
4626
+ ```bash
4627
+ $ dz brain snapshots --prune --keep 0 # a concurrent reindex is mid-flight and holds the lock
4628
+ dz brain snapshots: kept 0, removed 0 (0.0 MB)
4629
+ ⚠ 1 error(s): lock busy: the lock at …/.dz/locks/agentdb-snapshot.lock stayed held for 10000ms — …
4630
+ $ echo $?
4631
+ 1
4632
+ ```
4633
+
4634
+ A family a LIVE reindex marker names (`<db>.reindex-inprogress.json`, written for the duration of
4635
+ that reindex, not just the moment the lock is held) also survives `--prune`, even at `--keep 0`; an
4636
+ EXPIRED marker (older than 60 minutes) is cleaned up and named honestly in a `note:` line rather than
4637
+ either protecting it forever or treating its removal as a scan error:
4638
+
4639
+ ```bash
4640
+ $ dz brain snapshots --prune --keep 0
4641
+ dz brain snapshots: kept 2, removed 4 (12.3 MB)
4642
+ note: stale reindex marker (ms=1757754000000, startedAt=2026-09-13T01:00:00.000Z) ignored and removed
4643
+ ```
4644
+
4645
+ If the marker could not actually be removed (e.g. a permission error), the note says so instead of
4646
+ silently claiming success: `"… ignored, removal failed: <err>"` — never `"… ignored and removed"` for
4647
+ a removal that did not happen.
4648
+
4649
+ **Marker ownership + a stuck rollback (fix-round, 2026-09-13).** The marker file is now written and
4650
+ removed with an ownership token: a `dz brain reindex` genuinely still in flight refuses a SECOND
4651
+ concurrent reindex of the same store outright (no snapshot for the refused attempt), and nothing but
4652
+ the owning reindex can clear its own marker. If a reindex fails and its rollback cannot re-acquire the
4653
+ snapshot lock (e.g. `--prune` or another reindex is mid-critical-section), the marker is deliberately
4654
+ LEFT IN PLACE rather than cleared — the reported error names both the snapshot and the marker path and
4655
+ says `requires manual recovery`:
4656
+
4657
+ ```
4658
+ error: index failed: …; rollback failed: lock busy: …; snapshot at /home/you/.dz/brain/agentdb.db.pre-reindex-1757754000000.bak
4659
+ was not confirmed restored; marker at /home/you/.dz/brain/agentdb.db.reindex-inprogress.json is left
4660
+ in place — requires manual recovery
4661
+ ```
4662
+
4663
+ Recovery is manual: inspect the named snapshot, restore it by hand if the store looks wrong, then
4664
+ delete the marker file. **The snapshot lock's own stale threshold is 5 minutes** — a base whose own
4665
+ snapshot genuinely takes longer needs external coordination, not a bigger default.
4666
+
4376
4667
  **Before → after** (what changes when you enable it):
4377
4668
 
4378
4669
  | | Before (lexical only) | After (hybrid vector tier) |
@@ -4738,7 +5029,7 @@ refusal as the honest answer.
4738
5029
 
4739
5030
  ## Status
4740
5031
 
4741
- `harness-core v0.8.30` · `harness-cli v0.8.22` — **this release: the store guard tells "busy" from "broken",
5032
+ `harness-core v0.8.31` · `harness-cli v0.8.23` — **this release: the store guard tells "busy" from "broken",
4742
5033
  and a publish is not finished until the public mirror confirms it.** (1) The learning-store guard used to
4743
5034
  turn ANY read failure into `unreadable` and refuse the write; a neighbour holding the SQLite write lock
4744
5035
  (`SQLITE_BUSY`) or a store still being initialised (`no such table`) now gets bounded retries and, if still
@@ -4761,7 +5052,7 @@ exports (`commandInventory`, `declaredCommands`, `dispatchedCommands`, `document
4761
5052
  `stripNonCode`, `validateExceptionList`, `INTERNAL_ENTRY_POINTS`, `PSEUDO_COMMANDS`) — instead of a
4762
5053
  fourth private regex and a fourth number. Four hidden commands (`mr-rakes`, `retro`,
4763
5054
  `feature-adr-setup`, `bto-optimize`) gained USAGE lines, so `--help` changed; `help` is a documented
4764
- pseudo-command, not a `case` label. **91 canonical commands, 92 names in rendered `--help`** (the 91
5055
+ pseudo-command, not a `case` label. **92 canonical commands, 93 names in rendered `--help`** (the 92
4765
5056
  plus the built-in `help`) — both COMPUTED from `cli.ts`, neither typed into a test. Three cross-family QE rounds closed
4766
5057
  six parser defects the naive version had: four that INVENTED a command (regex literals with a lone
4767
5058
  `}`, `case` labels in comments or templates, sub-verb labels from nested switches, ` dz <name>` lines
@@ -4781,6 +5072,43 @@ plan-route choice. Every failure is fail-open; versioned `.fa-state/decision-rec
4781
5072
  receipt coverage and repeat-related outcomes derivable offline. The hypothesis is external `[SRC],
4782
5073
  n=1`, books were silent on retrieval timing, and no runtime threshold gates a stage.
4783
5074
 
5075
+ `harness-core` / `harness-cli` — **staged, not published: `ts` and `minutesSincePrev` on the run-cost
5076
+ ledger.** The writer could not measure a stage's full duration (the workflow sandbox never calls
5077
+ `Date.now()`, for resume-safety) but it always knew the moment of each write and the run it belonged
5078
+ to — so every ledger row now also gets `ts` (the ISO instant it was recorded, next to `date`), and an
5079
+ `auto:true` row that carries a `runId` additionally gets `minutesSincePrev` (a number, one decimal
5080
+ digit, ≥ 0) plus `minutesSource: 'ledger-ts-delta'`, measured against the LAST row of the same run —
5081
+ found via `dz feature-adr-record --run-id <id>` or a `runId` already in the payload. When there is no
5082
+ prior row, or it carries no `ts`, the pair reads `minutesSincePrev: null` / `minutesSource:
5083
+ 'unavailable'` rather than a guess. `minutes` itself is untouched and stays `null` on every auto row:
5084
+ a partial measurement gets its own named field and source, and is never folded into the field that
5085
+ implies the whole stage's duration was known. `dz cadence`'s round summary reads unaffected fixtures
5086
+ unchanged — the new fields are additive, never repositioning an existing key.
5087
+
5088
+ **Fix-round-1 (cross-family review, grade B → 5 findings closed), still staged, not published:**
5089
+ a Codex `gpt-5.6-sol` review of the above found five contract gaps, all closed by measurement:
5090
+ (1) the reverse scan for the previous row's `ts` used to skip PAST a torn or non-object ledger line
5091
+ and keep looking further back, so an OLDER row for the same `runId` could be returned while a newer
5092
+ one sat hidden behind the corrupt line; it now stops and reports `unavailable` — named
5093
+ `ledger-corrupt-line` — the instant it hits a line it cannot parse as an object, never a guess made
5094
+ by skipping past what it could not read. (2) `--run-id` filling a MISSING `runId` was already correct
5095
+ — "missing" means absent, `null`, `''`, or a non-string value, the same "missing when absent or
5096
+ blank" rule `runnerId` uses — but it was untested beyond the plain non-empty-string case and
5097
+ undocumented; both are fixed now, behavior unchanged. (3) when `--run-id` actually fills the gap, the
5098
+ row is now tagged `runIdSource: 'cli-flag'` — the same provenance discipline `resolved-at-write`
5099
+ already gets for the OTHER runId source (write-time auto-resolution) — and an existing non-empty
5100
+ `runIdSource` is left untouched. (4) a payload's own `ts` is no longer trusted for the delta: `ts` on
5101
+ a ledger row is now ALWAYS the actual write instant, and a caller-supplied one survives, renamed,
5102
+ as `payloadTs` — no data lost, just no longer the value the delta measures from. (5) object key order
5103
+ now matches the documented convention exactly: a `runnerId` this call itself stamps keeps its position
5104
+ ahead of `ts`/`minutesSincePrev`/`minutesSource`, which land strictly last regardless of which fields
5105
+ the incoming payload already carried. `test/run-records.test.ts` (48, +10) and
5106
+ `test/feature-adr-record-cli.test.ts` (22, +7) cover all five, plus fractional-minute rounding
5107
+ (2m30s → 2.5, 1m3s → 1.1) and a pinned negative-delta case; the `ledger-minutes-since-prev-measured`
5108
+ mutation-gate entry was RE-MEASURED at `failingCount: 3` (three of the new tests now discriminate the
5109
+ same mutation, up from one) and the registry updated to match — `dz mutation-gate --only
5110
+ ledger-minutes-since-prev-measured` verdict `PROVEN`, exit 0.
5111
+
4784
5112
  `v0.8.8` — **staged, not published.** `dz init` now reports exactly one MCP and one hook outcome,
4785
5113
  adds `integrations-verify`, content-bound authorization, explicit skills-only opt-out, and named
4786
5114
  partial-failure exits; only receipt-proven live registration may be called emitted.
@@ -4911,7 +5239,7 @@ partial design). The compat floor is unchanged — this CLI uses no new core exp
4911
5239
 
4912
5240
  **v0.5.1** — published 2026-08-20. Ships `dz workflow run`, the portable plan enactor: it INTERPRETS a `loop-plan/1` plan instead of executing a rendered script, dispatching to `codex exec` or an isolated `claude -p`, exit 0/1/2/75 (75 = a typed pause whose last stdout line is a `wf-pause-envelope/1`). Requires `@dzhechkov/harness-core >= 0.5.1` (the compat guard refuses below it by name). See "Who writes the trace" above for the stated scope of the cross-host equivalence claim — it is narrower than "the two hosts agree".
4913
5241
 
4914
- `v0.5.0` — published. Also available as [Claude Plugin](#claude-plugin). Part of [DZ Harness Hub](https://github.com/djd1m/dz-harness-hub).
5242
+ `v0.5.0` — published. Claude Plugin packaging exists for contributors only (see [Claude Plugin](#claude-plugin) — a public marketplace install does not work today). Part of [DZ Harness Hub](https://github.com/djd1m/dz-harness).
4915
5243
 
4916
5244
  New in 0.5.0 (feature `qe-bridge-claude`, cross-runtime leg 3/4): `dz qe-bridge --family claude`
4917
5245
  runs an INDEPENDENT Claude reviewer from any host — a Codex session included — and lands a PARSED
@@ -4935,21 +5263,21 @@ Previously: the global `dz --version` / `-v` / `dz version` surface (one parseab
4935
5263
 
4936
5264
  ## Claude Plugin
4937
5265
 
4938
- DZ Harness Hub is available as a Claude Code plugin:
5266
+ Using the whole hub as a Claude Code plugin needs the source repo — this is a path for
5267
+ **contributors and plugin developers**, not something an `npm install -g` user needs (see
5268
+ Install above). A public marketplace install does **not** work today: `claude plugin marketplace add djd1m/dz-harness` fails with `Marketplace file not found` (MEASURED 2026-09-13, scratch probe) — the public mirror ships each package's `npm pack` contents only, never the root-level `.claude-plugin/marketplace.json`.
4939
5269
 
4940
5270
  ```bash
4941
- # Via marketplace (when published):
4942
- claude plugin marketplace add djd1m/dz-harness-hub
4943
- claude plugin install dz-harness-hub@dz-harness-hub
4944
-
4945
- # Or test locally:
5271
+ # From a cloned repo (contributors / plugin developers only):
4946
5272
  claude --plugin-dir /path/to/dz-harness-hub
4947
5273
 
4948
5274
  # Generate plugin manifest from current inventory:
4949
5275
  dz plugin --version 0.3.86
4950
5276
  ```
4951
5277
 
4952
- The `.claude-plugin/` directory contains `plugin.json` + `marketplace.json` compatible with [pi-claude-marketplace](https://npm.im/pi-claude-marketplace) and [skill-hub](https://npm.im/@jasonwen/skill-hub).
5278
+ The `.claude-plugin/` directory (source repo only) contains `plugin.json` + `marketplace.json`
5279
+ compatible with [pi-claude-marketplace](https://npm.im/pi-claude-marketplace) and
5280
+ [skill-hub](https://npm.im/@jasonwen/skill-hub).
4953
5281
 
4954
5282
  ## Related Projects
4955
5283
 
@@ -5031,3 +5359,18 @@ Core APIs: `settleDeadRuns` and `planRegistryArchive` are pure registry decision
5031
5359
  `planWorktreeCleanup` accepts injected `WorktreeFact` values and returns remove/keep decisions;
5032
5360
  `renderCleanupPlan` renders them. `worktreeRemovalsToApply` selects removals only when apply is true.
5033
5361
  No process is terminated and no branch is deleted.
5362
+
5363
+ ## From source (contributors only)
5364
+
5365
+ Everything below is for people building or testing `dz` itself — not for `npm install -g` users.
5366
+ The buildable source lives in the public mirror, [github.com/djd1m/dz-harness](https://github.com/djd1m/dz-harness) (each package's own npm-pack contents; the full monorepo, including this feature's design docs, stays in the private source repo).
5367
+
5368
+ ### Test execution
5369
+
5370
+ `npx vitest run` uses two projects and returns one combined verdict: `parallel` runs the ordinary
5371
+ suites concurrently, while `serial` runs process-spawning and real-time suites one file at a time.
5372
+ The serial paths in `test/serial-suites.txt` are regenerated from
5373
+ `test/serial-suites-census.test.ts`, which scans test sources for synchronous and asynchronous
5374
+ process markers, including `execSync(` and `execFile(`, and fails when the list and census differ.
5375
+ CF-2 records uncommented explicit test budgets in each package's `test/budget-debt-ceiling.json`: growth fails with `file:line` findings; shrinkage passes and suggests a lower ceiling.
5376
+ The ceiling files live in `packages/@dzhechkov/harness-core/test/` and `packages/@dzhechkov/harness-cli/test/` and are lowered by hand: a ratchet on existing debt, not a migration that clears it in one shot.