@dzhechkov/harness-cli 0.8.21 → 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,19 +2,22 @@
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
 
15
18
  > **`dz` is a package manager + cross-compiler for your AI agent harness.** Write a skill once in one canonical form; `dz` installs it into any agent's harness, holds it to a quality bar, and lets the harness learn over time.
16
19
 
17
- **The problem.** You accumulate ~179 skills (design-thinking, QE, devops, web3, MCP, academic…). Five pains follow:
20
+ **The problem.** You accumulate 260 skills (design-thinking, QE, devops, web3, MCP, academic…). Five pains follow:
18
21
 
19
22
  1. **Every agent wants a different layout.** Claude Code reads `.claude/skills/`, Codex `.codex/`, OpenCode/Hermes/OpenClaude their own. Hand-maintaining N copies is sync hell.
20
23
  2. **Skills arrive from many upstream repos** — they must be *canonicalized* (brought to one form) and kept in sync without losing provenance.
@@ -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
@@ -353,9 +356,9 @@ dz help # see all commands
353
356
  dz pretrain # analyze project files → recommend by tech stack
354
357
  dz recommend "build API and deploy to K8s" # keyword match → skills + toolkits
355
358
  dz recommend "work on this project" # unmatched? → labels suggestions as PROJECT-STACK, not task-derived
356
- dz stats # 54 packages, 201 skills, 10 targets, 14 presets
359
+ dz stats # 58 packages, 260 skills, 10 targets, 14 presets
357
360
  dz dashboard # visual panel — packages, adapters, skill packs
358
- dz registry # browse all 179 skills by category
361
+ dz registry # browse all 260 skills by category
359
362
  dz registry search kubernetes # find specific skills
360
363
  dz registry --category devops # filter by domain
361
364
  dz downloads # npm weekly download stats
@@ -503,12 +506,36 @@ dz release # gates for the whole workspace;
503
506
  dz publish --dry-run # preview
504
507
  dz publish --filter skills-devops # publish specific package
505
508
  dz publish # publish all changed packages
509
+ dz publish --yes --mirror-cmd "node scripts/publish-mirror.mjs" # live publish + required mirror receipt
510
+ dz publish --yes --no-mirror # explicit one-run mirror opt-out
506
511
 
507
512
  # Export portable, self-contained skill bundles for a generic consumer (e.g. a LangGraph app):
508
513
  dz bundle --preset news --out ./dist # → ./dist/skills/<id>/ (SKILL.md + references/scripts/assets)
509
514
  dz bundle --select news-digest,goap-research-ed25519 --out ./dist
510
515
  ```
511
516
 
517
+ For a live sweep that publishes at least one package, `dz publish` can run a command-hook epilogue.
518
+ Set it for one invocation with `--mirror-cmd`, or persist the command in `.dz/config.json`:
519
+
520
+ ```json
521
+ {
522
+ "publish": {
523
+ "mirrorCommand": "node scripts/publish-mirror.mjs"
524
+ }
525
+ }
526
+ ```
527
+
528
+ The CLI exports `DZ_PUBLISHED=name@version,…` and appends `--expect name@version,… --json`. It marks
529
+ the mirror `confirmed` only when the command returns JSON containing the live-manifest receipt;
530
+ `unconfirmed`, `skipped`, and `not-configured` remain separate report states. If npm publication
531
+ failed, the command returns exit `1`. If npm publication landed but a configured mirror was not
532
+ confirmed, it returns exit `3` and prints a usable `re-run:` command. `--dry-run`, `--no-mirror`,
533
+ `--bump-only`, and a live sweep with zero published packages do not execute the hook. A malformed or
534
+ unreadable `.dz/config.json` leaves the mirror `not-configured`, returns the ordinary publish exit code,
535
+ and prints `⚠ mirror: .dz/config.json unreadable — …`; an explicit `--mirror-cmd` still takes precedence.
536
+ The repository wrapper prints human-readable output by default; `scripts/publish-mirror.mjs --json`
537
+ prints exactly one JSON document (the `dz publish` epilogue always selects this mode).
538
+
512
539
  ---
513
540
 
514
541
  ## Three Ways to Install Skills
@@ -997,17 +1024,15 @@ extracts you scope are what leaves the machine. RU: мост в обратную
997
1024
  ### Пересмотр после аварийного само-ревью — `dz reqe`
998
1025
 
999
1026
  The feature-adr pipeline's cross-model guard says *the model that writes code must not review it*.
1000
- Under limit pressure (usage-adaptive `>=70%` switch) that guard is CONSCIOUSLY suspended: coder AND
1001
- Step-8 QE both run on Codex (FR-2.9 a Claude reviewer is exactly the agent that dies at the
1002
- limit). The rule used to say "re-review manually after limits reset" an instruction nobody
1003
- remembers. `dz reqe` turns it into a **debt with a lifecycle**: the run records
1004
- `features/<slug>/.fa-state/reqe-due.json`, `dz usage` surfaces the count the moment you check your
1005
- 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.
1006
1032
 
1007
1033
  ```bash
