@dzhechkov/harness-cli 0.3.193 → 0.3.197
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 +64 -7
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +214 -5
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
- package/src/cli.ts +220 -5
package/README.md
CHANGED
|
@@ -356,29 +356,32 @@ 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 (
|
|
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 --
|
|
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]
|
|
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
|
|
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
381
|
dz brain query "<q>" [--source <slug>] [--limit <N>] [--any] [--rerank] [--json] # cross-source recall (--any = OR match; --rerank reorders on-point first)
|
|
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
|
-
dz brain ground "<prompt>" [--k <N>] [--source <slug>] [--text] # retrieve + emit grounding citations (hook entrypoint; silent if irrelevant)
|
|
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
|
|
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)
|
|
382
385
|
dz brain init [--project <dir>] [--k <N>] # opt-in: wire the grounding UserPromptSubmit hook into settings.json
|
|
383
386
|
dz brain primer <slug> [--json] # print a source's capability card (histogram + top decision moments)
|
|
384
387
|
dz brain export --source <slug> --out <file> # write a portable per-book KB slice (ships inside the pack, §8.1)
|
|
@@ -415,6 +418,32 @@ dz import-ecc [--local-path <dir>] [--select id,id,...] [--limit N] [--out
|
|
|
415
418
|
dz help
|
|
416
419
|
```
|
|
417
420
|
|
|
421
|
+
### Grounding: three tiers + the token trade-off (`dz brain ground` / `dz brain expand`)
|
|
422
|
+
|
|
423
|
+
The `dz brain ground` hook fires on **every turn**, so what it injects is a token trade-off. There are
|
|
424
|
+
three tiers — the default costs nothing extra every turn, and expansion is always opt-in and bounded:
|
|
425
|
+
|
|
426
|
+
- **Pointer (default, no flags)** — one compact `[Kn] book гл.N с.X–Y — name: snippet` line per KU.
|
|
427
|
+
Zero overhead every turn. Backward-compatible (byte-identical to prior releases). A pointer tells the
|
|
428
|
+
model *where* the knowledge is, not *what* it is.
|
|
429
|
+
- **Model-driven expand (`--budget N > 0` or `--full`)** — the grounding directive additionally tells
|
|
430
|
+
the model it can pull any citation's FULL content **on demand** by running `dz brain expand <kuId>`.
|
|
431
|
+
Each citation shows its `kuId` as the first field so the model knows what to pass. Cost: ~1 directive
|
|
432
|
+
sentence + a `kuId` per citation — latency is paid only when the model actually expands a KU.
|
|
433
|
+
- **Budgeted eager (`--budget N`)** — eagerly inlines the full `content` of the top-K KUs within ~N
|
|
434
|
+
tokens (approximate: chars/4 for Latin, chars/2 for Cyrillic — a multilingual tokenizer emits ~2
|
|
435
|
+
tokens per Cyrillic char, so a naive chars/4 would undercount Russian content and overshoot the
|
|
436
|
+
budget). KUs are worth-ranked (high → medium → **unset → low**: an explicit `low` ranks below an
|
|
437
|
+
unrated KU). A KU whose content would overflow the budget **stays a pointer** (never truncated — atomic KUs). `--full`
|
|
438
|
+
= `--budget 8000` (inlines everything the top-K recall holds in practice). `--budget 0` / absent =
|
|
439
|
+
pointers-only (the default).
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
# The model can turn a citation like `ddia-ch05-ku01 (с.188–189)` into the actual knowledge:
|
|
443
|
+
dz brain expand ddia-ch05-ku01 # prints name, problem, pages, book + FULL content
|
|
444
|
+
echo "how do I handle replication lag?" | dz brain ground --budget 2000 # eager-inline top KUs ≤ ~2000 tokens
|
|
445
|
+
```
|
|
446
|
+
|
|
418
447
|
### Live self-learning panel (`dz statusline`)
|
|
419
448
|
|
|
420
449
|
`dz statusline` renders a compact Claude Code statusline that surfaces dz's learning at a glance:
|
|
@@ -436,6 +465,32 @@ distills new learnings. It's **opt-in** and **non-clobbering**: `--install` merg
|
|
|
436
465
|
existing `statusLine` config rather than overwriting it, and nothing changes until you run it.
|
|
437
466
|
Modeled on the Agentic QE statusline pattern (e.g. AQE's `🎓 12 patterns`).
|
|
438
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
|
+
|
|
439
494
|
### Targets (10 platforms — 5 lossless + Copilot + AGENTS.md + Cursor + Gemini + Windsurf)
|
|
440
495
|
|
|
441
496
|
Five platforms natively support the [agentskills.io](https://agentskills.io) `SKILL.md` format:
|
|
@@ -1087,7 +1142,9 @@ The learned store round-trips across machines in **two** formats, and de-duplica
|
|
|
1087
1142
|
|
|
1088
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.)
|
|
1089
1144
|
|
|
1090
|
-
**`dz vector harmonize` — semantic
|
|
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.
|
|
1091
1148
|
|
|
1092
1149
|
**Before → after** (one near-dup cluster):
|
|
1093
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;
|
|
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;AA6sGD,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, 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
|
|
@@ -46,10 +46,12 @@ Usage:
|
|
|
46
46
|
dz brain reindex [--json] (snapshot, re-embed book-KU brain vectors, stamp current model)
|
|
47
47
|
dz brain primer <slug> [--json] (print a source's capability card — KU-type histogram + top decision moments)
|
|
48
48
|
dz brain export --source <slug> --out <file> (export ONE source as a portable, lexical-only books.sqlite slice)
|
|
49
|
-
dz brain ground [<prompt>] [--k <N>] [--source <slug>] [--text]
|
|
49
|
+
dz brain ground [<prompt>] [--k <N>] [--source <slug>] [--text] [--budget <N>] [--full] (UserPromptSubmit hook; --budget inlines top-K KUs within ~N tokens; --full = ~8000)
|
|
50
|
+
dz brain expand <kuId> [--source <slug>] [--json] (full-content lookup for a citation kuId; --json emits the full KU object)
|
|
50
51
|
dz brain init [--project <dir>] [--k <N>] (wire the grounding hook into .claude/settings.json — opt-in)
|
|
51
52
|
dz statusline [--json] [--install] [--project <dir>] (live self-learning panel for Claude Code's status bar; reads the CC JSON payload from STDIN)
|
|
52
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)
|
|
53
55
|
dz pretrain [--project <dir>]
|
|
54
56
|
dz recommend "<task description>"
|
|
55
57
|
dz compose <preset1+preset2+...> [--target <name>]
|
|
@@ -885,17 +887,21 @@ function cmdStatuslineFaRecord(options, cwd, write) {
|
|
|
885
887
|
const stored = parseCount('stored');
|
|
886
888
|
if (stored === undefined)
|
|
887
889
|
return 1;
|
|
890
|
+
const reinforced = parseCount('reinforced');
|
|
891
|
+
if (reinforced === undefined)
|
|
892
|
+
return 1;
|
|
888
893
|
const mode = options.get('mode');
|
|
889
894
|
const projectRoot = resolve(cwd, options.get('project') ?? '.');
|
|
890
895
|
const state = writeFeatureAdrState(projectRoot, {
|
|
891
896
|
slug, step, recalled, stored,
|
|
897
|
+
...(reinforced > 0 ? { reinforced } : {}),
|
|
892
898
|
...(mode !== undefined && mode.trim() !== '' ? { mode: mode.trim() } : {}),
|
|
893
899
|
});
|
|
894
900
|
if (state === undefined) {
|
|
895
901
|
write(`dz statusline --fa-record: could not write learning state under ${projectRoot}/.dz/feature-adr/`);
|
|
896
902
|
return 1;
|
|
897
903
|
}
|
|
898
|
-
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`);
|
|
899
905
|
return 0;
|
|
900
906
|
}
|
|
901
907
|
/**
|
|
@@ -930,7 +936,7 @@ function cmdStatusline(options, flags, cwd, write, readStdin) {
|
|
|
930
936
|
// Live /feature-adr run in flight → PREPEND the pipeline learning segment to the base dz line.
|
|
931
937
|
const fa = data.featureAdr;
|
|
932
938
|
if (fa !== undefined) {
|
|
933
|
-
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}`;
|
|
934
940
|
}
|
|
935
941
|
write(line);
|
|
936
942
|
return 0;
|
|
@@ -941,6 +947,72 @@ function cmdStatusline(options, flags, cwd, write, readStdin) {
|
|
|
941
947
|
return 0;
|
|
942
948
|
}
|
|
943
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
|
+
}
|
|
944
1016
|
async function cmdTeach(options, flags, cwd, write) {
|
|
945
1017
|
const projectRoot = options.get('project') ?? cwd;
|
|
946
1018
|
// Vector tier (dz-rvf-vector-bridge FR-1): best-effort mirror AFTER the lexical write is
|
|
@@ -1015,8 +1087,35 @@ async function cmdTeach(options, flags, cwd, write) {
|
|
|
1015
1087
|
write(` Backend: memory (@dzhechkov/memory) Total now: ${loadStorePatternsSync(projectRoot).length}`);
|
|
1016
1088
|
// ONE batched mirror call through the same seam as single-teach (QR-6 — no bespoke path).
|
|
1017
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
|
+
}
|
|
1018
1094
|
return 0;
|
|
1019
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
|
+
}
|
|
1020
1119
|
const pattern = options.get('_positional_0');
|
|
1021
1120
|
if (!pattern) {
|
|
1022
1121
|
write('dz teach: pattern description required');
|
|
@@ -1027,6 +1126,22 @@ async function cmdTeach(options, flags, cwd, write) {
|
|
|
1027
1126
|
}
|
|
1028
1127
|
const reward = parseFloat(options.get('reward') ?? '0.8');
|
|
1029
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
|
+
}
|
|
1030
1145
|
// Distill pattern into actionable rule (claude-smart inspired)
|
|
1031
1146
|
// Convert "what happened" into "what to do next time"
|
|
1032
1147
|
const isRule = pattern.toLowerCase().startsWith('always') || pattern.toLowerCase().startsWith('never') ||
|
|
@@ -1130,6 +1245,24 @@ async function cmdRecall(options, flags, cwd, write) {
|
|
|
1130
1245
|
// With --json this is the portable export the agentdb-memory MCP bridge consumes.
|
|
1131
1246
|
if (all) {
|
|
1132
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
|
+
}
|
|
1133
1266
|
if (asJson) {
|
|
1134
1267
|
write(JSON.stringify(patterns));
|
|
1135
1268
|
}
|
|
@@ -1386,6 +1519,12 @@ async function cmdVector(options, flags, cwd, write) {
|
|
|
1386
1519
|
/* ------------------------------------------------------------------ */
|
|
1387
1520
|
/* brain — the durable, cross-project knowledge brain (ADR §5.2 P0) */
|
|
1388
1521
|
/* ------------------------------------------------------------------ */
|
|
1522
|
+
/**
|
|
1523
|
+
* Token budget used when `dz brain ground --full` is specified (chars/4 heuristic, approximate). A
|
|
1524
|
+
* typical DDIA-scale KU is ~500–750 tokens; at k=5 the sum is well under 8000, so `--full` inlines
|
|
1525
|
+
* everything the top-K recall returns in practice. Documented in the brain usage + wiki.
|
|
1526
|
+
*/
|
|
1527
|
+
const GROUND_FULL_BUDGET = 8_000;
|
|
1389
1528
|
const BRAIN_USAGE = `dz brain — the durable, cross-project knowledge brain
|
|
1390
1529
|
|
|
1391
1530
|
Usage:
|
|
@@ -1399,7 +1538,8 @@ Usage:
|
|
|
1399
1538
|
dz brain reindex [--json]
|
|
1400
1539
|
dz brain primer <slug> [--json]
|
|
1401
1540
|
dz brain export --source <slug> --out <file>
|
|
1402
|
-
dz brain ground [<prompt>] [--k <N>] [--source <slug>] [--text]
|
|
1541
|
+
dz brain ground [<prompt>] [--k <N>] [--source <slug>] [--text] [--budget <N>] [--full]
|
|
1542
|
+
dz brain expand <kuId> [--source <slug>] [--json]
|
|
1403
1543
|
dz brain init [--project <dir>] [--k <N>]
|
|
1404
1544
|
|
|
1405
1545
|
add: default promotes THIS project's digitized-book KUs into the brain. The three --from-* modes
|
|
@@ -1421,6 +1561,15 @@ Usage:
|
|
|
1421
1561
|
Always exits 0 — grounding is advisory and must never fail a prompt. Reranking is ON by
|
|
1422
1562
|
default here (grounding wants the most on-point citation first). --text prints the raw
|
|
1423
1563
|
citation block instead of the JSON wrapper (for manual inspection).
|
|
1564
|
+
Three grounding tiers: (1) pointer (default, no flags) — one compact citation line per KU,
|
|
1565
|
+
cheap every-turn; (2) model-driven expand (--budget N>0 / --full) — each citation carries
|
|
1566
|
+
its kuId and the directive tells the model to pull full content via \`dz brain expand\`;
|
|
1567
|
+
(3) budgeted eager (--budget N) — eager-inlines full content of the top-K KUs within ~N
|
|
1568
|
+
tokens (chars/4 approx), worth-ranked; a KU that would overflow stays a pointer. --full =
|
|
1569
|
+
--budget 8000. --budget 0 / absent = pointers-only.
|
|
1570
|
+
expand: full-content lookup by kuId — the command the grounding directive names when --budget/--full
|
|
1571
|
+
is used. Prints name, problem, pages, book, and the FULL content (untruncated); --json emits
|
|
1572
|
+
the whole KU object. Exit 1 if the kuId is not found.
|
|
1424
1573
|
init: wires \`brain ground\` into .claude/settings.json as an opt-in UserPromptSubmit hook.`;
|
|
1425
1574
|
/**
|
|
1426
1575
|
* Extract the user prompt from a Claude Code `UserPromptSubmit` hook STDIN payload. Tries the
|
|
@@ -1869,6 +2018,21 @@ async function cmdBrain(options, flags, cwd, write, readStdin) {
|
|
|
1869
2018
|
const source = options.get('source');
|
|
1870
2019
|
const kRaw = options.get('k');
|
|
1871
2020
|
const k = kRaw !== undefined ? Math.max(1, parseInt(kRaw, 10) || 5) : undefined;
|
|
2021
|
+
// brain-ground-expand: --budget N eager-inlines top-K KU content within ~N tokens (chars/4);
|
|
2022
|
+
// --full = --budget GROUND_FULL_BUDGET; --budget 0 / absent ⇒ pointers-only (byte-identical).
|
|
2023
|
+
const budgetRaw = options.get('budget');
|
|
2024
|
+
let contentBudget;
|
|
2025
|
+
if (budgetRaw !== undefined) {
|
|
2026
|
+
const parsed = parseInt(budgetRaw, 10);
|
|
2027
|
+
if (isNaN(parsed) || parsed < 0) {
|
|
2028
|
+
write('dz brain ground: --budget must be a non-negative integer');
|
|
2029
|
+
return 1;
|
|
2030
|
+
}
|
|
2031
|
+
contentBudget = parsed === 0 ? undefined : parsed; // 0 → pointer-only (FR-03.6)
|
|
2032
|
+
}
|
|
2033
|
+
else if (flags.has('full')) {
|
|
2034
|
+
contentBudget = GROUND_FULL_BUDGET;
|
|
2035
|
+
}
|
|
1872
2036
|
const gopts = {
|
|
1873
2037
|
prompt,
|
|
1874
2038
|
depsRoot: resolveDepsRoot(cwd),
|
|
@@ -1877,6 +2041,8 @@ async function cmdBrain(options, flags, cwd, write, readStdin) {
|
|
|
1877
2041
|
gopts.k = k;
|
|
1878
2042
|
if (source !== undefined)
|
|
1879
2043
|
gopts.source = source;
|
|
2044
|
+
if (contentBudget !== undefined)
|
|
2045
|
+
gopts.contentBudget = contentBudget;
|
|
1880
2046
|
const res = await groundPrompt(gopts);
|
|
1881
2047
|
if (!res.emitted)
|
|
1882
2048
|
return 0; // no relevant citations → inject nothing
|
|
@@ -1893,6 +2059,47 @@ async function cmdBrain(options, flags, cwd, write, readStdin) {
|
|
|
1893
2059
|
}));
|
|
1894
2060
|
return 0;
|
|
1895
2061
|
}
|
|
2062
|
+
// ── dz brain expand <kuId> ───────────────────────────────────────────────────────────────────
|
|
2063
|
+
// Full-content lookup by kuId — the command named in GROUNDING_DIRECTIVE_EXPAND (brain-ground-expand
|
|
2064
|
+
// Tier 1). Prints the FULL `content` (+ name/problem/pages/book) for one citation the model wants
|
|
2065
|
+
// to read; --json emits the whole KU object. Exit 1 on missing kuId / not-found.
|
|
2066
|
+
if (sub === 'expand') {
|
|
2067
|
+
const kuId = options.get('_positional_1');
|
|
2068
|
+
if (!kuId) {
|
|
2069
|
+
write('dz brain expand: a kuId is required');
|
|
2070
|
+
write(' Example: dz brain expand ddia-ch05-ku01');
|
|
2071
|
+
return 1;
|
|
2072
|
+
}
|
|
2073
|
+
const source = options.get('source');
|
|
2074
|
+
const res = expandKu({
|
|
2075
|
+
kuId,
|
|
2076
|
+
depsRoot: resolveDepsRoot(cwd),
|
|
2077
|
+
...(source !== undefined ? { source } : {}),
|
|
2078
|
+
});
|
|
2079
|
+
if (res.error !== undefined) {
|
|
2080
|
+
write(`dz brain expand: ${res.error}`);
|
|
2081
|
+
return 1;
|
|
2082
|
+
}
|
|
2083
|
+
const ku = res.ku;
|
|
2084
|
+
if (asJson) {
|
|
2085
|
+
write(JSON.stringify(ku, null, 2));
|
|
2086
|
+
return 0;
|
|
2087
|
+
}
|
|
2088
|
+
const ch = ku.chapter !== undefined && ku.chapter !== '' ? `гл.${ku.chapter}` : '';
|
|
2089
|
+
const pg = ku.pages !== undefined && ku.pages.length > 0 ? `с.${ku.pages.join('–')}` : '';
|
|
2090
|
+
write(`kuId: ${ku.kuId}`);
|
|
2091
|
+
write(`book: ${ku.book}`);
|
|
2092
|
+
if (ch)
|
|
2093
|
+
write(`chapter: ${ch}`);
|
|
2094
|
+
if (pg)
|
|
2095
|
+
write(`pages: ${pg}`);
|
|
2096
|
+
write(`name: ${ku.name}`);
|
|
2097
|
+
write(`problem: ${ku.problem}`);
|
|
2098
|
+
write('');
|
|
2099
|
+
write('content:');
|
|
2100
|
+
write(ku.content);
|
|
2101
|
+
return 0;
|
|
2102
|
+
}
|
|
1896
2103
|
// ── dz brain init ────────────────────────────────────────────────────────────────────────────
|
|
1897
2104
|
// Opt-in: wire `dz brain ground` into .claude/settings.json as a UserPromptSubmit hook.
|
|
1898
2105
|
// Idempotent read-merge-write — preserve every existing hook/key (e.g. the agentic-qe route hook).
|
|
@@ -3037,6 +3244,8 @@ export async function runCli(argv, io = {}) {
|
|
|
3037
3244
|
return await cmdBrain(options, flags, cwd, write, readStdin);
|
|
3038
3245
|
case 'statusline':
|
|
3039
3246
|
return cmdStatusline(options, flags, cwd, write, readStdin);
|
|
3247
|
+
case 'usage':
|
|
3248
|
+
return cmdUsage(options, flags, cwd, write);
|
|
3040
3249
|
case 'setup':
|
|
3041
3250
|
return await cmdSetup(options, flags, cwd, write);
|
|
3042
3251
|
case 'pretrain':
|