@dzhechkov/harness-cli 0.3.195 → 0.3.198

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
@@ -356,27 +356,29 @@ Get the whole set with `dz init --target claude-code --preset meta`, or pick one
356
356
 
357
357
  > **A skill and its npx toolkit are not duplicates — they're a graduation.** Several skills (e.g. `feature-adr`, `design-thinking`) exist BOTH as a skill inside a `dz` preset AND as a standalone `npx` package. The preset's SKILL.md is **fully functional on its own** (the whole methodology — modules + references — travels with it, and it auto-activates by description), and it's the only way to compile that capability to the **non-Claude platforms** (Codex/OpenCode/Hermes/OpenClaude) via `dz`. The npx package adds **project-level runtime governance** around the same skill: a slash command, governance rules, a context shard, and (for feature-adr) reward-learning + `/harvest`. So: pick the **skill/preset** for a working capability across platforms; pick the **npx toolkit** when you want it as a governed, command-driven fixture of one project.
358
358
 
359
- ## All Commands (39)
359
+ ## All Commands (40)
360
360
 
361
361
  ```
362
362
  dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force]
363
363
  dz init --target <name> [--preset <name>] [--select id,id,...] [--force]
364
364
  dz install <npm-pkg> [--target <name>] [--project <dir>] [--force]
365
365
  dz bundle [--preset <name> | --select id,...] [--out <dir>] [--skills-dir <dir>] [--force]
366
- dz teach "<pattern>" [--reward <0-1>] [--domain <name>] [--type rule|success-pattern|lesson-learned] [--project <dir>] [--no-mirror] # --project pins the learned store to <dir>/.dz (not the cwd) — pin to a canonical brain
367
- dz teach --from-json <file> [--project <dir>] [--no-mirror] # bulk-import a `dz recall --all --json` export (share a learned store across machines)
366
+ dz teach "<pattern>" [--reward <0-1>] [--domain <name>] [--type rule|success-pattern|lesson-learned] [--project <dir>] [--no-mirror] [--guard] # --project pins the learned store to <dir>/.dz (not the cwd) — pin to a canonical brain
367
+ dz teach --reinforce "<dzId-or-exact-text>" [--project <dir>] # bump an existing learned pattern instead of writing a near-duplicate
368
+ dz teach --from-json <file> [--project <dir>] [--no-mirror] [--harmonize] # bulk-import a `dz recall --all --json` export; prints a harmonize dry-run advisory
368
369
  dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror]
369
370
  dz recall "<query>" [--limit <N>] [--semantic | --no-semantic] [--project <dir>] # hybrid lexical+vector when the vector tier is enabled
370
- dz recall --all [--json] # export the WHOLE learned store (portable SHARING form the input for `dz teach --from-json`)
371
+ dz recall --all [--json] [--stats] # export the WHOLE learned store, or inspect learning stats/top uses
371
372
  dz recall --books "<query>" [--book <slug>] # digitized-book KUs; --book narrows to one book
372
373
  dz vector status [--project <dir>] [--json] # semantic tier: engine availability, mirrored vs lexical counts, pending queue
373
374
  dz vector export <path> [--project <dir>] # portable VECTOR form (.rvf checkpoint; needs the opt-in RVF engine)
374
375
  dz vector import <file.rvf> [--project <dir>] [--json] # RVF import — UPSERT-BY-dzId (idempotent, never overwrites; orphans skipped)
375
- dz vector harmonize [--apply] [--threshold <0..1>] [--json] # SEMANTIC dedup of the learned store (dry-run default; --apply after a restorable backup)
376
+ dz vector harmonize [--apply] [--threshold <0..1>] [--json] # SEMANTIC merge of near-dups (dry-run default; --apply after a restorable backup)
376
377
  dz teach --harmonize [--apply] [--threshold <0..1>] # alias of `dz vector harmonize`
377
378
  dz statusline [--json] [--install] # compact Claude Code statusline: live self-learning pattern count + brain sources
379
+ dz usage [--json] [--project <dir>] # ESTIMATE Claude session (5h-block) + weekly (7d) token usage from local transcripts; exit 0 ALWAYS; pct=null when limits unconfigured
378
380
  dz brain list [--json] # the durable cross-project knowledge brain
379
- dz brain query "<q>" [--source <slug>] [--limit <N>] [--any] [--rerank] [--json] # cross-source recall (--any = OR match; --rerank reorders on-point first)
381
+ 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).
380
382
  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
381
383
  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
382
384
  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)
@@ -463,6 +465,32 @@ distills new learnings. It's **opt-in** and **non-clobbering**: `--install` merg
463
465
  existing `statusLine` config rather than overwriting it, and nothing changes until you run it.
464
466
  Modeled on the Agentic QE statusline pattern (e.g. AQE's `🎓 12 patterns`).
465
467
 
468
+ ### Usage estimate (`dz usage`)
469
+
470
+ `dz usage` prints a READONLY, never-throw ESTIMATE of Claude SESSION (active 5h-block) and WEEKLY
471
+ (rolling 7d) token usage, aggregated from your local `~/.claude/projects/**/*.jsonl` transcripts:
472
+
473
+ ```bash
474
+ dz usage # session ~74% (resets 19:00) · week ~52% (resets Sat) · estimated
475
+ dz usage --json # {"sessionPct":74,"weeklyPct":52,"sessionTokens":…,"resetsAt":{…},"limits":{…},"estimated":true}
476
+ ```
477
+
478
+ **Exit code is 0 ALWAYS** (even unconfigured/error → all-null JSON) — a probe must never distinguish
479
+ "usage unknown" from "command failed" via a non-zero exit. Percentages are **ESTIMATES** (there is no
480
+ official usage API); they are `null` until you configure the plan-dependent limits in `.dz/config.json`:
481
+
482
+ ```json
483
+ { "memory": { "usage": { "sessionTokenLimit": 200000000, "weeklyTokenLimit": 1000000000 } } }
484
+ ```
485
+
486
+ Both keys are OPTIONAL — absent ⇒ that `pct` is `null` (unknown, never `0`). **Calibrate** by scaling a
487
+ limit by `X/100` when a real limit-hit lands at an estimated `X%` (observed-exhaustion calibration).
488
+ This feeds `/feature-adr`'s **usage-adaptive routing** — the pipeline probes `dz usage --json` at each
489
+ phase boundary and pre-emptively routes the remaining stages to Codex when usage crosses the threshold
490
+ (default 70%). **Honest caveat:** at TRUE exhaustion even the Codex dispatch dies (`codex:codex-rescue`
491
+ is a Claude wrapper), so the switch must happen BEFORE — the pre-emptive probe, not reactive detection,
492
+ is the real defense.
493
+
466
494
  ### Targets (10 platforms — 5 lossless + Copilot + AGENTS.md + Cursor + Gemini + Windsurf)
467
495
 
468
496
  Five platforms natively support the [agentskills.io](https://agentskills.io) `SKILL.md` format:
@@ -1114,7 +1142,9 @@ The learned store round-trips across machines in **two** formats, and de-duplica
1114
1142
 
1115
1143
  **Does `dz vector import` overwrite my store? NO.** Import is **upsert-by-dzId** (our vectors are keyed by the content-addressed `dzId`), so re-importing the *same* `.rvf` twice adds **zero** duplicates and **deletes nothing** — it only inserts new dzIds and replaces the embedding of dzIds it already knows. A dzId with no local pattern is an **orphan**: skipped + counted, with a hint to import the *text* first (`dz teach --from-json`), because embeddings are model-locked but text is not. (Grounded in RuVector's own upsert-by-id merge model — see [rUv RVF](https://github.com/ruvnet/ruvector); the shipped `@ruvector/rvf` SDK exposes no vector read-out, so import reads the `.rvf.idmap.json` dzId sidecar and re-embeds the local text under a **manifest guard** that refuses a foreign model/dim.)
1116
1144
 
1117
- **`dz vector harmonize` — semantic dedup.** `dz teach --from-json` only dedups by *exact* text, so paraphrases survive: *"use DataLoader to batch queries"* and *"batch DB round-trips with a dataloader"* are two rows forever. Harmonize finds near-duplicate **clusters** by cosine similarity (θ default `0.92`, `--threshold`), keeps the **highest-reward** member of each (tie-break: longer/more-specific text, then newer `ts`), and drops the rest **dry-run by default** (previews, writes nothing), reversible on `--apply` (writes a restorable backup first). It **never** drops a unique (non-duplicated) pattern. With no vector engine it degrades to **exact-text dedup + an honest note** — never throws.
1145
+ **`dz vector harmonize` — semantic merge.** `dz teach --from-json` only dedups by *exact* text, so paraphrases survive: *"use DataLoader to batch queries"* and *"batch DB round-trips with a dataloader"* are separate rows. Harmonize finds near-duplicate **clusters** by cosine similarity (θ default `0.92`, `--threshold`), keeps the **highest-reward** member of each (tie-break: longer/more-specific text, then newer `ts`), folds reinforcement signal into the keeper (`uses = Σ member uses + drops`, `avgReward` = honest mean of observed member rewards, `mergedFrom` = dropped ids), then removes the rest only on `--apply`. Dry-run previews and writes nothing; apply writes a restorable backup first. It **never** drops a unique pattern. With no vector engine it degrades to **exact-text dedup + an honest note** — never throws.
1146
+
1147
+ **Learning signal seam.** Ranking reinforcement is behind `memory.learning.backend`: default `native` (bounded uses/recency/reward signal), kill switch `off`, and reserved `ruvector-gnn` (accepted by config with an honest fallback to native; no RuVector dependency is installed). `memory.learning.onRecallHits:false` disables the default recall-hit auto-bump. `dz teach --reinforce "<id-or-text>"` records an explicit use; `dz teach --guard` is opt-in and only reinforces near-duplicates at θ ≥ `0.95`, while a different reward still writes a new record. `dz recall --all --stats` shows store size, domains, top uses, duplicate groups, and re-teach/reinforce trend counters.
1118
1148
 
1119
1149
  **Before → after** (one near-dup cluster):
1120
1150
 
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAmJH,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAykGD,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA8G5E"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAyJH,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAitGD,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAgH5E"}
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import { execSync } from 'node:child_process';
9
9
  import { homedir } from 'node:os';
10
10
  import { createRequire } from 'node:module';
11
11
  import { fileURLToPath } from 'node:url';
12
- import { createSkill, getSkillInfo, getWorkflow, isTargetName, listSkills, runDoctor, runInit, benchmarkSkill, benchmarkSkills, scanMcp, reconcileCapabilities, RECONCILE_BANNER, buildRegistry, discoverSkillPackDirs, checkUpstream, compareSkills, checkAllUpstream, sweepSkillDrift, syncCanonicalSkill, checkUpgrades, discoverPackages, discoverSourcePackages, fetchAllDownloads, filterByCategory, pretrain, recommend, generatePlugin, publishPackages, runSetup, runMigrate, searchRegistry, runSync, runVerify, runInitAgentsMd, runInitGeminiMd, TARGET_NAMES, WORKFLOW_NAMES, importEcc, recordPattern, consolidateSessions, pruneNoisePatterns, recallHybrid, mirrorPatternsToVector, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, queryBookKnowledge, loadStorePatternsSync, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, } from '@dzhechkov/harness-core';
12
+ import { createSkill, getSkillInfo, getWorkflow, isTargetName, listSkills, runDoctor, runInit, benchmarkSkill, benchmarkSkills, scanMcp, reconcileCapabilities, RECONCILE_BANNER, buildRegistry, discoverSkillPackDirs, checkUpstream, compareSkills, checkAllUpstream, sweepSkillDrift, syncCanonicalSkill, checkUpgrades, discoverPackages, discoverSourcePackages, fetchAllDownloads, filterByCategory, pretrain, recommend, generatePlugin, publishPackages, runSetup, runMigrate, searchRegistry, runSync, runVerify, runInitAgentsMd, runInitGeminiMd, TARGET_NAMES, WORKFLOW_NAMES, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, recallHybrid, teachGuard, mirrorPatternsToVector, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, readUsageLimits, queryBookKnowledge, loadStorePatternsSync, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, } from '@dzhechkov/harness-core';
13
13
  import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
14
14
  import { scanGitHub, analyzeRepo, generateReport, deepAnalyze, scanAllSources, ScoutMemory } from '@dzhechkov/scout';
15
15
  const USAGE = `dz - DZ cross-platform harness CLI