1008
- $ dz usage
1009
- usage: session ~12% (resets 03:00) · week ~41% (resets 06:00) · estimated
1010
- 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}
1011
1036
 
1012
1037
  $ dz reqe # the ledger
1013
1038
  dz reqe — 1 unsettled re-QE debt(s):
@@ -1018,8 +1043,8 @@ $ dz reqe --slug add-x --done --report features/add-x/08b_reqe_report.md
1018
1043
  dz reqe: debt settled: re-QE grade C (report …) — settlement appended to features/add-x/08_qe_report.md
1019
1044
  ```
1020
1045
 
1021
- **When to use:** any time `dz usage` prints a `re-QE due` line, or a feature-adr result carried
1022
- `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
1023
1048
  one line-anchored `GRADE` (the boilerplate phrase `GRADE A-F` does not count); the run's own
1024
1049
  `08_qe_report.md` can never settle its own debt (real-path AND inode compared — a hard link doesn't
1025
1050
  fool it); the settlement epilogue lands in `08_qe_report.md` and the due-file rotates to
@@ -1027,12 +1052,13 @@ fool it); the settlement epilogue lands in `08_qe_report.md` and the due-file ro
1027
1052
  nothing re-runs QE automatically, and the validator proves the settlement is procedurally sound —
1028
1053
  which model authored the report stays with the human running the brief. RU: гард «кодер не ревьюит
1029
1054
  сам себя» осознанно снимается под лимитом; `dz reqe` превращает инструкцию «перепроверь потом»
1030
- в долг на диске — виден в `dz usage`, гасится только настоящим кросс-семейным отчётом с грейдом.
1055
+ в долг на диске — виден как `reqeDue` в `dz usage --json`, гасится только настоящим кросс-семейным отчётом с грейдом.
1031
1056
 
1032
- ### Usage estimates you can act on — `dz usage`
1057
+ ### Spend you can inspect — `dz usage`
1033
1058
 
1034
- `dz usage` estimates how much of your Claude session (5h block) and week you have spent, from local
1035
- 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.
1036
1062
 
1037
1063
  Two things changed to make it mean something:
1038
1064
 
@@ -1042,20 +1068,44 @@ Two things changed to make it mean something:
1042
1068
  1-hour TTL write, cache-read 0.1x, output 5x).
1043
1069
  - **Subagent transcripts count.** `<session>/subagents/*.jsonl` carry real, non-duplicated usage and
1044
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.
1045
1073
  - **The walk is safe and bounded.** Only regular files are read, symlinked files and directory
1046
- components are skipped (a FIFO used to block it, a symlink used to be followed), and the file cap
1047
- 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.
1048
1076
 
