@dzhechkov/harness-cli 0.3.205 → 0.3.207

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
@@ -1290,6 +1290,59 @@ in silence.
1290
1290
  Disable it by removing the two hook entries from `.claude/settings.json`; stop the daemon with
1291
1291
  `printf '{"op":"stop"}\n' | nc -U .dz/embed.sock`.
1292
1292
 
1293
+ #### Cleaning the store: dry-run first, always
1294
+
1295
+ The learned store lives in a git-ignored `.dz/` and has no history. A wrong sweep cannot be undone,
1296
+ so both destructive surfaces **preview by default** and snapshot before they delete.
1297
+
1298
+ ```bash
1299
+ dz consolidate --prune-noise # DRY RUN: names every record it would remove
1300
+ dz consolidate --prune-noise --apply # snapshots to .dz/patterns-pre-prune-<ts>.json, then removes
1301
+
1302
+ dz recall --forget <dzId>[,<dzId>…] # DRY RUN
1303
+ dz recall --forget <dzId> --apply # snapshots, then removes exactly those records
1304
+ ```
1305
+
1306
+ They cover **different** junk. `--prune-noise` removes what `isNoiseInsight` recognises: tool
1307
+ telemetry and system-wrapper "user responses" — text that was never a lesson. It cannot reach a record
1308
+ that is structurally fine but semantically worthless (this repo's store carried `mismatch probe A`,
1309
+ written by a `dz teach --project` path bug). `--forget` is for those: it takes explicit ids, which you
1310
+ find with `dz recall --all --json` or in the `never read` list of `dz recall --usage`.
1311
+
1312
+ An unknown id exits `1` rather than reporting a successful removal of nothing. After `--forget`, run
1313
+ `dz vector reindex` — the lexical store and the vector mirror are separate copies.
1314
+
1315
+ > **Behaviour change:** `--prune-noise` used to delete immediately. It now previews; pass `--apply`.
1316
+
1317
+ #### Measuring the loop: usage, not size
1318
+
1319
+ `N patterns stored` proves nothing about a learning system. The only question that matters is *were
1320
+ the lessons ever read?* — and the answer is usually humbling. So the statusline reports both:
1321
+
1322
+ ```
1323
+ 🎓 dz: 103 patterns · 3 used · 🧠 2 sources
1324
+ ```
1325
+
1326
+ ```bash
1327
+ dz recall --usage # how many were read, which most, which NEVER
1328
+ dz recall --usage --json # {totalPatterns, usedPatterns, neverReadPatterns, top, neverRead, …}
1329
+ ```
1330
+
1331
+ `neverRead` is the dead weight — patterns that have never once matched a real prompt. It is the input
1332
+ a future prune should consider, and the diagnostic that would have exposed a missing apply leg years
1333
+ earlier. (It also surfaces junk that should never have been learned: on this repo it immediately
1334
+ revealed patterns extracted from task-notification text.)
1335
+
1336
+ Every injected hit is appended to `.dz/recall-usage.jsonl` — append-only, no locks, no schema. The
1337
+ embedding daemon opens the pattern store **read-only** and never writes, so it can never be the writer
1338
+ that tears a database file for a concurrent reader; the hook does the recording instead. The count and
1339
+ the last-read timestamp are derived from the **same records**, so they cannot disagree the way
1340
+ agentic-qe's do (its top row shows `usage_count = 1326` with `last_used_at: never`). The log is bounded:
1341
+ past `1 MiB` it compacts read events into aggregate rows that preserve both counts and timestamps.
1342
+
1343
+ Recording never costs the injection: an unwritable log, a torn last line, a missing module — the hook
1344
+ still injects, still exits 0. Instrumentation must not break what it measures.
1345
+
1293
1346
  #### Cross-lingual recall, and changing the embedding model
1294
1347
 
1295
1348
  The default embedder (`Xenova/paraphrase-multilingual-MiniLM-L12-v2`) is **multilingual**: a Russian
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;AA8JH,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;AA+iHD,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkH5E"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAuKH,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;AAmsHD,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkH5E"}
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, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, recallHybrid, teachGuard, mirrorPatternsToVector, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, claimCheck, summarize, 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, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, claimCheck, summarize, queryBookKnowledge, loadStorePatternsSync, patternRecordId, loadStoreRecords, recordToPattern, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, RECALL_USAGE_LOG_RELATIVE, RECALL_USAGE_LOG_MAX_BYTES, parseRecallUsageLog, buildRecallUsageReport, } 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
@@ -32,8 +32,8 @@ Usage:
32
32
  dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--project <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force] [--enrich]
