@dzhechkov/harness-cli 0.8.22 → 0.8.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,13 +2,16 @@
2
2
 
3
3
  The **`dz`** CLI — the main entry point to the DZ Harness Hub. Install AI skills for **Claude Code, Codex, OpenCode, Hermes, OpenClaude, GitHub Copilot** from a single command.
4
4
 
5
- ## Test execution
5
+ ## Install in 30 seconds
6
6
 
7
- `npx vitest run` uses two projects and returns one combined verdict: `parallel` runs the ordinary
8
- suites concurrently, while `serial` runs process-spawning and real-time suites one file at a time.
9
- The serial paths in `test/serial-suites.txt` are regenerated from
10
- `test/serial-suites-census.test.ts`, which scans test sources for synchronous and asynchronous
11
- process markers, including `execSync(` and `execFile(`, and fails when the list and census differ.
7
+ ```bash
8
+ npm install -g @dzhechkov/harness-cli
9
+ dz setup --target claude-code --preset devops
10
+ dz doctor
11
+ ```
12
+
13
+ You do NOT need to clone any repository to use dz. Source is for contributors — see
14
+ [From source (contributors only)](#from-source-contributors-only) at the end of this README.
12
15
 
13
16
  ## Why dz?
14
17
 
@@ -120,7 +123,7 @@ Re-running is safe (existing files are skipped unless `--force`). Targets: `clau
120
123
 
121
124
  Don't see your tool below? Two fallbacks: if it reads `CLAUDE.md`/`.claude/` use `--target claude-code`;
122
125
  otherwise `dz bundle --select <ids> --out <dir>` gives a portable tree of **raw `SKILL.md`** files you can
123
- point any tool at (a skill is just Markdown). Dedicated targets are on the [roadmap](https://github.com/djd1m/dz-harness-hub/blob/main/docs/target-expansion-research.md).
126
+ point any tool at (a skill is just Markdown). Dedicated targets are on the roadmap (contributors — see `docs/target-expansion-research.md` in the source repo).
124
127
 
125
128
  **✅ Shipped native targets** (each compiles to the tool's own layout):
126
129
 
@@ -287,7 +290,7 @@ you know exactly which file is at fault, and you know whose defect it is.
287
290
 
288
291
  ## User Journey — from install to mastery
289
292
 
290
- All 91 commands (MEASURED — reproducer: `node --input-type=module -e "import('./dist/index.js').then(m=>console.log(m.DZ_COMMANDS.length))"` from this package; rendered help documents 92 unique top-level names — the 91 plus the built-in `help` — pinned NAME-FOR-NAME by `test/command-inventory-parity.test.ts`) mapped to a real workflow:
293
+ All 92 commands (MEASURED — reproducer: `node --input-type=module -e "import('./dist/index.js').then(m=>console.log(m.DZ_COMMANDS.length))"` from this package; rendered help documents 93 unique top-level names — the 92 plus the built-in `help` — pinned NAME-FOR-NAME by `test/command-inventory-parity.test.ts`) mapped to a real workflow:
291
294
 
292
295
  ```
293
296
  DISCOVER → INSTALL → USE → CREATE → MAINTAIN → SHARE
@@ -975,7 +978,13 @@ stated fact rather than an inference from an absent file. Records and reports ar
975
978
  the state directory itself is contained the same way, before anything is created in it. The audit
976
979
  trail is written BEFORE the report and corrected after it, so `reportWritten` can only ever
977
980
  understate; if the trail cannot be written at all, the run FAILS (`audit-write-failed`) rather than
978
- shipping a verdict nobody can re-derive.
981
+ shipping a verdict nobody can re-derive. The record also carries `writeSequence` — diagnostic
982
+ sequencing metadata: a self-reported, process-local trace of the `signoff-write-started` /
983
+ `report-written` / `record-update-prepared` steps with monotonic stamps taken at each named event
984
+ (the start of the first record write, the moment the report is on disk, and just before the atomic
985
+ record update). It replaces a file-mtime comparison in the tests (a race); the write ORDER itself is
986
+ proven by the report-failure test (record exists and says `false` when the report never lands) and
987
+ the failpoint test (`reportWritten:true` only after the report), not by this field.
979
988
 
980
989
  **Exit codes:** `0` a signoff was parsed (ANY grade — a grade F still exits 0: the bridge reports, it
981
990
  does not gate), `1` a named failure, `2` a usage error. **Honest limits:** it proves the call was
@@ -1021,17 +1030,15 @@ extracts you scope are what leaves the machine. RU: мост в обратную
1021
1030
  ### Пересмотр после аварийного само-ревью — `dz reqe`
1022
1031
 
1023
1032
  The feature-adr pipeline's cross-model guard says *the model that writes code must not review it*.
1024
- Under limit pressure (usage-adaptive `>=70%` switch) that guard is CONSCIOUSLY suspended: coder AND
1025
- Step-8 QE both run on Codex (FR-2.9 a Claude reviewer is exactly the agent that dies at the
1026
- limit). The rule used to say "re-review manually after limits reset" an instruction nobody
1027
- remembers. `dz reqe` turns it into a **debt with a lifecycle**: the run records
1028
- `features/<slug>/.fa-state/reqe-due.json`, `dz usage` surfaces the count the moment you check your
1029
- freed-up limits, and settlement is FAIL-CLOSED.
1033
+ Historical usage-switched runs could suspend that guard under provider-limit pressure: coder AND
1034
+ Step-8 QE both ran on Codex. The rule used to say "re-review manually after limits reset" an
1035
+ instruction nobody remembers. `dz reqe` turns it into a **debt with a lifecycle**: the run records
1036
+ `features/<slug>/.fa-state/reqe-due.json`, `dz usage --json` carries the outstanding count as
1037
+ `reqeDue`, and settlement is FAIL-CLOSED.
1030
1038
 
1031
1039
  ```bash
1032
- $ dz usage
1033
- usage: session ~12% (resets 03:00) · week ~41% (resets 06:00) · estimated
1034
- re-QE due: 1 usage-switched run(s) kept same-family QE — run `dz reqe` for the cross-family pass
1040
+ $ dz usage --json
1041
+ {"sessionPct":null,"weeklyPct":null,"routing":"disabled-by-design","spend":{"days":[...],"total7d":{...},"byModel":{...}},"reqeDue":1}
1035
1042
 
1036
1043
  $ dz reqe # the ledger
1037
1044
  dz reqe — 1 unsettled re-QE debt(s):
@@ -1042,8 +1049,8 @@ $ dz reqe --slug add-x --done --report features/add-x/08b_reqe_report.md
1042
1049
  dz reqe: debt settled: re-QE grade C (report …) — settlement appended to features/add-x/08_qe_report.md
1043
1050
  ```
1044
1051
 
1045
- **When to use:** any time `dz usage` prints a `re-QE due` line, or a feature-adr result carried
1046
- `reqeDue: true`. **Fail-closed settlement:** the report must exist, be non-trivial, and name exactly
1052
+ **When to use:** any time `dz usage --json` reports a positive `reqeDue`, or a feature-adr result
1053
+ carried `reqeDue: true`. **Fail-closed settlement:** the report must exist, be non-trivial, and name exactly
1047
1054
  one line-anchored `GRADE` (the boilerplate phrase `GRADE A-F` does not count); the run's own
1048
1055
  `08_qe_report.md` can never settle its own debt (real-path AND inode compared — a hard link doesn't
1049
1056
  fool it); the settlement epilogue lands in `08_qe_report.md` and the due-file rotates to
@@ -1051,12 +1058,13 @@ fool it); the settlement epilogue lands in `08_qe_report.md` and the due-file ro
1051
1058
  nothing re-runs QE automatically, and the validator proves the settlement is procedurally sound —
1052
1059
  which model authored the report stays with the human running the brief. RU: гард «кодер не ревьюит
1053
1060
  сам себя» осознанно снимается под лимитом; `dz reqe` превращает инструкцию «перепроверь потом»
1054
- в долг на диске — виден в `dz usage`, гасится только настоящим кросс-семейным отчётом с грейдом.
1061
+ в долг на диске — виден как `reqeDue` в `dz usage --json`, гасится только настоящим кросс-семейным отчётом с грейдом.
1055
1062
 
1056
- ### Usage estimates you can act on — `dz usage`
1063
+ ### Spend you can inspect — `dz usage`
1057
1064
 
1058
- `dz usage` estimates how much of your Claude session (5h block) and week you have spent, from local
1059
- transcripts. It drives feature-adr's pre-emptive "switch to Codex before the limit" routing.
1065
+ `dz usage` reports cost-weighted token spend from local Claude Code and subagent transcripts. It does
1066
+ not predict provider limits: there is no provider usage API, weekly resets are per-account, and
1067
+ ad-hoc resets make a local denominator non-measurable. Feature routing is therefore disabled by design.
1060
1068
 
1061
1069
  Two things changed to make it mean something:
1062
1070
 
@@ -1066,20 +1074,44 @@ Two things changed to make it mean something:
1066
1074
  1-hour TTL write, cache-read 0.1x, output 5x).
1067
1075
  - **Subagent transcripts count.** `<session>/subagents/*.jsonl` carry real, non-duplicated usage and
1068
1076
  were silently excluded.
1077
+ - **Symlinked project directories are scanned by real path, each real directory once.** Broken links
1078
+ and symlinks to files are skipped.
1069
1079
  - **The walk is safe and bounded.** Only regular files are read, symlinked files and directory
1070
- components are skipped (a FIFO used to block it, a symlink used to be followed), and the file cap
1071
- keeps the NEWEST transcripts so a long history cannot push current usage out of view.
1080
+ components cannot create duplicate walks or loops, and the file cap keeps the NEWEST transcripts
1081
+ so a long history cannot push current usage out of view.
1072
1082
 