1049
1077
  ```bash
1050
- dz usage # session ~13% (resets 10:24) · week ~15% (resets 08:59) · estimated
1051
- dz usage --json # machine-readable; pct is null when limits are unconfigured
1052
- dz usage --calibrate --session 42 --weekly 61 # teach it YOUR real numbers from claude.ai
1053
- ```
1054
-
1055
- **`pct` is `null` until you configure limits that is deliberate**, an unconfigured estimate is not
1056
- a guess dressed as a number. The authoritative calibration is `--calibrate` with the percentages
1057
- shown on claude.ai/settings/usage; absent that, limits set from your own observed peak mean "unusually
1058
- 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`).
1059
1109
 
1060
1110
  ### Where did the run's budget actually go? — `dz usage --by-stage`
1061
1111
 
@@ -1303,6 +1353,22 @@ Four rules the gate itself obeys — these are what distinguish it from a green-
1303
1353
  undefended property lives in this repo's test suite, and the gate MUST fail on it — a gate that
1304
1354
  cannot fail cannot pass.
1305
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
+
1306
1372
  When to reach for it: after a QE round names safety properties (seed the registry so they STAY
1307
1373
  defended); in CI for a package whose protections have burned you before (`npm run test:mutation`);
1308
1374
  and in feature-adr Step 8, where the QE reviewer runs it whenever the touched package has a
@@ -1425,15 +1491,16 @@ Each pack is an npm package — click through for the **full per-skill documenta
1425
1491
  | [@dzhechkov/skills-academic](https://www.npmjs.com/package/@dzhechkov/skills-academic) | 5 | Thesis-defense toolkit — dissertation review, questions, doc-check, defense eval |
1426
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) |
1427
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 |
1428
- | [@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)) |
1429
- | [@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)) |
1430
- | [@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)) |
1431
- | [@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)) |
1432
- | [@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)) |
1433
- | [@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)) |
1434
- | [@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) |
1435
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,…`) |
1436
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`) |
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. |
1437
1504
 
1438
1505
  ### Available Presets (14)
1439
1506
 
@@ -1947,10 +2014,10 @@ runs a command that MEASURES the declared artifacts itself. It refuses a null re
1947
2014
  never recorded as done), an absent or partially-present artifact set, and a stage that declares nothing
1948
2015
  to witness — so a stage that did not happen can no longer be recorded, which the old mechanism allowed.
1949
2016
 
1950
- ## All Commands (91)
2017
+ ## All Commands (92)
1951
2018
 
1952
- *(91 MEASURED from the bounded command inventory below; rendered `dz --help` exposes 92 unique
1953
- 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
1954
2021
  is therefore documented without being a command. Both numbers are COMPUTED, never typed: the name
1955
2022
  sets of this section, the root README, the docs site and `DZ_COMMANDS` are pinned to each other by
1956
2023
  `test/command-inventory-parity.test.ts`.)*
@@ -1967,7 +2034,7 @@ dz teach --from-json <file> [--project <dir>] [--no-mirror] [--harmonize] # bu
1967
2034
  dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror]
1968
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)
1969
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
1970
- 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
1971
2038
  dz vector status [--project <dir>] [--json] # semantic tier: engine availability, mirrored vs lexical counts, pending queue
1972
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`)
1973
2040
  dz vector export <path> [--project <dir>] # portable VECTOR form (.rvf checkpoint; needs the opt-in RVF engine)
@@ -1976,13 +2043,13 @@ dz vector harmonize [--apply] [--threshold <0..1>] [--json] # SEMANTIC me
1976
2043
  dz teach --harmonize [--apply] [--threshold <0..1>] # alias of `dz vector harmonize`
1977
2044
  dz statusline [--json] [--install] # compact Claude Code statusline: live self-learning pattern count + brain sources
1978
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
1979
- 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
1980
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)
1981
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
1982
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)
1983
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
1984
2051
  dz brain list [--json] # the durable cross-project knowledge brain
1985
- 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.
1986
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
1987
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
1988
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)
@@ -1990,6 +2057,8 @@ dz brain init [--project <dir>] [--k <N>] # opt-in: w
1990
2057
  dz brain primer <slug> [--json] # print a source's capability card (histogram + top decision moments)
1991
2058
  dz brain export --source <slug> --out <file> # write a portable per-book KB slice (ships inside the pack, §8.1)
1992
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
1993
2062
  dz pretrain [--project <dir>]
1994
2063
  dz recommend "<task description>" [--json] (RU/EN lexical topics; explicit task/project-stack/none provenance)
1995
2064
  dz compose <preset1+preset2+...> [--target <name>]
@@ -2016,7 +2085,86 @@ dz discrimination-check --test <f[,f]> [--base <ref>] [--name <filter>] [--runne
2016
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)
2017
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)
2018
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
2019
- 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.
2020
2168
  dz runs [--settle] [--stall-minutes N] [--json] [--project <dir>] (read the run registry: live / stalled / orphaned / inconclusive / finished)
2021
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)
2022
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)
@@ -2031,7 +2179,7 @@ dz epoch-replay --judge <filled-work-order.json> [--out <file>] [--json] # bli
2031
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
2032
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
2033
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
2034
- 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
2035
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)
2036
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"
2037
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
@@ -2046,10 +2194,10 @@ dz backlog <sub> add "<idea>" | list | show <id> | goals [--validate] | roul
2046
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
2047
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
2048
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
2049
- 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)
2050
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/
2051
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
2052
- dz publish [--filter <name>] [--bump-only] [--claim-check <off|warn|error>] (dry-run by default; pass --yes/--confirm to go live; claim-check gate defaults to warn surfaces README claim findings, never blocks)
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)
2053
2201
  dz parity [--target <name>] [--json] # honest feature×target map COMPUTED from the capability model — full / manual (via which form) / absent, per target
