@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 +53 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +157 -7
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
- package/src/cli.ts +163 -6
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;
|
|
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
|
-
|
|
1346
|
-
|
|
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
|
-
|
|
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) {
|