33
33
  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)
34
34
  dz teach --from-json <file> [--project <dir>] [--no-mirror] (bulk-import a 'dz recall --all --json' export — share a learned store across machines)
35
- dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror] [--prune-noise]
36
- dz recall "<query>" [--limit <N>] [--semantic | --no-semantic] [--books [--book <slug>]] [--project <dir>] | dz recall --all [--json]
35
+ dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror] [--prune-noise [--apply]] (prune-noise: DRY-RUN by default; --apply snapshots then deletes)
36
+ dz recall "<query>" [--limit <N>] [--semantic | --no-semantic] [--books [--book <slug>]] [--project <dir>] | dz recall --all [--json] | dz recall --usage [--json] | dz recall --forget <dzId>[,<dzId>] [--apply] (forget: dry-run default; snapshots before removing)
37
37
  dz vector status [--project <dir>] [--json] (semantic tier: engine, mirrored vs lexical counts, pending queue)
38
38
  dz vector reindex [--project <dir>] [--json] (snapshot, re-embed learned-pattern vectors, stamp current model)
39
39
  dz vector export <path> [--project <dir>] (portable VECTOR form (.rvf, opt-in RVF engine); patterns ship via recall --all --json)
@@ -932,7 +932,7 @@ function cmdStatusline(options, flags, cwd, write, readStdin) {
932
932
  write(JSON.stringify(data));
933
933
  return 0;
934
934
  }
935
- let line = `🎓 dz: ${data.patterns} patterns · 🧠 ${data.brainSources} sources`;
935
+ let line = `🎓 dz: ${data.patterns} patterns${data.usedPatterns !== undefined ? ` · ${data.usedPatterns} used` : ''} · 🧠 ${data.brainSources} sources`;
936
936
  const branch = statuslineGitBranch(projectRoot);
937
937
  if (branch !== undefined)
938
938
  line += ` · ⎇ ${branch}`;