2054
2202
  dz release [--filter <name>] [--affected] [--audit-dev] [--tag] [--publish] [--json] [--dry-run] [--no-issue] # VERIFIED release: 4 HARD gates in front of dz publish — package test suites, pnpm audit --prod >=high (--audit-dev widens), node --check of every dist/bin file (unbuilt package with a build script ⇒ MISSING_DIST fail), bin smoke-boot "node <bin> --help" (temp cwd + timeout); --affected narrows to git-touched packages (fail-open); any red gate STOPS the release (exit 1) + best-effort gh issue; green ⇒ re-sign reminder, then the ready dz publish command (never with --yes injected)
2055
2203
  dz auto-canonicalize --source <github-url> --pack <skills-pack>
@@ -2076,7 +2224,7 @@ dz import-ecc [--local-path <dir>] [--select id,id,...] [--limit N] [--out
2076
2224
  ```
2077
2225
 
2078
2226
  Built-in, not a command: `dz help` prints this same USAGE and is handled before the dispatch switch,
2079
- 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
2080
2228
  `command-count-triad`).
2081
2229
 
2082
2230
  **Adding a command — the whole checklist, in order.** It replaces the seven-place folklore that used
@@ -2219,27 +2367,25 @@ echo "how do I handle replication lag?" | dz brain ground --budget 2000 # eage
2219
2367
 
2220
2368
  ### Live self-learning panel (`dz statusline`)
2221
2369
 
2222
- The owner's combined panel keeps the mirror indicator silent on equal lesson counts. On divergence
2223
- it shows `dz 🎓 N (mirror M ⚠)`. A mirror that EXISTS but cannot be read shows
2224
- `dz 🎓 N (mirror unreadable ⚠)`; a project with NO mirror at all stays silent — there is nothing to
2225
- compare, and lighting up there would make the indicator permanent noise.
2226
- The mirror figure counts LESSONS only — backlog ideas and book units are excluded, so the indicator
2227
- is comparable with the lexical count instead of lighting up permanently. It is read from
2228
- `vectorLessonRows`, never from `vectorRows`: that field keeps counting the whole mirror because the
2229
- store guard reads it as an integrity signal, and narrowing it would look like a store collapse.
2230
- The JSON payload exposes these warnings as `patternMirror`; parity omits that field. Both surfaces
2231
- 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.
2232
2377
 
2233
2378
  `dz statusline` renders a compact Claude Code statusline that surfaces dz's learning at a glance:
2234
2379
 
2235
2380
  ```
2236
- 🎓 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)
2237
2382
  ```
2238
2383
 
2239
- 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
2240
2386
  when parsed metadata has `qStatus: "quarantined"`; historical fields do not count. The quarantine
2241
2387
  fragment is omitted at zero, `⚠` appears at one third of the pool, and `⚠ tiers ΔN` reports a
2242
- 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
2243
2389
  brain sources (book-KBs / grounding stores) are wired, and the parenthesised list after it gives the
2244
2390
  KU volume of EACH source in brain order — four sources of 300 units and four of three read
2245
2391
  identically without it. The list is omitted (bare source count) when the volumes cannot be
@@ -2401,75 +2547,36 @@ the exact `dz store-guard --reset --project '<dir>'` command. `--status` and gua
2401
2547
  selected lexical source; when both SQLite and JSONL exist they also name the ignored JSONL row count.
2402
2548
  The snapshot helper supports both lexical formats.
2403
2549
 
2404
- ### Usage estimate (`dz usage`)
2405
- > **Pin the weekly reset to an ABSOLUTE instant.** `weeklyResetAnchor: "Wed 08:59"` is
2406
- > server-timezone-relative — measured: the same moment lands a week apart under UTC vs `+03:00`, so
2407
- > after a real account reset the counter can keep showing the OLD week for hours while printing the
2408
- > "correct" clock time. Add your offset: `"Wed 08:59 +03:00"` in `.dz/config.json` — the boundary
2409
- > then never moves with the machine's timezone, and `dz usage` prints the full anchor
2410
- > (`resets Wed 08:59 +03:00`). Without an offset it warns on every run.
2550
+ ### Usage spend (`dz usage`)
2411
2551
 
2412
-
2413
- `dz usage` prints a READONLY, never-throw ESTIMATE of Claude SESSION and WEEKLY token usage,
2414
- aggregated from your local `~/.claude/projects/**/*.jsonl` transcripts. Weekly counts start at the
2415
- configured fixed local anchor (`memory.usage.weeklyResetAnchor`, for example `Wed 08:59`). Session
2416
- 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.
2417
2556
 
2418
2557
  ```bash
