@dzhechkov/harness-cli 0.3.237 → 0.3.238
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/.dz-manifest.json +8 -8
- package/README.md +35 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +100 -6
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
- package/sbom.json +7 -7
- package/src/cli.ts +97 -5
package/.dz-manifest.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"path": "README.md",
|
|
12
|
-
"sha256": "
|
|
12
|
+
"sha256": "ef6603652143fc1517c81506dad9d5e7b79e5d5138aad9f6281ad77492d58b71"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"path": "coverage/coverage-final.json",
|
|
@@ -41,15 +41,15 @@
|
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"path": "dist/cli.d.ts.map",
|
|
44
|
-
"sha256": "
|
|
44
|
+
"sha256": "8f8807920c7ddd901361eb5a602df9c85fa69726e47535a146937038eab6b281"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"path": "dist/cli.js",
|
|
48
|
-
"sha256": "
|
|
48
|
+
"sha256": "5f5fbcc7c040a272f6af992ef1f9c39482b941d74dbcadec9f3d9008babcb08a"
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
"path": "dist/cli.js.map",
|
|
52
|
-
"sha256": "
|
|
52
|
+
"sha256": "420d1178a513e54f315095a17e9c5722543ca6cbdbebd9a674d4d50ef61b8aa0"
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"path": "dist/index.d.ts",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
"path": "package.json",
|
|
76
|
-
"sha256": "
|
|
76
|
+
"sha256": "8fbaf10d29b5733cf9445e1ef5804a3e124f733c3390a99937d2d8dc3b4cf02a"
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
"path": "src/bin.ts",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
"path": "src/cli.ts",
|
|
84
|
-
"sha256": "
|
|
84
|
+
"sha256": "131f23a50187e1be19875d4ccd76d8c354b1fbb8d193c03e73fda16ff64f51dc"
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
"path": "src/index.ts",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
"path": "test/cli.test.ts",
|
|
92
|
-
"sha256": "
|
|
92
|
+
"sha256": "3deb99917f3d8b4a5c9a3b0315bd65a3099ce2b508121cfa2bdeed19b7554d48"
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"path": "tsconfig.json",
|
|
@@ -101,5 +101,5 @@
|
|
|
101
101
|
}
|
|
102
102
|
]
|
|
103
103
|
},
|
|
104
|
-
"signature": "
|
|
104
|
+
"signature": "spl/yr0En3yUAxhYjhIdot8D/Tri4JeoMrweQkA9GF2F9UyzDco3FLpaqCkvwlhGvj4phKb4qB0Nm3e+GAMaDw=="
|
|
105
105
|
}
|
package/README.md
CHANGED
|
@@ -372,6 +372,41 @@ the release job gets a perfectly attested malicious package.
|
|
|
372
372
|
A ready-to-install workflow is at `features/publish-provenance/07_code_changes/publish.yml`; copy it to
|
|
373
373
|
`.github/workflows/` and add an `NPM_TOKEN` secret.
|
|
374
374
|
|
|
375
|
+
### Lesson quarantine — a fresh lesson is a hypothesis, not knowledge
|
|
376
|
+
|
|
377
|
+
Self-learning has a poisoning problem: the moment `dz teach` stores a lesson, it ranks alongside
|
|
378
|
+
patterns proven over months and can ride the auto-inject hook into your next task's context — even
|
|
379
|
+
if it is wrong, one-off, or junk. Quarantine (opt-in) closes the gap between COLLECT and RANK:
|
|
380
|
+
|
|
381
|
+
```jsonc
|
|
382
|
+
// .dz/config.json
|
|
383
|
+
{ "memory": { "learning": {
|
|
384
|
+
"quarantine": true, // fresh lessons start as quarantined hypotheses
|
|
385
|
+
"quarantineDamp": 0.5, // rank multiplier for ⚠q hits in recall (0..1]
|
|
386
|
+
"quarantineExpireDays": 30 // unconfirmed after N days ⇒ expiry CANDIDATE (report only)
|
|
387
|
+
} } }
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Three surfaces, three strictness levels — and promotion is EARNED, never automatic:
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
dz teach "..." # → "⚠ quarantined: excluded from auto-inject, damped in recall"
|
|
394
|
+
dz recall "topic" # ⚠q hits are VISIBLE but marked + rank-damped (never hidden)
|
|
395
|
+
# the UserPromptSubmit auto-inject hook EXCLUDES ⚠q lessons entirely (logged, never silent)
|
|
396
|
+
|
|
397
|
+
dz teach --reinforce "<exact text>" # confirming a lesson IS its promotion
|
|
398
|
+
dz recall --promote <dzId> --apply # or promote explicitly (dry-run by default)
|
|
399
|
+
|
|
400
|
+
dz consolidate --prune-quarantine # report expired unconfirmed lessons (dry-run)
|
|
401
|
+
dz consolidate --prune-quarantine --apply # remove them (snapshots first) — a SEPARATE gate,
|
|
402
|
+
# never coupled to --prune-noise (unproven ≠ garbage)
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Absent config = zero behavior change. Existing lessons are grandfathered as promoted; a corrupt
|
|
406
|
+
quarantine marker reads as promoted (a metadata glitch must never isolate proven knowledge).
|
|
407
|
+
**When to use:** any project where subagents teach lessons unattended — the quarantine is the gate
|
|
408
|
+
between "an agent wrote this down" and "agents now act on it".
|
|
409
|
+
|
|
375
410
|
### Target parity — `dz parity` (the honest feature × target map)
|
|
376
411
|
|
|
377
412
|
The harness runs on 10 targets, but not every feature runs everywhere: hooks, MCP (Model Context
|
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;AA8QH,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;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;CAC5C;AAED,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KACvD;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAyiL9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAkJ5E"}
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
import { execSync } from 'node:child_process';
|
|
10
10
|
import { homedir, tmpdir } from 'node:os';
|
|
11
11
|
import { createRequire } from 'node:module';
|
|
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, buildParityMatrix, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_NAMES, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, 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, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, decideProvenance, isInsideTree, signManifest, verifyManifest, listSignablePackFiles, assertKeyOutsideTree, decidePublishGate, collectPackageFacts, planReleaseGates, selectAffectedPackages, classifyGateExecutions, buildFailureIssue, buildReleaseNotes, releaseTagName, firstOutputLine, formatPublishError, MANIFEST_NAME, SBOM_NAME, buildArchitectureMap, renderMapHuman, findArchitectureDrift, renderDriftReport, scanWorkspacePackages, loadSubsystemManifest, loadProductVision, checkFeatureAgainstArchitecture, renderArchCheck, planProjectSkills, guidanceForStage, renderInjectionReport, analyzeCorpus, renderRakeReport, renderCriticSection, rakeAsLesson, rakeReward, DEFAULT_RAKE_THRESHOLDS, streamSessionEvents, findLatestTranscript, detectProcessRakes, buildRetro, renderRetro, retroLessonText, PROCESS_SIGNATURES, RETRO_DOMAIN, scanForSetup, buildSetupPlan, scaffoldFromSpec, renderScaffoldPreview, readExistingForScaffold, assembleChallengeContext, buildChallengeBrief, planDiscriminationCheck, classifyDiscrimination, pickAdversaryModel, CHALLENGE_QUESTIONS, loadOutcomes, renderOutcomes, statsForKey, selectAutoCost, recordProvisional, finalizeOutcome, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, } 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, buildParityMatrix, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_NAMES, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, 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, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, decideProvenance, isInsideTree, signManifest, verifyManifest, listSignablePackFiles, assertKeyOutsideTree, decidePublishGate, collectPackageFacts, planReleaseGates, selectAffectedPackages, classifyGateExecutions, buildFailureIssue, buildReleaseNotes, releaseTagName, firstOutputLine, formatPublishError, MANIFEST_NAME, SBOM_NAME, buildArchitectureMap, renderMapHuman, findArchitectureDrift, renderDriftReport, scanWorkspacePackages, loadSubsystemManifest, loadProductVision, checkFeatureAgainstArchitecture, renderArchCheck, planProjectSkills, guidanceForStage, renderInjectionReport, analyzeCorpus, renderRakeReport, renderCriticSection, rakeAsLesson, rakeReward, DEFAULT_RAKE_THRESHOLDS, streamSessionEvents, findLatestTranscript, detectProcessRakes, buildRetro, renderRetro, retroLessonText, PROCESS_SIGNATURES, RETRO_DOMAIN, scanForSetup, buildSetupPlan, scaffoldFromSpec, renderScaffoldPreview, readExistingForScaffold, assembleChallengeContext, buildChallengeBrief, planDiscriminationCheck, classifyDiscrimination, pickAdversaryModel, CHALLENGE_QUESTIONS, loadOutcomes, renderOutcomes, statsForKey, selectAutoCost, recordProvisional, finalizeOutcome, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, } 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
|
|
@@ -38,8 +38,8 @@ Usage:
|
|
|
38
38
|
dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--project <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force] [--enrich]
|
|
39
39
|
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)
|
|
40
40
|
dz teach --from-json <file> [--project <dir>] [--no-mirror] (bulk-import a 'dz recall --all --json' export — share a learned store across machines)
|
|
41
|
-
dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror] [--prune-noise [--apply]] (
|
|
42
|
-
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)
|
|
41
|
+
dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror] [--prune-noise [--apply]] [--prune-quarantine [--apply]] (both prunes: DRY-RUN by default; --apply snapshots then deletes; prune-quarantine = expired unproven lessons ONLY, never coupled to noise)
|
|
42
|
+
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] | dz recall --promote <dzId>[,<dzId>] [--apply] (forget/promote: dry-run default; forget snapshots before removing; promote lifts lesson-quarantine)
|
|
43
43
|
dz vector status [--project <dir>] [--json] (semantic tier: engine, mirrored vs lexical counts, pending queue)
|
|
44
44
|
dz vector reindex [--project <dir>] [--json] (snapshot, re-embed learned-pattern vectors, stamp current model)
|
|
45
45
|
dz vector export <path> [--project <dir>] (portable VECTOR form (.rvf, opt-in RVF engine); patterns ship via recall --all --json)
|
|
@@ -1199,6 +1199,18 @@ async function cmdTeach(options, flags, cwd, write) {
|
|
|
1199
1199
|
if (receipt.mirrored > 0)
|
|
1200
1200
|
write(` ↳ mirrored to vector tier (${receipt.engine ?? 'vector'})`);
|
|
1201
1201
|
};
|
|
1202
|
+
// lesson-quarantine FR-8: the fresh-teach mirror carries the qStatus marker so the hook daemon
|
|
1203
|
+
// (which reads only the mirror's metadata) can exclude unproven lessons from auto-inject.
|
|
1204
|
+
const emitMirrorQ = async (root, records, source, quarantined) => {
|
|
1205
|
+
if (flags.has('no-mirror') || records.length === 0 || !vectorMirrorEnabled(root))
|
|
1206
|
+
return;
|
|
1207
|
+
const entries = records
|
|
1208
|
+
.map((r) => patternVectorEntry(r, source, quarantined ? { quarantined: true } : {}))
|
|
1209
|
+
.filter((e) => e !== undefined);
|
|
1210
|
+
const receipt = await mirrorEntriesToVector(root, entries);
|
|
1211
|
+
if (receipt.mirrored > 0)
|
|
1212
|
+
write(` ↳ mirrored to vector tier (${receipt.engine ?? 'vector'})${quarantined ? ' [quarantined]' : ''}`);
|
|
1213
|
+
};
|
|
1202
1214
|
// `dz teach --harmonize` — documented ALIAS of `dz vector harmonize`: SEMANTIC dedup of the
|
|
1203
1215
|
// learned store. ONE implementation (harmonizeVectorStore), two entry points (QR-6). Routed
|
|
1204
1216
|
// BEFORE the --from-json / single-teach paths. Dry-run by default; --apply after a backup.
|
|
@@ -1278,6 +1290,10 @@ async function cmdTeach(options, flags, cwd, write) {
|
|
|
1278
1290
|
const trained = await backend.train();
|
|
1279
1291
|
if (trained.flushed > 0) {
|
|
1280
1292
|
write(`↳ reinforced ${reinforce}`);
|
|
1293
|
+
// lesson-quarantine: reinforcement IS promotion — keep the hook daemon's mirror in step.
|
|
1294
|
+
const clearedQ = clearAgentdbQuarantine(projectRoot, [reinforce]);
|
|
1295
|
+
if (clearedQ.cleared > 0)
|
|
1296
|
+
write(` ↳ promoted out of quarantine (mirror updated)`);
|
|
1281
1297
|
return 0;
|
|
1282
1298
|
}
|
|
1283
1299
|
// HIGH-fix: a no-match must NOT auto-teach the raw argument — callers pass dzIds or truncated
|
|
@@ -1309,6 +1325,9 @@ async function cmdTeach(options, flags, cwd, write) {
|
|
|
1309
1325
|
// teach below so the lesson is NEVER silently discarded (the exact silent-drop the ADR forbids).
|
|
1310
1326
|
if (trained.flushed > 0) {
|
|
1311
1327
|
write(`↳ reinforced existing pattern ${verdict.dzId} (cos=${verdict.cosine.toFixed(2)}) — not re-added`);
|
|
1328
|
+
const clearedQ = clearAgentdbQuarantine(projectRoot, [verdict.dzId]);
|
|
1329
|
+
if (clearedQ.cleared > 0)
|
|
1330
|
+
write(' ↳ promoted out of quarantine (mirror updated)');
|
|
1312
1331
|
return 0;
|
|
1313
1332
|
}
|
|
1314
1333
|
write(`dz teach --guard: reinforce of ${verdict.dzId} did not flush (backend off or write failure) — teaching the lesson normally instead`);
|
|
@@ -1335,18 +1354,45 @@ async function cmdTeach(options, flags, cwd, write) {
|
|
|
1335
1354
|
// Tier-2 (ADR-005): persist through the unified @dzhechkov/memory store. recordPattern
|
|
1336
1355
|
// folds any legacy .dz/patterns.jsonl into the backend (idempotent) and returns the
|
|
1337
1356
|
// total count. The lossy `npx agentdb add` dual-write was removed in Tier-1 (audit #6).
|
|
1338
|
-
|
|
1357
|
+
// lesson-quarantine (opt-in): a fresh lesson is a HYPOTHESIS until it earns promotion.
|
|
1358
|
+
const quarantineOn = readMemoryLearningConfig(projectRoot).quarantine;
|
|
1359
|
+
const count = await recordPattern(projectRoot, entry, quarantineOn ? { quarantine: true } : {});
|
|
1339
1360
|
write(`Learned: "${pattern.slice(0, 60)}${pattern.length > 60 ? '...' : ''}"`);
|
|
1340
1361
|
write(` Domain: ${domain} Reward: ${reward} Backend: memory (@dzhechkov/memory)`);
|
|
1341
1362
|
write(` Total patterns: ${count}`);
|
|
1363
|
+
if (quarantineOn) {
|
|
1364
|
+
write(' ⚠ quarantined: excluded from auto-inject, damped in recall — promote by confirming it (dz teach --reinforce "<text>") or dz recall --promote <dzId> --apply');
|
|
1365
|
+
}
|
|
1342
1366
|
// The lexical write above is durable — the vector mirror is strictly best-effort (I-3).
|
|
1343
|
-
await
|
|
1367
|
+
await emitMirrorQ(projectRoot, [entry], 'dz-teach', quarantineOn);
|
|
1344
1368
|
return 0;
|
|
1345
1369
|
}
|
|
1346
1370
|
async function cmdConsolidate(options, flags, cwd, write) {
|
|
1347
1371
|
const projectRoot = resolve(cwd, options.get('project') ?? '.');
|
|
1348
1372
|
const sessionsDirOpt = options.get('sessions-dir');
|
|
1349
1373
|
const pruneNoise = flags.has('prune-noise');
|
|
1374
|
+
// lesson-quarantine FR-7: expiry is a SEPARATE, explicit surface — never coupled to prune-noise
|
|
1375
|
+
// (the recalled decay-vs-noise lesson: valid-but-unproven is not garbage). Dry-run by default.
|
|
1376
|
+
if (flags.has('prune-quarantine')) {
|
|
1377
|
+
const cfg = readMemoryLearningConfig(projectRoot);
|
|
1378
|
+
const res = pruneQuarantinePatterns(projectRoot, { dryRun: !flags.has('apply'), expireDays: cfg.quarantineExpireDays });
|
|
1379
|
+
if (!flags.has('apply')) {
|
|
1380
|
+
write(`dz consolidate --prune-quarantine: DRY RUN — ${res.candidates.length} expired quarantined lesson(s) (> ${cfg.quarantineExpireDays}d, never reinforced)`);
|
|
1381
|
+
for (const c of res.candidates)
|
|
1382
|
+
write(` [${c.ageDays < 0 ? '?' : c.ageDays}d] ${c.dzId} ${c.text.slice(0, 70)}`);
|
|
1383
|
+
if (res.candidates.length > 0)
|
|
1384
|
+
write(' → re-run with --apply to remove (snapshots first); or promote keepers: dz recall --promote <dzId> --apply');
|
|
1385
|
+
return 0;
|
|
1386
|
+
}
|
|
1387
|
+
if (res.error !== undefined) {
|
|
1388
|
+
write(`dz consolidate --prune-quarantine: ${res.error}`);
|
|
1389
|
+
return 1;
|
|
1390
|
+
}
|
|
1391
|
+
write(`dz consolidate --prune-quarantine: removed ${res.removed} expired quarantined lesson(s)`);
|
|
1392
|
+
if (res.snapshot !== undefined)
|
|
1393
|
+
write(` snapshot: ${res.snapshot}`);
|
|
1394
|
+
return 0;
|
|
1395
|
+
}
|
|
1350
1396
|
// --prune-noise: RETRO-PRUNE legacy noise (tool telemetry + system-wrapper "responses") from
|
|
1351
1397
|
// the lexical store AND the agentdb vector mirror BEFORE harvesting, so this run's watermark
|
|
1352
1398
|
// never re-learns from junk. Best-effort — a prune error is reported, never fatal.
|
|
@@ -1572,6 +1618,44 @@ async function cmdRecallForget(options, flags, projectRoot, write) {
|
|
|
1572
1618
|
write(' the vector mirror still holds them — run `dz vector reindex` to resync');
|
|
1573
1619
|
return 0;
|
|
1574
1620
|
}
|
|
1621
|
+
/**
|
|
1622
|
+
* `dz recall --promote <dzId>[,<dzId>…] [--apply]` — lift quarantine from NAMED records
|
|
1623
|
+
* (lesson-quarantine FR-6b). Dry-run by default, the --forget symmetry. Also clears the
|
|
1624
|
+
* agentdb mirror's qStatus (best-effort) so the hook daemon stops excluding promoted lessons.
|
|
1625
|
+
*/
|
|
1626
|
+
async function cmdRecallPromote(options, flags, projectRoot, write) {
|
|
1627
|
+
const ids = (options.get('promote') ?? '').split(',').map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1628
|
+
if (ids.length === 0) {
|
|
1629
|
+
write('dz recall --promote: no ids given (comma-separated dzIds; quarantined ones are marked ⚠q in recall)');
|
|
1630
|
+
return 1;
|
|
1631
|
+
}
|
|
1632
|
+
if (!flags.has('apply')) {
|
|
1633
|
+
const records = loadStoreRecords(projectRoot);
|
|
1634
|
+
const found = ids.filter((id) => records.some((r) => r.id === id));
|
|
1635
|
+
write(`dz recall --promote: DRY RUN — ${found.length}/${ids.length} id(s) match the store. Re-run with --apply to promote.`);
|
|
1636
|
+
return 0;
|
|
1637
|
+
}
|
|
1638
|
+
const res = await promotePatterns(projectRoot, ids);
|
|
1639
|
+
// Codex-QE finding 5: even on a mid-batch failure, the ALREADY-promoted records must get their
|
|
1640
|
+
// mirror rows cleared — otherwise the canonical store and the hook-visible mirror split-brain.
|
|
1641
|
+
if (res.promoted.length > 0) {
|
|
1642
|
+
const cleared = clearAgentdbQuarantine(projectRoot, res.promoted);
|
|
1643
|
+
if (cleared.cleared > 0)
|
|
1644
|
+
write(` ↳ mirror updated (${cleared.cleared} row(s) un-quarantined in agentdb)`);
|
|
1645
|
+
else if (cleared.error !== undefined)
|
|
1646
|
+
write(` ⚠ mirror not updated (${cleared.error}) — run dz vector reindex to resync`);
|
|
1647
|
+
}
|
|
1648
|
+
if (!res.ok) {
|
|
1649
|
+
write(`dz recall --promote: failed — ${res.error ?? 'unknown error'} (promoted so far: ${res.promoted.length})`);
|
|
1650
|
+
return 1;
|
|
1651
|
+
}
|
|
1652
|
+
write(`dz recall --promote: promoted ${res.promoted.length} record(s)`);
|
|
1653
|
+
if (res.notQuarantined.length > 0)
|
|
1654
|
+
write(` already promoted (not quarantined): ${res.notQuarantined.join(', ')}`);
|
|
1655
|
+
if (res.notFound.length > 0)
|
|
1656
|
+
write(` not found: ${res.notFound.join(', ')}`);
|
|
1657
|
+
return 0;
|
|
1658
|
+
}
|
|
1575
1659
|
async function cmdRecall(options, flags, cwd, write) {
|
|
1576
1660
|
const projectRoot = resolve(cwd, options.get('project') ?? '.');
|
|
1577
1661
|
const asJson = flags.has('json');
|
|
@@ -1580,6 +1664,8 @@ async function cmdRecall(options, flags, cwd, write) {
|
|
|
1580
1664
|
return cmdRecallUsage(options, flags, projectRoot, write);
|
|
1581
1665
|
if (options.has('forget'))
|
|
1582
1666
|
return cmdRecallForget(options, flags, projectRoot, write);
|
|
1667
|
+
if (options.has('promote'))
|
|
1668
|
+
return cmdRecallPromote(options, flags, projectRoot, write);
|
|
1583
1669
|
// --all: dump the entire learned store (backend-agnostic, via loadStorePatternsSync).
|
|
1584
1670
|
// With --json this is the portable export the agentdb-memory MCP bridge consumes.
|
|
1585
1671
|
if (all) {
|
|
@@ -1679,9 +1765,17 @@ async function cmdRecall(options, flags, cwd, write) {
|
|
|
1679
1765
|
const lexLabel = result.lexicalBackend === 'sqlite' ? 'SQLite FTS5' : 'keyword (JSON)';
|
|
1680
1766
|
const ranking = vectorOn ? `${lexLabel} + vector (${result.vectorEngine}) ranking` : `${lexLabel} ranking (lexical)`;
|
|
1681
1767
|
write(`dz recall "${query}" — ${hits.length} hit(s), ${ranking}`);
|
|
1768
|
+
let sawQuarantined = false;
|
|
1682
1769
|
for (const h of hits) {
|
|
1683
1770
|
const backendTag = vectorOn ? ` ⟨${h.backend}⟩` : '';
|
|
1684
|
-
|
|
1771
|
+
const qTag = h.quarantined === true ? ' ⚠q' : '';
|
|
1772
|
+
if (h.quarantined === true)
|
|
1773
|
+
sawQuarantined = true;
|
|
1774
|
+
write(` [${h.pattern.reward.toFixed(2)}] (${h.pattern.domain})${backendTag}${qTag} ${h.pattern.pattern.slice(0, 80)}`);
|
|
1775
|
+
}
|
|
1776
|
+
if (sawQuarantined) {
|
|
1777
|
+
// The loop stays VISIBLE (ADR D2): a quarantined hit is shown, marked, and explained.
|
|
1778
|
+
write(' ⚠q = quarantined (unproven hypothesis, rank damped) — confirm with dz teach --reinforce, or dz recall --promote <dzId> --apply');
|
|
1685
1779
|
}
|
|
1686
1780
|
if (result.vectorError !== undefined && mode !== 'lexical') {
|
|
1687
1781
|
// Engine present but the semantic leg failed/timed out — one honest line, exit 0 (05 §2.3).
|