recallnest 1.4.1 → 2.2.0
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/.dockerignore +11 -0
- package/CLAUDE.md +7 -3
- package/Dockerfile +20 -0
- package/README.md +202 -130
- package/README_CN.md +263 -190
- package/ROADMAP.md +128 -81
- package/SKILL.md +250 -0
- package/assets/dashboard.png +0 -0
- package/assets/knowledge-graph.png +0 -0
- package/assets/screenshots/ui-full.png +0 -0
- package/assets/screenshots/ui-overview.png +0 -0
- package/assets/screenshots/ui-search.png +0 -0
- package/assets/ui/app.js +227 -3
- package/assets/ui/index.html +81 -11
- package/assets/ui/styles.css +178 -2
- package/bin/recallnest.mjs +62 -0
- package/bun.lock +337 -0
- package/daily-recall.sh +23 -0
- package/daily-reflections/2026-04-02.md +35 -0
- package/daily-reflections/2026-04-03.md +37 -0
- package/daily-reflections/2026-04-04.md +37 -0
- package/daily-reflections/2026-04-05.md +35 -0
- package/daily-reflections/2026-04-06.md +36 -0
- package/daily-reflections/2026-04-07.md +35 -0
- package/daily-reflections/2026-04-08.md +35 -0
- package/daily-reflections/2026-04-12.md +35 -0
- package/data/alias-map.example.json +22 -0
- package/docs/plans/2026-04-01-recall-boost.md +100 -0
- package/docs/superpowers/plans/2026-04-11-constructive-retrieval-emotion.md +1195 -0
- package/docs/superpowers/specs/2026-04-11-constructive-retrieval-emotion-design.md +398 -0
- package/glama.json +6 -0
- package/integrations/setup-all.sh +31 -0
- package/package.json +10 -3
- package/plan-philosophy-upgrades.md +363 -0
- package/plan-product-upgrades.md +155 -0
- package/plan.md +140 -0
- package/research-memory-evolution.md +440 -0
- package/research-rl-agent-memory.md +330 -0
- package/scripts/backfill-anchors.ts +130 -0
- package/scripts/diagnose-vector-search.ts +132 -0
- package/scripts/migrate-language.ts +75 -0
- package/scripts/verify-emotion-decay.ts +148 -0
- package/src/__tests__/activity-counter.test.ts +96 -0
- package/src/__tests__/admission-control.test.ts +206 -0
- package/src/__tests__/anchor-generator.test.ts +67 -0
- package/src/__tests__/babel-integration.test.ts +139 -0
- package/src/__tests__/capture-engine.test.ts +5 -3
- package/src/__tests__/cascade-forget.test.ts +1 -1
- package/src/__tests__/cc8-cc1-essential-context.test.ts +298 -0
- package/src/__tests__/circuit-breaker.test.ts +99 -0
- package/src/__tests__/cluster-consolidation.test.ts +712 -0
- package/src/__tests__/confidence-metadata.test.ts +125 -0
- package/src/__tests__/confidence-tracker.test.ts +53 -0
- package/src/__tests__/consolidation-engine.test.ts +1 -1
- package/src/__tests__/constructive-retrieval.test.ts +509 -0
- package/src/__tests__/context-collapse-renderer.test.ts +214 -0
- package/src/__tests__/context-reconstructor.test.ts +65 -0
- package/src/__tests__/conversation-importer.test.ts +692 -0
- package/src/__tests__/data-checkup.test.ts +225 -0
- package/src/__tests__/decay-exemption.test.ts +84 -0
- package/src/__tests__/dedup-guard.test.ts +39 -0
- package/src/__tests__/distill-lock.test.ts +176 -0
- package/src/__tests__/dream-pipeline.test.ts +232 -0
- package/src/__tests__/emotion-decay.test.ts +46 -0
- package/src/__tests__/emotion-detector.test.ts +56 -0
- package/src/__tests__/emotion-phase1-integration.test.ts +153 -0
- package/src/__tests__/emotion-retrieval.test.ts +56 -0
- package/src/__tests__/ethics-layer.test.ts +476 -0
- package/src/__tests__/feature-flags-integration.test.ts +54 -0
- package/src/__tests__/frequency-tracker.test.ts +113 -0
- package/src/__tests__/graph-export.test.ts +489 -0
- package/src/__tests__/interference-detector.test.ts +203 -0
- package/src/__tests__/mcp-efficiency.test.ts +288 -0
- package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +243 -0
- package/src/__tests__/memory-evolution.test.ts +271 -0
- package/src/__tests__/memory-lint.test.ts +271 -0
- package/src/__tests__/narrative-architecture.test.ts +456 -0
- package/src/__tests__/pending-review.test.ts +77 -0
- package/src/__tests__/post-retrieval-filter.test.ts +178 -0
- package/src/__tests__/prediction-engine.test.ts +349 -0
- package/src/__tests__/predictive-prospective-memory.test.ts +416 -0
- package/src/__tests__/preference-slots.test.ts +99 -0
- package/src/__tests__/query-expander-alias.test.ts +89 -0
- package/src/__tests__/recall-governor.test.ts +227 -0
- package/src/__tests__/resume-context.test.ts +156 -1
- package/src/__tests__/salience-scoring.test.ts +106 -0
- package/src/__tests__/session-distiller.test.ts +611 -0
- package/src/__tests__/skill-engine.test.ts +546 -0
- package/src/__tests__/skill-promotion.test.ts +349 -0
- package/src/__tests__/supersede-chain.test.ts +154 -0
- package/src/__tests__/temporal-validity.test.ts +160 -0
- package/src/__tests__/topic-tag.test.ts +113 -0
- package/src/__tests__/trusted-memory.test.ts +828 -0
- package/src/__tests__/write-verifier.test.ts +122 -0
- package/src/access-tracker.ts +3 -2
- package/src/activity-counter.ts +109 -0
- package/src/adaptive-retrieval.ts +6 -5
- package/src/admission-control.ts +163 -0
- package/src/anchor-generator.ts +80 -0
- package/src/api-server.ts +10 -0
- package/src/audit-log.ts +122 -0
- package/src/auto-gc.ts +98 -29
- package/src/auto-recall.ts +33 -4
- package/src/capture-engine.ts +372 -23
- package/src/cascade-forget.ts +3 -2
- package/src/cli.ts +107 -5
- package/src/compat.ts +22 -0
- package/src/confidence-tracker.ts +210 -47
- package/src/conflict-store.ts +12 -3
- package/src/consolidation-engine.ts +296 -8
- package/src/context-collapse-renderer.ts +222 -0
- package/src/context-composer-stable-selection.ts +5 -1
- package/src/context-composer.ts +239 -4
- package/src/context-reconstructor.ts +404 -0
- package/src/conversation-importer.ts +441 -0
- package/src/data-checkup.ts +232 -0
- package/src/decay-engine.ts +84 -0
- package/src/distill-lock.ts +122 -0
- package/src/doctor.ts +8 -10
- package/src/dream-pipeline.ts +227 -0
- package/src/emotion-detector.ts +90 -0
- package/src/eval.ts +3 -2
- package/src/forget-engine.ts +269 -0
- package/src/frequency-tracker.ts +175 -0
- package/src/graph-export.ts +644 -0
- package/src/ingest.ts +36 -6
- package/src/interference-detector.ts +187 -0
- package/src/kg-extractor.ts +15 -24
- package/src/kg-store.ts +2 -1
- package/src/language-hook.ts +67 -0
- package/src/llm-client.ts +289 -6
- package/src/mcp-server.ts +707 -80
- package/src/memory-assets.ts +22 -5
- package/src/memory-evolution.ts +365 -0
- package/src/memory-lint.ts +429 -0
- package/src/memory-output.ts +69 -0
- package/src/memory-schema.ts +74 -2
- package/src/narrative-schema.ts +79 -0
- package/src/narrative-tagger.ts +164 -0
- package/src/pii-detector.ts +98 -0
- package/src/post-retrieval-filter.ts +76 -0
- package/src/prediction-engine.ts +253 -0
- package/src/preference-slots.ts +65 -2
- package/src/prospective-memory.ts +215 -0
- package/src/query-expander.ts +67 -0
- package/src/recall-governor.ts +183 -0
- package/src/retention-policy.ts +90 -0
- package/src/retriever.ts +828 -55
- package/src/rif.ts +34 -8
- package/src/runtime-config.ts +23 -8
- package/src/scope-inventory.ts +4 -3
- package/src/scope-policy.ts +4 -0
- package/src/session-distiller.ts +307 -0
- package/src/session-output.ts +40 -0
- package/src/session-schema.ts +37 -0
- package/src/session-store.ts +7 -2
- package/src/skill-engine.ts +208 -0
- package/src/skill-promotion.ts +293 -0
- package/src/skill-schema.ts +39 -0
- package/src/store.ts +96 -10
- package/src/topic-tag.ts +66 -0
- package/src/tracker.ts +3 -1
- package/src/ui-server.ts +80 -8
- package/src/workflow-observation-scope-review.ts +2 -1
- package/src/workflow-observation-store.ts +2 -1
- package/src/write-verifier.ts +126 -0
package/src/cli.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
|
14
14
|
import { join, resolve } from "node:path";
|
|
15
15
|
import { homedir } from "node:os";
|
|
16
16
|
|
|
17
|
+
import { metaDir } from "./compat.js";
|
|
17
18
|
import type { RetrievalResult } from "./retriever.js";
|
|
18
19
|
import { applyRetrievalProfile, listRetrievalProfiles } from "./retrieval-profiles.js";
|
|
19
20
|
import { distillResults, formatExplainResults, formatSearchResults, selectBriefSeedResults, summarizeResults } from "./memory-output.js";
|
|
@@ -280,19 +281,19 @@ function filterConflictsByAttention<T extends { status: string; createdAt: strin
|
|
|
280
281
|
function workflowPatternSeedsPath(file?: string): string {
|
|
281
282
|
return file
|
|
282
283
|
? resolve(file)
|
|
283
|
-
: resolve(import.meta
|
|
284
|
+
: resolve(metaDir(import.meta), "../eval/continuity/pattern-seeds.json");
|
|
284
285
|
}
|
|
285
286
|
|
|
286
287
|
function caseMemorySeedsPath(file?: string): string {
|
|
287
288
|
return file
|
|
288
289
|
? resolve(file)
|
|
289
|
-
: resolve(import.meta
|
|
290
|
+
: resolve(metaDir(import.meta), "../eval/continuity/case-seeds.json");
|
|
290
291
|
}
|
|
291
292
|
|
|
292
293
|
function memorySeedsPath(file?: string): string {
|
|
293
294
|
return file
|
|
294
295
|
? resolve(file)
|
|
295
|
-
: resolve(import.meta
|
|
296
|
+
: resolve(metaDir(import.meta), "../eval/continuity/memory-seeds.json");
|
|
296
297
|
}
|
|
297
298
|
|
|
298
299
|
function parseMetadata(raw?: string): Record<string, unknown> {
|
|
@@ -1409,7 +1410,7 @@ program
|
|
|
1409
1410
|
if (source === "all" || source === "desktop") {
|
|
1410
1411
|
const desktopSource = config.sources.desktop;
|
|
1411
1412
|
if (desktopSource) {
|
|
1412
|
-
const desktopPath = resolve(import.meta
|
|
1413
|
+
const desktopPath = resolve(metaDir(import.meta), "..", desktopSource.path);
|
|
1413
1414
|
if (existsSync(desktopPath)) {
|
|
1414
1415
|
console.log("🖥️ 导入 Claude Desktop 对话...");
|
|
1415
1416
|
const r = await ingestCCTranscripts(store, embedder, desktopPath, ingestOpts);
|
|
@@ -1580,7 +1581,7 @@ program
|
|
|
1580
1581
|
}
|
|
1581
1582
|
|
|
1582
1583
|
const config = loadConfig();
|
|
1583
|
-
const dbPath = resolve(import.meta
|
|
1584
|
+
const dbPath = resolve(metaDir(import.meta), "..", expandHome(config.dbPath));
|
|
1584
1585
|
|
|
1585
1586
|
if (existsSync(dbPath)) {
|
|
1586
1587
|
const { rmSync } = await import("node:fs");
|
|
@@ -1898,6 +1899,51 @@ program
|
|
|
1898
1899
|
}
|
|
1899
1900
|
});
|
|
1900
1901
|
|
|
1902
|
+
program
|
|
1903
|
+
.command("import <path>")
|
|
1904
|
+
.description("导入多格式对话文件到记忆索引")
|
|
1905
|
+
.option("-f, --format <format>", "格式: auto|claude-code|chatgpt|slack|claude-ai|plaintext", "auto")
|
|
1906
|
+
.option("-s, --scope <scope>", "目标 scope(必须)")
|
|
1907
|
+
.action(async (filePath: string, options) => {
|
|
1908
|
+
if (!options.scope) {
|
|
1909
|
+
console.error("❌ --scope is required");
|
|
1910
|
+
process.exitCode = 1;
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
const resolvedPath = resolve(filePath);
|
|
1914
|
+
if (!existsSync(resolvedPath)) {
|
|
1915
|
+
console.error(`❌ File not found: ${resolvedPath}`);
|
|
1916
|
+
process.exitCode = 1;
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
const validFormats = ["auto", "claude-code", "chatgpt", "slack", "claude-ai", "plaintext"] as const;
|
|
1920
|
+
if (!validFormats.includes(options.format)) {
|
|
1921
|
+
console.error(`❌ Invalid format: ${options.format}. Must be one of: ${validFormats.join(", ")}`);
|
|
1922
|
+
process.exitCode = 1;
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
const config = loadConfig();
|
|
1927
|
+
const { store, embedder, llm } = createComponents(config);
|
|
1928
|
+
|
|
1929
|
+
const { importConversation } = await import("./conversation-importer.js");
|
|
1930
|
+
const result = await importConversation(
|
|
1931
|
+
{ store, embedder, llm },
|
|
1932
|
+
resolvedPath,
|
|
1933
|
+
options.scope,
|
|
1934
|
+
options.format as "auto" | "claude-code" | "chatgpt" | "slack" | "claude-ai" | "plaintext",
|
|
1935
|
+
);
|
|
1936
|
+
|
|
1937
|
+
console.log(`\n📥 Import complete`);
|
|
1938
|
+
console.log(` Total messages: ${result.total}`);
|
|
1939
|
+
console.log(` Stored: ${result.stored}`);
|
|
1940
|
+
console.log(` Rejected: ${result.rejected}`);
|
|
1941
|
+
if (result.errors.length > 0) {
|
|
1942
|
+
console.log(` Errors: ${result.errors.length}`);
|
|
1943
|
+
for (const e of result.errors) console.log(` ${e}`);
|
|
1944
|
+
}
|
|
1945
|
+
});
|
|
1946
|
+
|
|
1901
1947
|
program
|
|
1902
1948
|
.command("doctor")
|
|
1903
1949
|
.description("检查安装状态和配置")
|
|
@@ -1909,6 +1955,62 @@ program
|
|
|
1909
1955
|
if (hasFailure) process.exitCode = 1;
|
|
1910
1956
|
});
|
|
1911
1957
|
|
|
1958
|
+
program
|
|
1959
|
+
.command("lint")
|
|
1960
|
+
.description("Run memory quality lint checks")
|
|
1961
|
+
.option("--scope <scope>", "Limit to a specific scope")
|
|
1962
|
+
.option("--json", "Output raw JSON instead of formatted report")
|
|
1963
|
+
.option("--verbose", "Show all individual findings")
|
|
1964
|
+
.action(async (options) => {
|
|
1965
|
+
const { runMemoryLint, formatMemoryLintReport } = await import("./memory-lint.js");
|
|
1966
|
+
const config = loadConfig();
|
|
1967
|
+
const { store } = createComponents(config);
|
|
1968
|
+
|
|
1969
|
+
const report = await runMemoryLint({
|
|
1970
|
+
store,
|
|
1971
|
+
scope: options.scope,
|
|
1972
|
+
verbose: options.verbose,
|
|
1973
|
+
});
|
|
1974
|
+
|
|
1975
|
+
if (options.json) {
|
|
1976
|
+
console.log(JSON.stringify(report, null, 2));
|
|
1977
|
+
} else {
|
|
1978
|
+
console.log();
|
|
1979
|
+
console.log(formatMemoryLintReport(report));
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
program
|
|
1984
|
+
.command("graph")
|
|
1985
|
+
.description("Export interactive knowledge graph as HTML")
|
|
1986
|
+
.option("--scope <scope>", "Limit to a specific scope")
|
|
1987
|
+
.option("--max-nodes <n>", "Maximum nodes (default 200)", "200")
|
|
1988
|
+
.option("--out <path>", "Output file path")
|
|
1989
|
+
.option("--open", "Open in browser after export")
|
|
1990
|
+
.action(async (options) => {
|
|
1991
|
+
const { exportMemoryGraph, formatGraphExportResult } = await import("./graph-export.js");
|
|
1992
|
+
const config = loadConfig();
|
|
1993
|
+
const { store } = createComponents(config);
|
|
1994
|
+
|
|
1995
|
+
const maxNodes = parseInt(options.maxNodes, 10) || 200;
|
|
1996
|
+
const { path, graph } = await exportMemoryGraph(store, {
|
|
1997
|
+
scope: options.scope,
|
|
1998
|
+
maxNodes,
|
|
1999
|
+
outputPath: options.out,
|
|
2000
|
+
});
|
|
2001
|
+
|
|
2002
|
+
console.log();
|
|
2003
|
+
console.log(formatGraphExportResult(path, graph));
|
|
2004
|
+
|
|
2005
|
+
if (options.open) {
|
|
2006
|
+
const { execFileSync } = await import("node:child_process");
|
|
2007
|
+
try {
|
|
2008
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
2009
|
+
execFileSync(cmd, [path], { stdio: "ignore" });
|
|
2010
|
+
} catch {}
|
|
2011
|
+
}
|
|
2012
|
+
});
|
|
2013
|
+
|
|
1912
2014
|
// Run
|
|
1913
2015
|
loadDotEnv();
|
|
1914
2016
|
program.parseAsync(process.argv).catch((error) => {
|
package/src/compat.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-runtime compatibility helpers
|
|
3
|
+
*
|
|
4
|
+
* Bridges Bun-specific APIs to work with Node.js as well.
|
|
5
|
+
*/
|
|
6
|
+
import { dirname } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Cross-runtime replacement for Bun's `import.meta.dir`.
|
|
11
|
+
* - Bun: returns import.meta.dir (native, fast)
|
|
12
|
+
* - Node.js: derives from import.meta.url
|
|
13
|
+
*
|
|
14
|
+
* Usage: replace `import.meta.dir` with `metaDir(import.meta)`
|
|
15
|
+
*/
|
|
16
|
+
export function metaDir(meta: ImportMeta): string {
|
|
17
|
+
// Bun sets import.meta.dir natively
|
|
18
|
+
const dir = (meta as Record<string, unknown>).dir;
|
|
19
|
+
if (typeof dir === "string") return dir;
|
|
20
|
+
// Node.js fallback
|
|
21
|
+
return dirname(fileURLToPath(meta.url));
|
|
22
|
+
}
|
|
@@ -19,9 +19,13 @@
|
|
|
19
19
|
*
|
|
20
20
|
* Retrieval integration: confidence multiplies into the score pipeline
|
|
21
21
|
* as `score *= (0.5 + 0.5 * confidence)` — same pattern as importance.
|
|
22
|
+
*
|
|
23
|
+
* Design: returns metadata patches instead of directly mutating the store.
|
|
24
|
+
* Callers apply patches via their own store.update() / patchMetadata().
|
|
25
|
+
* This keeps the module store-agnostic and easier to test.
|
|
22
26
|
*/
|
|
23
27
|
|
|
24
|
-
import type {
|
|
28
|
+
import type { MemoryEntry } from "./store.js";
|
|
25
29
|
|
|
26
30
|
// ---------------------------------------------------------------------------
|
|
27
31
|
// Constants
|
|
@@ -32,24 +36,60 @@ export const CONFIDENCE_CONFIRMED = 1.0;
|
|
|
32
36
|
export const CONFIDENCE_CORRECTED = 0.3;
|
|
33
37
|
export const CONFIDENCE_CONTRADICTED = 0.0;
|
|
34
38
|
|
|
39
|
+
/** Max history entries to keep per memory (prevents unbounded growth). */
|
|
40
|
+
const MAX_CONFIDENCE_HISTORY = 20;
|
|
41
|
+
|
|
35
42
|
// ---------------------------------------------------------------------------
|
|
36
|
-
//
|
|
43
|
+
// F1: Structured Confidence Metadata
|
|
37
44
|
// ---------------------------------------------------------------------------
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
export type ConfidenceReliability = "direct" | "inferred" | "hearsay";
|
|
47
|
+
|
|
48
|
+
export interface ConfidenceMetadata {
|
|
49
|
+
/** 0–1 composite confidence score. */
|
|
50
|
+
score: number;
|
|
51
|
+
/** Source reliability tier. */
|
|
52
|
+
reliability: ConfidenceReliability;
|
|
53
|
+
/** Last verification timestamp (ms). */
|
|
54
|
+
verifiedAt?: number;
|
|
55
|
+
/** Who verified: user / system / llm. */
|
|
56
|
+
verifiedBy?: string;
|
|
41
57
|
}
|
|
42
58
|
|
|
43
59
|
/**
|
|
44
|
-
*
|
|
60
|
+
* F1: Assign default confidence based on memory source.
|
|
61
|
+
* Returns a ConfidenceMetadata object to embed in metadata JSON.
|
|
45
62
|
*/
|
|
46
|
-
export function
|
|
47
|
-
|
|
48
|
-
|
|
63
|
+
export function assignDefaultConfidence(
|
|
64
|
+
source: string,
|
|
65
|
+
explicitConfidence?: Partial<ConfidenceMetadata>,
|
|
66
|
+
): ConfidenceMetadata {
|
|
67
|
+
// If caller provided explicit values, use them (fill in defaults for missing fields)
|
|
68
|
+
if (explicitConfidence?.score != null) {
|
|
69
|
+
return {
|
|
70
|
+
score: explicitConfidence.score,
|
|
71
|
+
reliability: explicitConfidence.reliability ?? "inferred",
|
|
72
|
+
verifiedAt: explicitConfidence.verifiedAt,
|
|
73
|
+
verifiedBy: explicitConfidence.verifiedBy,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
switch (source) {
|
|
77
|
+
case "manual":
|
|
78
|
+
return { score: 0.9, reliability: "direct" };
|
|
79
|
+
case "agent":
|
|
80
|
+
case "api":
|
|
81
|
+
return { score: 0.7, reliability: "inferred" };
|
|
82
|
+
case "session_distill":
|
|
83
|
+
return { score: 0.6, reliability: "inferred" };
|
|
84
|
+
case "conversation_import":
|
|
85
|
+
return { score: 0.5, reliability: "hearsay" };
|
|
86
|
+
default:
|
|
87
|
+
return { score: CONFIDENCE_DEFAULT, reliability: "inferred" };
|
|
88
|
+
}
|
|
49
89
|
}
|
|
50
90
|
|
|
51
91
|
// ---------------------------------------------------------------------------
|
|
52
|
-
//
|
|
92
|
+
// Types
|
|
53
93
|
// ---------------------------------------------------------------------------
|
|
54
94
|
|
|
55
95
|
export interface ConfidenceUpdate {
|
|
@@ -58,46 +98,118 @@ export interface ConfidenceUpdate {
|
|
|
58
98
|
newConfidence: number;
|
|
59
99
|
}
|
|
60
100
|
|
|
101
|
+
export interface ConfidencePatch {
|
|
102
|
+
/** The metadata object to merge/write back. */
|
|
103
|
+
metadata: Record<string, unknown>;
|
|
104
|
+
/** Summary of what changed. */
|
|
105
|
+
update: ConfidenceUpdate;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface ConfidenceHistoryEntry {
|
|
109
|
+
action: "confirmed" | "corrected" | "contradicted";
|
|
110
|
+
from: number;
|
|
111
|
+
to: number;
|
|
112
|
+
date: string;
|
|
113
|
+
correctedBy?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Helpers
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
function parseMeta(entry: MemoryEntry): Record<string, unknown> {
|
|
121
|
+
try { return JSON.parse(entry.metadata || "{}"); } catch { return {}; }
|
|
122
|
+
}
|
|
123
|
+
|
|
61
124
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
125
|
+
* Read confidence score from metadata. Handles both flat number (legacy)
|
|
126
|
+
* and structured ConfidenceMetadata object (F1).
|
|
127
|
+
* Returns default (0.7) if not set.
|
|
64
128
|
*/
|
|
65
|
-
export
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
129
|
+
export function getConfidence(entry: MemoryEntry): number {
|
|
130
|
+
const meta = parseMeta(entry);
|
|
131
|
+
if (typeof meta.confidence === "number") return meta.confidence;
|
|
132
|
+
if (meta.confidence && typeof meta.confidence === "object") {
|
|
133
|
+
const obj = meta.confidence as Record<string, unknown>;
|
|
134
|
+
if (typeof obj.score === "number") return obj.score;
|
|
135
|
+
}
|
|
136
|
+
return CONFIDENCE_DEFAULT;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* F1: Read full ConfidenceMetadata from entry. Returns null if not set.
|
|
141
|
+
*/
|
|
142
|
+
export function getConfidenceMetadata(entry: MemoryEntry): ConfidenceMetadata | null {
|
|
143
|
+
const meta = parseMeta(entry);
|
|
144
|
+
if (meta.confidence && typeof meta.confidence === "object") {
|
|
145
|
+
const obj = meta.confidence as Record<string, unknown>;
|
|
146
|
+
if (typeof obj.score === "number") {
|
|
147
|
+
return {
|
|
148
|
+
score: obj.score,
|
|
149
|
+
reliability: (obj.reliability as ConfidenceReliability) ?? "inferred",
|
|
150
|
+
verifiedAt: typeof obj.verifiedAt === "number" ? obj.verifiedAt : undefined,
|
|
151
|
+
verifiedBy: typeof obj.verifiedBy === "string" ? obj.verifiedBy : undefined,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Legacy flat number → wrap
|
|
156
|
+
if (typeof meta.confidence === "number") {
|
|
157
|
+
return { score: meta.confidence, reliability: "inferred" };
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function appendHistory(
|
|
163
|
+
meta: Record<string, unknown>,
|
|
164
|
+
entry: ConfidenceHistoryEntry,
|
|
165
|
+
): void {
|
|
166
|
+
if (!Array.isArray(meta.confidence_history)) meta.confidence_history = [];
|
|
167
|
+
const history = meta.confidence_history as ConfidenceHistoryEntry[];
|
|
168
|
+
history.push(entry);
|
|
169
|
+
// Cap history length
|
|
170
|
+
if (history.length > MAX_CONFIDENCE_HISTORY) {
|
|
171
|
+
meta.confidence_history = history.slice(-MAX_CONFIDENCE_HISTORY);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Patch builders (store-agnostic — return patches, don't write)
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Build a patch to mark a memory as confirmed (user validated it).
|
|
181
|
+
* Returns null if entry is null (not found).
|
|
182
|
+
*/
|
|
183
|
+
export function buildConfirmPatch(
|
|
184
|
+
entry: MemoryEntry | null,
|
|
185
|
+
): ConfidencePatch | null {
|
|
71
186
|
if (!entry) return null;
|
|
72
187
|
|
|
73
188
|
const meta = parseMeta(entry);
|
|
74
189
|
const oldConfidence = typeof meta.confidence === "number" ? meta.confidence : CONFIDENCE_DEFAULT;
|
|
75
190
|
|
|
76
191
|
meta.confidence = CONFIDENCE_CONFIRMED;
|
|
77
|
-
|
|
78
|
-
meta.confidence_history.push({
|
|
192
|
+
appendHistory(meta, {
|
|
79
193
|
action: "confirmed",
|
|
80
194
|
from: oldConfidence,
|
|
81
195
|
to: CONFIDENCE_CONFIRMED,
|
|
82
196
|
date: new Date().toISOString().slice(0, 10),
|
|
83
197
|
});
|
|
84
198
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
199
|
+
return {
|
|
200
|
+
metadata: meta,
|
|
201
|
+
update: { entryId: entry.id, oldConfidence, newConfidence: CONFIDENCE_CONFIRMED },
|
|
202
|
+
};
|
|
88
203
|
}
|
|
89
204
|
|
|
90
205
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
206
|
+
* Build a patch to mark a memory as corrected (user provided updated info).
|
|
207
|
+
* Returns null if entry is null (not found).
|
|
93
208
|
*/
|
|
94
|
-
export
|
|
95
|
-
|
|
96
|
-
entryId: string,
|
|
97
|
-
scope: string,
|
|
209
|
+
export function buildCorrectPatch(
|
|
210
|
+
entry: MemoryEntry | null,
|
|
98
211
|
correctedById?: string,
|
|
99
|
-
):
|
|
100
|
-
const entry = await store.getById(entryId);
|
|
212
|
+
): ConfidencePatch | null {
|
|
101
213
|
if (!entry) return null;
|
|
102
214
|
|
|
103
215
|
const meta = parseMeta(entry);
|
|
@@ -105,8 +217,7 @@ export async function correctMemory(
|
|
|
105
217
|
|
|
106
218
|
meta.confidence = CONFIDENCE_CORRECTED;
|
|
107
219
|
if (correctedById) meta.corrected_by = correctedById;
|
|
108
|
-
|
|
109
|
-
meta.confidence_history.push({
|
|
220
|
+
appendHistory(meta, {
|
|
110
221
|
action: "corrected",
|
|
111
222
|
from: oldConfidence,
|
|
112
223
|
to: CONFIDENCE_CORRECTED,
|
|
@@ -114,40 +225,92 @@ export async function correctMemory(
|
|
|
114
225
|
date: new Date().toISOString().slice(0, 10),
|
|
115
226
|
});
|
|
116
227
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
228
|
+
return {
|
|
229
|
+
metadata: meta,
|
|
230
|
+
update: { entryId: entry.id, oldConfidence, newConfidence: CONFIDENCE_CORRECTED },
|
|
231
|
+
};
|
|
120
232
|
}
|
|
121
233
|
|
|
122
234
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
235
|
+
* Build a patch to mark a memory as contradicted (explicitly wrong).
|
|
236
|
+
* Returns null if entry is null (not found).
|
|
125
237
|
*/
|
|
126
|
-
export
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
scope: string,
|
|
130
|
-
): Promise<ConfidenceUpdate | null> {
|
|
131
|
-
const entry = await store.getById(entryId);
|
|
238
|
+
export function buildContradictPatch(
|
|
239
|
+
entry: MemoryEntry | null,
|
|
240
|
+
): ConfidencePatch | null {
|
|
132
241
|
if (!entry) return null;
|
|
133
242
|
|
|
134
243
|
const meta = parseMeta(entry);
|
|
135
244
|
const oldConfidence = typeof meta.confidence === "number" ? meta.confidence : CONFIDENCE_DEFAULT;
|
|
136
245
|
|
|
137
246
|
meta.confidence = CONFIDENCE_CONTRADICTED;
|
|
138
|
-
|
|
139
|
-
meta.confidence_history.push({
|
|
247
|
+
appendHistory(meta, {
|
|
140
248
|
action: "contradicted",
|
|
141
249
|
from: oldConfidence,
|
|
142
250
|
to: CONFIDENCE_CONTRADICTED,
|
|
143
251
|
date: new Date().toISOString().slice(0, 10),
|
|
144
252
|
});
|
|
145
253
|
|
|
146
|
-
|
|
254
|
+
return {
|
|
255
|
+
metadata: meta,
|
|
256
|
+
update: { entryId: entry.id, oldConfidence, newConfidence: CONFIDENCE_CONTRADICTED },
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// Convenience wrappers (for callers who DO have a store)
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Confirm a memory and write back to store. Convenience wrapper.
|
|
266
|
+
*/
|
|
267
|
+
export async function confirmMemory(
|
|
268
|
+
store: { getById(id: string): Promise<MemoryEntry | null>; update(id: string, data: { metadata: string }, scopes: string[]): Promise<void> },
|
|
269
|
+
entryId: string,
|
|
270
|
+
scope: string,
|
|
271
|
+
): Promise<ConfidenceUpdate | null> {
|
|
272
|
+
const entry = await store.getById(entryId);
|
|
273
|
+
const patch = buildConfirmPatch(entry);
|
|
274
|
+
if (!patch) return null;
|
|
275
|
+
await store.update(entryId, { metadata: JSON.stringify(patch.metadata) }, [scope]);
|
|
276
|
+
return patch.update;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Correct a memory and write back to store. Convenience wrapper.
|
|
281
|
+
*/
|
|
282
|
+
export async function correctMemory(
|
|
283
|
+
store: { getById(id: string): Promise<MemoryEntry | null>; update(id: string, data: { metadata: string }, scopes: string[]): Promise<void> },
|
|
284
|
+
entryId: string,
|
|
285
|
+
scope: string,
|
|
286
|
+
correctedById?: string,
|
|
287
|
+
): Promise<ConfidenceUpdate | null> {
|
|
288
|
+
const entry = await store.getById(entryId);
|
|
289
|
+
const patch = buildCorrectPatch(entry, correctedById);
|
|
290
|
+
if (!patch) return null;
|
|
291
|
+
await store.update(entryId, { metadata: JSON.stringify(patch.metadata) }, [scope]);
|
|
292
|
+
return patch.update;
|
|
293
|
+
}
|
|
147
294
|
|
|
148
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Contradict a memory and write back to store. Convenience wrapper.
|
|
297
|
+
*/
|
|
298
|
+
export async function contradictMemory(
|
|
299
|
+
store: { getById(id: string): Promise<MemoryEntry | null>; update(id: string, data: { metadata: string }, scopes: string[]): Promise<void> },
|
|
300
|
+
entryId: string,
|
|
301
|
+
scope: string,
|
|
302
|
+
): Promise<ConfidenceUpdate | null> {
|
|
303
|
+
const entry = await store.getById(entryId);
|
|
304
|
+
const patch = buildContradictPatch(entry);
|
|
305
|
+
if (!patch) return null;
|
|
306
|
+
await store.update(entryId, { metadata: JSON.stringify(patch.metadata) }, [scope]);
|
|
307
|
+
return patch.update;
|
|
149
308
|
}
|
|
150
309
|
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
// Retrieval integration (unchanged — already store-agnostic)
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
|
|
151
314
|
/**
|
|
152
315
|
* Apply confidence weighting to retrieval scores.
|
|
153
316
|
* Formula: score *= (0.5 + 0.5 * confidence)
|
package/src/conflict-store.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
|
|
4
|
+
import { metaDir } from "./compat.js";
|
|
4
5
|
import type { ConflictCandidateRecord, ConflictStatus } from "./conflict-schema.js";
|
|
5
6
|
import { ConflictCandidateRecordSchema } from "./conflict-schema.js";
|
|
6
7
|
|
|
@@ -24,7 +25,7 @@ function sortNewestFirst(records: ConflictCandidateRecord[]): ConflictCandidateR
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export class ConflictCandidateStore {
|
|
27
|
-
constructor(private readonly dir = resolve(import.meta
|
|
28
|
+
constructor(private readonly dir = resolve(metaDir(import.meta), "../data/conflict-candidates")) {}
|
|
28
29
|
|
|
29
30
|
get dataDir(): string {
|
|
30
31
|
return ensureDir(this.dir);
|
|
@@ -34,7 +35,11 @@ export class ConflictCandidateStore {
|
|
|
34
35
|
const parsed = ConflictCandidateRecordSchema.parse(record);
|
|
35
36
|
const timestampToken = parsed.createdAt.replace(/[:.]/g, "-");
|
|
36
37
|
const path = join(this.dataDir, `${timestampToken}-${parsed.conflictId}.json`);
|
|
37
|
-
|
|
38
|
+
try {
|
|
39
|
+
writeFileSync(path, JSON.stringify(parsed, null, 2) + "\n");
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error("[recallnest] Failed to save conflict record:", err instanceof Error ? err.message : String(err));
|
|
42
|
+
}
|
|
38
43
|
return parsed;
|
|
39
44
|
}
|
|
40
45
|
|
|
@@ -43,7 +48,11 @@ export class ConflictCandidateStore {
|
|
|
43
48
|
const existingPath = this.findPathById(parsed.conflictId);
|
|
44
49
|
const timestampToken = parsed.createdAt.replace(/[:.]/g, "-");
|
|
45
50
|
const path = existingPath || join(this.dataDir, `${timestampToken}-${parsed.conflictId}.json`);
|
|
46
|
-
|
|
51
|
+
try {
|
|
52
|
+
writeFileSync(path, JSON.stringify(parsed, null, 2) + "\n");
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error("[recallnest] Failed to replace conflict record:", err instanceof Error ? err.message : String(err));
|
|
55
|
+
}
|
|
47
56
|
return parsed;
|
|
48
57
|
}
|
|
49
58
|
|