2419
- dz usage # session ~74% (resets 19:00) · week ~76% fable-bound (resets 08:59) · estimated
2420
- dz usage --json # {"sessionPct":74,"weeklyPct":76,"sessionTokens":...,"weeklyTokens":...,"resetsAt":{...},"limits":{...},"estimated":true}
2421
- ```
2422
-
2423
- **Exit code is 0 ALWAYS** (even unconfigured/error → all-null JSON) — a probe must never distinguish
2424
- "usage unknown" from "command failed" via a non-zero exit. Percentages are **ESTIMATES** from local
2425
- transcripts; claude.ai/settings/usage is authoritative. They are `null` until you configure the
2426
- plan-dependent limits in `.dz/config.json`:
2427
-
2428
- ```json
2429
- {
2430
- "memory": {
2431
- "usage": {
2432
- "sessionTokenLimit": 200000000,
2433
- "weeklyTokenLimit": 1000000000,
2434
- "weeklyResetAnchor": "Wed 08:59",
2435
- "sessionBlockHours": 5,
2436
- "weeklyTokenLimitByModel": { "fable": 500000000 }
2437
- }
2438
- }
2439
- }
2558
+ dz usage
2559
+ dz usage --json
2440
2560
  ```
2441
2561
 
2442
- `sessionTokenLimit` and `weeklyTokenLimit` are optional; absent means that pct is `null` (unknown,
2443
- never `0`). `weeklyTokenLimit` is the all-model weekly limit. `weeklyTokenLimitByModel` is optional;
2444
- when it has at least one valid model limit (`fable`, `opus`, `sonnet`, or `haiku`), `weeklyPct` is the
2445
- 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:
2446
2564
 
2447
2565
  ```json
2448
2566
  {
2449
- "weeklyByModel": { "fable": { "tokens": 380, "pct": 76 } },
2450
- "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
2451
2572
  }
2452
2573
  ```
2453
2574
 
2454
- When no per-model limits are configured, the JSON shape stays at the legacy fields:
2455
- `sessionPct`, `weeklyPct`, `sessionTokens`, `weeklyTokens`, `resetsAt`, `limits`, `estimated`.
2456
-
2457
- Calibrate from human-transcribed claude.ai percentages with one command:
2458
-
2459
- ```bash
2460
- dz usage --calibrate --session 20 --weekly 50 --model fable=76 --project .
2461
- ```
2462
-
2463
- It computes each limit as `currentTokens / (pct / 100)`, writes only `memory.usage` in
2464
- `.dz/config.json`, and records `calibratedAt` plus `source: "claude.ai/settings/usage"`. Invalid
2465
- percentages, unknown models, missing transcripts, and zero-token calibration requests are skipped with
2466
- exit code `0`.
2467
-
2468
- This feeds `/feature-adr`'s **usage-adaptive routing** — the pipeline probes `dz usage --json` at each
2469
- phase boundary and pre-emptively routes the remaining stages to Codex when `sessionPct` or the binding
2470
- `weeklyPct` crosses the threshold (default 70%). **Honest caveat:** at TRUE exhaustion even the Codex
2471
- dispatch dies (`codex:codex-rescue` is a Claude wrapper), so the switch must happen BEFORE — the
2472
- 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.
2473
2580
 
2474
2581
  ### Claim check (`dz claim-check`)
2475
2582
 
@@ -3391,6 +3498,102 @@ dz setup --target claude-code --preset devops --memory agentdb # AgentDB (vect
3391
3498
  | **MCP tools** | 0 | pattern, reflexion, causal, skill, hierarchy (whatever the pinned `agentdb` build exposes — `dz` hardcodes no count) |
3392
3499
  | **Dependencies** | None | agentdb (optional, via npx) |
3393
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
+
3394
3597
  ### AgentDB self-learning algorithms
3395
3598
 
3396
3599
  When using `--memory agentdb`, the following algorithms automatically tune search quality:
@@ -3731,8 +3934,8 @@ Learned routing outcomes (what `auto-cost` currently believes):
3731
3934
  Cold-start (no history) walks a cheapest-first chain and escalates on failure; once a model clears the bar it is
3732
3935
  chosen directly. Two invariants held hard: `auto-cost` on the **qe** stage only ever ranks the **cross-family of
3733
3936
  the coder** (a model never self-QEs), and the whole thing is **opt-in** — with no `auto-cost` spec anywhere the
3734
- pipeline is byte-identical and the store is never touched. Layered under the usage-adaptive override (a session
3735
- 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.
3736
3939
 
3737
3940
  ### `dz bto-optimize` — when you want to improve a skill WITHOUT gaming its own benchmark
3738
3941
 
@@ -3866,7 +4069,7 @@ invariants: HARD rules **block** the operation, SOFT rules warn. Zero config nee
3866
4069
  cover the known rakes; `.dz/guard.json` (via `dz guard --init`) exists only if you want to tune a severity
3867
4070
  or disable a rule.
3868
4071
  ```bash