@@ -1341,9 +1341,27 @@ async function cmdConsolidate(options, flags, cwd, write) {
1341
1341
  // --prune-noise: RETRO-PRUNE legacy noise (tool telemetry + system-wrapper "responses") from
1342
1342
  // the lexical store AND the agentdb vector mirror BEFORE harvesting, so this run's watermark
1343
1343
  // never re-learns from junk. Best-effort — a prune error is reported, never fatal.
1344
+ //
1345
+ // DRY-RUN BY DEFAULT. The learned store lives in a git-ignored `.dz/` and has no history: a
1346
+ // wrong sweep is unrecoverable. `--prune-noise` therefore only PREVIEWS; `--apply` performs the
1347
+ // deletion, and snapshots the store to a restorable JSON export first. (Behaviour change: before
1348
+ // this, `--prune-noise` deleted immediately and silently.)
1349
+ const applyPrune = flags.has('apply');
1344
1350
  let pruned;
1345
- if (pruneNoise)
1346
- pruned = pruneNoisePatterns(projectRoot);
1351
+ let pruneSnapshot;
1352
+ if (pruneNoise) {
1353
+ if (applyPrune) {
1354
+ const dest = join(projectRoot, '.dz', `patterns-pre-prune-${Date.now()}.json`);
1355
+ const snap = snapshotStore(projectRoot, dest);
1356
+ if (snap.error === undefined)
1357
+ pruneSnapshot = snap.path;
1358
+ else
1359
+ write(` ⚠ snapshot failed (${snap.error}) — prune SKIPPED, the store is not versioned`);
1360
+ if (snap.error !== undefined)
1361
+ return 1; // never delete what we could not back up
1362
+ }
1363
+ pruned = pruneNoisePatterns(projectRoot, { dryRun: !applyPrune });
1364
+ }
1347
1365
  // Throw-safety (QE P3): this command also runs DETACHED from the SessionEnd hook (stdio
1348
1366
  // ignored) — an uncaught store/watermark error would be fully invisible there. Catch, leave
1349
1367
  // an observable note next to the session markers, and report on stdout for manual runs.
@@ -1374,7 +1392,19 @@ async function cmdConsolidate(options, flags, cwd, write) {
1374
1392
  // Echo active flags in the header (learned pattern: flags must be visible in command output).
1375
1393
  write(`dz consolidate${pruneNoise ? ' (--prune-noise)' : ''}`);
1376
1394
  if (pruned !== undefined) {
1377
- write(` pruned: ${pruned.lexicalRemoved} lexical, ${pruned.vectorRemoved} vector`);
1395
+ const verb = pruned.dryRun === true ? 'would prune' : 'pruned';
1396
+ write(` ${verb}: ${pruned.lexicalRemoved} lexical, ${pruned.vectorRemoved} vector`);
1397
+ for (const c of (pruned.candidates ?? []).slice(0, 10)) {
1398
+ write(` ${c.id} ${c.text.replace(/\s+/g, ' ').slice(0, 72)}`);
1399
+ }
1400
+ const extra = (pruned.candidates?.length ?? 0) - 10;
1401
+ if (extra > 0)
1402
+ write(` … and ${extra} more`);
1403
+ if (pruned.dryRun === true && pruned.lexicalRemoved + pruned.vectorRemoved > 0) {
1404
+ write(' DRY RUN — nothing deleted. Re-run with --apply to remove (snapshots first).');
1405
+ }
1406
+ if (pruneSnapshot !== undefined)
1407
+ write(` snapshot: ${pruneSnapshot}`);
1378
1408
  if (pruned.error !== undefined)
1379
1409
  write(` Prune: ${pruned.error}`);
1380
1410
  }
@@ -1400,10 +1430,130 @@ async function cmdConsolidate(options, flags, cwd, write) {
1400
1430
  }
1401
1431
  return 0;
1402
1432
  }
1433
+ function recallUsagePatternRefs(projectRoot) {
1434
+ return loadStoreRecords(projectRoot).map((record) => {
1435
+ const pattern = recordToPattern(record);
1436
+ return { dzId: record.id, pattern: pattern.pattern, domain: pattern.domain, reward: pattern.reward };
1437
+ });
1438
+ }
1439
+ function readRecallUsageReport(projectRoot) {
1440
+ let text = '';
1441
+ const logPath = join(projectRoot, RECALL_USAGE_LOG_RELATIVE);
1442
+ try {
1443
+ if (existsSync(logPath))
1444
+ text = readFileSync(logPath, 'utf-8');
1445
+ }
1446
+ catch {
1447
+ text = '';
1448
+ }
1449
+ return buildRecallUsageReport(recallUsagePatternRefs(projectRoot), parseRecallUsageLog(text));
1450
+ }
1451
+ function fmtUsageRow(row) {
1452
+ const last = row.lastReadAt !== undefined ? ` last=${row.lastReadAt}` : '';
1453
+ const score = row.avgScore !== undefined ? ` avg=${row.avgScore.toFixed(3)}` : '';
1454
+ const domain = row.domain !== undefined ? ` (${row.domain})` : '';
1455
+ return `${row.reads}×${last}${score} ${row.dzId}${domain} ${row.pattern.slice(0, 100)}`;
1456
+ }
1457
+ function cmdRecallUsage(options, flags, projectRoot, write) {
1458
+ const report = readRecallUsageReport(projectRoot);
1459
+ if (flags.has('json')) {
1460
+ write(JSON.stringify({
1461
+ ...report,
1462
+ log: join(projectRoot, RECALL_USAGE_LOG_RELATIVE),
1463
+ retention: { maxBytes: RECALL_USAGE_LOG_MAX_BYTES },
1464
+ }));
1465
+ return 0;
1466
+ }
1467
+ const displayLimit = Math.max(1, parseInt(options.get('limit') ?? '20', 10) || 20);
1468
+ write(`dz recall --usage — ${report.usedPatterns}/${report.totalPatterns} pattern(s) read, ${report.totalReads} read event(s)`);
1469
+ write(` log: ${RECALL_USAGE_LOG_RELATIVE} (bounded at ${RECALL_USAGE_LOG_MAX_BYTES} bytes)`);
1470
+ if (report.invalidLines > 0)
1471
+ write(` skipped invalid/torn line(s): ${report.invalidLines}`);
1472
+ if (report.unknownReadPatterns > 0)
1473
+ write(` usage rows for missing pattern id(s): ${report.unknownReadPatterns}`);
1474
+ write(' most read:');
1475
+ if (report.top.length === 0) {
1476
+ write(' none yet');
1477
+ }
1478
+ else {
1479
+ for (const row of report.top.slice(0, displayLimit))
1480
+ write(` ${fmtUsageRow(row)}`);
1481
+ if (report.top.length > displayLimit)
1482
+ write(` ... ${report.top.length - displayLimit} more (use --json for the full list)`);
1483
+ }
1484
+ write(' never read:');
1485
+ if (report.neverRead.length === 0) {
1486
+ write(' none');
1487
+ }
1488
+ else {
1489
+ for (const row of report.neverRead.slice(0, displayLimit))
1490
+ write(` ${row.dzId}${row.domain !== undefined ? ` (${row.domain})` : ''} ${row.pattern.slice(0, 100)}`);
1491
+ if (report.neverRead.length > displayLimit)
1492
+ write(` ... ${report.neverRead.length - displayLimit} more (use --json for the full list)`);
1493
+ }
1494
+ return 0;
1495
+ }
1496
+ /**
1497
+ * `dz recall --forget <dzId>[,<dzId>…] [--apply]` — remove NAMED records from the learned store.
1498
+ *
1499
+ * `--prune-noise` only removes what `isNoiseInsight` recognises. It cannot reach the other junk
1500
+ * class: records that are structurally fine but were never lessons. This repo has two of them —
1501
+ * `mismatch probe A` / `match probe B`, written into the LIVE store on 2026-07-07 by the
1502
+ * `dz teach --project <relative>` path bug fixed in `83b1bac` (the relative root resolved to the
1503
+ * cwd store instead of the temp project). The bug is gone; its residue is not, and no predicate
1504
+ * will ever classify it.
1505
+ *
1506
+ * DRY-RUN BY DEFAULT, and it snapshots before deleting: `.dz/` is git-ignored and unversioned, so a
1507
+ * mistaken id is unrecoverable. Use `dz recall --all --json` to find ids.
1508
+ */
1509
+ async function cmdRecallForget(options, flags, projectRoot, write) {
1510
+ const raw = options.get('forget') ?? '';
1511
+ const ids = new Set(raw.split(',').map((s) => s.trim()).filter((s) => s !== ''));
1512
+ if (ids.size === 0) {
1513
+ write('dz recall --forget: no ids given (comma-separated dzIds; find them with `dz recall --all --json`)');
1514
+ return 1;
1515
+ }
1516
+ // Use the CANONICAL store ids, not `patternRecordId(pattern)`. `removePatternsByIds` matches on
1517
+ // `MemoryRecord.id`, and the two coincide only for records dz itself wrote (`dz teach` stores the
1518
+ // hash as the id). A record seeded or migrated with a different id would be reported as "found",
1519
+ // then silently NOT removed — the command would claim success while deleting nothing.
1520
+ const known = new Map(loadStoreRecords(projectRoot).map((r) => [r.id, r.text]));
1521
+ const found = [...ids].filter((id) => known.has(id));
1522
+ const missing = [...ids].filter((id) => !known.has(id));
1523
+ for (const id of found)
1524
+ write(` ${id} ${(known.get(id) ?? '').replace(/\s+/g, ' ').slice(0, 72)}`);
1525
+ for (const id of missing)
1526
+ write(` ${id} (not in the store — nothing to forget)`);
1527
+ if (found.length === 0) {
1528
+ write('dz recall --forget: nothing matched.');
1529
+ return 1;
1530
+ }
1531
+ if (!flags.has('apply')) {
1532
+ write(`dz recall --forget: DRY RUN — ${found.length} record(s) would be removed. Re-run with --apply.`);
1533
+ return 0;
1534
+ }
1535
+ const dest = join(projectRoot, '.dz', `patterns-pre-forget-${Date.now()}.json`);
1536
+ const snap = snapshotStore(projectRoot, dest);
1537
+ if (snap.error !== undefined) {
1538
+ write(`dz recall --forget: snapshot failed (${snap.error}) — nothing removed; the store is not versioned`);
1539
+ return 1;
1540
+ }
1541
+ const result = removePatternsByIds(projectRoot, new Set(found));
1542
+ write(`dz recall --forget: removed ${result.removed} record(s)`);
1543
+ write(` snapshot: ${snap.path} (${snap.count} record(s))`);
1544
+ if (result.error !== undefined)
1545
+ write(` ⚠ ${result.error}`);
1546
+ write(' the vector mirror still holds them — run `dz vector reindex` to resync');
1547
+ return 0;
1548
+ }
1403
1549
  async function cmdRecall(options, flags, cwd, write) {
1404
1550
  const projectRoot = resolve(cwd, options.get('project') ?? '.');
1405
1551
  const asJson = flags.has('json');
1406
1552
  const all = flags.has('all');
1553
+ if (flags.has('usage'))
1554
+ return cmdRecallUsage(options, flags, projectRoot, write);
1555
+ if (options.has('forget'))
1556
+ return cmdRecallForget(options, flags, projectRoot, write);
1407
1557
  // --all: dump the entire learned store (backend-agnostic, via loadStorePatternsSync).
1408
1558
  // With --json this is the portable export the agentdb-memory MCP bridge consumes.
1409
1559
  if (all) {