1073
1083
  ```bash
1074
- dz usage # session ~13% (resets 10:24) · week ~15% (resets 08:59) · estimated
1075
- dz usage --json # machine-readable; pct is null when limits are unconfigured
1076
- dz usage --calibrate --session 42 --weekly 61 # teach it YOUR real numbers from claude.ai
1077
- ```
1078
-
1079
- **`pct` is `null` until you configure limits that is deliberate**, an unconfigured estimate is not
1080
- a guess dressed as a number. The authoritative calibration is `--calibrate` with the percentages
1081
- shown on claude.ai/settings/usage; absent that, limits set from your own observed peak mean "unusually
1082
- heavy **for you**", which is exactly what a pre-emptive routing switch needs.
1084
+ dz usage # seven UTC days + seven-day total + model shares + today-by-model
1085
+ dz usage --json # sessionPct/weeklyPct stay null; spend carries days, total7d, byModel, daysByModel
1086
+ ```
1087
+
1088
+ The compatibility keys `sessionPct` and `weeklyPct` remain present and always `null`, so existing
1089
+ feature-adr probes keep parsing the payload without treating a guess as a routing signal.
1090
+
1091
+ **What `unknown` means, and which window each block covers.** The "by model" table is a
1092
+ **seven-day window total** it is the same seven days as the daily table above it, collapsed
1093
+ across models, never a per-day figure (a defect this project has hit before: a weekly figure read
1094
+ as a daily one). A model row named `unknown` is the aggregation's fallback bucket, literally
1095
+ `event.model ?? 'unknown'` in `spendReport` — it catches BOTH of the two ways an event can carry no
1096
+ usable model: an event with **no model field at all**, and one whose model string did not match
1097
+ any of the four known substrings (`fable`/`opus`/`sonnet`/`haiku`). (Fix-round-1, Codex review
1098
+ MEDIUM #3: an earlier revision of this paragraph said `unknown` was "not an event without a
1099
+ model" — the opposite of what the code does; corrected to name both causes.) MEASURED on this
1100
+ machine, the second cause dominates — almost entirely `<synthetic>` assistant-turn markers, not a
1101
+ parsing failure — and `unknown` is always shown, never hidden, because a hidden `unknown` row would
1102
+ make the visible rows undercount the real total. Both the "by model" table and the "today by model"
1103
+ block print a third column: the model's **share as a fraction 0..1** (two decimals), not a
1104
+ percentage — `by model` shares are of the **seven-day total**, `today by model` shares are of
1105
+ **today's own total**, and a complete breakdown's column sums to `1.00` in either block (never
1106
+ `100` — that would be the percentage form). The `--json` payload additionally carries
1107
+ `spend.daysByModel`: one entry per day (same dates as `spend.days`, same order) with that single
1108
+ day's `{model → weightedTokens}`, `unknown` included — read `daysByModel.at(-1)` for **today's**
1109
+ per-model spend, which the plain-text report prints as the `today (<date>) by model` block right
1110
+ after the seven-day "by model" table. The invariant this rests on — every model breakdown
1111
+ reconciles exactly with the totals it was sliced from — is `spendInvariantViolations()` in
1112
+ `harness-core` (relative tolerance `max(1e-6, 1e-9·max(|a|,|b|))`, and a `NaN`/`Infinity` sum is
1113
+ always a violation, never a silent pass), covered by a dedicated mutation-registry entry
1114
+ (`usage-days-by-model-invariant`).
1083
1115
 
1084
1116
  ### Where did the run's budget actually go? — `dz usage --by-stage`
1085
1117
 
@@ -1327,6 +1359,22 @@ Four rules the gate itself obeys — these are what distinguish it from a green-
1327
1359
  undefended property lives in this repo's test suite, and the gate MUST fail on it — a gate that
1328
1360
  cannot fail cannot pass.
1329
1361
 
1362
+ **Where the full output of a RED baseline/rebaseline line lives** (gate-stability, 2026-09-12,
1363
+ fix-round-1 2026-09-12): the bounded 3-line/20-line tail in the verdict is a teaser, and under a
1364
+ multi-entry `--only a,b` run it was measured to hand back an unrelated neighbour's stderr —
1365
+ undiagnosable. When the initial baseline or a per-entry/final rebaseline line comes back non-zero,
1366
+ the executor saves the FULL stdout+stderr to
1367
+ `<os.tmpdir()>/dz-mutgate-output/dz-mutgate-<entryId|baseline>-<phase>-<ISO-timestamp>.log` and the
1368
+ verdict text names the path (`; full output: <path>`); a green line writes nothing. Files older
1369
+ than 7 days in that directory are rotated away on the next write, and rotation only ever touches a
1370
+ name carrying BOTH our own `dz-mutgate-` prefix AND our exact ISO-timestamp shape — a foreign file
1371
+ (even one shaped like `service-baseline-backup.log`, which the pre-fix-round-1 pattern would have
1372
+ matched and deleted) is never touched. If the save itself fails (EACCES/ENOSPC/EROFS/a blocked
1373
+ output directory), the verdict says so instead of silently losing the diagnostic artifact
1374
+ (`; full output NOT saved: <error>`) — the gate's own exit code is never changed by a logging
1375
+ failure. Override the directory with `DZ_MUTGATE_OUTPUT_DIR` (used by this package's own tests to
1376
+ avoid touching the real tmpdir).
1377
+
1330
1378
  When to reach for it: after a QE round names safety properties (seed the registry so they STAY
1331
1379
  defended); in CI for a package whose protections have burned you before (`npm run test:mutation`);
1332
1380
  and in feature-adr Step 8, where the QE reviewer runs it whenever the touched package has a
@@ -1449,13 +1497,13 @@ Each pack is an npm package — click through for the **full per-skill documenta
1449
1497
  | [@dzhechkov/skills-academic](https://www.npmjs.com/package/@dzhechkov/skills-academic) | 5 | Thesis-defense toolkit — dissertation review, questions, doc-check, defense eval |
1450
1498
  | [@dzhechkov/skills-news](https://www.npmjs.com/package/@dzhechkov/skills-news) | 3 | *dz-original* — news digests (`news-digest`) + delta watches (`news-monitor`) + bundled `goap-research-ed25519` verified-research backend (mandatory) |
1451
1499
  | [@dzhechkov/skills-demo-publisher](https://github.com/djd1m/dz-harness/tree/main/packages/%40dzhechkov/skills-demo-publisher) | 1 | *dz-original, staged* — scenario-driven product recording → budgeted static HTML5 video site with Russian captions and fail-closed Pages delivery checks |
1452
- | [@dzhechkov/skills-idea2prd](https://www.npmjs.com/package/@dzhechkov/skills-idea2prd) | 1 | *dz-original* — `idea2prd-manual`: idea/problem → PRD+ADR+DDD+C4+Pseudocode+Tests+Completion (9 checkpoints); bundles the analyst trio as a sources.json-tracked vendor ([ADR-0001](https://github.com/djd1m/dz-harness-hub/blob/main/docs/adr/0001-skill-canonicalization-and-dependency-model.md)) |
1453
- | [@dzhechkov/skills-reverse-engineering](https://www.npmjs.com/package/@dzhechkov/skills-reverse-engineering) | 1 | *dz-original* — `reverse-engineering-unicorn`: company → launch playbook (+CJM) via 6-module QUICK/DEEP/VERIFIED pipeline; canonical home that resolved the keysarium↔p-replicator drift ([ADR-0001](https://github.com/djd1m/dz-harness-hub/blob/main/docs/adr/0001-skill-canonicalization-and-dependency-model.md)) |
1454
- | [@dzhechkov/skills-presentation-storyteller](https://www.npmjs.com/package/@dzhechkov/skills-presentation-storyteller) | 1 | *dz-original* — `presentation-storyteller`: selling deck + verified sources + slide-by-slide speaker script; referenced (not vendored) by reverse-engineering-unicorn's Post-M6 step ([ADR-0001](https://github.com/djd1m/dz-harness-hub/blob/main/docs/adr/0001-skill-canonicalization-and-dependency-model.md)) |
1455
- | [@dzhechkov/skills-website-cloner](https://www.npmjs.com/package/@dzhechkov/skills-website-cloner) | 1 | *imported (MIT)* — `clone-website`: live site → pixel-perfect Next.js clone (recon → specs → parallel build → visual QA); needs a browser-MCP + Next.js scaffold; referenced by p-replicator's `/replicate` ([ADR-0001](https://github.com/djd1m/dz-harness-hub/blob/main/docs/adr/0001-skill-canonicalization-and-dependency-model.md)) |
1456
- | [@dzhechkov/skills-pm](https://www.npmjs.com/package/@dzhechkov/skills-pm) | 18 | *imported (MIT)* — product-management toolkit: OST, RICE/ICE prioritization, product-strategy, pricing, OKRs, NSM/metrics/A-B/cohort, outcome-roadmap, stakeholder-map, sprint-plan, strategy-red-team, GTM/growth/beachhead, market-sizing; curated from phuryn/pm-skills (`dz init --preset pm`) ([ADR-0002](https://github.com/djd1m/dz-harness-hub/blob/main/docs/adr/0002-product-and-design-expansion.md)) |
1457
- | [@dzhechkov/skills-taste](https://www.npmjs.com/package/@dzhechkov/skills-taste) | 1 | *imported (MIT)* — `design-taste-frontend`: anti-slop landing/portfolio/redesign framework (dials + pre-flight + GSAP skeletons); complements frontend-design (`dz init --select design-taste-frontend`) ([ADR-0002](https://github.com/djd1m/dz-harness-hub/blob/main/docs/adr/0002-product-and-design-expansion.md)) |
1458
- | [@dzhechkov/skills-book-digitizer](https://www.npmjs.com/package/@dzhechkov/skills-book-digitizer) | 8 | *dz-original* — book → installable methodology pack: `digitize-book` (orchestrator) + ingest/extract/distill/pack/kb-index + `book-brain-register` (CP6 promote → cross-project brain) + `source-brain-ingest` (repo sibling). Verified provenance, IP-safe, resumable (`dz init --select digitize-book`) ([ADR-001](https://github.com/djd1m/dz-harness-hub/blob/main/features/book-knowledge-digitizer/03_adr/001-book-to-skillpack-pipeline.md)) |
1500
+ | [@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) |
1501
+ | [@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) |
1502
+ | [@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) |
1503
+ | [@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) |
1504
+ | [@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) |
1505
+ | [@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) |
1506
+ | [@dzhechkov/skills-book-digitizer](https://www.npmjs.com/package/@dzhechkov/skills-book-digitizer) | 8 | *dz-original* — book → installable methodology pack: `digitize-book` (orchestrator) + ingest/extract/distill/pack/kb-index + `book-brain-register` (CP6 promote → cross-project brain) + `source-brain-ingest` (repo sibling). Verified provenance, IP-safe, resumable (`dz init --select digitize-book`) (ADR-001, contributors-only doc) |
1459
1507
  | [@dzhechkov/skills-12factor](https://www.npmjs.com/package/@dzhechkov/skills-12factor) | 12 | *generated by the digitizer, CC BY 4.0* — The Twelve-Factor App distilled into 12 decision-moment skills (one per factor). The first PUBLIC digitized-book pack; paraphrased (shingling-gated), routing-gated (every factor carries triggers), attributed (`NOTICE`) (`dz init --select 12factor-config-in-environment,…`) |
1460
1508
  | [@dzhechkov/skills-book-ai-apps](https://www.npmjs.com/package/@dzhechkov/skills-book-ai-apps) | 17 | *generated by the digitizer, CP5-published* — «Building Applications with AI Agents» (Albada, рус. пер.) distilled into 17 decision-moment skills across the whole agent-building arc: agent-fit & model choice, single-vs-multi, orchestration, tool design, knowledge & memory, context engineering, evaluation, probabilistic behaviour checks, release gates, improvement loops, drift, human-in-the-loop, agent UX, governance, security. Ships our page-anchored Knowledge Units, NOT the book text — shingling-gated at 0 uncited verbatim runs >=8 words; publication is the recorded CP5 owner decision; `trust_tier 1` (routing-gated, not human-reviewed) Since 0.2.2 it also ships `brain/ai-apps.sqlite`, the 223-KU knowledge slice: `dz brain add --from-pack @dzhechkov/skills-book-ai-apps` loads it into your `~/.dz/brain`, then `dz brain query --source ai-apps` answers in any project (`dz install @dzhechkov/skills-book-ai-apps --target claude-code`) |
1461
1509
  | [@dzhechkov/skills-book-clean-code](https://www.npmjs.com/package/@dzhechkov/skills-book-clean-code) | 9 | *generated by the digitizer, CP5-published 2026-09-11* — «Чистый код» (Robert C. Martin, рус. пер.): intent/comment contract, source layout, function contracts, object/data ownership, error and boundary handling, test-suite feedback, legacy refactoring loop, architecture assembly, concurrency safety; ships `brain/clean-code.sqlite` (259 KU). Shingling IP gate 0 violations on the published bytes; trust tier 1. |
@@ -1972,10 +2020,10 @@ runs a command that MEASURES the declared artifacts itself. It refuses a null re
1972
2020
  never recorded as done), an absent or partially-present artifact set, and a stage that declares nothing
1973
2021
  to witness — so a stage that did not happen can no longer be recorded, which the old mechanism allowed.
1974
2022
 
1975
- ## All Commands (91)
2023
+ ## All Commands (92)
1976
2024
 
1977
- *(91 MEASURED from the bounded command inventory below; rendered `dz --help` exposes 92 unique
1978
- top-level names — the 91 plus the built-in `help`, which prints USAGE before the dispatch switch and
2025
+ *(92 MEASURED from the bounded command inventory below; rendered `dz --help` exposes 93 unique
2026
+ top-level names — the 92 plus the built-in `help`, which prints USAGE before the dispatch switch and
1979
2027
  is therefore documented without being a command. Both numbers are COMPUTED, never typed: the name
1980
2028
  sets of this section, the root README, the docs site and `DZ_COMMANDS` are pinned to each other by
1981
2029
  `test/command-inventory-parity.test.ts`.)*
@@ -1992,7 +2040,7 @@ dz teach --from-json <file> [--project <dir>] [--no-mirror] [--harmonize] # bu
1992
2040
  dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror]
1993
2041
  dz recall "<query>" [--limit <N>] [--domain <name>] [--semantic | --no-semantic] [--full] [--project <dir>] # hybrid lexical+vector when the vector tier is enabled; --domain BOOSTS same-domain lessons (never filters)
1994
2042
  dz recall --all [--json] [--stats] [--include-domain <name,…>] # export the learned store (held-out domains are withheld unless named), or inspect learning stats/top uses
1995
- dz recall --books "<query>" [--book <slug>] # digitized-book KUs; --book narrows to one book
2043
+ dz recall --books "<query>" [--book <slug>] # digitized-book KUs; --book narrows to one book. Reads via a read-only opener (ADR-001, store-readonly-reads): works from a read-only-mounted store directory (falls back to a temporary copy) and no longer writes `-wal`/`-shm` on every invocation
1996
2044
  dz vector status [--project <dir>] [--json] # semantic tier: engine availability, mirrored vs lexical counts, pending queue
1997
2045
  dz vector reindex [--project <dir>] [--json] # re-embed learned vectors with the CONFIGURED model + stamp the manifest (snapshot first; atomic — a mid-way failure restores the store). Warns about task types it does not own (e.g. book-knowledge → run `dz brain reindex`)
1998
2046
  dz vector export <path> [--project <dir>] # portable VECTOR form (.rvf checkpoint; needs the opt-in RVF engine)
@@ -2001,13 +2049,13 @@ dz vector harmonize [--apply] [--threshold <0..1>] [--json] # SEMANTIC me
2001
2049
  dz teach --harmonize [--apply] [--threshold <0..1>] # alias of `dz vector harmonize`
2002
2050
  dz statusline [--json] [--install] # compact Claude Code statusline: live self-learning pattern count + brain sources
2003
2051
  dz store-guard [--status|--reset] [--yes] [--project <dir>] # inspect the monotonic external high-water mark; --reset is the only lowering path and requires confirmation or --yes
2004
- dz usage [--json] [--project <dir>] | --calibrate --session <pct> --weekly <pct> [--model fable=<pct>] # ESTIMATE Claude usage from fixed reset windows; optional per-model weekly binding; exit 0 ALWAYS
2052
+ dz usage [--json] [--project <dir>] # 7-day UTC spend from local Claude Code + subagent transcripts; provider-limit routing disabled by design
2005
2053
  --by-stage [--run <id> | --slug <s>] [--epsilon <0..1>] [--write <file.jsonl>] [--json] # per-stage cost ledger for ONE feature-adr run + reconciliation invariant (BALANCED | DEFECT | INSUFFICIENT_DATA)
2006
2054
  dz chain [--project <dir>] [--json] # verify EVERY hash-chained journal in one command; coverage is DERIVED from the CHAINED_JOURNALS registry, so a journal cannot be chained and checked by nobody; an ABSENT journal is NAMED, never omitted; exit 1 on broken/unreadable
2007
2055
  dz claim-check [paths...] [--json] [--fail-on high|medium|none] [--project <dir>] # enforce the Integrity Rule: flag untagged/overstated accuracy claims; default scan = READMEs + features' 08_qe_report.md; exit 1 only at/above --fail-on (default high)
2008
2056
  dz lint [paths...] [--json] [--config <file>] [--registry <file>] [--project <dir>] # advisory EN/RU prose-style lint; findings exit 0, incomplete input/policy exits 1, usage exits 2
2009
2057
  dz brain list [--json] # the durable cross-project knowledge brain
2010
- dz brain query "<q>" [--source <slug>] [--limit <N>] [--any] [--rerank] [--json] # cross-source recall (--any = OR match; --rerank reorders on-point first). Auto-broadens: if strict all-terms match yields 0 hits, it retries once as OR and labels the result "broadened" (text note + broadened:true in --json) instead of returning empty. Explicit --any is OR from the start (never labeled broadened).
2058
+ dz brain query "<q>" [--source <slug>] [--limit <N>] [--any] [--rerank] [--json] # cross-source recall (--any = OR match; --rerank reorders on-point first). Auto-broadens: if strict all-terms match yields 0 hits, it retries once as OR and labels the result "broadened" (text note + broadened:true in --json) instead of returning empty. Explicit --any is OR from the start (never labeled broadened). `dz brain query`/`dz brain ground` read via the same read-only opener as `dz recall --books` — see ADR-001, store-readonly-reads.
2011
2059
  dz brain add [--source <slug>] [--project <dir>] [--from-slice <f>|--from-pack <p>|--from-kus <f> --slug <s> --kind <k> --license <spdx> [--override]] [--json] # grow the brain
2012
2060
  dz brain ground "<prompt>" [--k <N>] [--source <slug>] [--text] [--budget <N>] [--full] # retrieve + emit grounding citations (hook entrypoint; silent if irrelevant); --budget N eager-inlines top-K KU content within ~N tokens (chars/4 approx); --full = --budget 8000
2013
2061
  dz brain expand <kuId> [--source <slug>] [--json] # full-content lookup by kuId — the command the grounding directive names; prints name/problem/pages/book + FULL content (untruncated)
@@ -2015,6 +2063,8 @@ dz brain init [--project <dir>] [--k <N>] # opt-in: w
2015
2063
  dz brain primer <slug> [--json] # print a source's capability card (histogram + top decision moments)
2016
2064
  dz brain export --source <slug> --out <file> # write a portable per-book KB slice (ships inside the pack, §8.1)
2017
2065
  dz brain update <slug> [--project <dir>] [--json] # non-destructive refresh: re-mirror a re-ingested source, evict stale corpus
2066
+ 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)
2067
+ dz brain snapshots [--keep <N>] [--prune] [--json] [--project <dir>] # list (default) — or, with --prune, rotate — the brain's own `agentdb.db.pre-reindex-<ms>.bak` snapshot families; reaches the same rotation WITHOUT running a reindex
2018
2068
  dz pretrain [--project <dir>]
2019
2069
  dz recommend "<task description>" [--json] (RU/EN lexical topics; explicit task/project-stack/none provenance)
2020
2070
  dz compose <preset1+preset2+...> [--target <name>]
@@ -2041,7 +2091,86 @@ dz discrimination-check --test <f[,f]> [--base <ref>] [--name <filter>] [--runne
2041
2091
  dz amendment-check --slug <slug> | --feature-dir <dir> | --all [--json] (every AM-N / AM-CP-N amendment row must resolve to a test found INSIDE the file the row names; the PLAN is authoritative when it carries rows, and an ideation amendment the plan drops is a failure; --all is a census that always exits 0; exit 0 pass/skip, 1 fail, 3 NOT-ESTABLISHED — ZERO parsed rows may skip only when the whole first paragraph or heading suffix is exactly "None"/"N/A"/"нет", optionally with a full stop; an optional CommonMark closing # sequence is heading furniture, qualified text is refused, and AM-like content beside the declaration is NOT-ESTABLISHED)
2042
2092
  dz contract-check --slug <s> [--json] (read-only retrospective feature contract gate: canonical AC-N + ADR Confirmation → CC-N; every item needs one artifact-anchored met|unmet|not-testable verdict; A/B with unmet is refused; exit 0 pass / 1 readable violation / 2 invalid invocation or unreadable/not-established artifacts)
2043
2093
  dz journal add --kind decision|verdict|run|error|block "<text>" [--ref <trace>] [--at <ISO>] [--quote <file>] [--commit-quote]; show [--day|--week] [--at <date>] [--kind <kind>] [--json] # UTC day files, witnessed append; quotes local by default
2044
- dz feature-adr-record --kind ledger|training-pair --stage <s> [--slug <s>] [--row|--pair <json>] [--mark <n>] [--once] [--json] (the witnessed writer: payload as an ARGUMENT never as shell, refused before any write, timestamp stamped before serialising, append verified by re-reading the tail; exit 0 written|duplicate|skipped, 2 refused, 3 not-verified, never blocking)
2094
+ 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)
2095
+ 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
2096
+
2097
+ The round owner is the session shell (`process.ppid`) by default. Under `setsid`, or in a container
2098
+ where that parent is PID 1, pass `--owner-pid <pid>` for the real supervising process; otherwise PID 1
2099
+ can make an abandoned round appear permanently owned by a live process.
2100
+
2101
+ `--state-root <dir>` (or `DZ_ROUND_STATE_ROOT=<dir>`, flag wins when both are set) names where round
2102
+ state (`.dz/rounds/`) AND its ledger row live — every subcommand reads/writes under this root instead
2103
+ of cwd when it is given. **`--project` is unrelated and stays recall-only**: `open` only ever passes
2104
+ it to `dz recall` for lessons, round state itself has always lived in cwd (lesson `2ac30a70`), and
2105
+ `--state-root` is the flag that actually redirects that state. The value must be an absolute path —
2106
+ a relative one is refused with exit 2 before anything is written. Without the flag or the variable,
2107
+ behavior is unchanged (state in cwd, exactly as before this flag existed). `open` always prints the
2108
+ resolved root as `state root: <dir>` (text) / `stateRoot` (JSON), so a caller never has to assume it
2109
+ landed in cwd; `status` prints the same `stateRoot` in JSON always, and the `state root: <dir>` text
2110
+ line only when `--state-root`/`DZ_ROUND_STATE_ROOT` was actually given (its default-cwd text output
2111
+ is otherwise unchanged, byte-for-byte, from before this flag existed).
2112
+
2113
+ Every mutation of `.dz/rounds/<slug>-<round>.json` (`open`'s archive+write, `exec`'s owner-claim and
2114
+ owner-restore writes, `close`'s final reread+delete) runs under ONE named lock,
2115
+ `<stateRoot>/.dz/locks/round-state.lock` (`withNamedLockSync`, feature round-state-lock) — two `dz
2116
+ round` processes sharing a `--state-root` can no longer lose each other's update. Recall (`open`) and
2117
+ the run-cost ledger write (`close`) stay OUTSIDE the lock — they can be slow, and the lock must not be
2118
+ held across anything that isn't a short, synchronous read-modify-write. `open` rereads state again
2119
+ UNDER the lock right before writing: if recall ran for a while and a different process opened the
2120
+ same round in the meantime, `open` refuses instead of silently overwriting that process's state.
2121
+ `status` never takes the lock (read-only) and keeps working while it is held elsewhere. Two refusals,
2122
+ both observable, neither one leaves a state file half-written:
2123
+ - **`already-open`** — a different process's state appeared while `open` was recalling: exit 1,
2124
+ `круг уже открыт (pid …) — состояние не перезаписано`, `--json` carries `{"refused":"already-open",
2125
+ "pid": <n>}`. The ORIGINAL state (the other process's) is left on disk untouched.
2126
+ - **`lock-busy`** — the lock could not be acquired before its deadline (another process is still
2127
+ inside its own critical section): exit 1, `lock busy: …`, `--json` carries `{"refused":"lock-busy"}`.
2128
+ Nothing is written; for `close` specifically, the run-cost ledger row is written BEFORE this lock is
2129
+ ever attempted (see above), so a `close` that hits `lock-busy` on its final delete has already logged
2130
+ the outcome — only the round's own state file is left undeleted (retry `close` to finish it).
2131
+
2132
+ **Fix-round (identity beats pid, `stateId`).** Every state `open` writes carries a `stateId` (16
2133
+ random hex chars) — the token `exec`/`close` compare against, not pid: `process.ppid` coincides for
2134
+ two `dz` launched from the same shell, and every run-owned state carries pid 0, so pid alone could
2135
+ not tell a genuine race from an innocent coincidence.
2136
+ - `open`'s recheck-under-lock now refuses on ANY raw-byte change since the pre-recall snapshot
2137
+ (pid is diagnostic-only in the refusal text), except a vanished file — a round closed while `open`
2138
+ was recalling is not a race to refuse, it is the ordinary "no existing round" case, `--force` or not.
2139
+ - `exec`'s claim AND owner-restore sections both refuse — leaving the file untouched — when the state
2140
+ under the lock is gone (`круг закрыт во время exec, владелец не менялся`) or carries a DIFFERENT
2141
+ `stateId` (`состояние круга заменено (stateId …), возврат владельца пропущен`); neither section ever
2142
+ falls back to resurrecting or overwriting with a stale pre-lock snapshot. The owner-restore lock is
2143
+ retried up to 3 times at the same timeout before giving up; if it still cannot recover ownership, the
2144
+ round is left `ownerKind: 'exec'` and the command says so in prose (`владелец круга не восстановлен
2145
+ (ownerKind=exec остался): повторите dz round exec для этого круга, когда блокировка освободится`) —
2146
+ no new flag is added for this. `open` and `status` warn on their own when a round has been sitting at
2147
+ `ownerKind: 'exec'` for 10+ minutes, so a stuck claim is never silently left for someone to trip over.
2148
+ - `close`'s final delete deletes ONLY the state instance its own ledger row was written for (matched by
2149
+ `stateId`): a state that vanished under the lock is `круг уже закрыт (строка леджера записана)` (exit
2150
+ 0 — the same round reaching its own success postcondition a different way, not a failure); a state
2151
+ replaced by a different `stateId` is `состояние заменено, не удалено` (exit 1, never deleted). A
2152
+ `close` that hits `lock-busy` on the delete step now says exactly what already happened:
2153
+ `строка леджера записана, состояние круга осталось открытым — повторите close`, `--json` carries
2154
+ `{"refused":"lock-busy","ledgerWritten":true}` — and the retry is idempotent: if the ledger already
2155
+ carries this attempt's row (same slug/round/closedAt), the retry does not append a second one.
2156
+
2157
+ `feature-adr` is one outer round, not one round per stage. Step 0 opens it with `--round auto` and
2158
+ `--run fa:<slug>` plus `--owner-run <runId>` when the run-registry start receipt supplies a non-empty
2159
+ id, so the run registry—not the short-lived command process—owns its liveness. If that best-effort
2160
+ registry write fails, the workflow omits `--owner-run`, logs the fallback explicitly, and keeps round
2161
+ tracking under the command's ordinary explicit owner. Step 8 maps the QE grade to the outcome and
2162
+ closes with `--no-cost`. The existing stage ledger commands remain byte-stable and remain the only
2163
+ cost source, so cadence/usage readers do not count the same minutes or tokens twice. A refused close
2164
+ is logged and returned as `roundClosed:false` without failing the feature run.
2165
+
2166
+ In round rows, `tokens: null` means tokens were not measured; an explicit `--tokens 0` remains zero.
2167
+ Automatic feature-adr stage rows keep total `tokens` null and record the measurable partial cost as
2168
+ `tokensOut`, with `tokensOutSource` naming `budget.spent` or `unavailable`.
2169
+
2170
+ When `args.dzBin` is supplied, feature-adr uses it without a selection/version agent call and logs
2171
+ version `not probed (dzBin given)`. With no `dzBin`, binary selection costs no more than **3 additional
2172
+ agent calls**: up to 2 `probeSessionCwd` attempts when the workspace is not yet known, plus 1 combined
2173
+ probe that both selects the workspace build or PATH fallback and prints the selected binary's version.
2045
2174
  dz runs [--settle] [--stall-minutes N] [--json] [--project <dir>] (read the run registry: live / stalled / orphaned / inconclusive / finished)
2046
2175
  dz runs-record --event started|heartbeat|finished [--run-id <id>] [--kind <kind>] [--slug <slug>] [--pid <pid|host>] [--parent-run-id <id>] [--outcome <text>] [--project <dir>] [--json] (append one run event)
2047
2176
  dz runs-clean [--apply] [--retention-days N] [--project <dir>] [--json] (plan cleanup of old clean merged worktrees and dead/finished registry histories; apply explicitly)
@@ -2056,7 +2185,7 @@ dz epoch-replay --judge <filled-work-order.json> [--out <file>] [--json] # bli
2056
2185
  dz epoch-replay --score <judgments.json> --work-order <file> [--slice <name>] [--json] # un-blind against the VERIFIED pre-registered assignment; ONE paired binomial over DECISIVE pairs (ties excluded, reported) → SUPPORTED only when the lift interval (2p−1) lies entirely above zero; FALSIFIED only on harm or a passed non-superiority test (lift upper bound below the margin PRE-REGISTERED in the work order, default 0.05, at 10+ decisive pairs); else INCONCLUSIVE (min 5 decisive pairs). Refuses a forged work order, a --margin flag, or duplicate judgement ids; the verdict is data, not an exit code
2057
2186
  dz score --slug <feature> [--project <dir>] [--json] # process scorecard for ONE feature-adr run, from its artifacts: ADR confirmation, discrimination proof, cross-model QE grade, live verification, README-first, learning loop, amendments — DESCRIPTIVE-ONLY (a low score exits 0); evidence lines are shown so the reader judges the heuristics
2058
2187
  dz recap [--day|--week|--month] [--at <ISO date>] [--refresh-publishes] [--project <dir>] [--json] # what was done over a window, from records only. `--refresh-publishes` fills the third-party publish-times cache first (the ONLY place this command touches the network — 51 packages in ~7s, batches of 8); the report itself always reads the cache, prints its AGE, and names any package the registry did not answer for: those are MISSING from the numbers, not zero. Deliveries carry the grade an independent review STATED — a report naming two different grades is reported AMBIGUOUS, never guessed; registry publishes come from a cache (51 packages cost 18.3s over the network, MEASURED — never inside a report); gate verdicts and knowledge reuse come from the local stores. --quarter/--half-year/--year are RECOGNISED and REFUSED with the real span in days: there is exactly ONE complete quarter and the longest record is 174 days, so a quarter-over-quarter comparison is arithmetically impossible and a year would be fabrication. Every section carries its own data-start date, and "the source was not read" never prints as a zero. Contaminated measures are NOT computed and the report says so: commit count (this project mandates a commit per logical change), lines changed (352 of 1318 commits are docs), token spend (self-declared an estimate, once wrong sixfold), learning-event volume (the curve tracks when hooks were installed), inventory counts (monotonic — they can only flatter), lesson count (54% of the pool has never been read). exit 0 reported / 2 refused
2059
- dz cadence [--window day|week|month|quarter|halfyear|year] [--json] # the RHYTHM view of what shipped: graded-shipment counts bucketed by ISO week, npm-publish cadence (reads `dz recap`'s publish-times cache — run `dz recap --refresh-publishes` to warm it), guard repeat DECAY on the FIXED rule set (a rule joins only with pre-window history, so a newborn rule's zero is youth, not virtue — the no-stubs false-zero class excluded by construction), and recall reuse per week. A window deeper than 2× the record is REFUSED with the measured depth and the largest honest window named (a cadence from under two full windows is scale forgery). Sibling of `dz recap`: recap is the narrative what-was-done report over one window; cadence is the week-by-week rhythm across the window. exit 0 / 2 refused-window / 1 usage
2188
+ dz cadence [--window day|week|month|quarter|halfyear|year] [--json] # the RHYTHM view of what shipped: graded-shipment counts bucketed by ISO week, round/round-exec outcomes with every unfinished round named, packages/ commits in the window (`not measured` when git is unavailable), npm-publish cadence (reads `dz recap`'s publish-times cache — run `dz recap --refresh-publishes` to warm it), guard repeat DECAY on the FIXED rule set (a rule joins only with pre-window history, so a newborn rule's zero is youth, not virtue — the no-stubs false-zero class excluded by construction), and recall reuse per week. A window deeper than 2× the record is REFUSED with the measured depth and the largest honest window named (a cadence from under two full windows is scale forgery). Sibling of `dz recap`: recap is the narrative what-was-done report over one window; cadence is the week-by-week rhythm across the window. exit 0 / 2 refused-window / 1 usage
2060
2189
  dz profile init | show | set <field> <value> | sync [--target claude] # WHO is being talked to. Per-USER store ~/.dz/profile.json (0600, never under a project root), delivered as a marked block in ~/.claude/CLAUDE.md so it loads in EVERY project on the machine, dz installed or not. Two axes: default register (pro | pro-lite | plain; RU aliases профи / профи лайт / просто) and named domains that move it — `deep` = full pro no scaffolding, `weak` = one plain sentence EVERY time unprompted, MANDATORY there. Three fixed rules ride along and the register cannot override them: an explanation is SELF-CONTAINED (every term glossed at first use in THIS passage), the register governs dialogue and owner-facing surfaces but NEVER ADRs / commits / QE reports / npm READMEs, and it changes FORM not FACTS. `show` always prints the store path and the profile's age; an unknown register value is REFUSED naming the accepted set, never silently defaulted; a hand-edited block is reported as drift and `sync` repairs it with a timestamped backup, foreign content preserved byte-for-byte. Redacted from training-pair capture (.dz/fa-training/ records the full prompt and is deliberately not gitignored)
2061
2190
  dz qe-rounds (--slug <feature> | --feature-dir <abs>) [--ceiling <n>] [--project <dir>] [--json] # how many Step-8 review rounds has this feature ALREADY had? The rule "Max iterations: 3" lived only as a sentence in a prose module, so every restart of the agent forgot it — MEASURED, one real slug reached 38 graded rounds. Reads what `dz qe-bridge` already wrote (signoff-<runId>.json / failed-*.json under features/<slug>/.fa-state/qe-bridge) and writes NOTHING itself, so it answers for runs already past. A round is a runId, not a file; an attempt with no verdict is counted separately and never merged; ONE directory, never a union across checkouts. FAILS CLOSED: if any record cannot be counted the verdict is NOT ESTABLISHED, never a smaller number presented as the answer. exit 0 under the ceiling / 1 at-or-over — the owner decides, this never judges whether the rounds were warranted / 2 NOT ESTABLISHED, which is never "zero rounds"
2062
2191
  dz restart-advisor --slug <feature> [--threshold C|D] [--rounds <n>] [--json] # manual, read-only advice over this feature's QE history. Defaults: threshold D, rounds 2. Reads only features/<slug>/.fa-state/checkpoints.jsonl and .dz/fa-training/<slug>/qe.jsonl; when both carry QE rounds they must normalize identically and are never unioned. RESTART_CODE_STAGE is a recommendation for the operator, not an action: autoAction is always false, and every firing result includes an explicit decision-log line. exit 0 established recommendation/no-recommendation / 2 invalid or not established / 1 unexpected runtime failure
@@ -2071,12 +2200,12 @@ dz backlog <sub> add "<idea>" | list | show <id> | goals [--validate] | roul
2071
2200
  dz sign --init --out <path> | --pack <dir> --key <path> # --init: generate the Ed25519 keypair (private OUTSIDE the repo, prints the public key for keys/dz.pub); else sign a pack's manifest + CycloneDX SBOM
2072
2201
  dz sbom --pack <dir> [--out <file>] # emit the CycloneDX 1.5 SBOM for a pack standalone (file-level bill of materials); print to stdout or write to a file
2073
2202
  dz verify-pack --pack <dir> [--pubkey <path>] # signature check of a pack: fail-closed, and the trust root comes from the repo, never from the pack it is verifying
2074
- dz guard check --op <publish|teach|consolidate|reindex> [--text <s>] [--json] [--force <reason>] # declarative constraint layer before self-mutating ops: HARD violation → block (exit 1), SOFT → warn; zero-config defaults, .dz/guard.json to customise; dz guard --init | dz guard log (append-only audit). dz publish runs it automatically (--no-guard "<reason>" = logged escape hatch)
2203
+ dz guard check --op <publish|teach|consolidate|reindex> [--text <s>] [--json] [--force <reason>] # declarative constraint layer before self-mutating ops: HARD violation → block (exit 1), SOFT → warn; zero-config defaults, .dz/guard.json to customise; dz guard --init | dz guard log (append-only audit). The built-in SOFT `rounds-traced` rule warns at 10 packages/ commits since the latest round receipt (or the ledger's first row when no round exists); a null git fact is `not measured`, and `.dz/config.json` `rounds.traced:false` is a named skip. dz publish runs it automatically (--no-guard "<reason>" = logged escape hatch)
2075
2204
  dz guard promote [--dry-run | --apply] [--window-days <N>] [--periods <N>] [--json] # lesson → guard-rule promotion: ranks lessons by firings × cost, SHADOW-replays each candidate over real commits, and proposes a rule only after TWO consecutive wins AND two window-lengths of REAL elapsed time since first observation. Non-dry runs add bounded prospective funnel evidence to .dz/promotion-state.json without feeding the verdict; --dry-run remains write-free. --apply installs SOFT rules only; promotions/refusals remain under features/guard-promotion/promotions/
2076
2205
  dz feature-adr-setup --guards [--loc-cap <n>] [--apply] # P3: scaffold DETERMINISTIC guard tests into the project — guards.config.json + a zero-dependency check.mjs runner (LOC cap, secret scan, frozen-file sha256 pins, waivers-with-reasons); wire `node architecture/guards/check.mjs` into CI
2077
- 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)
2206
+ dz publish [--filter <name>] [--bump-only] [--claim-check <off|warn|error>] [--mirror-cmd <cmd>|--no-mirror] [--allow-sibling-drift] [--include-drifted] (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; sibling-drift + packed-install-smoke gates, feature publish-sibling-drift-gate, ADR-001 — a workspace sibling whose registry build differs blocks with a `--filter <batch>,<S>` fix-it, --allow-sibling-drift overrides (logged to .dz/guard-audit.jsonl), --include-drifted auto-extends the batch; the whole batch is packed + installed together and every bin runs `--version`, exit 0 + non-empty stdout required)
2078
2207
  dz parity [--target <name>] [--json] # honest feature×target map COMPUTED from the capability model — full / manual (via which form) / absent, per target
2079
- 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)
2208
+ 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), packed-install smoke (same rule as dz publish's, feature publish-sibling-drift-gate — pack the batch, install together, `--version` every bin) when the set has a bin; --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)
2080
2209
  dz auto-canonicalize --source <github-url> --pack <skills-pack>
2081
2210
  dz sync-upstream [--package <dir>] [--list] [--all]
2082
2211
  dz drift-check [--all] [--json] [--project <dir>] # CI gate: exit 1 on NEW shared-skill drift (baseline: .dz/drift-allowlist.json; --all incl .claude dogfood)
@@ -2101,7 +2230,7 @@ dz import-ecc [--local-path <dir>] [--select id,id,...] [--limit N] [--out
2101
2230
  ```