3869
- 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
3870
4073
  dz guard check --op teach --text "the fix: export sk-abc..." # → BLOCK (exit 1): looks like a credential
3871
4074
  dz guard log # append-only audit: every verdict + every forced override
3872
4075
  ```
@@ -4348,6 +4551,119 @@ stay in the previous embedding space until their own reindex runs) instead of le
4348
4551
  Only models listed in `KNOWN_EMBED_DIMS` are accepted — the store's `vectorDim` is 384, so a 768-dim
4349
4552
  model is rejected rather than silently writing mismatched vectors.
4350
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
+
4351
4667
  **Before → after** (what changes when you enable it):
4352
4668
 
4353
4669
  | | Before (lexical only) | After (hybrid vector tier) |
@@ -4713,23 +5029,19 @@ refusal as the honest answer.
4713
5029
 
4714
5030
  ## Status
4715
5031
 
4716
- `harness-core v0.8.29` · `harness-cli v0.8.21` — **this release: the release itself is checked
4717
- before it is pressed, and the repository boundary is honest.** (1) `dz guard` gains two rules:
4718
- `release-line-in-sync` the version line in the root README and the CLI README must name the
4719
- versions `package.json` carries, and `dz publish` now rewrites those lines itself; `signature-fresh`
4720
- a changed pack whose Ed25519 manifest was signed over older bytes is named BEFORE publication
4721
- (`workspace:` specs are rewritten before hashing, exactly as the pack is published). (2) `readme-first`
4722
- compares the `version` field against HEAD instead of "package.json changed", so a repository-field
4723
- edit no longer reads as a release. (3) All 56 workspace `package.json` files carry the real
4724
- `repository` origin, pinned by a test. (4) Suites that spawn processes or hold real locks run in a
4725
- serial vitest project (`test/serial-suites.txt`, audited by a census test); the CLI suite dropped from
4726
- ~6 minutes to ~2m20 once the in-process marketplace guard left the worker-RPC path. (5) `isRepoBoundary`:
4727
- a `.git` directory is a repository boundary only with a real `HEAD` (or a worktree `gitdir:` file), so
4728
- `dz` run from `/tmp` beside a stray empty `.git` no longer adopts it as a project and no longer creates
4729
- `/tmp/.dz`. A planned fallback lock location was MEASURED unsound (two processes could hold two
4730
- different locks for one root) and withdrawn before release — named locks stay `<root>/.dz/locks`, a
4731
- pure function of the root. (6) `dz amendment-check` recognises the Russian challenge-panel placeholder
4732
- as a stub.
5032
+ `harness-core v0.8.31` · `harness-cli v0.8.23` — **this release: the store guard tells "busy" from "broken",
5033
+ and a publish is not finished until the public mirror confirms it.** (1) The learning-store guard used to
5034
+ turn ANY read failure into `unreadable` and refuse the write; a neighbour holding the SQLite write lock
5035
+ (`SQLITE_BUSY`) or a store still being initialised (`no such table`) now gets bounded retries and, if still
5036
+ busy, the verdict `busy` = **NOT MEASURED** the write proceeds under the store lock, the high-water mark
5037
+ is left untouched, and both the text panel and `dz statusline --json` say so from one verdict. MEASURED:
5038
+ eight barrier-synchronised `dz teach` writers from two worktrees no longer lose a lesson to a false
5039
+ refusal. (2) `dz publish` gained a mirror epilogue: after a live sweep lands at least one package it runs
5040
+ `publish.mirrorCommand` from `.dz/config.json` (or `--mirror-cmd`), passes the published versions, and reads
5041
+ a JSON receipt whose proof is the live raw `MIRROR-MANIFEST.json` carrying those versions. Publication
5042
+ errors stay exit `1`; packages published but the mirror NOT confirmed exit **`3`** with the exact `re-run:`
5043
+ command; dry runs, `--no-mirror`, `--bump-only` and zero-package sweeps never invoke the hook, and each of
5044
+ those states is named in the report. This is the first release published THROUGH that epilogue.
4733
5045
 
4734
5046
  `dz guard check --op publish` now warns when either release line disagrees with the core/CLI package versions, and a registry-confirmed live core or CLI publish synchronizes the first such line in both release READMEs: each README is rewritten atomically; the pair is not one transaction (dry-run and bump-only never write them).
4735
5047
 
@@ -4740,7 +5052,7 @@ exports (`commandInventory`, `declaredCommands`, `dispatchedCommands`, `document
4740
5052
  `stripNonCode`, `validateExceptionList`, `INTERNAL_ENTRY_POINTS`, `PSEUDO_COMMANDS`) — instead of a