@@ -51,6 +51,7 @@ Usage:
51
51
  dz brain init [--project <dir>] [--k <N>] (wire the grounding hook into .claude/settings.json — opt-in)
52
52
  dz statusline [--json] [--install] [--project <dir>] (live self-learning panel for Claude Code's status bar; reads the CC JSON payload from STDIN)
53
53
  dz statusline --fa-record --slug <s> --step "<label>" [--recalled <n>] [--stored <n>] [--mode <m>] (feature-adr: record live per-run learning state → 📐 panel segment)
54
+ dz usage [--json] [--project <dir>] (ESTIMATE Claude session (5h-block) + weekly (7d) token usage from local transcripts; exit 0 ALWAYS; pct=null when memory.usage.{sessionTokenLimit,weeklyTokenLimit} unconfigured in .dz/config.json)
54
55
  dz pretrain [--project <dir>]
55
56
  dz recommend "<task description>"
56
57
  dz compose <preset1+preset2+...> [--target <name>]
@@ -886,17 +887,21 @@ function cmdStatuslineFaRecord(options, cwd, write) {
886
887
  const stored = parseCount('stored');
887
888
  if (stored === undefined)
888
889
  return 1;
890
+ const reinforced = parseCount('reinforced');
891
+ if (reinforced === undefined)
892
+ return 1;
889
893
  const mode = options.get('mode');
890
894
  const projectRoot = resolve(cwd, options.get('project') ?? '.');
891
895
  const state = writeFeatureAdrState(projectRoot, {
892
896
  slug, step, recalled, stored,
897
+ ...(reinforced > 0 ? { reinforced } : {}),
893
898
  ...(mode !== undefined && mode.trim() !== '' ? { mode: mode.trim() } : {}),
894
899
  });
895
900
  if (state === undefined) {
896
901
  write(`dz statusline --fa-record: could not write learning state under ${projectRoot}/.dz/feature-adr/`);
897
902
  return 1;
898
903
  }
899
- write(`dz statusline: recorded /feature-adr learning state for "${slug}" (${step}) — 🎓 ${state.pool} pool · ↑${state.recalled} used · +${state.stored} new`);
904
+ write(`dz statusline: recorded /feature-adr learning state for "${slug}" (${step}) — 🎓 ${state.pool} pool · ↑${state.recalled} used · +${state.stored} new · ↻${state.reinforced ?? 0} reinforced`);
900
905
  return 0;
901
906
  }
902
907
  /**
@@ -931,7 +936,7 @@ function cmdStatusline(options, flags, cwd, write, readStdin) {
931
936
  // Live /feature-adr run in flight → PREPEND the pipeline learning segment to the base dz line.
932
937
  const fa = data.featureAdr;
933
938
  if (fa !== undefined) {
934
- line = `📐 feature-adr ${fa.step} · 🎓 ${fa.pool} pool · ↑${fa.recalled} used · +${fa.stored} new · ${line}`;
939
+ line = `📐 feature-adr ${fa.step} · 🎓 ${fa.pool} pool · ↑${fa.recalled} used · +${fa.stored} new · ↻${fa.reinforced ?? 0} reinforced · ${line}`;
935
940
  }
936
941
  write(line);
937
942
  return 0;
@@ -942,6 +947,72 @@ function cmdStatusline(options, flags, cwd, write, readStdin) {
942
947
  return 0;
943
948
  }
944
949
  }
950
+ /**
951
+ * `dz usage` — print an ESTIMATE of Claude session (active 5h-block) + weekly (rolling 7d) token
952
+ * usage, aggregated READONLY from the local transcript store (see {@link computeUsage}). `--json`
953
+ * emits the single-line contract the feature-adr usage-probe agent parses; the human path prints a
954
+ * compact `session ~74% (resets 19:00) · week ~52% (resets Sat)` or an unconfigured hint.
955
+ *
956
+ * **Exit code is 0 ALWAYS** — including on internal error the whole body is guarded and prints the
957
+ * all-null JSON, so a probe can NEVER distinguish "usage unknown" from "command failed" via a
958
+ * non-zero exit (unknown is a first-class value, INV-3). `--project <dir>` scopes ONLY the
959
+ * `.dz/config.json` limits read; the measurement is account-wide (all projects, FR-1.6).
960
+ */
961
+ function cmdUsage(options, flags, cwd, write) {
962
+ const projectRoot = resolve(cwd, options.get('project') ?? '.');
963
+ const nullContract = () => JSON.stringify({
964
+ sessionPct: null,
965
+ weeklyPct: null,
966
+ sessionTokens: 0,
967
+ weeklyTokens: 0,
968
+ resetsAt: { session: null, weekly: null },
969
+ limits: { session: null, weekly: null },
970
+ estimated: true,
971
+ });
972
+ try {
973
+ const u = computeUsage(projectRoot);
974
+ const lim = readUsageLimits(projectRoot);
975
+ if (flags.has('json')) {
976
+ write(JSON.stringify({
977
+ sessionPct: u.sessionPct,
978
+ weeklyPct: u.weeklyPct,
979
+ sessionTokens: u.sessionTokens,
980
+ weeklyTokens: u.weeklyTokens,
981
+ resetsAt: { session: u.sessionResetsAt, weekly: u.weeklyResetsAt },
982
+ limits: { session: lim.sessionTokenLimit ?? null, weekly: lim.weeklyTokenLimit ?? null },
983
+ estimated: true,
984
+ }));
985
+ return 0;
986
+ }
987
+ if (u.sessionPct === null && u.weeklyPct === null) {
988
+ write('usage: unconfigured — set memory.usage.sessionTokenLimit / weeklyTokenLimit in .dz/config.json (percentages are ESTIMATES calibrated from observed exhaustion)');
989
+ return 0;
990
+ }
991
+ // Compact human line — a short HH:MM / weekday hint on the resets, best-effort.
992
+ const clock = (iso) => {
993
+ if (!iso)
994
+ return '?';
995
+ try {
996
+ return new Date(iso).toISOString().slice(11, 16);
997
+ }
998
+ catch {
999
+ return '?';
1000
+ }
1001
+ };
1002
+ const s = u.sessionPct === null ? 'n/a' : '~' + u.sessionPct + '%';
1003
+ const w = u.weeklyPct === null ? 'n/a' : '~' + u.weeklyPct + '%';
1004
+ write('usage: session ' + s + ' (resets ' + clock(u.sessionResetsAt) + ') · week ' + w + ' (resets ' + clock(u.weeklyResetsAt) + ') · estimated');
1005
+ return 0;
1006
+ }
1007
+ catch {
1008
+ // never let a probe see a non-zero exit — print the all-null contract and exit 0.
1009
+ if (flags.has('json'))
1010
+ write(nullContract());
1011
+ else
1012
+ write('usage: unconfigured — set memory.usage.sessionTokenLimit / weeklyTokenLimit in .dz/config.json');
1013
+ return 0;
1014
+ }
1015
+ }
945
1016
  async function cmdTeach(options, flags, cwd, write) {
946
1017
  const projectRoot = options.get('project') ?? cwd;
947
1018
  // Vector tier (dz-rvf-vector-bridge FR-1): best-effort mirror AFTER the lexical write is
@@ -1016,8 +1087,35 @@ async function cmdTeach(options, flags, cwd, write) {
1016
1087
  write(` Backend: memory (@dzhechkov/memory) Total now: ${loadStorePatternsSync(projectRoot).length}`);
1017
1088
  // ONE batched mirror call through the same seam as single-teach (QR-6 — no bespoke path).
1018
1089
  await emitMirror(projectRoot, importedRecs, 'dz-teach-import');
1090
+ if (imported > 0) {
1091
+ const report = await harmonizeVectorStore(projectRoot, {});
1092
+ write(` ℹ ${imported} imported — ${report.clusters.length} near-duplicate cluster(s): review with dz vector harmonize (dry-run); merge with dz vector harmonize --apply after backup`);
1093
+ }
1019
1094
  return 0;
1020
1095
  }
1096
+ const reinforce = options.get('reinforce');
1097
+ if (reinforce !== undefined && reinforce.trim() !== '') {
1098
+ const backend = resolveLearningBackend(projectRoot);
1099
+ const sampleReward = options.has('reward') ? parseFloat(options.get('reward') ?? '0.8') : undefined;
1100
+ backend.addSample({
1101
+ dzId: reinforce,
1102
+ kind: 'reinforce',
1103
+ ts: new Date().toISOString(),
1104
+ ...(sampleReward !== undefined ? { reward: sampleReward } : {}),
1105
+ });
1106
+ const trained = await backend.train();
1107
+ if (trained.flushed > 0) {
1108
+ write(`↳ reinforced ${reinforce}`);
1109
+ return 0;
1110
+ }
1111
+ // HIGH-fix: a no-match must NOT auto-teach the raw argument — callers pass dzIds or truncated
1112
+ // text, so auto-teach minted garbage lessons (observed live). Fail with an honest advisory:
1113
+ // if the lesson is genuinely new, the caller teaches it EXPLICITLY with the full text.
1114
+ write(`dz teach --reinforce: no existing pattern matched ${JSON.stringify(reinforce)} — nothing reinforced`);
1115
+ write(' If this is a genuinely NEW lesson, teach it explicitly: dz teach "<full lesson text>" --reward <0-1> --domain <area>');
1116
+ write(' To find the exact pattern to reinforce: dz recall "<terms>" (match by its full text)');
1117
+ return 1;
1118
+ }
1021
1119
  const pattern = options.get('_positional_0');
1022
1120
  if (!pattern) {
1023
1121
  write('dz teach: pattern description required');
@@ -1028,6 +1126,22 @@ async function cmdTeach(options, flags, cwd, write) {
1028
1126
  }
1029
1127
  const reward = parseFloat(options.get('reward') ?? '0.8');
1030
1128
  const domain = options.get('domain') ?? 'general';
1129
+ if (flags.has('guard')) {
1130
+ const verdict = await teachGuard(projectRoot, pattern, { reward: Math.max(0, Math.min(1, reward)) });
1131
+ if (verdict.action === 'reinforce') {
1132
+ const backend = resolveLearningBackend(projectRoot);
1133
+ backend.addSample({ dzId: verdict.dzId, kind: 'reinforce', reward: Math.max(0, Math.min(1, reward)), ts: new Date().toISOString() });
1134
+ const trained = await backend.train();
1135
+ // HIGH-fix: only claim success when the reinforce actually FLUSHED. With backend 'off'
1136
+ // (NoopLearningBackend) or a flush failure, flushed === 0 — falling through to the plain
1137
+ // teach below so the lesson is NEVER silently discarded (the exact silent-drop the ADR forbids).
1138
+ if (trained.flushed > 0) {
1139
+ write(`↳ reinforced existing pattern ${verdict.dzId} (cos=${verdict.cosine.toFixed(2)}) — not re-added`);
1140
+ return 0;
1141
+ }
1142
+ write(`dz teach --guard: reinforce of ${verdict.dzId} did not flush (backend off or write failure) — teaching the lesson normally instead`);
1143
+ }
1144
+ }
1031
1145
  // Distill pattern into actionable rule (claude-smart inspired)
1032
1146
  // Convert "what happened" into "what to do next time"
1033
1147
  const isRule = pattern.toLowerCase().startsWith('always') || pattern.toLowerCase().startsWith('never') ||
@@ -1131,6 +1245,24 @@ async function cmdRecall(options, flags, cwd, write) {
1131
1245
  // With --json this is the portable export the agentdb-memory MCP bridge consumes.
1132
1246
  if (all) {
1133
1247
  const patterns = loadStorePatternsSync(projectRoot);
1248
+ if (flags.has('stats')) {
1249
+ const stats = storeStats(projectRoot);
1250
+ const backendStats = resolveLearningBackend(projectRoot).getStats();
1251
+ if (asJson) {
1252
+ write(JSON.stringify({ patterns, stats, learning: backendStats }));
1253
+ return 0;
1254
+ }
1255
+ write(`dz recall --all --stats — ${patterns.length} learned pattern(s)`);
1256
+ write(` backend: ${backendStats.backend}${backendStats.advisory !== undefined ? ` (${backendStats.advisory})` : ''}`);
1257
+ write(` domains: ${Object.entries(stats.perDomain).map(([k, v]) => `${k}=${v}`).join(', ') || 'none'}`);
1258
+ write(` exact-dup groups: ${stats.exactDupGroups}`);
1259
+ write(` re-teach trend: ${stats.teachEvents} teach event(s), ${stats.reinforceEvents} reinforce event(s)`);
1260
+ write(' top uses:');
1261
+ for (const row of stats.topUses)
1262
+ write(` ${row.uses}× [${row.reward.toFixed(2)}] (${row.domain}) ${row.pattern.slice(0, 80)}`);
1263
+ write(' near-dup density: run dz vector harmonize (dry-run)');
1264
+ return 0;
1265
+ }
1134
1266
  if (asJson) {
1135
1267
  write(JSON.stringify(patterns));
1136
1268
  }
@@ -1594,12 +1726,11 @@ async function cmdBrain(options, flags, cwd, write, readStdin) {
1594
1726
  // Default recall = pure FTS order (nothing regresses). `--rerank` opts into the deterministic
1595
1727
  // lexical reranker (over-fetch → field-weighted reorder → trim) for a more on-point top-K.
1596
1728
  const rerank = flags.has('rerank');
1597
- // Default MATCH = all (AND) — precise. `--any` (OR) is recall-friendly for morphologically-varied
1598
- // queries: nominative query forms (репликация) don't prefix stored genitive forms (репликации),
1599
- // so AND can return 0 while a great KU exists (grounding already uses OR internally). Pairs well
1600
- // with --rerank: over-fetch broadly (OR), then reorder on-point first.
1729
+ // Default MATCH = all (AND) — precise when it hits. If it returns zero hits, queryBrain retries
1730
+ // once with OR and labels the result as broadened. Explicit `--any` still starts broad and is not
1731
+ // labeled as a fallback. Pairs well with --rerank: over-fetch broadly, then reorder on-point first.
1601
1732
  const anyMatch = flags.has('any');
1602
- const { hits, error } = await queryBrain({
1733
+ const queryResult = await queryBrain({
1603
1734
  query,
1604
1735
  limit,
1605
1736
  depsRoot: resolveDepsRoot(cwd),
@@ -1607,11 +1738,14 @@ async function cmdBrain(options, flags, cwd, write, readStdin) {
1607
1738
  ...(rerank ? { rerank: true } : {}),
1608
1739
  ...(anyMatch ? { match: 'any' } : {}),
1609
1740
  });
1741
+ const { hits, error, broadened } = queryResult;
1610
1742
  if (asJson) {
1611
- write(JSON.stringify(hits));
1743
+ write(JSON.stringify(broadened === true ? { hits, broadened: true } : hits));
1612
1744
  return 0;
1613
1745
  }
1614
1746
  write(`dz brain query "${query}"${source !== undefined ? ` --source ${source}` : ''}${anyMatch ? ' --any' : ''}${rerank ? ' --rerank' : ''} — ${hits.length} KU hit(s)`);
1747
+ if (broadened === true)
1748
+ write(` broadened to any-term match: ${hits.length} hit(s)`);
1615
1749
  if (error !== undefined)
1616
1750
  write(` (${error})`);
1617
1751
  for (const h of hits) {
@@ -3112,6 +3246,8 @@ export async function runCli(argv, io = {}) {
3112
3246
  return await cmdBrain(options, flags, cwd, write, readStdin);
3113
3247
  case 'statusline':
3114
3248
  return cmdStatusline(options, flags, cwd, write, readStdin);
3249
+ case 'usage':
3250
+ return cmdUsage(options, flags, cwd, write);
3115
3251
  case 'setup':
3116
3252
  return await cmdSetup(options, flags, cwd, write);
3117
3253
  case 'pretrain':