2102
2231
 
2103
2232
  Built-in, not a command: `dz help` prints this same USAGE and is handled before the dispatch switch,
2104
- so `dz --help` lists 92 names while the canonical inventory above holds 91 (ADR-001 of feature
2233
+ so `dz --help` lists 93 names while the canonical inventory above holds 92 (ADR-001 of feature
2105
2234
  `command-count-triad`).
2106
2235
 
2107
2236
  **Adding a command — the whole checklist, in order.** It replaces the seven-place folklore that used
@@ -2244,27 +2373,25 @@ echo "how do I handle replication lag?" | dz brain ground --budget 2000 # eage
2244
2373
 
2245
2374
  ### Live self-learning panel (`dz statusline`)
2246
2375
 
2247
- The owner's combined panel keeps the mirror indicator silent on equal lesson counts. On divergence
2248
- it shows `dz 🎓 N (mirror M ⚠)`. A mirror that EXISTS but cannot be read shows
2249
- `dz 🎓 N (mirror unreadable ⚠)`; a project with NO mirror at all stays silent — there is nothing to
2250
- compare, and lighting up there would make the indicator permanent noise.
2251
- The mirror figure counts LESSONS only — backlog ideas and book units are excluded, so the indicator
2252
- is comparable with the lexical count instead of lighting up permanently. It is read from
2253
- `vectorLessonRows`, never from `vectorRows`: that field keeps counting the whole mirror because the
2254
- store guard reads it as an integrity signal, and narrowing it would look like a store collapse.
2255
- The JSON payload exposes these warnings as `patternMirror`; parity omits that field. Both surfaces
2256
- carry the indicator — the combined panel helper and `dz statusline` itself.
2376
+ The panel always names the mirror: `mirror: N lessons (pending M)` when readable, or `mirror: absent` when it is not configured.
2377
+ Its JSON `mirror` section reports `{available, rows, lessons, pending, source:'agentdb'}`; `patternMirror` compares only mirrorable lexical lessons and reports class/noise exclusions.
2378
+
2379
+ A mirror that EXISTS but cannot be read remains a distinct degraded state (`mirror: unreadable ⚠`).
2380
+ Mirror lesson counts exclude backlog ideas and book units, while the lexical comparison also excludes
2381
+ class lessons and noise rejected by the writer. `vectorRows` remains the full physical count because
2382
+ the store guard consumes it as an integrity signal.
2257
2383
 
2258
2384
  `dz statusline` renders a compact Claude Code statusline that surfaces dz's learning at a glance:
2259
2385
 
2260
2386
  ```
2261
- 🎓 dz: 12 (9 active · 3 quarantined) (mirror 11 ) · 🧠 3 sources (223/315/60)
2387
+ 🎓 dz: 12 (9 active · 3 quarantined) · mirror: 11 lessons (pending 1) · 🧠 3 sources (223/315/60)
2262
2388
  ```
2263
2389
 
2264
- The parenthesized split is read only from the primary lexical store. A row is quarantined exactly
2390
+ The parenthesized split is read from the primary lexical store; its JSON source is `lexical+mirror`
2391
+ when the mirror is readable and `lexical` otherwise. A row is quarantined exactly
2265
2392
  when parsed metadata has `qStatus: "quarantined"`; historical fields do not count. The quarantine
2266
2393
  fragment is omitted at zero, `⚠` appears at one third of the pool, and `⚠ tiers ΔN` reports a
2267
- quarantine-label difference greater than five rows from the vector mirror. `M sources` is how many
2394
+ quarantine-label difference greater than five rows across the mirrorable populations. `M sources` is how many
2268
2395
  brain sources (book-KBs / grounding stores) are wired, and the parenthesised list after it gives the
2269
2396
  KU volume of EACH source in brain order — four sources of 300 units and four of three read
2270
2397
  identically without it. The list is omitted (bare source count) when the volumes cannot be
@@ -2426,75 +2553,36 @@ the exact `dz store-guard --reset --project '<dir>'` command. `--status` and gua
2426
2553
  selected lexical source; when both SQLite and JSONL exist they also name the ignored JSONL row count.
2427
2554
  The snapshot helper supports both lexical formats.
2428
2555
 
2429
- ### Usage estimate (`dz usage`)
2430
- > **Pin the weekly reset to an ABSOLUTE instant.** `weeklyResetAnchor: "Wed 08:59"` is
2431
- > server-timezone-relative — measured: the same moment lands a week apart under UTC vs `+03:00`, so
2432
- > after a real account reset the counter can keep showing the OLD week for hours while printing the
2433
- > "correct" clock time. Add your offset: `"Wed 08:59 +03:00"` in `.dz/config.json` — the boundary
2434
- > then never moves with the machine's timezone, and `dz usage` prints the full anchor
2435
- > (`resets Wed 08:59 +03:00`). Without an offset it warns on every run.
2556
+ ### Usage spend (`dz usage`)
2436
2557
 
2437
-
2438
- `dz usage` prints a READONLY, never-throw ESTIMATE of Claude SESSION and WEEKLY token usage,
2439
- aggregated from your local `~/.claude/projects/**/*.jsonl` transcripts. Weekly counts start at the
2440
- configured fixed local anchor (`memory.usage.weeklyResetAnchor`, for example `Wed 08:59`). Session
2441
- counts use the active fixed-length transcript block (`memory.usage.sessionBlockHours`, normally `5`).
2558
+ `dz usage` prints a READONLY, never-throw spend report over the last seven UTC calendar days,
2559
+ aggregated from local `~/.claude/projects/**/*.jsonl` Claude Code and subagent transcripts. Every day
2560
+ shows weighted tokens, raw input/output/cache-read/cache-write tokens, and event count; the report
2561
+ also carries a seven-day total and cost-weighted model shares.
2442
2562
 
2443
2563
  ```bash
2444
- dz usage # session ~74% (resets 19:00) · week ~76% fable-bound (resets 08:59) · estimated
2445
- dz usage --json # {"sessionPct":74,"weeklyPct":76,"sessionTokens":...,"weeklyTokens":...,"resetsAt":{...},"limits":{...},"estimated":true}
2446
- ```
2447
-
2448
- **Exit code is 0 ALWAYS** (even unconfigured/error → all-null JSON) — a probe must never distinguish
2449
- "usage unknown" from "command failed" via a non-zero exit. Percentages are **ESTIMATES** from local
2450
- transcripts; claude.ai/settings/usage is authoritative. They are `null` until you configure the
2451
- plan-dependent limits in `.dz/config.json`:
2452
-
2453
- ```json
2454
- {
2455
- "memory": {
2456
- "usage": {
2457
- "sessionTokenLimit": 200000000,
2458
- "weeklyTokenLimit": 1000000000,
2459
- "weeklyResetAnchor": "Wed 08:59",
2460
- "sessionBlockHours": 5,
2461
- "weeklyTokenLimitByModel": { "fable": 500000000 }
2462
- }
2463
- }
2464
- }
2564
+ dz usage
2565
+ dz usage --json
2465
2566
  ```
2466
2567
 
2467
- `sessionTokenLimit` and `weeklyTokenLimit` are optional; absent means that pct is `null` (unknown,
2468
- never `0`). `weeklyTokenLimit` is the all-model weekly limit. `weeklyTokenLimitByModel` is optional;
2469
- when it has at least one valid model limit (`fable`, `opus`, `sonnet`, or `haiku`), `weeklyPct` is the
2470
- binding per-model percentage and `--json` adds:
2568
+ The weighting is input `1x`, five-minute cache write `1.25x`, one-hour cache write `2x`, cache read
2569
+ `0.1x`, and output `5x`. The JSON contract is:
2471
2570
 
2472
2571
  ```json
2473
2572
  {
2474
- "weeklyByModel": { "fable": { "tokens": 380, "pct": 76 } },
2475
- "limits": { "session": 200000000, "weekly": 1000000000, "weeklyByModel": { "fable": 500 } }
2573
+ "sessionPct": null,
2574
+ "weeklyPct": null,
2575
+ "routing": "disabled-by-design",
2576
+ "spend": { "days": [], "total7d": {}, "byModel": {} },
2577
+ "reqeDue": 0
2476
2578
  }
2477
2579
  ```
2478
2580
 
2479
- When no per-model limits are configured, the JSON shape stays at the legacy fields:
2480
- `sessionPct`, `weeklyPct`, `sessionTokens`, `weeklyTokens`, `resetsAt`, `limits`, `estimated`.
2481
-
2482
- Calibrate from human-transcribed claude.ai percentages with one command:
2483
-
2484
- ```bash
2485
- dz usage --calibrate --session 20 --weekly 50 --model fable=76 --project .
2486
- ```
2487
-
2488
- It computes each limit as `currentTokens / (pct / 100)`, writes only `memory.usage` in
2489
- `.dz/config.json`, and records `calibratedAt` plus `source: "claude.ai/settings/usage"`. Invalid
2490
- percentages, unknown models, missing transcripts, and zero-token calibration requests are skipped with
2491
- exit code `0`.
2492
-
2493
- This feeds `/feature-adr`'s **usage-adaptive routing** — the pipeline probes `dz usage --json` at each
2494
- phase boundary and pre-emptively routes the remaining stages to Codex when `sessionPct` or the binding
2495
- `weeklyPct` crosses the threshold (default 70%). **Honest caveat:** at TRUE exhaustion even the Codex
2496
- dispatch dies (`codex:codex-rescue` is a Claude wrapper), so the switch must happen BEFORE — the
2497
- pre-emptive probe, not reactive detection, is the real defense.
2581
+ The percentage keys deliberately remain present and always `null` for the existing feature-adr probe.
2582
+ Provider limits are not measurable locally: there is no provider usage API, weekly resets are tied to
2583
+ an account, and ad-hoc resets invalidate a learned denominator. `--calibrate` was removed and returns
2584
+ exit `2` with that reason. Existing `.dz/config.json` limit fields are neither read for this report nor
2585
+ modified.
2498
2586
 
2499
2587
  ### Claim check (`dz claim-check`)
2500
2588
 
@@ -3135,6 +3223,61 @@ dz publish --filter skills-devops # publish specific package
3135
3223
  dz publish --filter skills-devops --bump-only # bump version only, no publish
3136
3224
  ```
3137
3225
 
3226
+ #### Sibling-drift + packed-install-smoke gates (feature `publish-sibling-drift-gate`, ADR-001)
3227
+
3228
+ `workspace:^`/`workspace:~`/`workspace:*` pins a sibling dependency to the exact version on disk —
3229
+ but the REGISTRY under that version can carry an OLDER build if the sibling changed without a
3230
+ version bump (the 2026-09-13 incident: 15 minutes of a broken `@dzhechkov/harness-cli` on npm). Two
3231
+ HARD gates catch this before anything ships, and both run on `--dry-run` too — a dry run ALWAYS
3232
+ prints both verdicts, even once sibling-drift already blocks (no gate goes silent because an
3233
+ earlier one failed):
3234
+
3235
+ 1. **Sibling drift.** For every workspace `S` a batch package depends on (`dependencies`,
3236
+ `peerDependencies` AND `optionalDependencies` — all three pin and ship identically) that is NOT
3237
+ itself part of the batch, the gate hashes `S`'s published inventory — `dist/**`, every path
3238
+ named in `package.json#files`, and every `bin` target (not `dist/**` alone: a changed bin script
3239
+ or template outside `dist/` is drift too) — plus a normalized `package.json` (version/gitHead/
3240
+ `_*`/`imports`/`browser`/`sideEffects`/`man` compared, not just entry points) against the
3241
+ workspace copy. A mismatch blocks; anything this gate cannot build — a fetch that fails
3242
+ (offline, 404), an unreadable/invalid `package.json` on either side, or a `workspace:`-spec'd
3243
+ name it does not recognize — is `unavailable` and blocks too; `unavailable` is never silently
3244
+ treated as a pass. `--include-drifted` auto-extends the batch and RE-CHECKS the expanded batch's
3245
+ own new edges until nothing new drifts (a fixed point over transitive drift, capped at the
3246
+ package count) — a folded-in sibling that itself depends on a drifted sibling is not missed.
3247
+ 2. **Packed-install smoke.** The batch's `.tgz` files are installed TOGETHER into a clean directory
3248
+ (siblings outside the batch resolve from the registry — exactly like a fresh user), then every
3249
+ declared `bin` — including one whose target file turns out NOT to exist after the install,
3250
+ which blocks with `declared bin missing after packed install` rather than silently reading as
3251
+ n/a — runs `--version` and must exit 0 with non-empty stdout. On a **live** publish this gate
3252
+ packs each package's tarball exactly ONCE, right after its own version bump — the SAME bytes are
3253
+ then smoke-tested and handed to `npm publish <tgz>`; their sha256 is printed
3254
+ (`tarball <pkg>@<ver> sha256:<hex>`) and written to `.dz/guard-audit.jsonl` alongside every
3255
+ pass/block/override/n-a verdict, so "the smoke tested what shipped" is a checkable claim rather
3256
+ than an architectural one. `dz release --dry-run` plans the same steps inside its `smoke` gate
3257
+ (`smoke:packed-install:*`) and `dz release`'s own execution judges the `--version` step through
3258
+ the identical rule — the two doors apply one rule, for real, not only on paper.
3259
+
3260
+ ```bash
3261
+ dz publish --filter harness-cli # ✓ sibling drift: none / ✓ packed install smoke, or BLOCKED with a fix-it command
3262
+ dz publish --filter harness-cli --allow-sibling-drift # override (logged to .dz/guard-audit.jsonl) — refused if the audit write itself fails
3263
+ dz publish --filter harness-cli --include-drifted # auto-extend the batch (transitively) with the drifted sibling(s) instead of blocking
3264
+ ```
3265
+
3266
+ A BLOCKED sibling-drift verdict always names the fix: `add S to the batch (--filter <batch>,<S>) or
3267
+ publish it first`.
3268
+
3269
+ ```
3270
+ $ dz publish --filter harness-cli
3271
+ dz publish: BLOCKED harness-cli — sibling drift: @dzhechkov/memory@0.2.20 on the registry differs from the workspace (3 file(s)); add @dzhechkov/memory to the batch (--filter harness-cli,@dzhechkov/memory) or publish it first
3272
+ dz publish: refusing to publish (1 sibling-drift violation(s))
3273
+
3274
+ $ dz publish --filter harness-cli --yes
3275
+ dz publish: tarball @dzhechkov/harness-cli@0.8.24 sha256:9f2c…e10a
3276
+ dz publish: ✓ packed install smoke
3277
+ ✓ @dzhechkov/harness-cli 1.0.0 → 1.0.1 published (confirmed by registry after 1 probes)
3278
+ sha256:9f2c…e10a
3279
+ ```
3280
+
3138
3281
  ### dz auto-canonicalize — discover skills in GitHub repos
3139
3282
 
3140
3283
  ```bash
@@ -3416,6 +3559,136 @@ dz setup --target claude-code --preset devops --memory agentdb # AgentDB (vect
3416
3559
  | **MCP tools** | 0 | pattern, reflexion, causal, skill, hierarchy (whatever the pinned `agentdb` build exposes — `dz` hardcodes no count) |
3417
3560
  | **Dependencies** | None | agentdb (optional, via npx) |
3418
3561
 
3562
+ ### Repeat setup — the backend comes from your config, not from a forgotten flag
3563
+
3564
+ **A plain `dz setup --target claude-code` re-run (no `--memory`) now keeps the backend `.dz/config.json`
3565
+ already names — a repeat setup takes the backend from your config; downgrading to jsonl needs an
3566
+ explicit `--memory jsonl`.** Before this feature, a repeat `dz setup --target claude-code` with no
3567
+ `--memory` decided the backend as "`--memory` or jsonl" and silently reset an agentdb project back
3568
+ to jsonl, dropping `.dz/agentdb-writer.mjs` from the `SessionStart` hook — the config still said
3569
+ `agentdb`, the hooks quietly stopped writing to it.
3570
+
3571
+ | You run | `.dz/config.json` before | Result |
3572
+ |---|---|---|
3573
+ | `dz setup --target claude-code` | `memory.backend: "agentdb"` | stays **agentdb** — hooks/writer untouched, byte-identical to the flagged run |
3574
+ | `dz setup --target claude-code --memory agentdb` | anything, or absent | **agentdb** (explicit, unchanged from before) |
3575
+ | `dz setup --target claude-code --memory jsonl` | `memory.backend: "agentdb"` | **downgrades to jsonl** — printed loudly: `⚠ memory backend downgraded agentdb → jsonl by --memory jsonl`; the config is rewritten to jsonl too (even without `--force`), so the two never disagree |
3576
+ | `dz setup --target claude-code` | no config yet | **jsonl** (the documented default — unchanged) |
3577
+
3578
+ Every run also prints where the backend came from, e.g. `memory backend: agentdb (from
3579
+ .dz/config.json)` / `memory backend: jsonl (from --memory)` / `memory backend: jsonl (default — no
3580
+ .dz/config.json)` — never left to be inferred from the flag alone.
3581
+
3582
+ `dz doctor` cross-checks the two truths too: a new `memory hooks match config` row goes red when
3583
+ `.dz/config.json`'s `memory.backend` and the ACTUAL `SessionStart`/`SessionEnd`/`PreCompact` hooks in
3584
+ `.claude/settings.json` disagree in either direction, naming the exact fix (`run: dz setup --target
3585
+ claude-code --memory agentdb`, or the jsonl equivalent).
3586
+
3587
+ ### The apply leg — `dz setup --memory agentdb` installs the whole loop, not two of three
3588
+
3589
+ Self-learning is a three-leg loop: **collect** (session hooks write into the store above),
3590
+ **rank** (`dz teach` / `dz recall` / `dz consolidate` score and retrieve), and **apply** (a
3591
+ `UserPromptSubmit` hook injects the top-ranked lessons back into your next prompt, automatically).
3592
+ Before this feature `dz setup` shipped the first two legs only — the apply leg's files existed
3593
+ solely in this repo's own `.claude/helpers/`, so every OTHER project that ran `dz setup --memory
3594
+ agentdb` got collection and ranking, but never automatic recall (MEASURED: a clean install wrote no
3595
+ `UserPromptSubmit` entry at all, on 0.8.10 and 0.8.24 alike, with or without `--memory agentdb`).
3596
+
3597
+ `dz setup --target claude-code --memory agentdb` now installs all three, additively (a repeat run
3598
+ changes nothing; a foreign hook you wrote yourself is left exactly where it is):
3599
+
3600
+ | What | Where | Purpose |
3601
+ |---|---|---|
3602
+ | `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 |
3603
+ | `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 |
3604
+ | `UserPromptSubmit` entry | `.claude/settings.json` | Runs the recall hook (`node ".../recall-hook.cjs" 2>/dev/null \|\| true` — a broken hook never blocks a prompt) |
3605
+ | `SessionStart` entry | `.claude/settings.json` | Spawns the embed daemon detached (`sh -c 'nohup node ".../dz-embed-daemon.mjs" ... & exit 0'`) |
3606
+
3607
+ The hook resolves your harness-core installation by an ABSOLUTE PATH baked in at `dz setup` time
3608
+ (the installation actually running the command), first in its candidate list — no more guessing at
3609
+ `/usr/lib/node_modules/...`, which silently failed on any other npm prefix (nvm, `/usr/local`, a
3610
+ global install elsewhere). Project-local candidates (`node_modules/`, a monorepo checkout) stay as
3611
+ fallbacks after it.
3612
+
3613
+ **On the jsonl backend** (no `--memory agentdb`) the step reports `skipped` with the reason named —
3614
+ the embed daemon needs agentdb's transitive `@huggingface/transformers`/`@xenova/transformers`
3615
+ dependency, which jsonl mode never installs. This is an honest boundary, not a missing feature: run
3616
+ `dz setup --memory agentdb` when you want the automatic apply leg.
3617
+
3618
+ **Memory footprint and idle lifecycle.** OWNER-MEASURED: the resident embed daemon holds ~1.0 GB
3619
+ RSS with only 7 patterns loaded — the multilingual embedding model dominates that number, not the
3620
+ pattern count, so it does not grow materially with a larger store. The recommended mental model is
3621
+ "spawns on session start, exits after 30 minutes idle" (`DZ_EMBED_IDLE_MS`, default
3622
+ `30 * 60 * 1000`) — not "runs forever": a machine that cannot spare ~1 GB resident for a background
3623
+ process should stay on the jsonl backend rather than tune the idle window down to zero, since `0`
3624
+ (or a negative/non-numeric value) now DISABLES the idle exit entirely (issue #10 defect 7) rather
3625
+ than exiting immediately — the daemon is meant to be reaped by inactivity, not pinned forever by a
3626
+ misconfigured knob. A value above Node's 32-bit `setTimeout` ceiling (2 147 483 647 ms, ~24.9 days)
3627
+ is clamped to that ceiling with a logged line (`idle clamp: <value> -> 2147483647 ms`) instead of
3628
+ silently wrapping to ~1 ms — the pre-fix bug that made a "30 days" idle setting exit in about a
3629
+ second.
3630
+
3631
+ **`dz doctor` and `dz parity` measure this, they don't declare it.** Both read the SAME
3632
+ `applyLegStatus(root)` function — a plain filesystem check (do the two files exist? does
3633
+ `settings.json` reference them?) — so neither can say "installed" about a project that only has the
3634
+ memory backend configured:
3635
+
3636
+ ```bash
3637
+ dz setup --target claude-code --memory agentdb # installs all three legs
3638
+ dz doctor # apply-leg installed: ✓ (or a red row naming: dz setup --target claude-code --memory agentdb)
3639
+ dz parity --target claude-code # ✓ Self-learning: automatic apply-leg via UserPromptSubmit hook (auto recall)
3640
+ ```
3641
+
3642
+ Without the apply leg installed, `dz doctor` prints a RED `apply-leg installed` check with the
3643
+ exact fix command, and `dz parity`'s `learning-apply` row for `claude-code` reads `◐ manual` with
3644
+ `not installed — run dz setup --target claude-code --memory agentdb` — never a silent `✓` read off
3645
+ a capability table that has never looked at your project.
3646
+
3647
+ **Deeply nested checkout? The socket moves to a short tmpdir path, automatically.** A unix socket
3648
+ path is capped at ~100-108 bytes by the OS (`sun_path`); `<project>/.dz/embed.sock` can exceed that
3649
+ in a project nested several directories deep, and past the limit the daemon used to bind nowhere
3650
+ while still looking "ready". It now falls back to a short, deterministic path under your system temp
3651
+ directory and drops a pointer file (`.dz/embed.sock.path`) the recall hook and `dz doctor` both
3652
+ read — `dz doctor`'s liveness line names the actual path in that case: `embed socket present at
3653
+ /tmp/dz-embed-<hash>.sock (tmpdir-short: project path 118 bytes > 100)`. Nothing to configure; set
3654
+ `DZ_EMBED_SOCKET` yourself only if you need to pin an exact path.
3655
+
3656
+ #### Post-install verification (acceptance check, dz-harness-hub issue #10)
3657
+
3658
+ A live end-to-end check for a fresh `--memory agentdb` install — every line below is something the
3659
+ install must produce, not a smoke test of the CLI parsing its own flags:
3660
+
3661
+ ```bash
3662
+ # 1. Install — all three legs, on a bare Node 20/22 host (no C toolchain required: AM-2 pins
3663
+ # better-sqlite3@^11, the range with a prebuilt binary for Node 20's ABI 115).
3664
+ dz setup --target claude-code --project . --memory agentdb
3665
+
3666
+ # 2. Both hook entries actually landed in settings.json.
3667
+ grep -c recall-hook.cjs .claude/settings.json # → 1
3668
+ grep -c dz-embed-daemon.mjs .claude/settings.json # → 1
3669
+
3670
+ # 3. The pinned dependency resolved to a version WITH a prebuilt binary.
3671
+ node -e "console.log(require('./node_modules/better-sqlite3/package.json').version)" # → 11.x
3672
+
3673
+ # 4. A lesson taught right after setup — before any session has ever ended — still mirrors
3674
+ # (AM-4: setup pre-creates an empty .dz/agentdb.db so this is never silent).
3675
+ dz teach "acceptance check lesson" --allow-cold-start # → "↳ mirrored to vector tier (agentdb)"
3676
+
3677
+ # 5. The embed daemon comes up (spawned by SessionStart; give it up to ~20s to load the model).
3678
+ timeout 20 sh -c 'until [ -S .dz/embed.sock ]; do sleep 1; done' && echo "daemon socket is up"
3679
+
3680
+ # 6. The recall hook actually injects context for a relevant prompt, and stays silent for one
3681
+ # that clears no relevance floor — never a hang, never a stray print on stdout.
3682
+ echo '{"prompt":"acceptance check lesson"}' | node .claude/helpers/recall-hook.cjs # → one line of JSON, hookSpecificOutput.additionalContext non-empty
3683
+ echo '{"prompt":"unrelated weather report"}' | node .claude/helpers/recall-hook.cjs # → empty stdout, exit 0
3684
+
3685
+ # 7. Doctor confirms the leg is alive, not merely installed.
3686
+ dz doctor # → "apply-leg alive (embed daemon): embed socket present at <path> — recall injection can run"
3687
+ ```
3688
+
3689
+ Every step above is a REPRODUCER, not a claim: run it on a scratch project before trusting a `dz
3690
+ setup --memory agentdb` install on a new machine.
3691
+
3419
3692
  ### AgentDB self-learning algorithms
3420
3693
 
3421
3694
  When using `--memory agentdb`, the following algorithms automatically tune search quality:
@@ -3756,8 +4029,8 @@ Learned routing outcomes (what `auto-cost` currently believes):
3756
4029
  Cold-start (no history) walks a cheapest-first chain and escalates on failure; once a model clears the bar it is
3757
4030
  chosen directly. Two invariants held hard: `auto-cost` on the **qe** stage only ever ranks the **cross-family of
3758
4031
  the coder** (a model never self-QEs), and the whole thing is **opt-in** — with no `auto-cost` spec anywhere the
3759
- pipeline is byte-identical and the store is never touched. Layered under the usage-adaptive override (a session
3760
- near its limit still switches to Codex first). Storage is a plain JSON file — zero native deps.
4032
+ pipeline is byte-identical and the store is never touched. Provider-limit overrides are disabled by design;
4033
+ `auto-cost` decisions use only their own gated outcome history. Storage is a plain JSON file — zero native deps.
3761
4034
 
3762
4035
  ### `dz bto-optimize` — when you want to improve a skill WITHOUT gaming its own benchmark
3763
4036
 
@@ -3891,7 +4164,7 @@ invariants: HARD rules **block** the operation, SOFT rules warn. Zero config nee
3891
4164
  cover the known rakes; `.dz/guard.json` (via `dz guard --init`) exists only if you want to tune a severity
3892
4165
  or disable a rule.
3893
4166
  ```bash
3894
- dz guard check --op publish # no-workspace-star · no-skill-drift · no-secrets · readme-consistency · readme-first · signature-fresh · skills-registrable · lockfile-in-sync · marketplace-parity · no-stubs · review-round · licence-hold
4167
+ dz guard check --op publish # no-workspace-star · no-skill-drift · no-secrets · readme-consistency · readme-first · signature-fresh · skills-registrable · lockfile-in-sync · marketplace-parity · no-stubs · review-round · rounds-closed · rounds-traced · licence-hold
3895
4168
  dz guard check --op teach --text "the fix: export sk-abc..." # → BLOCK (exit 1): looks like a credential
3896
4169
  dz guard log # append-only audit: every verdict + every forced override
3897
4170
  ```
@@ -4373,6 +4646,119 @@ stay in the previous embedding space until their own reindex runs) instead of le
4373
4646
  Only models listed in `KNOWN_EMBED_DIMS` are accepted — the store's `vectorDim` is 384, so a 768-dim
4374
4647
  model is rejected rather than silently writing mismatched vectors.
4375
4648
 
4649
+ #### Pre-reindex snapshots pile up — rotating them
4650
+
4651
+ Every `dz brain reindex` (and every `dz vector reindex`) copies the store to
4652
+ `<db>.pre-reindex-<ms>.bak` first, as an undo point. Nothing pruned those copies before this feature —
4653
+ 13 snapshots / 50 MB accumulated on one real brain home before anyone noticed. Two ways to clear them:
4654
+
4655
+ ```bash
4656
+ $ dz brain reindex
4657
+ dz brain reindex: re-embedded 812 KU vector(s) with Xenova/paraphrase-multilingual-MiniLM-L12-v2 (manifest v4)
4658
+ snapshot: /home/you/.dz/brain/agentdb.db.pre-reindex-1757754000000.bak
4659
+ ↳ snapshots: kept 6, removed 8 (41.2 MB)
4660
+ ```
4661
+
4662
+ `dz brain reindex` now rotates automatically after a SUCCESSFUL reindex, keeping the newest 3
4663
+ snapshot families by default (`opts.keepSnapshots`) — the family it just created is never removed,
4664
+ even at `keep=0`. A reindex that itself fails rotates nothing (the old snapshots may be the only
4665
+ working copy left).
4666
+
4667
+ When you cannot or do not want to run a reindex — e.g. a hub where reindexing itself is off-limits
4668
+ today, but old snapshots have piled up regardless — rotate directly:
4669
+
4670
+ ```bash
4671
+ $ dz brain snapshots
4672
+ dz brain snapshots — 9 family(-ies) @ /home/you/.dz/brain/agentdb.db
4673
+ 2026-09-01T08:00:00.000Z ms=1756713600000 2 file(s) 4.1 MB
4674
+ 2026-09-05T08:00:00.000Z ms=1757059200000 2 file(s) 4.1 MB
4675
+ ...
4676
+ (dry run — pass --prune to remove families older than --keep)
4677
+
4678
+ # The PROJECT store (.dz/agentdb.db, written by the vector-tier reindex) is addressed with --project:
4679
+ $ dz brain snapshots --project .
4680
+ dz brain snapshots — 13 family(-ies) @ /path/to/project/.dz/agentdb.db
4681
+
4682
+ $ dz brain snapshots --prune --keep 3
4683
+ dz brain snapshots: kept 6, removed 12 (37.1 MB)
4684
+ removed: agentdb.db.pre-reindex-1756713600000.bak, agentdb.db.pre-reindex-1756713600000.bak.embed-manifest.json, ...
4685
+ ```
4686
+
4687
+ `dz brain snapshots` never touches anything but the brain's own strictly-named
4688
+ `agentdb.db.pre-reindex-<ms>.bak` family (a symlink or a differently-named neighbour is left alone);
4689
+ without `--prune` it only lists, and a bad `--keep` — negative, non-numeric, empty, or whitespace-
4690
+ padded (`--keep ' 2'`) — is refused with exit 2 rather than silently treated as 0 or 2.
4691
+
4692
+ **Fix-round hardenings (2026-09-13, after a Codex review found the earlier version could delete
4693
+ every snapshot on a bad `--keep`):**
4694
+
4695
+ - **10-minute grace period.** A family younger than 10 minutes is never removed, even past `--keep`
4696
+ — there is no flag to shorten or disable this; it protects a snapshot a DIFFERENT, still-running
4697
+ process just wrote from looking "old" to a `--prune` invoked moments later.
4698
+ - **Removal order inside a family: siblings first, `.bak` last, only if every sibling actually
4699
+ unlinked.** If a sibling (the manifest, `-shm`, `-wal`) fails to unlink, the `.bak` — the one file
4700
+ that alone still proves the snapshot existed — is left in place, and the family's `⚠ .bak
4701
+ preserved after a sibling failure` line names it.
4702
+ - **A scan error blocks the whole rotation, not just that one file.** If the brain directory itself
4703
+ cannot be listed (or a file's status cannot be read) for a reason other than "it does not exist",
4704
+ the command prints `⚠ scan error(s), nothing removed this call` and removes NOTHING — an
4705
+ incomplete file list is never a safe basis for "these are the old ones".
4706
+
4707
+ ```bash
4708
+ $ dz brain snapshots --prune --keep 3
4709
+ dz brain snapshots: kept 6, removed 12 (37.1 MB)
4710
+ removed: agentdb.db.pre-reindex-1756713600000.bak, agentdb.db.pre-reindex-1756713600000.bak.embed-manifest.json, ...
4711
+ ⚠ .bak preserved after a sibling failure in famil(y/ies): 1756713600000 # only printed if a sibling unlink failed
4712
+ ⚠ scan error(s), nothing removed this call: readdir /home/you/.dz/brain: EACCES # only printed on a scan failure
4713
+ ```
4714
+
4715
+ **Snapshot lock (agentdb-snapshot-lock, 2026-09-13).** `--prune` now takes the SAME advisory lock a
4716
+ concurrent `dz brain reindex`/`reindexAgentdbRows` uses (`<store dir>/.dz/locks/agentdb-snapshot.lock`)
4717
+ before it removes anything — a `rotate --keep 0` racing a live reindex used to be able to delete the
4718
+ undo point the reindex was still relying on; now it waits for the lock, or reports the timeout
4719
+ explicitly instead of silently succeeding on an empty rotation:
4720
+
4721
+ ```bash
4722
+ $ dz brain snapshots --prune --keep 0 # a concurrent reindex is mid-flight and holds the lock
4723
+ dz brain snapshots: kept 0, removed 0 (0.0 MB)
4724
+ ⚠ 1 error(s): lock busy: the lock at …/.dz/locks/agentdb-snapshot.lock stayed held for 10000ms — …
4725
+ $ echo $?
4726
+ 1
4727
+ ```
4728
+
4729
+ A family a LIVE reindex marker names (`<db>.reindex-inprogress.json`, written for the duration of
4730
+ that reindex, not just the moment the lock is held) also survives `--prune`, even at `--keep 0`; an
4731
+ EXPIRED marker (older than 60 minutes) is cleaned up and named honestly in a `note:` line rather than
4732
+ either protecting it forever or treating its removal as a scan error:
4733
+
4734
+ ```bash
4735
+ $ dz brain snapshots --prune --keep 0
4736
+ dz brain snapshots: kept 2, removed 4 (12.3 MB)
4737
+ note: stale reindex marker (ms=1757754000000, startedAt=2026-09-13T01:00:00.000Z) ignored and removed
4738
+ ```
4739
+
4740
+ If the marker could not actually be removed (e.g. a permission error), the note says so instead of
4741
+ silently claiming success: `"… ignored, removal failed: <err>"` — never `"… ignored and removed"` for
4742
+ a removal that did not happen.
4743
+
4744
+ **Marker ownership + a stuck rollback (fix-round, 2026-09-13).** The marker file is now written and
4745
+ removed with an ownership token: a `dz brain reindex` genuinely still in flight refuses a SECOND
4746
+ concurrent reindex of the same store outright (no snapshot for the refused attempt), and nothing but
4747
+ the owning reindex can clear its own marker. If a reindex fails and its rollback cannot re-acquire the
4748
+ snapshot lock (e.g. `--prune` or another reindex is mid-critical-section), the marker is deliberately
4749
+ LEFT IN PLACE rather than cleared — the reported error names both the snapshot and the marker path and
4750
+ says `requires manual recovery`:
4751
+
4752
+ ```
4753
+ error: index failed: …; rollback failed: lock busy: …; snapshot at /home/you/.dz/brain/agentdb.db.pre-reindex-1757754000000.bak
4754
+ was not confirmed restored; marker at /home/you/.dz/brain/agentdb.db.reindex-inprogress.json is left
4755
+ in place — requires manual recovery
4756
+ ```
4757
+
4758
+ Recovery is manual: inspect the named snapshot, restore it by hand if the store looks wrong, then
4759
+ delete the marker file. **The snapshot lock's own stale threshold is 5 minutes** — a base whose own
4760
+ snapshot genuinely takes longer needs external coordination, not a bigger default.
4761
+
4376
4762
  **Before → after** (what changes when you enable it):
4377
4763
 
4378
4764
  | | Before (lexical only) | After (hybrid vector tier) |
@@ -4738,7 +5124,22 @@ refusal as the honest answer.
4738
5124
 
4739
5125
  ## Status
4740
5126
 
4741
- `harness-core v0.8.30` · `harness-cli v0.8.22` — **this release: the store guard tells "busy" from "broken",
5127
+ `harness-core v0.8.32` · `harness-cli v0.8.24` — **this release: `dz publish` refuses a broken pair, `dz setup`
5128
+ reads the memory backend from config, and the embed daemon says "ready" only with a socket that exists.**
5129
+ (1) Sibling-drift gate + packed install smoke: before any live `npm publish`, every `workspace:*` sibling on
5130
+ the registry is compared with the workspace (dist/files/bin + the shipping fields of package.json); a
5131
+ drifted sibling BLOCKS the batch (`add <sibling> to the batch or publish it first`), and each package that
5132
+ declares a `bin` is packed post-bump, installed into a clean directory and asked `--version` — the exact
5133
+ incident of 2026-09-13 (a CLI published against a core that did not export what it imports) is now caught
5134
+ in the dry run. Transport is transactional: the first failed `npm publish` stops the batch. (2) `dz setup`
5135
+ without `--memory` takes the backend from `.dz/config.json` (`memory backend: agentdb (from .dz/config.json)`),
5136
+ downgrades only on an explicit `--memory jsonl` with a warning, and `dz doctor` gained `memory hooks match
5137
+ config` — all three session events must invoke `.dz/agentdb-writer.mjs` when config says agentdb.
5138
+ (3) A project whose `.dz/embed.sock` path exceeds the unix limit gets a short socket in a private
5139
+ `<tmpdir>/dz-<uid>/` directory (mode 0700) with an atomically published pointer `.dz/embed.sock.path`; the
5140
+ daemon prints `ready` only after the socket exists and exits 3 on a bind failure. (4) The qe-bridge signoff
5141
+ record carries `writeSequence` (diagnostic sequencing metadata) so the tests no longer race on file mtimes.
5142
+ Previous release (0.8.30/0.8.22): **the store guard tells "busy" from "broken",
4742
5143
  and a publish is not finished until the public mirror confirms it.** (1) The learning-store guard used to
4743
5144
  turn ANY read failure into `unreadable` and refuse the write; a neighbour holding the SQLite write lock
4744
5145
  (`SQLITE_BUSY`) or a store still being initialised (`no such table`) now gets bounded retries and, if still
@@ -4761,7 +5162,7 @@ exports (`commandInventory`, `declaredCommands`, `dispatchedCommands`, `document
4761
5162
  `stripNonCode`, `validateExceptionList`, `INTERNAL_ENTRY_POINTS`, `PSEUDO_COMMANDS`) — instead of a
4762
5163
  fourth private regex and a fourth number. Four hidden commands (`mr-rakes`, `retro`,
4763
5164
  `feature-adr-setup`, `bto-optimize`) gained USAGE lines, so `--help` changed; `help` is a documented
4764
- pseudo-command, not a `case` label. **91 canonical commands, 92 names in rendered `--help`** (the 91
5165
+ pseudo-command, not a `case` label. **92 canonical commands, 93 names in rendered `--help`** (the 92
4765
5166
  plus the built-in `help`) — both COMPUTED from `cli.ts`, neither typed into a test. Three cross-family QE rounds closed
4766
5167
  six parser defects the naive version had: four that INVENTED a command (regex literals with a lone
4767
5168
  `}`, `case` labels in comments or templates, sub-verb labels from nested switches, ` dz <name>` lines
@@ -4781,6 +5182,43 @@ plan-route choice. Every failure is fail-open; versioned `.fa-state/decision-rec
4781
5182
  receipt coverage and repeat-related outcomes derivable offline. The hypothesis is external `[SRC],
4782
5183
  n=1`, books were silent on retrieval timing, and no runtime threshold gates a stage.
4783
5184
 
5185
+ `harness-core` / `harness-cli` — **staged, not published: `ts` and `minutesSincePrev` on the run-cost
5186
+ ledger.** The writer could not measure a stage's full duration (the workflow sandbox never calls
5187
+ `Date.now()`, for resume-safety) but it always knew the moment of each write and the run it belonged
5188
+ to — so every ledger row now also gets `ts` (the ISO instant it was recorded, next to `date`), and an
5189
+ `auto:true` row that carries a `runId` additionally gets `minutesSincePrev` (a number, one decimal
5190
+ digit, ≥ 0) plus `minutesSource: 'ledger-ts-delta'`, measured against the LAST row of the same run —
5191
+ found via `dz feature-adr-record --run-id <id>` or a `runId` already in the payload. When there is no
5192
+ prior row, or it carries no `ts`, the pair reads `minutesSincePrev: null` / `minutesSource:
5193
+ 'unavailable'` rather than a guess. `minutes` itself is untouched and stays `null` on every auto row:
5194
+ a partial measurement gets its own named field and source, and is never folded into the field that
5195
+ implies the whole stage's duration was known. `dz cadence`'s round summary reads unaffected fixtures
5196
+ unchanged — the new fields are additive, never repositioning an existing key.
5197
+
5198
+ **Fix-round-1 (cross-family review, grade B → 5 findings closed), still staged, not published:**
5199
+ a Codex `gpt-5.6-sol` review of the above found five contract gaps, all closed by measurement:
5200
+ (1) the reverse scan for the previous row's `ts` used to skip PAST a torn or non-object ledger line
5201
+ and keep looking further back, so an OLDER row for the same `runId` could be returned while a newer
5202
+ one sat hidden behind the corrupt line; it now stops and reports `unavailable` — named
5203
+ `ledger-corrupt-line` — the instant it hits a line it cannot parse as an object, never a guess made
5204
+ by skipping past what it could not read. (2) `--run-id` filling a MISSING `runId` was already correct
5205
+ — "missing" means absent, `null`, `''`, or a non-string value, the same "missing when absent or
5206
+ blank" rule `runnerId` uses — but it was untested beyond the plain non-empty-string case and
5207
+ undocumented; both are fixed now, behavior unchanged. (3) when `--run-id` actually fills the gap, the
5208
+ row is now tagged `runIdSource: 'cli-flag'` — the same provenance discipline `resolved-at-write`
5209
+ already gets for the OTHER runId source (write-time auto-resolution) — and an existing non-empty
5210
+ `runIdSource` is left untouched. (4) a payload's own `ts` is no longer trusted for the delta: `ts` on
5211
+ a ledger row is now ALWAYS the actual write instant, and a caller-supplied one survives, renamed,
5212
+ as `payloadTs` — no data lost, just no longer the value the delta measures from. (5) object key order
5213
+ now matches the documented convention exactly: a `runnerId` this call itself stamps keeps its position
5214
+ ahead of `ts`/`minutesSincePrev`/`minutesSource`, which land strictly last regardless of which fields
5215
+ the incoming payload already carried. `test/run-records.test.ts` (48, +10) and
5216
+ `test/feature-adr-record-cli.test.ts` (22, +7) cover all five, plus fractional-minute rounding
5217
+ (2m30s → 2.5, 1m3s → 1.1) and a pinned negative-delta case; the `ledger-minutes-since-prev-measured`
5218
+ mutation-gate entry was RE-MEASURED at `failingCount: 3` (three of the new tests now discriminate the
5219
+ same mutation, up from one) and the registry updated to match — `dz mutation-gate --only
5220
+ ledger-minutes-since-prev-measured` verdict `PROVEN`, exit 0.
5221
+
4784
5222
  `v0.8.8` — **staged, not published.** `dz init` now reports exactly one MCP and one hook outcome,
4785
5223
  adds `integrations-verify`, content-bound authorization, explicit skills-only opt-out, and named
4786
5224
  partial-failure exits; only receipt-proven live registration may be called emitted.
@@ -4911,7 +5349,7 @@ partial design). The compat floor is unchanged — this CLI uses no new core exp
4911
5349
 
4912
5350
  **v0.5.1** — published 2026-08-20. Ships `dz workflow run`, the portable plan enactor: it INTERPRETS a `loop-plan/1` plan instead of executing a rendered script, dispatching to `codex exec` or an isolated `claude -p`, exit 0/1/2/75 (75 = a typed pause whose last stdout line is a `wf-pause-envelope/1`). Requires `@dzhechkov/harness-core >= 0.5.1` (the compat guard refuses below it by name). See "Who writes the trace" above for the stated scope of the cross-host equivalence claim — it is narrower than "the two hosts agree".
4913
5351
 
4914
- `v0.5.0` — published. Also available as [Claude Plugin](#claude-plugin). Part of [DZ Harness Hub](https://github.com/djd1m/dz-harness-hub).
5352
+ `v0.5.0` — published. Claude Plugin packaging exists for contributors only (see [Claude Plugin](#claude-plugin) — a public marketplace install does not work today). Part of [DZ Harness Hub](https://github.com/djd1m/dz-harness).
4915
5353
 
4916
5354
  New in 0.5.0 (feature `qe-bridge-claude`, cross-runtime leg 3/4): `dz qe-bridge --family claude`
4917
5355
  runs an INDEPENDENT Claude reviewer from any host — a Codex session included — and lands a PARSED
@@ -4935,21 +5373,21 @@ Previously: the global `dz --version` / `-v` / `dz version` surface (one parseab
4935
5373
 
4936
5374
  ## Claude Plugin
4937
5375
 
4938
- DZ Harness Hub is available as a Claude Code plugin:
5376
+ Using the whole hub as a Claude Code plugin needs the source repo — this is a path for
5377
+ **contributors and plugin developers**, not something an `npm install -g` user needs (see
5378
+ Install above). A public marketplace install does **not** work today: `claude plugin marketplace add djd1m/dz-harness` fails with `Marketplace file not found` (MEASURED 2026-09-13, scratch probe) — the public mirror ships each package's `npm pack` contents only, never the root-level `.claude-plugin/marketplace.json`.
4939
5379
 
4940
5380
  ```bash
4941
- # Via marketplace (when published):
4942
- claude plugin marketplace add djd1m/dz-harness-hub
4943
- claude plugin install dz-harness-hub@dz-harness-hub
4944
-
4945
- # Or test locally:
5381
+ # From a cloned repo (contributors / plugin developers only):
4946
5382
  claude --plugin-dir /path/to/dz-harness-hub
4947
5383
 
4948
5384
  # Generate plugin manifest from current inventory:
4949
5385
  dz plugin --version 0.3.86
4950
5386
  ```
4951
5387
 
4952
- The `.claude-plugin/` directory contains `plugin.json` + `marketplace.json` compatible with [pi-claude-marketplace](https://npm.im/pi-claude-marketplace) and [skill-hub](https://npm.im/@jasonwen/skill-hub).
5388
+ The `.claude-plugin/` directory (source repo only) contains `plugin.json` + `marketplace.json`
5389
+ compatible with [pi-claude-marketplace](https://npm.im/pi-claude-marketplace) and
5390
+ [skill-hub](https://npm.im/@jasonwen/skill-hub).
4953
5391
 
4954
5392
  ## Related Projects
4955
5393
 
@@ -5031,3 +5469,18 @@ Core APIs: `settleDeadRuns` and `planRegistryArchive` are pure registry decision
5031
5469
  `planWorktreeCleanup` accepts injected `WorktreeFact` values and returns remove/keep decisions;
5032
5470
  `renderCleanupPlan` renders them. `worktreeRemovalsToApply` selects removals only when apply is true.
5033
5471
  No process is terminated and no branch is deleted.
5472
+
5473
+ ## From source (contributors only)
5474
+
5475
+ Everything below is for people building or testing `dz` itself — not for `npm install -g` users.
5476
+ 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).
5477
+
5478
+ ### Test execution
5479
+
5480
+ `npx vitest run` uses two projects and returns one combined verdict: `parallel` runs the ordinary
5481
+ suites concurrently, while `serial` runs process-spawning and real-time suites one file at a time.
5482
+ The serial paths in `test/serial-suites.txt` are regenerated from
5483
+ `test/serial-suites-census.test.ts`, which scans test sources for synchronous and asynchronous
5484
+ process markers, including `execSync(` and `execFile(`, and fails when the list and census differ.
5485
+ 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.
5486
+ 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.