4741
5053
  fourth private regex and a fourth number. Four hidden commands (`mr-rakes`, `retro`,
4742
5054
  `feature-adr-setup`, `bto-optimize`) gained USAGE lines, so `--help` changed; `help` is a documented
4743
- 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
4744
5056
  plus the built-in `help`) — both COMPUTED from `cli.ts`, neither typed into a test. Three cross-family QE rounds closed
4745
5057
  six parser defects the naive version had: four that INVENTED a command (regex literals with a lone
4746
5058
  `}`, `case` labels in comments or templates, sub-verb labels from nested switches, ` dz <name>` lines
@@ -4760,6 +5072,43 @@ plan-route choice. Every failure is fail-open; versioned `.fa-state/decision-rec
4760
5072
  receipt coverage and repeat-related outcomes derivable offline. The hypothesis is external `[SRC],
4761
5073
  n=1`, books were silent on retrieval timing, and no runtime threshold gates a stage.
4762
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
+
4763
5112
  `v0.8.8` — **staged, not published.** `dz init` now reports exactly one MCP and one hook outcome,
4764
5113
  adds `integrations-verify`, content-bound authorization, explicit skills-only opt-out, and named
4765
5114
  partial-failure exits; only receipt-proven live registration may be called emitted.
@@ -4890,7 +5239,7 @@ partial design). The compat floor is unchanged — this CLI uses no new core exp
4890
5239
 
4891
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".
4892
5241
 
4893
- `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).
4894
5243
 
4895
5244
  New in 0.5.0 (feature `qe-bridge-claude`, cross-runtime leg 3/4): `dz qe-bridge --family claude`
4896
5245
  runs an INDEPENDENT Claude reviewer from any host — a Codex session included — and lands a PARSED
@@ -4914,21 +5263,21 @@ Previously: the global `dz --version` / `-v` / `dz version` surface (one parseab
4914
5263
 
4915
5264
  ## Claude Plugin
4916
5265
 
4917
- 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`.
4918
5269
 
4919
5270
  ```bash
4920
- # Via marketplace (when published):
4921
- claude plugin marketplace add djd1m/dz-harness-hub
4922
- claude plugin install dz-harness-hub@dz-harness-hub
4923
-
4924
- # Or test locally:
5271
+ # From a cloned repo (contributors / plugin developers only):
4925
5272
  claude --plugin-dir /path/to/dz-harness-hub
4926
5273
 
4927
5274
  # Generate plugin manifest from current inventory:
4928
5275
  dz plugin --version 0.3.86
4929
5276
  ```
4930
5277
 
4931
- 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).
4932
5281
 
4933
5282
  ## Related Projects
4934
5283
 
@@ -5010,3 +5359,18 @@ Core APIs: `settleDeadRuns` and `planRegistryArchive` are pure registry decision
5010
5359
  `planWorktreeCleanup` accepts injected `WorktreeFact` values and returns remove/keep decisions;
5011
5360
  `renderCleanupPlan` renders them. `worktreeRemovalsToApply` selects removals only when apply is true.
5012
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.