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.
Files changed (249) hide show
  1. package/.env +4 -0
  2. package/CLAUDE.md +1 -1
  3. package/README.md +30 -5
  4. package/README_CN.md +30 -5
  5. package/config.json +55 -0
  6. package/connectors/examples/email-adapter.ts +73 -0
  7. package/connectors/examples/logs-adapter.ts +103 -0
  8. package/connectors/examples/rss-adapter.ts +95 -0
  9. package/logs/daily-recall.error.log +0 -0
  10. package/logs/daily-recall.log +60 -0
  11. package/logs/ingest-2026-04-11.log +110 -0
  12. package/logs/ingest-2026-04-12.log +90 -0
  13. package/logs/ingest-2026-04-13.log +87 -0
  14. package/logs/launchagent-stderr.log +0 -0
  15. package/logs/launchagent-stdout.log +0 -0
  16. package/package.json +1 -1
  17. package/src/cli.ts +30 -0
  18. package/src/connector-types.ts +57 -0
  19. package/src/conversation-importer.ts +25 -1
  20. package/src/data-checkup.ts +31 -1
  21. package/src/doctor.ts +29 -0
  22. package/src/ingest.ts +203 -0
  23. package/src/mcp-server.ts +2 -2
  24. package/src/obsidian-connector.ts +282 -0
  25. package/src/session-schema.ts +2 -2
  26. package/src/source-heartbeat.ts +108 -0
  27. package/.claude-plugin/CLAUDE.md +0 -29
  28. package/.claude-plugin/marketplace.json +0 -18
  29. package/.dockerignore +0 -11
  30. package/.github/workflows/ci.yml +0 -28
  31. package/.mcp.json +0 -9
  32. package/CHANGELOG.md +0 -51
  33. package/DEMO.md +0 -94
  34. package/Dockerfile +0 -20
  35. package/EVAL.md +0 -89
  36. package/FAILURES.md +0 -75
  37. package/OPERATIONS.md +0 -59
  38. package/ROADMAP.md +0 -245
  39. package/SKILL.md +0 -250
  40. package/assets/dashboard.png +0 -0
  41. package/assets/knowledge-graph.png +0 -0
  42. package/assets/recallnest-workbench.png +0 -0
  43. package/assets/screenshots/ui-full.png +0 -0
  44. package/assets/screenshots/ui-overview.png +0 -0
  45. package/assets/screenshots/ui-search.png +0 -0
  46. package/assets/ui/app.js +0 -1031
  47. package/assets/ui/index.html +0 -224
  48. package/assets/ui/styles.css +0 -781
  49. package/assets/wechat-qr.jpg +0 -0
  50. package/bun.lock +0 -337
  51. package/daily-reflections/2026-03-05.md +0 -15
  52. package/daily-reflections/2026-03-06.md +0 -15
  53. package/daily-reflections/2026-03-07.md +0 -7
  54. package/daily-reflections/2026-03-08.md +0 -34
  55. package/daily-reflections/2026-03-09.md +0 -35
  56. package/daily-reflections/2026-03-10.md +0 -36
  57. package/daily-reflections/2026-03-11.md +0 -35
  58. package/daily-reflections/2026-03-12.md +0 -36
  59. package/daily-reflections/2026-03-13.md +0 -35
  60. package/daily-reflections/2026-03-14.md +0 -35
  61. package/daily-reflections/2026-03-15.md +0 -35
  62. package/daily-reflections/2026-03-16.md +0 -41
  63. package/daily-reflections/2026-03-17.md +0 -10
  64. package/daily-reflections/2026-03-18.md +0 -10
  65. package/daily-reflections/2026-03-19.md +0 -10
  66. package/daily-reflections/2026-03-20.md +0 -10
  67. package/daily-reflections/2026-03-21.md +0 -10
  68. package/daily-reflections/2026-03-22.md +0 -10
  69. package/daily-reflections/2026-03-23.md +0 -10
  70. package/daily-reflections/2026-03-24.md +0 -10
  71. package/daily-reflections/2026-03-25.md +0 -32
  72. package/daily-reflections/2026-03-26.md +0 -37
  73. package/daily-reflections/2026-03-27.md +0 -35
  74. package/daily-reflections/2026-03-28.md +0 -35
  75. package/daily-reflections/2026-03-29.md +0 -35
  76. package/daily-reflections/2026-03-30.md +0 -36
  77. package/daily-reflections/2026-03-31.md +0 -35
  78. package/daily-reflections/2026-04-01.md +0 -35
  79. package/daily-reflections/2026-04-02.md +0 -35
  80. package/daily-reflections/2026-04-03.md +0 -37
  81. package/daily-reflections/2026-04-04.md +0 -37
  82. package/daily-reflections/2026-04-05.md +0 -35
  83. package/daily-reflections/2026-04-06.md +0 -36
  84. package/daily-reflections/2026-04-07.md +0 -35
  85. package/daily-reflections/2026-04-08.md +0 -35
  86. package/daily-reflections/2026-04-12.md +0 -35
  87. package/data/alias-map.example.json +0 -22
  88. package/docs/api-reference.md +0 -1291
  89. package/docs/architecture.md +0 -168
  90. package/docs/memory-boundary-contract.md +0 -226
  91. package/docs/memory-categories.md +0 -119
  92. package/docs/pipeline-audit-ultramemory.md +0 -177
  93. package/docs/plans/2026-03-08-query-expansion.md +0 -270
  94. package/docs/plans/2026-03-16-continuity-layer.md +0 -196
  95. package/docs/plans/2026-04-01-recall-boost.md +0 -100
  96. package/docs/plans/mcp-tiering-plan.md +0 -126
  97. package/docs/research/recall-quality-audit-2026-03-22.md +0 -153
  98. package/docs/self-evolution.md +0 -198
  99. package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +0 -782
  100. package/docs/superpowers/plans/2026-04-11-constructive-retrieval-emotion.md +0 -1195
  101. package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +0 -77
  102. package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +0 -95
  103. package/docs/superpowers/specs/2026-04-11-constructive-retrieval-emotion-design.md +0 -398
  104. package/eval/continuity/results-2026-03-16.md +0 -117
  105. package/eval/continuity/results-2026-03-19.md +0 -453
  106. package/eval/continuity/runbook.md +0 -110
  107. package/eval/continuity/seed-cases.md +0 -54
  108. package/eval/reports/2026-03-06-abstract-query-baseline.md +0 -96
  109. package/eval/reports/2026-03-06-aws-ssh-baseline.md +0 -82
  110. package/eval/reports/2026-03-06-baseline.md +0 -82
  111. package/eval/reports/query-expansion.md +0 -138
  112. package/glama.json +0 -6
  113. package/lm +0 -19
  114. package/plan-philosophy-upgrades.md +0 -363
  115. package/plan-product-upgrades.md +0 -155
  116. package/plan-v1.2-distributable.md +0 -105
  117. package/plan.md +0 -140
  118. package/research-memory-evolution.md +0 -440
  119. package/research-rl-agent-memory.md +0 -330
  120. package/scripts/archive-distilled-junk.ts +0 -116
  121. package/scripts/backfill-anchors.ts +0 -130
  122. package/scripts/backfill-l0l1l2.ts +0 -98
  123. package/scripts/blind-scan.ts +0 -588
  124. package/scripts/cleanup-step1-exact-dups.ts +0 -117
  125. package/scripts/cleanup-step2-template-dups.ts +0 -113
  126. package/scripts/cleanup-step3-stale-content.ts +0 -149
  127. package/scripts/cleanup-step4-orphan-scopes.ts +0 -131
  128. package/scripts/delete-distilled-junk.ts +0 -88
  129. package/scripts/diagnose-vector-search.ts +0 -132
  130. package/scripts/distill-facts.ts +0 -305
  131. package/scripts/health-check.ts +0 -343
  132. package/scripts/incremental-ingest.sh +0 -71
  133. package/scripts/migrate-language.ts +0 -75
  134. package/scripts/mlp-health-check.ts +0 -382
  135. package/scripts/pull-claude-desktop.ts +0 -515
  136. package/scripts/re-ingest-cc.sh +0 -66
  137. package/scripts/rebalance-memory-health.ts +0 -181
  138. package/scripts/run-desktop-ingest.sh +0 -11
  139. package/scripts/start-server.sh +0 -12
  140. package/scripts/verify-emotion-decay.ts +0 -148
  141. package/scripts/viz-dashboard.html +0 -423
  142. package/scripts/viz-export.ts +0 -703
  143. package/src/__tests__/access-tracker-novelty.test.ts +0 -228
  144. package/src/__tests__/activity-counter.test.ts +0 -96
  145. package/src/__tests__/admission-control.test.ts +0 -206
  146. package/src/__tests__/anchor-generator.test.ts +0 -67
  147. package/src/__tests__/auto-consolidation.test.ts +0 -168
  148. package/src/__tests__/auto-recall.test.ts +0 -131
  149. package/src/__tests__/babel-integration.test.ts +0 -139
  150. package/src/__tests__/batch-dedup.test.ts +0 -65
  151. package/src/__tests__/capture-engine.test.ts +0 -1144
  152. package/src/__tests__/capture-heuristic.test.ts +0 -171
  153. package/src/__tests__/cascade-forget.test.ts +0 -210
  154. package/src/__tests__/category-min-scores.test.ts +0 -33
  155. package/src/__tests__/cc8-cc1-essential-context.test.ts +0 -298
  156. package/src/__tests__/circuit-breaker.test.ts +0 -99
  157. package/src/__tests__/cluster-consolidation.test.ts +0 -712
  158. package/src/__tests__/confidence-metadata.test.ts +0 -125
  159. package/src/__tests__/confidence-tracker.test.ts +0 -249
  160. package/src/__tests__/conflict-advisor.test.ts +0 -206
  161. package/src/__tests__/conflict-escalation.test.ts +0 -149
  162. package/src/__tests__/conflict-lifecycle.test.ts +0 -83
  163. package/src/__tests__/conflict-output.test.ts +0 -85
  164. package/src/__tests__/conflict-store.test.ts +0 -374
  165. package/src/__tests__/consolidation-engine.test.ts +0 -181
  166. package/src/__tests__/constructive-retrieval.test.ts +0 -509
  167. package/src/__tests__/context-collapse-renderer.test.ts +0 -214
  168. package/src/__tests__/context-composer-pins.test.ts +0 -64
  169. package/src/__tests__/context-composer-task-selection.test.ts +0 -24
  170. package/src/__tests__/context-reconstructor.test.ts +0 -65
  171. package/src/__tests__/context-renderer.test.ts +0 -91
  172. package/src/__tests__/continuity-eval.test.ts +0 -275
  173. package/src/__tests__/continuity-schema.test.ts +0 -97
  174. package/src/__tests__/conversation-importer.test.ts +0 -692
  175. package/src/__tests__/core-recall-eval.test.ts +0 -235
  176. package/src/__tests__/core-summary.test.ts +0 -109
  177. package/src/__tests__/data-checkup.test.ts +0 -225
  178. package/src/__tests__/decay-exemption.test.ts +0 -84
  179. package/src/__tests__/dedup-actions.test.ts +0 -58
  180. package/src/__tests__/dedup-guard.test.ts +0 -39
  181. package/src/__tests__/deterministic-id.test.ts +0 -39
  182. package/src/__tests__/distill-lock.test.ts +0 -176
  183. package/src/__tests__/doctor.test.ts +0 -303
  184. package/src/__tests__/dream-pipeline.test.ts +0 -232
  185. package/src/__tests__/drill-down.test.ts +0 -108
  186. package/src/__tests__/embedder.test.ts +0 -122
  187. package/src/__tests__/emotion-decay.test.ts +0 -46
  188. package/src/__tests__/emotion-detector.test.ts +0 -56
  189. package/src/__tests__/emotion-phase1-integration.test.ts +0 -153
  190. package/src/__tests__/emotion-retrieval.test.ts +0 -56
  191. package/src/__tests__/entity-resolver.test.ts +0 -58
  192. package/src/__tests__/ethics-layer.test.ts +0 -476
  193. package/src/__tests__/event-segmenter.test.ts +0 -153
  194. package/src/__tests__/feature-flags-integration.test.ts +0 -54
  195. package/src/__tests__/frequency-tracker.test.ts +0 -113
  196. package/src/__tests__/graph-export.test.ts +0 -489
  197. package/src/__tests__/hotness-blend.test.ts +0 -76
  198. package/src/__tests__/ingest.test.ts +0 -360
  199. package/src/__tests__/interference-detector.test.ts +0 -203
  200. package/src/__tests__/kg-extractor.test.ts +0 -184
  201. package/src/__tests__/kg-store.test.ts +0 -198
  202. package/src/__tests__/llm-consolidation.test.ts +0 -226
  203. package/src/__tests__/mcp-efficiency.test.ts +0 -288
  204. package/src/__tests__/memory-boundaries.test.ts +0 -237
  205. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +0 -243
  206. package/src/__tests__/memory-evolution.test.ts +0 -271
  207. package/src/__tests__/memory-health-rebalance.test.ts +0 -144
  208. package/src/__tests__/memory-lint.test.ts +0 -271
  209. package/src/__tests__/memory-output.test.ts +0 -215
  210. package/src/__tests__/multi-vector.test.ts +0 -242
  211. package/src/__tests__/narrative-architecture.test.ts +0 -456
  212. package/src/__tests__/pending-review.test.ts +0 -77
  213. package/src/__tests__/post-retrieval-filter.test.ts +0 -178
  214. package/src/__tests__/ppr-traversal.test.ts +0 -256
  215. package/src/__tests__/prediction-engine.test.ts +0 -349
  216. package/src/__tests__/predictive-prospective-memory.test.ts +0 -416
  217. package/src/__tests__/preference-guard-smoke.test.ts +0 -211
  218. package/src/__tests__/preference-matcher.test.ts +0 -230
  219. package/src/__tests__/preference-slots.test.ts +0 -205
  220. package/src/__tests__/prospective-memory.test.ts +0 -283
  221. package/src/__tests__/query-entity-detector.test.ts +0 -171
  222. package/src/__tests__/query-expander-alias.test.ts +0 -89
  223. package/src/__tests__/query-expander.test.ts +0 -34
  224. package/src/__tests__/recall-governor.test.ts +0 -227
  225. package/src/__tests__/recall-mode.test.ts +0 -64
  226. package/src/__tests__/result-synthesizer.test.ts +0 -198
  227. package/src/__tests__/resume-context.test.ts +0 -4898
  228. package/src/__tests__/retrieval-trace.test.ts +0 -213
  229. package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
  230. package/src/__tests__/retroactive-boost.test.ts +0 -223
  231. package/src/__tests__/salience-scoring.test.ts +0 -106
  232. package/src/__tests__/scope-inventory.test.ts +0 -200
  233. package/src/__tests__/scope-policy.test.ts +0 -69
  234. package/src/__tests__/session-checkpoint.test.ts +0 -259
  235. package/src/__tests__/session-distiller.test.ts +0 -611
  236. package/src/__tests__/session-gc.test.ts +0 -215
  237. package/src/__tests__/skill-engine.test.ts +0 -546
  238. package/src/__tests__/skill-promotion.test.ts +0 -349
  239. package/src/__tests__/store-scope-audit.test.ts +0 -81
  240. package/src/__tests__/supersede-chain.test.ts +0 -154
  241. package/src/__tests__/temporal-parser.test.ts +0 -241
  242. package/src/__tests__/temporal-validity.test.ts +0 -160
  243. package/src/__tests__/term-registry.test.ts +0 -121
  244. package/src/__tests__/tool-output-compressor.test.ts +0 -133
  245. package/src/__tests__/topic-tag.test.ts +0 -113
  246. package/src/__tests__/trusted-memory.test.ts +0 -828
  247. package/src/__tests__/version-manager.test.ts +0 -213
  248. package/src/__tests__/workflow-observation.test.ts +0 -400
  249. 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
- });
@@ -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);