recallnest 2.2.0 → 2.3.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/.env +4 -0
- package/CLAUDE.md +1 -1
- package/README.md +30 -5
- package/README_CN.md +30 -5
- package/config.json +55 -0
- package/connectors/examples/email-adapter.ts +73 -0
- package/connectors/examples/logs-adapter.ts +103 -0
- package/connectors/examples/rss-adapter.ts +95 -0
- package/logs/daily-recall.error.log +0 -0
- package/logs/daily-recall.log +60 -0
- package/logs/ingest-2026-04-11.log +110 -0
- package/logs/ingest-2026-04-12.log +90 -0
- package/logs/ingest-2026-04-13.log +87 -0
- package/logs/launchagent-stderr.log +0 -0
- package/logs/launchagent-stdout.log +0 -0
- package/package.json +1 -1
- package/src/cli.ts +30 -0
- package/src/connector-types.ts +57 -0
- package/src/conversation-importer.ts +25 -1
- package/src/data-checkup.ts +31 -1
- package/src/doctor.ts +29 -0
- package/src/ingest.ts +203 -0
- package/src/mcp-server.ts +2 -2
- package/src/obsidian-connector.ts +282 -0
- package/src/session-schema.ts +2 -2
- package/src/source-heartbeat.ts +108 -0
- package/.claude-plugin/CLAUDE.md +0 -29
- package/.claude-plugin/marketplace.json +0 -18
- package/.dockerignore +0 -11
- package/.github/workflows/ci.yml +0 -28
- package/.mcp.json +0 -9
- package/CHANGELOG.md +0 -51
- package/DEMO.md +0 -94
- package/Dockerfile +0 -20
- package/EVAL.md +0 -89
- package/FAILURES.md +0 -75
- package/OPERATIONS.md +0 -59
- package/ROADMAP.md +0 -245
- package/SKILL.md +0 -250
- package/assets/dashboard.png +0 -0
- package/assets/knowledge-graph.png +0 -0
- package/assets/recallnest-workbench.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 +0 -1031
- package/assets/ui/index.html +0 -224
- package/assets/ui/styles.css +0 -781
- package/assets/wechat-qr.jpg +0 -0
- package/bun.lock +0 -337
- package/daily-reflections/2026-03-05.md +0 -15
- package/daily-reflections/2026-03-06.md +0 -15
- package/daily-reflections/2026-03-07.md +0 -7
- package/daily-reflections/2026-03-08.md +0 -34
- package/daily-reflections/2026-03-09.md +0 -35
- package/daily-reflections/2026-03-10.md +0 -36
- package/daily-reflections/2026-03-11.md +0 -35
- package/daily-reflections/2026-03-12.md +0 -36
- package/daily-reflections/2026-03-13.md +0 -35
- package/daily-reflections/2026-03-14.md +0 -35
- package/daily-reflections/2026-03-15.md +0 -35
- package/daily-reflections/2026-03-16.md +0 -41
- package/daily-reflections/2026-03-17.md +0 -10
- package/daily-reflections/2026-03-18.md +0 -10
- package/daily-reflections/2026-03-19.md +0 -10
- package/daily-reflections/2026-03-20.md +0 -10
- package/daily-reflections/2026-03-21.md +0 -10
- package/daily-reflections/2026-03-22.md +0 -10
- package/daily-reflections/2026-03-23.md +0 -10
- package/daily-reflections/2026-03-24.md +0 -10
- package/daily-reflections/2026-03-25.md +0 -32
- package/daily-reflections/2026-03-26.md +0 -37
- package/daily-reflections/2026-03-27.md +0 -35
- package/daily-reflections/2026-03-28.md +0 -35
- package/daily-reflections/2026-03-29.md +0 -35
- package/daily-reflections/2026-03-30.md +0 -36
- package/daily-reflections/2026-03-31.md +0 -35
- package/daily-reflections/2026-04-01.md +0 -35
- package/daily-reflections/2026-04-02.md +0 -35
- package/daily-reflections/2026-04-03.md +0 -37
- package/daily-reflections/2026-04-04.md +0 -37
- package/daily-reflections/2026-04-05.md +0 -35
- package/daily-reflections/2026-04-06.md +0 -36
- package/daily-reflections/2026-04-07.md +0 -35
- package/daily-reflections/2026-04-08.md +0 -35
- package/daily-reflections/2026-04-12.md +0 -35
- package/data/alias-map.example.json +0 -22
- package/docs/api-reference.md +0 -1291
- package/docs/architecture.md +0 -168
- package/docs/memory-boundary-contract.md +0 -226
- package/docs/memory-categories.md +0 -119
- package/docs/pipeline-audit-ultramemory.md +0 -177
- package/docs/plans/2026-03-08-query-expansion.md +0 -270
- package/docs/plans/2026-03-16-continuity-layer.md +0 -196
- package/docs/plans/2026-04-01-recall-boost.md +0 -100
- package/docs/plans/mcp-tiering-plan.md +0 -126
- package/docs/research/recall-quality-audit-2026-03-22.md +0 -153
- package/docs/self-evolution.md +0 -198
- package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +0 -782
- package/docs/superpowers/plans/2026-04-11-constructive-retrieval-emotion.md +0 -1195
- package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +0 -77
- package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +0 -95
- package/docs/superpowers/specs/2026-04-11-constructive-retrieval-emotion-design.md +0 -398
- package/eval/continuity/results-2026-03-16.md +0 -117
- package/eval/continuity/results-2026-03-19.md +0 -453
- package/eval/continuity/runbook.md +0 -110
- package/eval/continuity/seed-cases.md +0 -54
- package/eval/reports/2026-03-06-abstract-query-baseline.md +0 -96
- package/eval/reports/2026-03-06-aws-ssh-baseline.md +0 -82
- package/eval/reports/2026-03-06-baseline.md +0 -82
- package/eval/reports/query-expansion.md +0 -138
- package/glama.json +0 -6
- package/lm +0 -19
- package/plan-philosophy-upgrades.md +0 -363
- package/plan-product-upgrades.md +0 -155
- package/plan-v1.2-distributable.md +0 -105
- package/plan.md +0 -140
- package/research-memory-evolution.md +0 -440
- package/research-rl-agent-memory.md +0 -330
- package/scripts/archive-distilled-junk.ts +0 -116
- package/scripts/backfill-anchors.ts +0 -130
- package/scripts/backfill-l0l1l2.ts +0 -98
- package/scripts/blind-scan.ts +0 -588
- package/scripts/cleanup-step1-exact-dups.ts +0 -117
- package/scripts/cleanup-step2-template-dups.ts +0 -113
- package/scripts/cleanup-step3-stale-content.ts +0 -149
- package/scripts/cleanup-step4-orphan-scopes.ts +0 -131
- package/scripts/delete-distilled-junk.ts +0 -88
- package/scripts/diagnose-vector-search.ts +0 -132
- package/scripts/distill-facts.ts +0 -305
- package/scripts/health-check.ts +0 -343
- package/scripts/incremental-ingest.sh +0 -71
- package/scripts/migrate-language.ts +0 -75
- package/scripts/mlp-health-check.ts +0 -382
- package/scripts/pull-claude-desktop.ts +0 -515
- package/scripts/re-ingest-cc.sh +0 -66
- package/scripts/rebalance-memory-health.ts +0 -181
- package/scripts/run-desktop-ingest.sh +0 -11
- package/scripts/start-server.sh +0 -12
- package/scripts/verify-emotion-decay.ts +0 -148
- package/scripts/viz-dashboard.html +0 -423
- package/scripts/viz-export.ts +0 -703
- package/src/__tests__/access-tracker-novelty.test.ts +0 -228
- package/src/__tests__/activity-counter.test.ts +0 -96
- package/src/__tests__/admission-control.test.ts +0 -206
- package/src/__tests__/anchor-generator.test.ts +0 -67
- package/src/__tests__/auto-consolidation.test.ts +0 -168
- package/src/__tests__/auto-recall.test.ts +0 -131
- package/src/__tests__/babel-integration.test.ts +0 -139
- package/src/__tests__/batch-dedup.test.ts +0 -65
- package/src/__tests__/capture-engine.test.ts +0 -1144
- package/src/__tests__/capture-heuristic.test.ts +0 -171
- package/src/__tests__/cascade-forget.test.ts +0 -210
- package/src/__tests__/category-min-scores.test.ts +0 -33
- package/src/__tests__/cc8-cc1-essential-context.test.ts +0 -298
- package/src/__tests__/circuit-breaker.test.ts +0 -99
- package/src/__tests__/cluster-consolidation.test.ts +0 -712
- package/src/__tests__/confidence-metadata.test.ts +0 -125
- package/src/__tests__/confidence-tracker.test.ts +0 -249
- package/src/__tests__/conflict-advisor.test.ts +0 -206
- package/src/__tests__/conflict-escalation.test.ts +0 -149
- package/src/__tests__/conflict-lifecycle.test.ts +0 -83
- package/src/__tests__/conflict-output.test.ts +0 -85
- package/src/__tests__/conflict-store.test.ts +0 -374
- package/src/__tests__/consolidation-engine.test.ts +0 -181
- package/src/__tests__/constructive-retrieval.test.ts +0 -509
- package/src/__tests__/context-collapse-renderer.test.ts +0 -214
- package/src/__tests__/context-composer-pins.test.ts +0 -64
- package/src/__tests__/context-composer-task-selection.test.ts +0 -24
- package/src/__tests__/context-reconstructor.test.ts +0 -65
- package/src/__tests__/context-renderer.test.ts +0 -91
- package/src/__tests__/continuity-eval.test.ts +0 -275
- package/src/__tests__/continuity-schema.test.ts +0 -97
- package/src/__tests__/conversation-importer.test.ts +0 -692
- package/src/__tests__/core-recall-eval.test.ts +0 -235
- package/src/__tests__/core-summary.test.ts +0 -109
- package/src/__tests__/data-checkup.test.ts +0 -225
- package/src/__tests__/decay-exemption.test.ts +0 -84
- package/src/__tests__/dedup-actions.test.ts +0 -58
- package/src/__tests__/dedup-guard.test.ts +0 -39
- package/src/__tests__/deterministic-id.test.ts +0 -39
- package/src/__tests__/distill-lock.test.ts +0 -176
- package/src/__tests__/doctor.test.ts +0 -303
- package/src/__tests__/dream-pipeline.test.ts +0 -232
- package/src/__tests__/drill-down.test.ts +0 -108
- package/src/__tests__/embedder.test.ts +0 -122
- package/src/__tests__/emotion-decay.test.ts +0 -46
- package/src/__tests__/emotion-detector.test.ts +0 -56
- package/src/__tests__/emotion-phase1-integration.test.ts +0 -153
- package/src/__tests__/emotion-retrieval.test.ts +0 -56
- package/src/__tests__/entity-resolver.test.ts +0 -58
- package/src/__tests__/ethics-layer.test.ts +0 -476
- package/src/__tests__/event-segmenter.test.ts +0 -153
- package/src/__tests__/feature-flags-integration.test.ts +0 -54
- package/src/__tests__/frequency-tracker.test.ts +0 -113
- package/src/__tests__/graph-export.test.ts +0 -489
- package/src/__tests__/hotness-blend.test.ts +0 -76
- package/src/__tests__/ingest.test.ts +0 -360
- package/src/__tests__/interference-detector.test.ts +0 -203
- package/src/__tests__/kg-extractor.test.ts +0 -184
- package/src/__tests__/kg-store.test.ts +0 -198
- package/src/__tests__/llm-consolidation.test.ts +0 -226
- package/src/__tests__/mcp-efficiency.test.ts +0 -288
- package/src/__tests__/memory-boundaries.test.ts +0 -237
- package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +0 -243
- package/src/__tests__/memory-evolution.test.ts +0 -271
- package/src/__tests__/memory-health-rebalance.test.ts +0 -144
- package/src/__tests__/memory-lint.test.ts +0 -271
- package/src/__tests__/memory-output.test.ts +0 -215
- package/src/__tests__/multi-vector.test.ts +0 -242
- package/src/__tests__/narrative-architecture.test.ts +0 -456
- package/src/__tests__/pending-review.test.ts +0 -77
- package/src/__tests__/post-retrieval-filter.test.ts +0 -178
- package/src/__tests__/ppr-traversal.test.ts +0 -256
- package/src/__tests__/prediction-engine.test.ts +0 -349
- package/src/__tests__/predictive-prospective-memory.test.ts +0 -416
- package/src/__tests__/preference-guard-smoke.test.ts +0 -211
- package/src/__tests__/preference-matcher.test.ts +0 -230
- package/src/__tests__/preference-slots.test.ts +0 -205
- package/src/__tests__/prospective-memory.test.ts +0 -283
- package/src/__tests__/query-entity-detector.test.ts +0 -171
- package/src/__tests__/query-expander-alias.test.ts +0 -89
- package/src/__tests__/query-expander.test.ts +0 -34
- package/src/__tests__/recall-governor.test.ts +0 -227
- package/src/__tests__/recall-mode.test.ts +0 -64
- package/src/__tests__/result-synthesizer.test.ts +0 -198
- package/src/__tests__/resume-context.test.ts +0 -4898
- package/src/__tests__/retrieval-trace.test.ts +0 -213
- package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
- package/src/__tests__/retroactive-boost.test.ts +0 -223
- package/src/__tests__/salience-scoring.test.ts +0 -106
- package/src/__tests__/scope-inventory.test.ts +0 -200
- package/src/__tests__/scope-policy.test.ts +0 -69
- package/src/__tests__/session-checkpoint.test.ts +0 -259
- package/src/__tests__/session-distiller.test.ts +0 -611
- package/src/__tests__/session-gc.test.ts +0 -215
- package/src/__tests__/skill-engine.test.ts +0 -546
- package/src/__tests__/skill-promotion.test.ts +0 -349
- package/src/__tests__/store-scope-audit.test.ts +0 -81
- package/src/__tests__/supersede-chain.test.ts +0 -154
- package/src/__tests__/temporal-parser.test.ts +0 -241
- package/src/__tests__/temporal-validity.test.ts +0 -160
- package/src/__tests__/term-registry.test.ts +0 -121
- package/src/__tests__/tool-output-compressor.test.ts +0 -133
- package/src/__tests__/topic-tag.test.ts +0 -113
- package/src/__tests__/trusted-memory.test.ts +0 -828
- package/src/__tests__/version-manager.test.ts +0 -213
- package/src/__tests__/workflow-observation.test.ts +0 -400
- package/src/__tests__/write-verifier.test.ts +0 -122
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 诊断脚本:验证跨进程 vectorSearch 的 _distance 值
|
|
3
|
-
*
|
|
4
|
-
* 用法: bun run scripts/diagnose-vector-search.ts
|
|
5
|
-
*
|
|
6
|
-
* 做三件事:
|
|
7
|
-
* 1. list() 确认数据存在
|
|
8
|
-
* 2. 取第一条记录的 vector,用它自己做 vectorSearch(理论上 distance=0, score=1.0)
|
|
9
|
-
* 3. 打印 raw _distance 值,看是不是 NaN 或异常大
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import lancedb from "@lancedb/lancedb";
|
|
13
|
-
import { resolve } from "node:path";
|
|
14
|
-
|
|
15
|
-
const DB_PATH = resolve(import.meta.dir, "../data/lancedb");
|
|
16
|
-
const TABLE_NAME = "memories";
|
|
17
|
-
|
|
18
|
-
async function main() {
|
|
19
|
-
console.log(`[1] Opening LanceDB at: ${DB_PATH}`);
|
|
20
|
-
const db = await lancedb.connect(DB_PATH);
|
|
21
|
-
|
|
22
|
-
let table: lancedb.Table;
|
|
23
|
-
try {
|
|
24
|
-
table = await db.openTable(TABLE_NAME);
|
|
25
|
-
} catch (err) {
|
|
26
|
-
console.error(`Failed to open table "${TABLE_NAME}":`, err);
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Step 1: list a few entries to confirm data exists
|
|
31
|
-
console.log(`\n[2] Listing first 3 entries (SQL query, no vector)...`);
|
|
32
|
-
const sample = await table.query().limit(3).toArray();
|
|
33
|
-
console.log(` Found ${sample.length} entries`);
|
|
34
|
-
|
|
35
|
-
if (sample.length === 0) {
|
|
36
|
-
console.log(" ❌ No data in table. Nothing to diagnose.");
|
|
37
|
-
process.exit(0);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
for (const row of sample) {
|
|
41
|
-
const vec = row.vector;
|
|
42
|
-
const vecType = Object.prototype.toString.call(vec);
|
|
43
|
-
const isArray = Array.isArray(vec);
|
|
44
|
-
const len = vec?.length ?? "N/A";
|
|
45
|
-
const first3 = vec ? Array.from(vec).slice(0, 3) : "N/A";
|
|
46
|
-
console.log(` id=${(row.id as string).slice(0, 8)} vector.type=${vecType} isArray=${isArray} dim=${len} first3=${JSON.stringify(first3)}`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Step 2: use the first entry's vector as the query (self-search, should be distance≈0)
|
|
50
|
-
const firstVec = sample[0].vector;
|
|
51
|
-
if (!firstVec || !firstVec.length) {
|
|
52
|
-
console.log(" ❌ First entry has no vector.");
|
|
53
|
-
process.exit(1);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Convert to plain number[] in case it's a Float32Array or Arrow vector
|
|
57
|
-
const queryVec: number[] = Array.from(firstVec);
|
|
58
|
-
console.log(`\n[3] Self-search: using first entry's vector (dim=${queryVec.length}) as query...`);
|
|
59
|
-
console.log(` Query vector first 5: ${queryVec.slice(0, 5)}`);
|
|
60
|
-
|
|
61
|
-
// Test 3a: vectorSearch WITH distanceType('cosine')
|
|
62
|
-
console.log(`\n[3a] vectorSearch with distanceType('cosine')...`);
|
|
63
|
-
try {
|
|
64
|
-
const results = await table.vectorSearch(queryVec).distanceType('cosine').limit(5).toArray();
|
|
65
|
-
console.log(` Returned ${results.length} results`);
|
|
66
|
-
for (const r of results) {
|
|
67
|
-
const dist = r._distance;
|
|
68
|
-
const distNum = Number(dist);
|
|
69
|
-
const score = 1 / (1 + distNum);
|
|
70
|
-
const vecLen = r.vector?.length ?? "N/A";
|
|
71
|
-
const vecType = Object.prototype.toString.call(r.vector);
|
|
72
|
-
console.log(` id=${(r.id as string).slice(0, 8)} _distance=${dist} (Number: ${distNum}) score=${score.toFixed(4)} vec.type=${vecType} vec.dim=${vecLen}`);
|
|
73
|
-
}
|
|
74
|
-
} catch (err) {
|
|
75
|
-
console.error(` ❌ vectorSearch(cosine) failed:`, err);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Test 3b: vectorSearch WITHOUT distanceType (default = L2)
|
|
79
|
-
console.log(`\n[3b] vectorSearch without distanceType (default L2)...`);
|
|
80
|
-
try {
|
|
81
|
-
const results = await table.vectorSearch(queryVec).limit(5).toArray();
|
|
82
|
-
console.log(` Returned ${results.length} results`);
|
|
83
|
-
for (const r of results) {
|
|
84
|
-
const dist = r._distance;
|
|
85
|
-
const distNum = Number(dist);
|
|
86
|
-
console.log(` id=${(r.id as string).slice(0, 8)} _distance=${dist} (Number: ${distNum})`);
|
|
87
|
-
}
|
|
88
|
-
} catch (err) {
|
|
89
|
-
console.error(` ❌ vectorSearch(L2) failed:`, err);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Test 3c: check if the vector round-trips correctly
|
|
93
|
-
console.log(`\n[4] Vector round-trip check...`);
|
|
94
|
-
const refetched = await table.query().where(`id = '${sample[0].id}'`).limit(1).toArray();
|
|
95
|
-
if (refetched.length > 0) {
|
|
96
|
-
const storedVec = Array.from(refetched[0].vector);
|
|
97
|
-
const match = queryVec.every((v, i) => Math.abs(v - storedVec[i]) < 1e-6);
|
|
98
|
-
console.log(` Vectors match: ${match}`);
|
|
99
|
-
if (!match) {
|
|
100
|
-
console.log(` First 5 query: ${queryVec.slice(0, 5)}`);
|
|
101
|
-
console.log(` First 5 stored: ${storedVec.slice(0, 5)}`);
|
|
102
|
-
const diffs = queryVec.slice(0, 5).map((v, i) => Math.abs(v - storedVec[i]));
|
|
103
|
-
console.log(` Diffs: ${diffs}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Test 3d: manual cosine similarity computation
|
|
108
|
-
console.log(`\n[5] Manual cosine similarity (sanity check)...`);
|
|
109
|
-
if (sample.length >= 2) {
|
|
110
|
-
const vec1: number[] = Array.from(sample[0].vector);
|
|
111
|
-
const vec2: number[] = Array.from(sample[1].vector);
|
|
112
|
-
|
|
113
|
-
let dot = 0, norm1 = 0, norm2 = 0;
|
|
114
|
-
for (let i = 0; i < vec1.length; i++) {
|
|
115
|
-
dot += vec1[i] * vec2[i];
|
|
116
|
-
norm1 += vec1[i] * vec1[i];
|
|
117
|
-
norm2 += vec2[i] * vec2[i];
|
|
118
|
-
}
|
|
119
|
-
const cosSim = dot / (Math.sqrt(norm1) * Math.sqrt(norm2));
|
|
120
|
-
const cosDist = 1 - cosSim;
|
|
121
|
-
const score = 1 / (1 + cosDist);
|
|
122
|
-
console.log(` Manual cosine(entry0, entry1): sim=${cosSim.toFixed(6)} dist=${cosDist.toFixed(6)} score=${score.toFixed(4)}`);
|
|
123
|
-
console.log(` vec1 norm=${Math.sqrt(norm1).toFixed(4)} vec2 norm=${Math.sqrt(norm2).toFixed(4)}`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
console.log(`\n✅ Diagnosis complete.`);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
main().catch(err => {
|
|
130
|
-
console.error("Fatal:", err);
|
|
131
|
-
process.exit(1);
|
|
132
|
-
});
|
package/scripts/distill-facts.ts
DELETED
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Bulk distillation: Convert raw "fact" conversation fragments into
|
|
4
|
-
* structured knowledge using smartExtractBatch().
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* bun scripts/distill-facts.ts --dry-run # Process 1 session, print results
|
|
8
|
-
* bun scripts/distill-facts.ts # Batch all sessions with checkpoint
|
|
9
|
-
* bun scripts/distill-facts.ts --scope cc:09541b9c # Process specific session only
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import path from "path";
|
|
13
|
-
import fs from "fs";
|
|
14
|
-
import { loadConfig, createComponents } from "../src/runtime-config.js";
|
|
15
|
-
import { buildDistillationBoundary } from "../src/memory-boundaries.js";
|
|
16
|
-
import type { MemoryEntry } from "../src/store.js";
|
|
17
|
-
import type { SmartExtraction, LLMClient } from "../src/llm-client.js";
|
|
18
|
-
import type { MemoryStore } from "../src/store.js";
|
|
19
|
-
import type { Embedder } from "../src/embedder.js";
|
|
20
|
-
|
|
21
|
-
// ─── Config ─────────────────────────────────────────────────────────────────
|
|
22
|
-
|
|
23
|
-
const BATCH_SIZE = 20;
|
|
24
|
-
const PROGRESS_FILE = path.join(import.meta.dir, "..", "data", "distill-progress.json");
|
|
25
|
-
|
|
26
|
-
interface DistillProgress {
|
|
27
|
-
startedAt: string;
|
|
28
|
-
lastUpdated: string;
|
|
29
|
-
completedScopes: string[];
|
|
30
|
-
stats: {
|
|
31
|
-
totalFacts: number;
|
|
32
|
-
processedFacts: number;
|
|
33
|
-
newRecords: number;
|
|
34
|
-
dedupedSkips: number;
|
|
35
|
-
errors: number;
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function loadProgress(): DistillProgress | null {
|
|
40
|
-
try {
|
|
41
|
-
return JSON.parse(fs.readFileSync(PROGRESS_FILE, "utf-8"));
|
|
42
|
-
} catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function saveProgress(progress: DistillProgress): void {
|
|
48
|
-
fs.writeFileSync(PROGRESS_FILE, JSON.stringify(progress, null, 2));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ─── Core Logic ─────────────────────────────────────────────────────────────
|
|
52
|
-
|
|
53
|
-
async function distillBatch(
|
|
54
|
-
texts: string[],
|
|
55
|
-
originals: MemoryEntry[],
|
|
56
|
-
store: MemoryStore,
|
|
57
|
-
embedder: Embedder,
|
|
58
|
-
llm: LLMClient,
|
|
59
|
-
dryRun = false,
|
|
60
|
-
): Promise<{ newCount: number; dedupCount: number; errors: number }> {
|
|
61
|
-
let newCount = 0;
|
|
62
|
-
let dedupCount = 0;
|
|
63
|
-
let errors = 0;
|
|
64
|
-
|
|
65
|
-
// Step 1: Extract knowledge via LLM
|
|
66
|
-
// smartExtractBatch returns (SmartExtraction | null)[] with same length as input
|
|
67
|
-
let rawExtractions: (SmartExtraction | null)[];
|
|
68
|
-
try {
|
|
69
|
-
rawExtractions = await llm.smartExtractBatch(texts);
|
|
70
|
-
} catch (e) {
|
|
71
|
-
console.error(` LLM extraction failed: ${e}`);
|
|
72
|
-
return { newCount: 0, dedupCount: 0, errors: texts.length };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const validIndices: number[] = [];
|
|
76
|
-
for (let i = 0; i < rawExtractions.length; i++) {
|
|
77
|
-
const ext = rawExtractions[i];
|
|
78
|
-
if (ext && (ext.l0.length > 10 || ext.l1.length > 10)) {
|
|
79
|
-
validIndices.push(i);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (validIndices.length === 0) {
|
|
84
|
-
return { newCount: 0, dedupCount: texts.length, errors: 0 };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Step 3: Generate embeddings
|
|
88
|
-
const textsToEmbed = validIndices.map(i => {
|
|
89
|
-
const ext = rawExtractions[i]!;
|
|
90
|
-
return ext.l1 || ext.l0;
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
let vectors: number[][];
|
|
94
|
-
try {
|
|
95
|
-
vectors = await embedder.embedBatchPassage(textsToEmbed);
|
|
96
|
-
} catch (e) {
|
|
97
|
-
console.error(` Embedding failed: ${e}`);
|
|
98
|
-
return { newCount: 0, dedupCount: 0, errors: validIndices.length };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Step 4: Dedup + store each extracted record
|
|
102
|
-
const idMapping = new Map<number, string>();
|
|
103
|
-
for (let vi = 0; vi < validIndices.length; vi++) {
|
|
104
|
-
const idx = validIndices[vi];
|
|
105
|
-
const extraction = rawExtractions[idx]!;
|
|
106
|
-
const vector = vectors[vi];
|
|
107
|
-
const original = originals[idx];
|
|
108
|
-
|
|
109
|
-
// Skip per-record dedup — distilled L0/L1 text is unlikely to duplicate existing records,
|
|
110
|
-
// and dedupCheck was the main bottleneck (vector search per record). Post-distillation
|
|
111
|
-
// health check can detect duplicates if needed.
|
|
112
|
-
|
|
113
|
-
if (dryRun) {
|
|
114
|
-
console.log(`\n [${extraction.category}] importance=${extraction.importance}`);
|
|
115
|
-
console.log(` src: ${original.text.slice(0, 80)}...`);
|
|
116
|
-
console.log(` L0: ${extraction.l0}`);
|
|
117
|
-
console.log(` L1: ${(extraction.l1 || "").slice(0, 120)}`);
|
|
118
|
-
newCount++;
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Build new record
|
|
123
|
-
const boundary = buildDistillationBoundary(extraction.category as any);
|
|
124
|
-
const metadata = JSON.stringify({
|
|
125
|
-
source: "distillation",
|
|
126
|
-
distilledFrom: original.id,
|
|
127
|
-
originalScope: original.scope,
|
|
128
|
-
l0: extraction.l0,
|
|
129
|
-
l1: extraction.l1,
|
|
130
|
-
tier: extraction.importance >= 0.8 ? "working" : "peripheral",
|
|
131
|
-
boundary,
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
const entry = await store.store({
|
|
136
|
-
text: extraction.l1 || extraction.l0,
|
|
137
|
-
vector,
|
|
138
|
-
category: extraction.category as any,
|
|
139
|
-
scope: original.scope,
|
|
140
|
-
importance: extraction.importance,
|
|
141
|
-
metadata,
|
|
142
|
-
});
|
|
143
|
-
idMapping.set(idx, entry.id);
|
|
144
|
-
newCount++;
|
|
145
|
-
} catch (e) {
|
|
146
|
-
console.error(` Store failed: ${e}`);
|
|
147
|
-
errors++;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Step 5: Archive originals (skip in dry-run)
|
|
152
|
-
if (dryRun) return { newCount, dedupCount, errors };
|
|
153
|
-
for (let vi = 0; vi < validIndices.length; vi++) {
|
|
154
|
-
const idx = validIndices[vi];
|
|
155
|
-
const original = originals[idx];
|
|
156
|
-
const distilledId = idMapping.get(idx);
|
|
157
|
-
if (!distilledId) continue; // Was deduped or failed
|
|
158
|
-
try {
|
|
159
|
-
const existingMeta = original.metadata ? JSON.parse(original.metadata) : {};
|
|
160
|
-
const updatedMeta = JSON.stringify({
|
|
161
|
-
...existingMeta,
|
|
162
|
-
archived: true,
|
|
163
|
-
archivedAt: new Date().toISOString(),
|
|
164
|
-
distilledTo: [distilledId],
|
|
165
|
-
originalCategory: "fact",
|
|
166
|
-
});
|
|
167
|
-
await store.update(original.id, { metadata: updatedMeta });
|
|
168
|
-
} catch (e) {
|
|
169
|
-
console.error(` Archive mark failed for ${original.id}: ${e}`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return { newCount, dedupCount, errors };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
177
|
-
|
|
178
|
-
async function main() {
|
|
179
|
-
const args = process.argv.slice(2);
|
|
180
|
-
const dryRun = args.includes("--dry-run");
|
|
181
|
-
const scopeIdx = args.indexOf("--scope");
|
|
182
|
-
const scopeArg = args.find(a => a.startsWith("--scope="))?.split("=")[1]
|
|
183
|
-
|| (scopeIdx >= 0 ? args[scopeIdx + 1] : undefined);
|
|
184
|
-
|
|
185
|
-
const config = loadConfig();
|
|
186
|
-
const { store, embedder, llm } = createComponents(config);
|
|
187
|
-
|
|
188
|
-
if (!llm) {
|
|
189
|
-
console.error("LLM not configured. Check config.json llm section.");
|
|
190
|
-
process.exit(1);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const llmTest = await llm.test();
|
|
194
|
-
if (!llmTest.success) {
|
|
195
|
-
console.error(`LLM connection failed: ${llmTest.error}`);
|
|
196
|
-
process.exit(1);
|
|
197
|
-
}
|
|
198
|
-
console.log(`LLM: ${config.llm?.model}`);
|
|
199
|
-
|
|
200
|
-
// Fetch all fact records
|
|
201
|
-
console.log("\nReading fact records...");
|
|
202
|
-
const allFacts: MemoryEntry[] = await store.list(undefined, "fact", 50000, 0);
|
|
203
|
-
console.log(`Total: ${allFacts.length} fact records\n`);
|
|
204
|
-
|
|
205
|
-
// Filter out already-archived facts
|
|
206
|
-
const unarchived = allFacts.filter(f => {
|
|
207
|
-
if (!f.metadata) return true;
|
|
208
|
-
try { return !JSON.parse(f.metadata).archived; } catch { return true; }
|
|
209
|
-
});
|
|
210
|
-
console.log(`Unarchived: ${unarchived.length} (already archived: ${allFacts.length - unarchived.length})\n`);
|
|
211
|
-
|
|
212
|
-
// Group by scope
|
|
213
|
-
const byScope = new Map<string, MemoryEntry[]>();
|
|
214
|
-
for (const f of unarchived) {
|
|
215
|
-
const arr = byScope.get(f.scope) || [];
|
|
216
|
-
arr.push(f);
|
|
217
|
-
byScope.set(f.scope, arr);
|
|
218
|
-
}
|
|
219
|
-
const scopes = [...byScope.keys()].sort((a, b) => byScope.get(b)!.length - byScope.get(a)!.length);
|
|
220
|
-
console.log(`${scopes.length} scopes\n`);
|
|
221
|
-
|
|
222
|
-
// Load or create progress
|
|
223
|
-
let progress = loadProgress();
|
|
224
|
-
if (!progress) {
|
|
225
|
-
progress = {
|
|
226
|
-
startedAt: new Date().toISOString(),
|
|
227
|
-
lastUpdated: new Date().toISOString(),
|
|
228
|
-
completedScopes: [],
|
|
229
|
-
stats: { totalFacts: unarchived.length, processedFacts: 0, newRecords: 0, dedupedSkips: 0, errors: 0 },
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Filter scopes
|
|
234
|
-
let targetScopes = scopes;
|
|
235
|
-
if (scopeArg) {
|
|
236
|
-
targetScopes = scopes.filter(s => s === scopeArg);
|
|
237
|
-
if (targetScopes.length === 0) {
|
|
238
|
-
console.error(`Scope "${scopeArg}" not found`);
|
|
239
|
-
process.exit(1);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
if (dryRun) {
|
|
243
|
-
targetScopes = [targetScopes[0]];
|
|
244
|
-
console.log(`DRY RUN: processing scope "${targetScopes[0]}" (${byScope.get(targetScopes[0])!.length} records)\n`);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Process scopes — worker pool with immediate checkpoint per scope
|
|
248
|
-
const CONCURRENCY = 5;
|
|
249
|
-
|
|
250
|
-
// Filter to pending scopes, sort small-first for faster feedback
|
|
251
|
-
const pendingScopes = targetScopes.filter(s => !progress.completedScopes.includes(s));
|
|
252
|
-
pendingScopes.sort((a, b) => byScope.get(a)!.length - byScope.get(b)!.length);
|
|
253
|
-
const skipped = targetScopes.length - pendingScopes.length;
|
|
254
|
-
if (skipped > 0) console.log(`Skipping ${skipped} already-completed scopes`);
|
|
255
|
-
console.log(`Processing ${pendingScopes.length} scopes (small-first, concurrency=${CONCURRENCY})\n`);
|
|
256
|
-
|
|
257
|
-
let completed = 0;
|
|
258
|
-
const totalPending = pendingScopes.length;
|
|
259
|
-
let scopeQueue = [...pendingScopes];
|
|
260
|
-
|
|
261
|
-
async function worker() {
|
|
262
|
-
while (scopeQueue.length > 0) {
|
|
263
|
-
const scope = scopeQueue.shift()!;
|
|
264
|
-
const facts = byScope.get(scope)!;
|
|
265
|
-
let scopeNew = 0, scopeErrors = 0;
|
|
266
|
-
|
|
267
|
-
for (let i = 0; i < facts.length; i += BATCH_SIZE) {
|
|
268
|
-
const batch = facts.slice(i, i + BATCH_SIZE);
|
|
269
|
-
const texts = batch.map(f => f.text);
|
|
270
|
-
const result = await distillBatch(texts, batch, store, embedder, llm, dryRun);
|
|
271
|
-
scopeNew += result.newCount;
|
|
272
|
-
scopeErrors += result.errors;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Checkpoint immediately
|
|
276
|
-
progress.completedScopes.push(scope);
|
|
277
|
-
progress.stats.processedFacts += facts.length;
|
|
278
|
-
progress.stats.newRecords += scopeNew;
|
|
279
|
-
progress.stats.errors += scopeErrors;
|
|
280
|
-
progress.lastUpdated = new Date().toISOString();
|
|
281
|
-
completed++;
|
|
282
|
-
|
|
283
|
-
if (!dryRun) saveProgress(progress);
|
|
284
|
-
console.log(`[${completed}/${totalPending}] ${scope} (${facts.length} facts, +${scopeNew} new, ${scopeErrors} err)`);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Launch workers
|
|
289
|
-
await Promise.all(Array.from({ length: CONCURRENCY }, () => worker()));
|
|
290
|
-
|
|
291
|
-
// Final summary
|
|
292
|
-
console.log("\n" + "=".repeat(60));
|
|
293
|
-
console.log("Distillation summary:");
|
|
294
|
-
console.log(` Total facts: ${progress.stats.totalFacts}`);
|
|
295
|
-
console.log(` Processed: ${progress.stats.processedFacts}`);
|
|
296
|
-
console.log(` New knowledge: ${progress.stats.newRecords}`);
|
|
297
|
-
console.log(` Dedup skipped: ${progress.stats.dedupedSkips}`);
|
|
298
|
-
console.log(` Errors: ${progress.stats.errors}`);
|
|
299
|
-
console.log(` Compression: ${(progress.stats.processedFacts / Math.max(progress.stats.newRecords, 1)).toFixed(1)}:1`);
|
|
300
|
-
if (dryRun) {
|
|
301
|
-
console.log("\nDRY RUN — no data was written. Remove --dry-run to run for real.");
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
main().catch(console.error);
|