@tekmidian/pai 0.35.2 → 0.36.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/dist/{auto-route-DVM3U2ZY.mjs → auto-route-Byf8ENXj.mjs} +4 -4
- package/dist/{auto-route-DVM3U2ZY.mjs.map → auto-route-Byf8ENXj.mjs.map} +1 -1
- package/dist/{checkpoint-block-D3rm4dAJ.mjs → checkpoint-block-DKYxCkBL.mjs} +7 -6
- package/dist/checkpoint-block-DKYxCkBL.mjs.map +1 -0
- package/dist/cli/index.mjs +25 -17
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/probe2.mjs +2 -0
- package/dist/cli/program.d.mts.map +1 -1
- package/dist/cli/program.mjs +16 -267
- package/dist/{clusters-CzGxefB7.mjs → clusters-wZgTCYCB.mjs} +2 -2
- package/dist/{clusters-CzGxefB7.mjs.map → clusters-wZgTCYCB.mjs.map} +1 -1
- package/dist/{config-BSkVcvfq.mjs → config-B64vFg14.mjs} +3 -12
- package/dist/{config-BSkVcvfq.mjs.map → config-B64vFg14.mjs.map} +1 -1
- package/dist/config-C_ErGddD.mjs +3 -0
- package/dist/daemon/index.mjs +19 -19
- package/dist/{daemon-B8L2f5vy.mjs → daemon--N2JFnUs.mjs} +33 -38
- package/dist/daemon--N2JFnUs.mjs.map +1 -0
- package/dist/daemon-DJEFqV84.mjs +20 -0
- package/dist/daemon-mcp/index.mjs +2 -2
- package/dist/{db-BtuN768f.mjs → db-Ca5qfsMC.mjs} +2 -4
- package/dist/{db-BtuN768f.mjs.map → db-Ca5qfsMC.mjs.map} +1 -1
- package/dist/db-O-cyAPfS.mjs +3 -0
- package/dist/db-XEwJbuGO.mjs +3 -0
- package/dist/{db-CYmBWcjh.mjs → db-a1ixZQjr.mjs} +2 -4
- package/dist/{db-CYmBWcjh.mjs.map → db-a1ixZQjr.mjs.map} +1 -1
- package/dist/{detect-Bf2z-oKB.mjs → detect-CdaA48EI.mjs} +1 -1
- package/dist/{detect-Bf2z-oKB.mjs.map → detect-CdaA48EI.mjs.map} +1 -1
- package/dist/{detector-BU-bsDXs.mjs → detector--Gg5JRN5.mjs} +3 -5
- package/dist/{detector-BU-bsDXs.mjs.map → detector--Gg5JRN5.mjs.map} +1 -1
- package/dist/detector-DGAk1iBR.mjs +5 -0
- package/dist/embeddings-CEBGrzwu.mjs +3 -0
- package/dist/{embeddings-Bn86ssxR.mjs → embeddings-DOLZnT1X.mjs} +2 -12
- package/dist/{embeddings-Bn86ssxR.mjs.map → embeddings-DOLZnT1X.mjs.map} +1 -1
- package/dist/{factory-vPTPoBR7.mjs → factory-Bsp7xOpO.mjs} +9 -12
- package/dist/{factory-vPTPoBR7.mjs.map → factory-Bsp7xOpO.mjs.map} +1 -1
- package/dist/factory-CrokPMk2.mjs +3 -0
- package/dist/{helpers-crDEr6S2.mjs → helpers-IjZkXBhj.mjs} +1 -1
- package/dist/{helpers-crDEr6S2.mjs.map → helpers-IjZkXBhj.mjs.map} +1 -1
- package/dist/hooks/context-compression-hook.mjs +209 -70
- package/dist/hooks/context-compression-hook.mjs.map +4 -4
- package/dist/index.mjs +10 -10
- package/dist/{indexer-backend-Bg7VDpGt.mjs → indexer-backend-nQZuEx6N.mjs} +3 -3
- package/dist/{indexer-backend-Bg7VDpGt.mjs.map → indexer-backend-nQZuEx6N.mjs.map} +1 -1
- package/dist/{ipc-client-aVKVERjJ.mjs → ipc-client-BmypMNYk.mjs} +13 -7
- package/dist/ipc-client-BmypMNYk.mjs.map +1 -0
- package/dist/{kg-entity-r8duqhi9.mjs → kg-entity-DbOMPdF9.mjs} +1 -1
- package/dist/{kg-entity-r8duqhi9.mjs.map → kg-entity-DbOMPdF9.mjs.map} +1 -1
- package/dist/{latent-ideas-BL9m2HF9.mjs → latent-ideas-Bn6A5-5P.mjs} +4 -4
- package/dist/{latent-ideas-BL9m2HF9.mjs.map → latent-ideas-Bn6A5-5P.mjs.map} +1 -1
- package/dist/{link-boost-QFLrJwD6.mjs → link-boost-fYjUnxCN.mjs} +1 -1
- package/dist/{link-boost-QFLrJwD6.mjs.map → link-boost-fYjUnxCN.mjs.map} +1 -1
- package/dist/{main-resolver-CNSqU8wo.mjs → main-resolver-BAbhKpeX.mjs} +12 -14
- package/dist/main-resolver-BAbhKpeX.mjs.map +1 -0
- package/dist/main-resolver-Dxh444GO.mjs +4 -0
- package/dist/{migrate-fLD6rAdO.mjs → migrate-Cjzeefn9.mjs} +2 -2
- package/dist/{migrate-fLD6rAdO.mjs.map → migrate-Cjzeefn9.mjs.map} +1 -1
- package/dist/{neighborhood-BX89_nty.mjs → neighborhood-DpaEM991.mjs} +2 -2
- package/dist/{neighborhood-BX89_nty.mjs.map → neighborhood-DpaEM991.mjs.map} +1 -1
- package/dist/{note-context-d1wT_-GA.mjs → note-context-DrcY4cWm.mjs} +1 -1
- package/dist/{note-context-d1wT_-GA.mjs.map → note-context-DrcY4cWm.mjs.map} +1 -1
- package/dist/{pai-marker-B20KqhA8.mjs → pai-marker-CHtbJMwJ.mjs} +1 -1
- package/dist/{pai-marker-B20KqhA8.mjs.map → pai-marker-CHtbJMwJ.mjs.map} +1 -1
- package/dist/{postgres-CI3FjGq0.mjs → postgres-BVme6qX0.mjs} +2 -2
- package/dist/{postgres-CI3FjGq0.mjs.map → postgres-BVme6qX0.mjs.map} +1 -1
- package/dist/{pick-DatgS3Nk.mjs → program-BnMNFb4O.mjs} +1171 -223
- package/dist/program-BnMNFb4O.mjs.map +1 -0
- package/dist/query-feedback-BBMBp96K.mjs +3 -0
- package/dist/{query-feedback-D4U56Hz6.mjs → query-feedback-C1T6kS18.mjs} +2 -4
- package/dist/{query-feedback-D4U56Hz6.mjs.map → query-feedback-C1T6kS18.mjs.map} +1 -1
- package/dist/reranker-CwTCNsgA.mjs +3 -0
- package/dist/{reranker-CMNZcfVx.mjs → reranker-xPm04PXx.mjs} +2 -8
- package/dist/{reranker-CMNZcfVx.mjs.map → reranker-xPm04PXx.mjs.map} +1 -1
- package/dist/router-BMkOb62X.mjs +3 -0
- package/dist/{router-i9S19Usg.mjs → router-CsDm7HvK.mjs} +2 -4
- package/dist/{router-i9S19Usg.mjs.map → router-CsDm7HvK.mjs.map} +1 -1
- package/dist/{runtime-paths-B0P1TvUr.mjs → runtime-paths-rni52zHX.mjs} +1 -1
- package/dist/{runtime-paths-B0P1TvUr.mjs.map → runtime-paths-rni52zHX.mjs.map} +1 -1
- package/dist/search-CfPpJAWQ.mjs +4 -0
- package/dist/{search-C32zQ0V0.mjs → search-Rpk1cSBC.mjs} +4 -15
- package/dist/{search-C32zQ0V0.mjs.map → search-Rpk1cSBC.mjs.map} +1 -1
- package/dist/{sources-BDwN0B8i.mjs → sources-D8ZdNfvK.mjs} +2 -2
- package/dist/{sources-BDwN0B8i.mjs.map → sources-D8ZdNfvK.mjs.map} +1 -1
- package/dist/{sqlite-C6FHnMkn.mjs → sqlite-D1IaR8Am.mjs} +3 -3
- package/dist/{sqlite-C6FHnMkn.mjs.map → sqlite-D1IaR8Am.mjs.map} +1 -1
- package/dist/state-WaXhLr6R.mjs +70 -0
- package/dist/{state-DTvy-jRB.mjs.map → state-WaXhLr6R.mjs.map} +1 -1
- package/dist/state-qtmrBWCm.mjs +3 -0
- package/dist/{stop-words-BaMEGVeY.mjs → stop-words-Hfu8u22w.mjs} +1 -1
- package/dist/{stop-words-BaMEGVeY.mjs.map → stop-words-Hfu8u22w.mjs.map} +1 -1
- package/dist/{sync--BoxBBok.mjs → sync-BWbe8JTg.mjs} +3 -3
- package/dist/{sync--BoxBBok.mjs.map → sync-BWbe8JTg.mjs.map} +1 -1
- package/dist/{themes-BObEGMWn.mjs → themes-XPkj_bfP.mjs} +3 -3
- package/dist/{themes-BObEGMWn.mjs.map → themes-XPkj_bfP.mjs.map} +1 -1
- package/dist/tools-DEt6YPfc.mjs +5 -0
- package/dist/{tools-C1lCHerL.mjs → tools-ceiy7ANX.mjs} +28 -65
- package/dist/tools-ceiy7ANX.mjs.map +1 -0
- package/dist/{trace-h23JCcFD.mjs → trace-DfyGmMG_.mjs} +1 -1
- package/dist/{trace-h23JCcFD.mjs.map → trace-DfyGmMG_.mjs.map} +1 -1
- package/dist/{utils-BAxjW3j8.mjs → utils-9Err2RBW.mjs} +2 -22
- package/dist/{utils-BAxjW3j8.mjs.map → utils-9Err2RBW.mjs.map} +1 -1
- package/dist/utils-DhMex3Ox.mjs +3 -0
- package/dist/{vault-indexer-CUF9edbW.mjs → vault-indexer-CFvlPUMB.mjs} +2 -2
- package/dist/{vault-indexer-CUF9edbW.mjs.map → vault-indexer-CFvlPUMB.mjs.map} +1 -1
- package/dist/{work-queue-worker-BcDGAcF3.mjs → work-queue-worker-228XjABm.mjs} +234 -14
- package/dist/work-queue-worker-228XjABm.mjs.map +1 -0
- package/dist/work-queue-worker-LRA9Fj9z.mjs +11 -0
- package/dist/{zettelkasten-W-h8G2is.mjs → zettelkasten-CvjmMghT.mjs} +4 -4
- package/dist/{zettelkasten-W-h8G2is.mjs.map → zettelkasten-CvjmMghT.mjs.map} +1 -1
- package/package.json +1 -1
- package/src/hooks/ts/lib/context-fill.test.ts +515 -0
- package/src/hooks/ts/lib/context-fill.ts +585 -0
- package/src/hooks/ts/lib/context-handover-cache.ts +46 -0
- package/src/hooks/ts/lib/transcript-text.test.ts +125 -0
- package/src/hooks/ts/lib/transcript-text.ts +71 -0
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +112 -30
- package/statusline-command.sh +16 -0
- package/dist/checkpoint-block-D3rm4dAJ.mjs.map +0 -1
- package/dist/daemon-B8L2f5vy.mjs.map +0 -1
- package/dist/ipc-client-aVKVERjJ.mjs.map +0 -1
- package/dist/main-resolver-CNSqU8wo.mjs.map +0 -1
- package/dist/pick-DatgS3Nk.mjs.map +0 -1
- package/dist/rolldown-runtime-95iHPtFO.mjs +0 -18
- package/dist/state-DTvy-jRB.mjs +0 -102
- package/dist/tools-C1lCHerL.mjs.map +0 -1
- package/dist/work-queue-worker-BcDGAcF3.mjs.map +0 -1
- /package/dist/{indexer-AEcT8wHf.mjs → indexer-D7MvSQPY.mjs} +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//#region src/daemon/daemon/state.ts
|
|
2
|
+
let registryDb;
|
|
3
|
+
let storageBackend;
|
|
4
|
+
let daemonConfig;
|
|
5
|
+
let startTime = Date.now();
|
|
6
|
+
/** True while a project index pass is running. */
|
|
7
|
+
let indexInProgress = false;
|
|
8
|
+
let lastIndexTime = 0;
|
|
9
|
+
let indexSchedulerTimer = null;
|
|
10
|
+
/** True while an embedding pass is running. */
|
|
11
|
+
let embedInProgress = false;
|
|
12
|
+
let lastEmbedTime = 0;
|
|
13
|
+
let embedSchedulerTimer = null;
|
|
14
|
+
/** True while a vault index pass is running. */
|
|
15
|
+
let vaultIndexInProgress = false;
|
|
16
|
+
let lastVaultIndexTime = 0;
|
|
17
|
+
/** Mutable notification config — loaded from disk at startup, patchable at runtime. */
|
|
18
|
+
let notificationConfig;
|
|
19
|
+
/**
|
|
20
|
+
* Set to true when a SIGTERM/SIGINT is received so that long-running loops
|
|
21
|
+
* (embed, index) can detect the signal and exit their inner loops before the
|
|
22
|
+
* pool/backend is closed.
|
|
23
|
+
*/
|
|
24
|
+
let shutdownRequested = false;
|
|
25
|
+
function setRegistryDb(db) {
|
|
26
|
+
registryDb = db;
|
|
27
|
+
}
|
|
28
|
+
function setStorageBackend(b) {
|
|
29
|
+
storageBackend = b;
|
|
30
|
+
}
|
|
31
|
+
function setDaemonConfig(c) {
|
|
32
|
+
daemonConfig = c;
|
|
33
|
+
}
|
|
34
|
+
function setStartTime(t) {
|
|
35
|
+
startTime = t;
|
|
36
|
+
}
|
|
37
|
+
function setNotificationConfig(c) {
|
|
38
|
+
notificationConfig = c;
|
|
39
|
+
}
|
|
40
|
+
function setShutdownRequested(v) {
|
|
41
|
+
shutdownRequested = v;
|
|
42
|
+
}
|
|
43
|
+
function setIndexInProgress(v) {
|
|
44
|
+
indexInProgress = v;
|
|
45
|
+
}
|
|
46
|
+
function setLastIndexTime(v) {
|
|
47
|
+
lastIndexTime = v;
|
|
48
|
+
}
|
|
49
|
+
function setIndexSchedulerTimer(v) {
|
|
50
|
+
indexSchedulerTimer = v;
|
|
51
|
+
}
|
|
52
|
+
function setEmbedInProgress(v) {
|
|
53
|
+
embedInProgress = v;
|
|
54
|
+
}
|
|
55
|
+
function setLastEmbedTime(v) {
|
|
56
|
+
lastEmbedTime = v;
|
|
57
|
+
}
|
|
58
|
+
function setEmbedSchedulerTimer(v) {
|
|
59
|
+
embedSchedulerTimer = v;
|
|
60
|
+
}
|
|
61
|
+
function setVaultIndexInProgress(v) {
|
|
62
|
+
vaultIndexInProgress = v;
|
|
63
|
+
}
|
|
64
|
+
function setLastVaultIndexTime(v) {
|
|
65
|
+
lastVaultIndexTime = v;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//#endregion
|
|
69
|
+
export { setStorageBackend as C, storageBackend as D, startTime as E, vaultIndexInProgress as O, setStartTime as S, shutdownRequested as T, setLastIndexTime as _, indexSchedulerTimer as a, setRegistryDb as b, lastVaultIndexTime as c, setDaemonConfig as d, setEmbedInProgress as f, setLastEmbedTime as g, setIndexSchedulerTimer as h, indexInProgress as i, notificationConfig as l, setIndexInProgress as m, embedInProgress as n, lastEmbedTime as o, setEmbedSchedulerTimer as p, embedSchedulerTimer as r, lastIndexTime as s, daemonConfig as t, registryDb as u, setLastVaultIndexTime as v, setVaultIndexInProgress as w, setShutdownRequested as x, setNotificationConfig as y };
|
|
70
|
+
//# sourceMappingURL=state-WaXhLr6R.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-
|
|
1
|
+
{"version":3,"file":"state-WaXhLr6R.mjs","names":[],"sources":["../src/daemon/daemon/state.ts"],"sourcesContent":["/**\n * Shared mutable daemon state — module-level singletons used across all daemon sub-modules.\n */\n\nimport { openRegistry } from \"../../registry/db.js\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { NotificationConfig } from \"../../notifications/types.js\";\nimport type { PaiDaemonConfig } from \"../config.js\";\n\n// ---------------------------------------------------------------------------\n// Core singletons (assigned at startup by serve())\n// ---------------------------------------------------------------------------\n\nexport let registryDb: ReturnType<typeof openRegistry>;\nexport let storageBackend: StorageBackend;\nexport let daemonConfig: PaiDaemonConfig;\nexport let startTime = Date.now();\n\n// ---------------------------------------------------------------------------\n// Scheduler state\n// ---------------------------------------------------------------------------\n\n/** True while a project index pass is running. */\nexport let indexInProgress = false;\nexport let lastIndexTime = 0;\nexport let indexSchedulerTimer: ReturnType<typeof setInterval> | null = null;\n\n/** True while an embedding pass is running. */\nexport let embedInProgress = false;\nexport let lastEmbedTime = 0;\nexport let embedSchedulerTimer: ReturnType<typeof setInterval> | null = null;\n\n/** True while a vault index pass is running. */\nexport let vaultIndexInProgress = false;\nexport let lastVaultIndexTime = 0;\n\n// ---------------------------------------------------------------------------\n// Notification state\n// ---------------------------------------------------------------------------\n\n/** Mutable notification config — loaded from disk at startup, patchable at runtime. */\nexport let notificationConfig: NotificationConfig;\n\n// ---------------------------------------------------------------------------\n// Graceful shutdown flag\n// ---------------------------------------------------------------------------\n\n/**\n * Set to true when a SIGTERM/SIGINT is received so that long-running loops\n * (embed, index) can detect the signal and exit their inner loops before the\n * pool/backend is closed.\n */\nexport let shutdownRequested = false;\n\n// ---------------------------------------------------------------------------\n// Setters (TypeScript requires assignment functions for exported `let` vars\n// that need to be mutated across module boundaries)\n// ---------------------------------------------------------------------------\n\nexport function setRegistryDb(db: ReturnType<typeof openRegistry>): void { registryDb = db; }\nexport function setStorageBackend(b: StorageBackend): void { storageBackend = b; }\nexport function setDaemonConfig(c: PaiDaemonConfig): void { daemonConfig = c; }\nexport function setStartTime(t: number): void { startTime = t; }\nexport function setNotificationConfig(c: NotificationConfig): void { notificationConfig = c; }\nexport function setShutdownRequested(v: boolean): void { shutdownRequested = v; }\nexport function setIndexInProgress(v: boolean): void { indexInProgress = v; }\nexport function setLastIndexTime(v: number): void { lastIndexTime = v; }\nexport function setIndexSchedulerTimer(v: ReturnType<typeof setInterval> | null): void { indexSchedulerTimer = v; }\nexport function setEmbedInProgress(v: boolean): void { embedInProgress = v; }\nexport function setLastEmbedTime(v: number): void { lastEmbedTime = v; }\nexport function setEmbedSchedulerTimer(v: ReturnType<typeof setInterval> | null): void { embedSchedulerTimer = v; }\nexport function setVaultIndexInProgress(v: boolean): void { vaultIndexInProgress = v; }\nexport function setLastVaultIndexTime(v: number): void { lastVaultIndexTime = v; }\n"],"mappings":";AAaA,IAAW;AACX,IAAW;AACX,IAAW;AACX,IAAW,YAAY,KAAK,KAAK;;AAOjC,IAAW,kBAAkB;AAC7B,IAAW,gBAAgB;AAC3B,IAAW,sBAA6D;;AAGxE,IAAW,kBAAkB;AAC7B,IAAW,gBAAgB;AAC3B,IAAW,sBAA6D;;AAGxE,IAAW,uBAAuB;AAClC,IAAW,qBAAqB;;AAOhC,IAAW;;;;;;AAWX,IAAW,oBAAoB;AAO/B,SAAgB,cAAc,IAA2C;AAAE,cAAa;;AACxF,SAAgB,kBAAkB,GAAyB;AAAE,kBAAiB;;AAC9E,SAAgB,gBAAgB,GAA0B;AAAE,gBAAe;;AAC3E,SAAgB,aAAa,GAAiB;AAAE,aAAY;;AAC5D,SAAgB,sBAAsB,GAA6B;AAAE,sBAAqB;;AAC1F,SAAgB,qBAAqB,GAAkB;AAAE,qBAAoB;;AAC7E,SAAgB,mBAAmB,GAAkB;AAAE,mBAAkB;;AACzE,SAAgB,iBAAiB,GAAiB;AAAE,iBAAgB;;AACpE,SAAgB,uBAAuB,GAAgD;AAAE,uBAAsB;;AAC/G,SAAgB,mBAAmB,GAAkB;AAAE,mBAAkB;;AACzE,SAAgB,iBAAiB,GAAiB;AAAE,iBAAgB;;AACpE,SAAgB,uBAAuB,GAAgD;AAAE,uBAAsB;;AAC/G,SAAgB,wBAAwB,GAAkB;AAAE,wBAAuB;;AACnF,SAAgB,sBAAsB,GAAiB;AAAE,sBAAqB"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { C as setStorageBackend, D as storageBackend, E as startTime, O as vaultIndexInProgress, S as setStartTime, T as shutdownRequested, _ as setLastIndexTime, a as indexSchedulerTimer, b as setRegistryDb, c as lastVaultIndexTime, d as setDaemonConfig, f as setEmbedInProgress, g as setLastEmbedTime, h as setIndexSchedulerTimer, i as indexInProgress, l as notificationConfig, m as setIndexInProgress, n as embedInProgress, o as lastEmbedTime, p as setEmbedSchedulerTimer, r as embedSchedulerTimer, s as lastIndexTime, t as daemonConfig, u as registryDb, v as setLastVaultIndexTime, w as setVaultIndexInProgress, x as setShutdownRequested, y as setNotificationConfig } from "./state-WaXhLr6R.mjs";
|
|
2
|
+
|
|
3
|
+
export { lastVaultIndexTime, notificationConfig };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stop-words-
|
|
1
|
+
{"version":3,"file":"stop-words-Hfu8u22w.mjs","names":[],"sources":["../src/utils/stop-words.ts"],"sourcesContent":["/**\n * Shared stop-word list used across memory search, slug generation, graph clustering,\n * and zettelkasten modules. This is the union of all per-file sets that previously\n * existed in 7 different files.\n */\n\nexport const STOP_WORDS = new Set([\n // Articles / determiners\n \"a\", \"an\", \"the\",\n // Conjunctions\n \"and\", \"or\", \"but\", \"if\", \"then\", \"else\", \"so\", \"because\", \"as\",\n \"although\", \"however\", \"therefore\", \"thus\", \"hence\", \"meanwhile\",\n \"moreover\", \"furthermore\", \"otherwise\", \"instead\", \"anyway\",\n // Prepositions\n \"at\", \"to\", \"for\", \"of\", \"with\", \"by\", \"from\", \"in\", \"on\", \"out\",\n \"off\", \"over\", \"under\", \"up\", \"into\", \"without\", \"per\", \"via\",\n // Pronouns\n \"i\", \"you\", \"we\", \"they\", \"he\", \"she\", \"it\", \"me\", \"us\", \"him\", \"her\",\n \"my\", \"your\", \"our\", \"their\", \"his\", \"its\", \"who\", \"whom\", \"what\",\n \"which\", \"this\", \"that\", \"these\", \"those\",\n // Common verbs\n \"is\", \"are\", \"was\", \"were\", \"be\", \"been\", \"being\",\n \"have\", \"has\", \"had\", \"do\", \"does\", \"did\",\n \"will\", \"would\", \"could\", \"should\", \"may\", \"might\", \"can\", \"shall\",\n \"want\", \"need\", \"know\", \"think\", \"see\", \"look\", \"make\", \"get\", \"go\",\n \"come\", \"take\", \"use\", \"find\", \"give\", \"tell\", \"say\", \"said\", \"try\",\n \"keep\", \"run\", \"set\", \"put\", \"add\", \"show\", \"check\", \"let\",\n // Negation / common function words\n \"not\", \"no\", \"yes\", \"just\", \"also\", \"very\", \"really\",\n \"about\", \"after\", \"before\",\n \"more\", \"most\", \"some\", \"any\", \"all\", \"each\", \"every\", \"both\", \"few\",\n \"many\", \"much\", \"other\", \"another\", \"such\", \"only\", \"own\", \"same\",\n \"than\", \"too\", \"ok\", \"okay\", \"sure\",\n \"please\", \"thanks\", \"thank\", \"here\", \"there\", \"now\", \"well\", \"like\",\n \"going\", \"done\", \"got\",\n // Tech/URL junk\n \"https\", \"http\", \"www\", \"com\", \"org\", \"net\", \"io\",\n \"null\", \"undefined\", \"true\", \"false\",\n // Contractions (de-apostrophe'd forms that appear after tokenisation)\n \"ll\", \"ve\", \"re\", \"don\", \"thats\", \"heres\", \"theres\",\n \"youre\", \"theyre\", \"didnt\", \"dont\", \"doesnt\", \"havent\", \"hasnt\",\n \"wont\", \"cant\", \"shouldnt\", \"wouldnt\", \"couldnt\", \"isnt\", \"arent\",\n \"wasnt\", \"werent\",\n // Filler adverbs\n \"never\", \"ever\", \"still\", \"already\", \"yet\", \"back\",\n \"away\", \"down\", \"right\", \"left\", \"next\", \"last\", \"first\", \"second\",\n \"third\",\n \"then\", \"again\", \"once\", \"twice\", \"since\", \"while\", \"though\",\n \"actually\", \"basically\", \"literally\", \"simply\", \"exactly\", \"probably\",\n \"possibly\", \"maybe\", \"perhaps\", \"certainly\", \"definitely\", \"absolutely\",\n \"completely\", \"totally\", \"quite\", \"rather\", \"fairly\", \"nearly\",\n \"almost\", \"barely\", \"hardly\", \"quickly\", \"slowly\", \"easily\", \"likely\",\n \"unlikely\",\n // Numbers (spelled out)\n \"one\", \"two\", \"three\", \"four\", \"five\", \"six\", \"seven\",\n \"eight\", \"nine\", \"ten\",\n // Common nouns (too generic for search/slug)\n \"time\", \"way\", \"thing\", \"something\", \"anything\", \"nothing\",\n \"everything\", \"someone\", \"anyone\", \"everyone\",\n // Zettelkasten / vault-specific noise\n \"new\", \"note\", \"untitled\", \"page\", \"file\", \"doc\", \"code\",\n \"session\", \"notes\", \"moc\", \"template\", \"content\", \"attachment\",\n // Misc abbreviations\n \"etc\", \"ie\", \"eg\", \"vs\",\n // French stop words (for multilingual vaults)\n \"les\", \"des\", \"une\", \"est\", \"que\", \"qui\", \"dans\", \"pour\", \"sur\",\n \"par\", \"pas\", \"son\", \"ses\", \"aux\", \"avec\", \"tout\", \"mais\",\n // German stop words (for multilingual vaults)\n \"und\", \"der\", \"die\", \"das\", \"ein\", \"eine\", \"ist\", \"den\", \"dem\",\n \"von\", \"mit\", \"auf\", \"nicht\", \"sich\", \"auch\", \"noch\", \"wie\",\n]);\n\n/**\n * Alias used by graph modules that need stop-word filtering specifically for\n * vault note titles (same set, just semantically named for clarity).\n */\nexport const TITLE_STOP_WORDS = STOP_WORDS;\n"],"mappings":";;;;;;AAMA,MAAa,aAAa,IAAI,IAAI;CAEhC;CAAK;CAAM;CAEX;CAAO;CAAM;CAAO;CAAM;CAAQ;CAAQ;CAAM;CAAW;CAC3D;CAAY;CAAW;CAAa;CAAQ;CAAS;CACrD;CAAY;CAAe;CAAa;CAAW;CAEnD;CAAM;CAAM;CAAO;CAAM;CAAQ;CAAM;CAAQ;CAAM;CAAM;CAC3D;CAAO;CAAQ;CAAS;CAAM;CAAQ;CAAW;CAAO;CAExD;CAAK;CAAO;CAAM;CAAQ;CAAM;CAAO;CAAM;CAAM;CAAM;CAAO;CAChE;CAAM;CAAQ;CAAO;CAAS;CAAO;CAAO;CAAO;CAAQ;CAC3D;CAAS;CAAQ;CAAQ;CAAS;CAElC;CAAM;CAAO;CAAO;CAAQ;CAAM;CAAQ;CAC1C;CAAQ;CAAO;CAAO;CAAM;CAAQ;CACpC;CAAQ;CAAS;CAAS;CAAU;CAAO;CAAS;CAAO;CAC3D;CAAQ;CAAQ;CAAQ;CAAS;CAAO;CAAQ;CAAQ;CAAO;CAC/D;CAAQ;CAAQ;CAAO;CAAQ;CAAQ;CAAQ;CAAO;CAAQ;CAC9D;CAAQ;CAAO;CAAO;CAAO;CAAO;CAAQ;CAAS;CAErD;CAAO;CAAM;CAAO;CAAQ;CAAQ;CAAQ;CAC5C;CAAS;CAAS;CAClB;CAAQ;CAAQ;CAAQ;CAAO;CAAO;CAAQ;CAAS;CAAQ;CAC/D;CAAQ;CAAQ;CAAS;CAAW;CAAQ;CAAQ;CAAO;CAC3D;CAAQ;CAAO;CAAM;CAAQ;CAC7B;CAAU;CAAU;CAAS;CAAQ;CAAS;CAAO;CAAQ;CAC7D;CAAS;CAAQ;CAEjB;CAAS;CAAQ;CAAO;CAAO;CAAO;CAAO;CAC7C;CAAQ;CAAa;CAAQ;CAE7B;CAAM;CAAM;CAAM;CAAO;CAAS;CAAS;CAC3C;CAAS;CAAU;CAAS;CAAQ;CAAU;CAAU;CACxD;CAAQ;CAAQ;CAAY;CAAW;CAAW;CAAQ;CAC1D;CAAS;CAET;CAAS;CAAQ;CAAS;CAAW;CAAO;CAC5C;CAAQ;CAAQ;CAAS;CAAQ;CAAQ;CAAQ;CAAS;CAC1D;CACA;CAAQ;CAAS;CAAQ;CAAS;CAAS;CAAS;CACpD;CAAY;CAAa;CAAa;CAAU;CAAW;CAC3D;CAAY;CAAS;CAAW;CAAa;CAAc;CAC3D;CAAc;CAAW;CAAS;CAAU;CAAU;CACtD;CAAU;CAAU;CAAU;CAAW;CAAU;CAAU;CAC7D;CAEA;CAAO;CAAO;CAAS;CAAQ;CAAQ;CAAO;CAC9C;CAAS;CAAQ;CAEjB;CAAQ;CAAO;CAAS;CAAa;CAAY;CACjD;CAAc;CAAW;CAAU;CAEnC;CAAO;CAAQ;CAAY;CAAQ;CAAQ;CAAO;CAClD;CAAW;CAAS;CAAO;CAAY;CAAW;CAElD;CAAO;CAAM;CAAM;CAEnB;CAAO;CAAO;CAAO;CAAO;CAAO;CAAO;CAAQ;CAAQ;CAC1D;CAAO;CAAO;CAAO;CAAO;CAAO;CAAQ;CAAQ;CAEnD;CAAO;CAAO;CAAO;CAAO;CAAO;CAAQ;CAAO;CAAO;CACzD;CAAO;CAAO;CAAO;CAAS;CAAQ;CAAQ;CAAQ;CACvD,CAAC;;;;;AAMF,MAAa,mBAAmB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as parseSessionTitleChunk, c as yieldToEventLoop, f as sha256File, i as isPathTooBroadForContentScan, l as chunkMarkdown, n as chunkId, o as walkContentFiles, r as detectTier, s as walkMdFiles, t as INDEX_YIELD_EVERY } from "./helpers-
|
|
1
|
+
import { a as parseSessionTitleChunk, c as yieldToEventLoop, f as sha256File, i as isPathTooBroadForContentScan, l as chunkMarkdown, n as chunkId, o as walkContentFiles, r as detectTier, s as walkMdFiles, t as INDEX_YIELD_EVERY } from "./helpers-IjZkXBhj.mjs";
|
|
2
2
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
3
|
import { basename, join, relative } from "node:path";
|
|
4
4
|
|
|
@@ -267,7 +267,7 @@ async function indexAll(db, registryDb) {
|
|
|
267
267
|
* @param onProgress Optional callback called after each batch with running totals.
|
|
268
268
|
*/
|
|
269
269
|
async function embedChunks(db, projectId, batchSize = 50, onProgress) {
|
|
270
|
-
const { generateEmbedding, serializeEmbedding } = await import("./embeddings-
|
|
270
|
+
const { generateEmbedding, serializeEmbedding } = await import("./embeddings-CEBGrzwu.mjs");
|
|
271
271
|
const conditions = ["embedding IS NULL"];
|
|
272
272
|
const params = [];
|
|
273
273
|
if (projectId !== void 0) {
|
|
@@ -307,4 +307,4 @@ async function embedChunks(db, projectId, batchSize = 50, onProgress) {
|
|
|
307
307
|
|
|
308
308
|
//#endregion
|
|
309
309
|
export { indexProject as i, indexAll as n, indexFile as r, embedChunks as t };
|
|
310
|
-
//# sourceMappingURL=sync
|
|
310
|
+
//# sourceMappingURL=sync-BWbe8JTg.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync--BoxBBok.mjs","names":[],"sources":["../src/memory/indexer/sync.ts"],"sourcesContent":["/**\n * Synchronous (SQLite) indexer for the PAI federation memory engine.\n *\n * Scans project memory/ and Notes/ directories, chunks markdown files, and\n * inserts the resulting chunks into federation.db for BM25 search.\n *\n * Change detection: files whose SHA-256 hash has not changed since the last\n * index run are skipped, keeping incremental re-indexing fast.\n *\n * Uses raw better-sqlite3 Database directly for maximum SQLite performance\n * (synchronous transactions, no serialisation overhead).\n */\n\nimport { readFileSync, statSync, existsSync } from \"node:fs\";\nimport { join, relative, basename } from \"node:path\";\nimport type { Database } from \"better-sqlite3\";\nimport { chunkMarkdown } from \"../chunker.js\";\nimport {\n sha256File,\n chunkId,\n detectTier,\n walkMdFiles,\n walkContentFiles,\n isPathTooBroadForContentScan,\n parseSessionTitleChunk,\n yieldToEventLoop,\n INDEX_YIELD_EVERY,\n} from \"./helpers.js\";\nimport type { IndexResult, EmbedResult } from \"./types.js\";\n\nexport type { IndexResult, EmbedResult };\n\n// Re-export detectTier for backward-compatibility (consumers import it from indexer.js)\nexport { detectTier };\n\n// ---------------------------------------------------------------------------\n// Single-file indexing\n// ---------------------------------------------------------------------------\n\n/**\n * Index a single file into the federation database.\n *\n * @returns true if the file was re-indexed (changed or new), false if skipped.\n */\nexport function indexFile(\n db: Database,\n projectId: number,\n rootPath: string,\n relativePath: string,\n source: string,\n tier: string,\n): boolean {\n const absPath = join(rootPath, relativePath);\n\n // Read file content\n let content: string;\n let stat: ReturnType<typeof statSync>;\n try {\n content = readFileSync(absPath, \"utf8\");\n stat = statSync(absPath);\n } catch {\n // File unreadable or missing — skip silently\n return false;\n }\n\n const hash = sha256File(content);\n const mtime = Math.floor(stat.mtimeMs);\n const size = stat.size;\n\n // Check if the file has changed since last index\n const existing = db\n .prepare(\n \"SELECT hash FROM memory_files WHERE project_id = ? AND path = ?\",\n )\n .get(projectId, relativePath) as { hash: string } | undefined;\n\n if (existing?.hash === hash) {\n // Unchanged — skip\n return false;\n }\n\n // Delete old chunks for this file from both tables\n const oldChunkIds = db\n .prepare(\n \"SELECT id FROM memory_chunks WHERE project_id = ? AND path = ?\",\n )\n .all(projectId, relativePath) as Array<{ id: string }>;\n\n const deleteFts = db.prepare(\"DELETE FROM memory_fts WHERE id = ?\");\n const deleteChunk = db.prepare(\n \"DELETE FROM memory_chunks WHERE project_id = ? AND path = ?\",\n );\n\n db.transaction(() => {\n for (const row of oldChunkIds) {\n deleteFts.run(row.id);\n }\n deleteChunk.run(projectId, relativePath);\n })();\n\n // Chunk the new content\n const chunks = chunkMarkdown(content);\n\n // Insert new chunks into memory_chunks and memory_fts\n const insertChunk = db.prepare(`\n INSERT INTO memory_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n const insertFts = db.prepare(`\n INSERT INTO memory_fts (text, id, project_id, path, source, tier, start_line, end_line)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n const upsertFile = db.prepare(`\n INSERT INTO memory_files (project_id, path, source, tier, hash, mtime, size)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(project_id, path) DO UPDATE SET\n source = excluded.source,\n tier = excluded.tier,\n hash = excluded.hash,\n mtime = excluded.mtime,\n size = excluded.size\n `);\n\n const updatedAt = Date.now();\n\n db.transaction(() => {\n for (let i = 0; i < chunks.length; i++) {\n const chunk = chunks[i]!;\n const id = chunkId(projectId, relativePath, i, chunk.startLine, chunk.endLine);\n insertChunk.run(\n id,\n projectId,\n source,\n tier,\n relativePath,\n chunk.startLine,\n chunk.endLine,\n chunk.hash,\n chunk.text,\n updatedAt,\n );\n insertFts.run(\n chunk.text,\n id,\n projectId,\n relativePath,\n source,\n tier,\n chunk.startLine,\n chunk.endLine,\n );\n }\n upsertFile.run(projectId, relativePath, source, tier, hash, mtime, size);\n })();\n\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Project-level indexing\n// ---------------------------------------------------------------------------\n\n/**\n * Index all memory, Notes, and content files for a single registered project.\n *\n * Scans:\n * - {rootPath}/MEMORY.md → source='memory', tier='evergreen'\n * - {rootPath}/memory/ → source='memory', tier from detectTier()\n * - {rootPath}/Notes/ → source='notes', tier='session'\n * - {rootPath}/**\\/*.md → source='content', tier='topic' (all other .md files, recursive)\n * - {claudeNotesDir}/ → source='notes', tier='session' (if set and different)\n */\nexport async function indexProject(\n db: Database,\n projectId: number,\n rootPath: string,\n claudeNotesDir?: string | null,\n): Promise<IndexResult> {\n const result: IndexResult = {\n filesProcessed: 0,\n chunksCreated: 0,\n filesSkipped: 0,\n };\n\n const filesToIndex: Array<{ absPath: string; rootBase: string; source: string; tier: string }> = [];\n\n // Root-level MEMORY.md\n const rootMemoryMd = join(rootPath, \"MEMORY.md\");\n if (existsSync(rootMemoryMd)) {\n filesToIndex.push({ absPath: rootMemoryMd, rootBase: rootPath, source: \"memory\", tier: \"evergreen\" });\n }\n\n // memory/ directory\n const memoryDir = join(rootPath, \"memory\");\n for (const absPath of walkMdFiles(memoryDir)) {\n const relPath = relative(rootPath, absPath);\n const tier = detectTier(relPath);\n filesToIndex.push({ absPath, rootBase: rootPath, source: \"memory\", tier });\n }\n\n // {rootPath}/Notes/ directory\n const notesDir = join(rootPath, \"Notes\");\n for (const absPath of walkMdFiles(notesDir)) {\n filesToIndex.push({ absPath, rootBase: rootPath, source: \"notes\", tier: \"session\" });\n }\n\n // Synthetic session-title chunks for Notes files with the standard filename format:\n // \"NNNN - YYYY-MM-DD - Descriptive Title.md\"\n {\n const titleInsertChunk = db.prepare(`\n INSERT OR IGNORE INTO memory_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n const titleInsertFts = db.prepare(`\n INSERT OR IGNORE INTO memory_fts (text, id, project_id, path, source, tier, start_line, end_line)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n const updatedAt = Date.now();\n for (const absPath of walkMdFiles(notesDir)) {\n const fileName = basename(absPath);\n const text = parseSessionTitleChunk(fileName);\n if (!text) continue;\n const relPath = relative(rootPath, absPath);\n const syntheticPath = `${relPath}::title`;\n const id = chunkId(projectId, syntheticPath, 0, 0, 0);\n const hash = sha256File(text);\n db.transaction(() => {\n titleInsertChunk.run(id, projectId, \"notes\", \"session\", syntheticPath, 0, 0, hash, text, updatedAt);\n titleInsertFts.run(text, id, projectId, syntheticPath, \"notes\", \"session\", 0, 0);\n })();\n }\n }\n\n // {rootPath}/**/*.md — all other markdown content\n if (!isPathTooBroadForContentScan(rootPath)) {\n for (const absPath of walkContentFiles(rootPath)) {\n filesToIndex.push({ absPath, rootBase: rootPath, source: \"content\", tier: \"topic\" });\n }\n }\n\n // Claude Code session notes directory (~/.claude/projects/{encoded}/Notes/)\n if (claudeNotesDir && claudeNotesDir !== notesDir) {\n for (const absPath of walkMdFiles(claudeNotesDir)) {\n filesToIndex.push({ absPath, rootBase: claudeNotesDir, source: \"notes\", tier: \"session\" });\n }\n\n // Synthetic title chunks for claude notes dir\n {\n const updatedAt = Date.now();\n const titleInsertChunk2 = db.prepare(`\n INSERT OR IGNORE INTO memory_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n const titleInsertFts2 = db.prepare(`\n INSERT OR IGNORE INTO memory_fts (text, id, project_id, path, source, tier, start_line, end_line)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n for (const absPath of walkMdFiles(claudeNotesDir)) {\n const fileName = basename(absPath);\n const text = parseSessionTitleChunk(fileName);\n if (!text) continue;\n const relPath = relative(claudeNotesDir, absPath);\n const syntheticPath = `${relPath}::title`;\n const id = chunkId(projectId, syntheticPath, 0, 0, 0);\n const hash = sha256File(text);\n db.transaction(() => {\n titleInsertChunk2.run(id, projectId, \"notes\", \"session\", syntheticPath, 0, 0, hash, text, updatedAt);\n titleInsertFts2.run(text, id, projectId, syntheticPath, \"notes\", \"session\", 0, 0);\n })();\n }\n }\n\n // Derive the sibling memory/ directory: .../Notes/ → .../memory/\n if (claudeNotesDir.endsWith(\"/Notes\")) {\n const claudeProjectDir = claudeNotesDir.slice(0, -\"/Notes\".length);\n const claudeMemoryDir = join(claudeProjectDir, \"memory\");\n\n const claudeMemoryMd = join(claudeProjectDir, \"MEMORY.md\");\n if (existsSync(claudeMemoryMd)) {\n filesToIndex.push({\n absPath: claudeMemoryMd,\n rootBase: claudeProjectDir,\n source: \"memory\",\n tier: \"evergreen\",\n });\n }\n\n for (const absPath of walkMdFiles(claudeMemoryDir)) {\n const relPath = relative(claudeProjectDir, absPath);\n const tier = detectTier(relPath);\n filesToIndex.push({ absPath, rootBase: claudeProjectDir, source: \"memory\", tier });\n }\n }\n }\n\n // Yield after collection phase before processing\n await yieldToEventLoop();\n\n let filesSinceYield = 0;\n\n for (const { absPath, rootBase, source, tier } of filesToIndex) {\n if (filesSinceYield >= INDEX_YIELD_EVERY) {\n await yieldToEventLoop();\n filesSinceYield = 0;\n }\n filesSinceYield++;\n\n const relPath = relative(rootBase, absPath);\n const changed = indexFile(db, projectId, rootBase, relPath, source, tier);\n\n if (changed) {\n const count = db\n .prepare(\n \"SELECT COUNT(*) as n FROM memory_chunks WHERE project_id = ? AND path = ?\",\n )\n .get(projectId, relPath) as { n: number };\n\n result.filesProcessed++;\n result.chunksCreated += count.n;\n } else {\n result.filesSkipped++;\n }\n }\n\n // Prune stale paths: remove DB entries for files that no longer exist on disk.\n const livePaths = new Set<string>();\n for (const { absPath, rootBase } of filesToIndex) {\n livePaths.add(relative(rootBase, absPath));\n }\n\n const dbChunkPaths = db\n .prepare(\"SELECT DISTINCT path FROM memory_chunks WHERE project_id = ?\")\n .all(projectId) as Array<{ path: string }>;\n\n const stalePaths: string[] = [];\n for (const row of dbChunkPaths) {\n const basePath = row.path.endsWith(\"::title\")\n ? row.path.slice(0, -\"::title\".length)\n : row.path;\n if (!livePaths.has(basePath)) {\n stalePaths.push(row.path);\n }\n }\n\n if (stalePaths.length > 0) {\n const deleteChunksFts = db.prepare(\"DELETE FROM memory_fts WHERE id = ?\");\n const deleteChunks = db.prepare(\n \"DELETE FROM memory_chunks WHERE project_id = ? AND path = ?\",\n );\n const deleteFile = db.prepare(\n \"DELETE FROM memory_files WHERE project_id = ? AND path = ?\",\n );\n\n db.transaction(() => {\n for (const stalePath of stalePaths) {\n const chunkIds = db\n .prepare(\"SELECT id FROM memory_chunks WHERE project_id = ? AND path = ?\")\n .all(projectId, stalePath) as Array<{ id: string }>;\n for (const { id } of chunkIds) {\n deleteChunksFts.run(id);\n }\n deleteChunks.run(projectId, stalePath);\n deleteFile.run(projectId, stalePath);\n }\n })();\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Global indexing (all registered projects)\n// ---------------------------------------------------------------------------\n\n/**\n * Index all active projects registered in the registry DB.\n *\n * Async: yields to the event loop between each project so that the daemon's\n * Unix socket server can process IPC requests (e.g. status) while indexing.\n */\nexport async function indexAll(\n db: Database,\n registryDb: Database,\n): Promise<{ projects: number; result: IndexResult }> {\n const projects = registryDb\n .prepare(\"SELECT id, root_path, claude_notes_dir FROM projects WHERE status = 'active'\")\n .all() as Array<{ id: number; root_path: string; claude_notes_dir: string | null }>;\n\n const totals: IndexResult = {\n filesProcessed: 0,\n chunksCreated: 0,\n filesSkipped: 0,\n };\n\n for (const project of projects) {\n await yieldToEventLoop();\n const r = await indexProject(db, project.id, project.root_path, project.claude_notes_dir);\n totals.filesProcessed += r.filesProcessed;\n totals.chunksCreated += r.chunksCreated;\n totals.filesSkipped += r.filesSkipped;\n }\n\n return { projects: projects.length, result: totals };\n}\n\n// ---------------------------------------------------------------------------\n// Embedding generation\n// ---------------------------------------------------------------------------\n\n/**\n * Generate and store embeddings for chunks that do not yet have one.\n *\n * Because better-sqlite3 is synchronous but the embedding pipeline is async,\n * we fetch all unembedded chunk texts first, generate embeddings in batches,\n * and then write them back in a transaction.\n *\n * @param db Open federation database.\n * @param projectId Optional — restrict to a specific project.\n * @param batchSize Number of chunks to embed per round. Default 50.\n * @param onProgress Optional callback called after each batch with running totals.\n */\nexport async function embedChunks(\n db: Database,\n projectId?: number,\n batchSize = 50,\n onProgress?: (embedded: number, total: number) => void,\n): Promise<EmbedResult> {\n // Dynamic import — keeps the heavy ML runtime out of the module load path\n const { generateEmbedding, serializeEmbedding } = await import(\"../embeddings.js\");\n\n const conditions = [\"embedding IS NULL\"];\n const params: (string | number)[] = [];\n\n if (projectId !== undefined) {\n conditions.push(\"project_id = ?\");\n params.push(projectId);\n }\n\n const where = \"WHERE \" + conditions.join(\" AND \");\n\n const rows = db\n .prepare(`SELECT id, text FROM memory_chunks ${where} ORDER BY id`)\n .all(...params) as Array<{ id: string; text: string }>;\n\n if (rows.length === 0) {\n return { chunksEmbedded: 0, chunksSkipped: 0 };\n }\n\n const updateStmt = db.prepare(\n \"UPDATE memory_chunks SET embedding = ? WHERE id = ?\",\n );\n\n let embedded = 0;\n const total = rows.length;\n\n for (let i = 0; i < rows.length; i += batchSize) {\n const batch = rows.slice(i, i + batchSize);\n\n // Generate embeddings for the batch (async — must happen OUTSIDE transaction)\n const embeddings: Array<{ id: string; blob: Buffer }> = [];\n for (const row of batch) {\n const vec = await generateEmbedding(row.text);\n const blob = serializeEmbedding(vec);\n embeddings.push({ id: row.id, blob });\n }\n\n // Write the batch in a single transaction\n db.transaction(() => {\n for (const { id, blob } of embeddings) {\n updateStmt.run(blob, id);\n }\n })();\n\n embedded += embeddings.length;\n onProgress?.(embedded, total);\n }\n\n return { chunksEmbedded: embedded, chunksSkipped: 0 };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sync-BWbe8JTg.mjs","names":[],"sources":["../src/memory/indexer/sync.ts"],"sourcesContent":["/**\n * Synchronous (SQLite) indexer for the PAI federation memory engine.\n *\n * Scans project memory/ and Notes/ directories, chunks markdown files, and\n * inserts the resulting chunks into federation.db for BM25 search.\n *\n * Change detection: files whose SHA-256 hash has not changed since the last\n * index run are skipped, keeping incremental re-indexing fast.\n *\n * Uses raw better-sqlite3 Database directly for maximum SQLite performance\n * (synchronous transactions, no serialisation overhead).\n */\n\nimport { readFileSync, statSync, existsSync } from \"node:fs\";\nimport { join, relative, basename } from \"node:path\";\nimport type { Database } from \"better-sqlite3\";\nimport { chunkMarkdown } from \"../chunker.js\";\nimport {\n sha256File,\n chunkId,\n detectTier,\n walkMdFiles,\n walkContentFiles,\n isPathTooBroadForContentScan,\n parseSessionTitleChunk,\n yieldToEventLoop,\n INDEX_YIELD_EVERY,\n} from \"./helpers.js\";\nimport type { IndexResult, EmbedResult } from \"./types.js\";\n\nexport type { IndexResult, EmbedResult };\n\n// Re-export detectTier for backward-compatibility (consumers import it from indexer.js)\nexport { detectTier };\n\n// ---------------------------------------------------------------------------\n// Single-file indexing\n// ---------------------------------------------------------------------------\n\n/**\n * Index a single file into the federation database.\n *\n * @returns true if the file was re-indexed (changed or new), false if skipped.\n */\nexport function indexFile(\n db: Database,\n projectId: number,\n rootPath: string,\n relativePath: string,\n source: string,\n tier: string,\n): boolean {\n const absPath = join(rootPath, relativePath);\n\n // Read file content\n let content: string;\n let stat: ReturnType<typeof statSync>;\n try {\n content = readFileSync(absPath, \"utf8\");\n stat = statSync(absPath);\n } catch {\n // File unreadable or missing — skip silently\n return false;\n }\n\n const hash = sha256File(content);\n const mtime = Math.floor(stat.mtimeMs);\n const size = stat.size;\n\n // Check if the file has changed since last index\n const existing = db\n .prepare(\n \"SELECT hash FROM memory_files WHERE project_id = ? AND path = ?\",\n )\n .get(projectId, relativePath) as { hash: string } | undefined;\n\n if (existing?.hash === hash) {\n // Unchanged — skip\n return false;\n }\n\n // Delete old chunks for this file from both tables\n const oldChunkIds = db\n .prepare(\n \"SELECT id FROM memory_chunks WHERE project_id = ? AND path = ?\",\n )\n .all(projectId, relativePath) as Array<{ id: string }>;\n\n const deleteFts = db.prepare(\"DELETE FROM memory_fts WHERE id = ?\");\n const deleteChunk = db.prepare(\n \"DELETE FROM memory_chunks WHERE project_id = ? AND path = ?\",\n );\n\n db.transaction(() => {\n for (const row of oldChunkIds) {\n deleteFts.run(row.id);\n }\n deleteChunk.run(projectId, relativePath);\n })();\n\n // Chunk the new content\n const chunks = chunkMarkdown(content);\n\n // Insert new chunks into memory_chunks and memory_fts\n const insertChunk = db.prepare(`\n INSERT INTO memory_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n const insertFts = db.prepare(`\n INSERT INTO memory_fts (text, id, project_id, path, source, tier, start_line, end_line)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n const upsertFile = db.prepare(`\n INSERT INTO memory_files (project_id, path, source, tier, hash, mtime, size)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(project_id, path) DO UPDATE SET\n source = excluded.source,\n tier = excluded.tier,\n hash = excluded.hash,\n mtime = excluded.mtime,\n size = excluded.size\n `);\n\n const updatedAt = Date.now();\n\n db.transaction(() => {\n for (let i = 0; i < chunks.length; i++) {\n const chunk = chunks[i]!;\n const id = chunkId(projectId, relativePath, i, chunk.startLine, chunk.endLine);\n insertChunk.run(\n id,\n projectId,\n source,\n tier,\n relativePath,\n chunk.startLine,\n chunk.endLine,\n chunk.hash,\n chunk.text,\n updatedAt,\n );\n insertFts.run(\n chunk.text,\n id,\n projectId,\n relativePath,\n source,\n tier,\n chunk.startLine,\n chunk.endLine,\n );\n }\n upsertFile.run(projectId, relativePath, source, tier, hash, mtime, size);\n })();\n\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Project-level indexing\n// ---------------------------------------------------------------------------\n\n/**\n * Index all memory, Notes, and content files for a single registered project.\n *\n * Scans:\n * - {rootPath}/MEMORY.md → source='memory', tier='evergreen'\n * - {rootPath}/memory/ → source='memory', tier from detectTier()\n * - {rootPath}/Notes/ → source='notes', tier='session'\n * - {rootPath}/**\\/*.md → source='content', tier='topic' (all other .md files, recursive)\n * - {claudeNotesDir}/ → source='notes', tier='session' (if set and different)\n */\nexport async function indexProject(\n db: Database,\n projectId: number,\n rootPath: string,\n claudeNotesDir?: string | null,\n): Promise<IndexResult> {\n const result: IndexResult = {\n filesProcessed: 0,\n chunksCreated: 0,\n filesSkipped: 0,\n };\n\n const filesToIndex: Array<{ absPath: string; rootBase: string; source: string; tier: string }> = [];\n\n // Root-level MEMORY.md\n const rootMemoryMd = join(rootPath, \"MEMORY.md\");\n if (existsSync(rootMemoryMd)) {\n filesToIndex.push({ absPath: rootMemoryMd, rootBase: rootPath, source: \"memory\", tier: \"evergreen\" });\n }\n\n // memory/ directory\n const memoryDir = join(rootPath, \"memory\");\n for (const absPath of walkMdFiles(memoryDir)) {\n const relPath = relative(rootPath, absPath);\n const tier = detectTier(relPath);\n filesToIndex.push({ absPath, rootBase: rootPath, source: \"memory\", tier });\n }\n\n // {rootPath}/Notes/ directory\n const notesDir = join(rootPath, \"Notes\");\n for (const absPath of walkMdFiles(notesDir)) {\n filesToIndex.push({ absPath, rootBase: rootPath, source: \"notes\", tier: \"session\" });\n }\n\n // Synthetic session-title chunks for Notes files with the standard filename format:\n // \"NNNN - YYYY-MM-DD - Descriptive Title.md\"\n {\n const titleInsertChunk = db.prepare(`\n INSERT OR IGNORE INTO memory_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n const titleInsertFts = db.prepare(`\n INSERT OR IGNORE INTO memory_fts (text, id, project_id, path, source, tier, start_line, end_line)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n const updatedAt = Date.now();\n for (const absPath of walkMdFiles(notesDir)) {\n const fileName = basename(absPath);\n const text = parseSessionTitleChunk(fileName);\n if (!text) continue;\n const relPath = relative(rootPath, absPath);\n const syntheticPath = `${relPath}::title`;\n const id = chunkId(projectId, syntheticPath, 0, 0, 0);\n const hash = sha256File(text);\n db.transaction(() => {\n titleInsertChunk.run(id, projectId, \"notes\", \"session\", syntheticPath, 0, 0, hash, text, updatedAt);\n titleInsertFts.run(text, id, projectId, syntheticPath, \"notes\", \"session\", 0, 0);\n })();\n }\n }\n\n // {rootPath}/**/*.md — all other markdown content\n if (!isPathTooBroadForContentScan(rootPath)) {\n for (const absPath of walkContentFiles(rootPath)) {\n filesToIndex.push({ absPath, rootBase: rootPath, source: \"content\", tier: \"topic\" });\n }\n }\n\n // Claude Code session notes directory (~/.claude/projects/{encoded}/Notes/)\n if (claudeNotesDir && claudeNotesDir !== notesDir) {\n for (const absPath of walkMdFiles(claudeNotesDir)) {\n filesToIndex.push({ absPath, rootBase: claudeNotesDir, source: \"notes\", tier: \"session\" });\n }\n\n // Synthetic title chunks for claude notes dir\n {\n const updatedAt = Date.now();\n const titleInsertChunk2 = db.prepare(`\n INSERT OR IGNORE INTO memory_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n const titleInsertFts2 = db.prepare(`\n INSERT OR IGNORE INTO memory_fts (text, id, project_id, path, source, tier, start_line, end_line)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n for (const absPath of walkMdFiles(claudeNotesDir)) {\n const fileName = basename(absPath);\n const text = parseSessionTitleChunk(fileName);\n if (!text) continue;\n const relPath = relative(claudeNotesDir, absPath);\n const syntheticPath = `${relPath}::title`;\n const id = chunkId(projectId, syntheticPath, 0, 0, 0);\n const hash = sha256File(text);\n db.transaction(() => {\n titleInsertChunk2.run(id, projectId, \"notes\", \"session\", syntheticPath, 0, 0, hash, text, updatedAt);\n titleInsertFts2.run(text, id, projectId, syntheticPath, \"notes\", \"session\", 0, 0);\n })();\n }\n }\n\n // Derive the sibling memory/ directory: .../Notes/ → .../memory/\n if (claudeNotesDir.endsWith(\"/Notes\")) {\n const claudeProjectDir = claudeNotesDir.slice(0, -\"/Notes\".length);\n const claudeMemoryDir = join(claudeProjectDir, \"memory\");\n\n const claudeMemoryMd = join(claudeProjectDir, \"MEMORY.md\");\n if (existsSync(claudeMemoryMd)) {\n filesToIndex.push({\n absPath: claudeMemoryMd,\n rootBase: claudeProjectDir,\n source: \"memory\",\n tier: \"evergreen\",\n });\n }\n\n for (const absPath of walkMdFiles(claudeMemoryDir)) {\n const relPath = relative(claudeProjectDir, absPath);\n const tier = detectTier(relPath);\n filesToIndex.push({ absPath, rootBase: claudeProjectDir, source: \"memory\", tier });\n }\n }\n }\n\n // Yield after collection phase before processing\n await yieldToEventLoop();\n\n let filesSinceYield = 0;\n\n for (const { absPath, rootBase, source, tier } of filesToIndex) {\n if (filesSinceYield >= INDEX_YIELD_EVERY) {\n await yieldToEventLoop();\n filesSinceYield = 0;\n }\n filesSinceYield++;\n\n const relPath = relative(rootBase, absPath);\n const changed = indexFile(db, projectId, rootBase, relPath, source, tier);\n\n if (changed) {\n const count = db\n .prepare(\n \"SELECT COUNT(*) as n FROM memory_chunks WHERE project_id = ? AND path = ?\",\n )\n .get(projectId, relPath) as { n: number };\n\n result.filesProcessed++;\n result.chunksCreated += count.n;\n } else {\n result.filesSkipped++;\n }\n }\n\n // Prune stale paths: remove DB entries for files that no longer exist on disk.\n const livePaths = new Set<string>();\n for (const { absPath, rootBase } of filesToIndex) {\n livePaths.add(relative(rootBase, absPath));\n }\n\n const dbChunkPaths = db\n .prepare(\"SELECT DISTINCT path FROM memory_chunks WHERE project_id = ?\")\n .all(projectId) as Array<{ path: string }>;\n\n const stalePaths: string[] = [];\n for (const row of dbChunkPaths) {\n const basePath = row.path.endsWith(\"::title\")\n ? row.path.slice(0, -\"::title\".length)\n : row.path;\n if (!livePaths.has(basePath)) {\n stalePaths.push(row.path);\n }\n }\n\n if (stalePaths.length > 0) {\n const deleteChunksFts = db.prepare(\"DELETE FROM memory_fts WHERE id = ?\");\n const deleteChunks = db.prepare(\n \"DELETE FROM memory_chunks WHERE project_id = ? AND path = ?\",\n );\n const deleteFile = db.prepare(\n \"DELETE FROM memory_files WHERE project_id = ? AND path = ?\",\n );\n\n db.transaction(() => {\n for (const stalePath of stalePaths) {\n const chunkIds = db\n .prepare(\"SELECT id FROM memory_chunks WHERE project_id = ? AND path = ?\")\n .all(projectId, stalePath) as Array<{ id: string }>;\n for (const { id } of chunkIds) {\n deleteChunksFts.run(id);\n }\n deleteChunks.run(projectId, stalePath);\n deleteFile.run(projectId, stalePath);\n }\n })();\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Global indexing (all registered projects)\n// ---------------------------------------------------------------------------\n\n/**\n * Index all active projects registered in the registry DB.\n *\n * Async: yields to the event loop between each project so that the daemon's\n * Unix socket server can process IPC requests (e.g. status) while indexing.\n */\nexport async function indexAll(\n db: Database,\n registryDb: Database,\n): Promise<{ projects: number; result: IndexResult }> {\n const projects = registryDb\n .prepare(\"SELECT id, root_path, claude_notes_dir FROM projects WHERE status = 'active'\")\n .all() as Array<{ id: number; root_path: string; claude_notes_dir: string | null }>;\n\n const totals: IndexResult = {\n filesProcessed: 0,\n chunksCreated: 0,\n filesSkipped: 0,\n };\n\n for (const project of projects) {\n await yieldToEventLoop();\n const r = await indexProject(db, project.id, project.root_path, project.claude_notes_dir);\n totals.filesProcessed += r.filesProcessed;\n totals.chunksCreated += r.chunksCreated;\n totals.filesSkipped += r.filesSkipped;\n }\n\n return { projects: projects.length, result: totals };\n}\n\n// ---------------------------------------------------------------------------\n// Embedding generation\n// ---------------------------------------------------------------------------\n\n/**\n * Generate and store embeddings for chunks that do not yet have one.\n *\n * Because better-sqlite3 is synchronous but the embedding pipeline is async,\n * we fetch all unembedded chunk texts first, generate embeddings in batches,\n * and then write them back in a transaction.\n *\n * @param db Open federation database.\n * @param projectId Optional — restrict to a specific project.\n * @param batchSize Number of chunks to embed per round. Default 50.\n * @param onProgress Optional callback called after each batch with running totals.\n */\nexport async function embedChunks(\n db: Database,\n projectId?: number,\n batchSize = 50,\n onProgress?: (embedded: number, total: number) => void,\n): Promise<EmbedResult> {\n // Dynamic import — keeps the heavy ML runtime out of the module load path\n const { generateEmbedding, serializeEmbedding } = await import(\"../embeddings.js\");\n\n const conditions = [\"embedding IS NULL\"];\n const params: (string | number)[] = [];\n\n if (projectId !== undefined) {\n conditions.push(\"project_id = ?\");\n params.push(projectId);\n }\n\n const where = \"WHERE \" + conditions.join(\" AND \");\n\n const rows = db\n .prepare(`SELECT id, text FROM memory_chunks ${where} ORDER BY id`)\n .all(...params) as Array<{ id: string; text: string }>;\n\n if (rows.length === 0) {\n return { chunksEmbedded: 0, chunksSkipped: 0 };\n }\n\n const updateStmt = db.prepare(\n \"UPDATE memory_chunks SET embedding = ? WHERE id = ?\",\n );\n\n let embedded = 0;\n const total = rows.length;\n\n for (let i = 0; i < rows.length; i += batchSize) {\n const batch = rows.slice(i, i + batchSize);\n\n // Generate embeddings for the batch (async — must happen OUTSIDE transaction)\n const embeddings: Array<{ id: string; blob: Buffer }> = [];\n for (const row of batch) {\n const vec = await generateEmbedding(row.text);\n const blob = serializeEmbedding(vec);\n embeddings.push({ id: row.id, blob });\n }\n\n // Write the batch in a single transaction\n db.transaction(() => {\n for (const { id, blob } of embeddings) {\n updateStmt.run(blob, id);\n }\n })();\n\n embedded += embeddings.length;\n onProgress?.(embedded, total);\n }\n\n return { chunksEmbedded: embedded, chunksSkipped: 0 };\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as cosineSimilarity, r as deserializeEmbedding } from "./embeddings-
|
|
2
|
-
import { t as STOP_WORDS } from "./stop-words-
|
|
1
|
+
import { n as cosineSimilarity, r as deserializeEmbedding } from "./embeddings-DOLZnT1X.mjs";
|
|
2
|
+
import { t as STOP_WORDS } from "./stop-words-Hfu8u22w.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/zettelkasten/themes.ts
|
|
5
5
|
const MAX_CHUNKS = 5e3;
|
|
@@ -145,4 +145,4 @@ async function zettelThemes(backend, opts) {
|
|
|
145
145
|
|
|
146
146
|
//#endregion
|
|
147
147
|
export { zettelThemes as t };
|
|
148
|
-
//# sourceMappingURL=themes-
|
|
148
|
+
//# sourceMappingURL=themes-XPkj_bfP.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"themes-BObEGMWn.mjs","names":[],"sources":["../src/zettelkasten/themes.ts"],"sourcesContent":["import type { StorageBackend } from \"../storage/interface.js\";\nimport { deserializeEmbedding, cosineSimilarity } from \"../memory/embeddings.js\";\nimport { STOP_WORDS } from \"../utils/stop-words.js\";\n\nexport interface ThemeOptions {\n vaultProjectId: number;\n lookbackDays?: number;\n minClusterSize?: number;\n maxThemes?: number;\n similarityThreshold?: number;\n}\n\nexport interface ThemeCluster {\n id: number;\n label: string;\n notes: Array<{\n path: string;\n title: string | null;\n }>;\n size: number;\n folderDiversity: number;\n avgRecency: number;\n linkedRatio: number;\n suggestIndexNote: boolean;\n}\n\nexport interface ThemeResult {\n themes: ThemeCluster[];\n totalNotesAnalyzed: number;\n timeWindow: { from: number; to: number };\n}\n\nconst MAX_CHUNKS = 5000;\n\n// STOP_WORDS imported from utils/stop-words.ts\n\nfunction getTopFolder(vaultPath: string): string {\n const parts = vaultPath.split(\"/\");\n return parts.length > 1 ? parts[0] : \"\";\n}\n\nfunction generateLabel(titles: Array<string | null>): string {\n const wordCounts = new Map<string, number>();\n for (const title of titles) {\n if (!title) continue;\n const words = title\n .toLowerCase()\n .replace(/[^a-z0-9\\s]/g, \" \")\n .split(/\\s+/)\n .filter((w) => w.length > 2 && !STOP_WORDS.has(w));\n for (const word of words) {\n wordCounts.set(word, (wordCounts.get(word) ?? 0) + 1);\n }\n }\n const sorted = [...wordCounts.entries()].sort((a, b) => b[1] - a[1]);\n return sorted\n .slice(0, 3)\n .map(([w]) => w)\n .join(\" / \");\n}\n\nasync function computeLinkedRatio(backend: StorageBackend, paths: string[]): Promise<number> {\n if (paths.length < 2) return 0;\n const totalPairs = (paths.length * (paths.length - 1)) / 2;\n const pathSet = new Set(paths);\n let linkedPairs = 0;\n\n for (const path of paths) {\n const links = await backend.getLinksFromSource(path);\n for (const link of links) {\n if (link.targetPath && pathSet.has(link.targetPath)) {\n linkedPairs++;\n }\n }\n }\n\n // Each bidirectional pair might be counted once per direction; divide by 2 to normalize\n const uniquePairs = linkedPairs / 2;\n return Math.min(1, uniquePairs / totalPairs);\n}\n\ntype ClusterNode = {\n paths: string[];\n titles: Array<string | null>;\n indexedAts: number[];\n centroid: Float32Array;\n};\n\nfunction averageEmbeddings(embeddings: Float32Array[]): Float32Array {\n if (embeddings.length === 0) return new Float32Array(0);\n const dim = embeddings[0].length;\n const sum = new Float32Array(dim);\n for (const vec of embeddings) {\n for (let i = 0; i < dim; i++) {\n sum[i] += vec[i];\n }\n }\n const avg = new Float32Array(dim);\n for (let i = 0; i < dim; i++) {\n avg[i] = sum[i] / embeddings.length;\n }\n return avg;\n}\n\n/**\n * Detect emerging themes in recently-modified notes using agglomerative single-linkage\n * clustering of note-level embeddings.\n */\nexport async function zettelThemes(\n backend: StorageBackend,\n opts: ThemeOptions,\n): Promise<ThemeResult> {\n const lookbackDays = opts.lookbackDays ?? 30;\n const minClusterSize = opts.minClusterSize ?? 3;\n const maxThemes = opts.maxThemes ?? 10;\n const similarityThreshold = opts.similarityThreshold ?? 0.65;\n\n const now = Date.now();\n const from = now - lookbackDays * 86400000;\n\n // Step 1: get recent notes\n const recentFiles = await backend.getRecentVaultFiles(from);\n const recentNotes = recentFiles.map(f => ({ vault_path: f.vaultPath, title: f.title, indexed_at: f.indexedAt }));\n\n // Step 2: get file-level embeddings from memory_chunks\n const chunkRows = await backend.getChunksWithEmbeddings(opts.vaultProjectId, MAX_CHUNKS);\n\n const embeddingsByPath = new Map<string, Float32Array[]>();\n for (const row of chunkRows) {\n const vec = deserializeEmbedding(row.embedding);\n const arr = embeddingsByPath.get(row.path);\n if (!arr) {\n embeddingsByPath.set(row.path, [vec]);\n } else {\n arr.push(vec);\n }\n }\n\n const fileEmbeddings = new Map<string, Float32Array>();\n for (const [path, vecs] of embeddingsByPath) {\n fileEmbeddings.set(path, averageEmbeddings(vecs));\n }\n\n // Step 3: build initial clusters — only include notes that have embeddings\n const clusters: ClusterNode[] = [];\n for (const note of recentNotes) {\n const embedding = fileEmbeddings.get(note.vault_path);\n if (!embedding) continue;\n clusters.push({\n paths: [note.vault_path],\n titles: [note.title],\n indexedAts: [note.indexed_at],\n centroid: embedding,\n });\n }\n\n const totalNotesAnalyzed = clusters.length;\n\n // Step 4: agglomerative single-linkage clustering\n // Stop when no two clusters have similarity >= threshold\n let merged = true;\n while (merged && clusters.length > 1) {\n merged = false;\n let bestSim = similarityThreshold;\n let bestI = -1;\n let bestJ = -1;\n\n for (let i = 0; i < clusters.length; i++) {\n for (let j = i + 1; j < clusters.length; j++) {\n const sim = cosineSimilarity(clusters[i].centroid, clusters[j].centroid);\n if (sim > bestSim) {\n bestSim = sim;\n bestI = i;\n bestJ = j;\n }\n }\n }\n\n if (bestI === -1) break;\n\n // Merge cluster j into cluster i\n const ci = clusters[bestI];\n const cj = clusters[bestJ];\n const mergedPaths = [...ci.paths, ...cj.paths];\n const mergedTitles = [...ci.titles, ...cj.titles];\n const mergedIndexedAts = [...ci.indexedAts, ...cj.indexedAts];\n\n // Recompute centroid from averaged embeddings of all member paths\n const memberEmbeddings: Float32Array[] = [];\n for (const p of mergedPaths) {\n const emb = fileEmbeddings.get(p);\n if (emb) memberEmbeddings.push(emb);\n }\n\n clusters[bestI] = {\n paths: mergedPaths,\n titles: mergedTitles,\n indexedAts: mergedIndexedAts,\n centroid: averageEmbeddings(memberEmbeddings),\n };\n\n clusters.splice(bestJ, 1);\n merged = true;\n }\n\n // Step 5: filter and annotate clusters\n const themes: ThemeCluster[] = [];\n let clusterIndex = 0;\n\n for (const cluster of clusters) {\n if (cluster.paths.length < minClusterSize) continue;\n\n const label = generateLabel(cluster.titles) || `Theme ${clusterIndex + 1}`;\n const avgRecency =\n cluster.indexedAts.reduce((sum, t) => sum + t, 0) / cluster.indexedAts.length;\n\n const uniqueFolders = new Set(cluster.paths.map(getTopFolder));\n const folderDiversity = uniqueFolders.size / cluster.paths.length;\n\n const linkedRatio = await computeLinkedRatio(backend, cluster.paths);\n const suggestIndexNote = linkedRatio < 0.3 && cluster.paths.length >= 5;\n\n themes.push({\n id: clusterIndex++,\n label,\n notes: cluster.paths.map((path, idx) => ({\n path,\n title: cluster.titles[idx],\n })),\n size: cluster.paths.length,\n folderDiversity,\n avgRecency,\n linkedRatio,\n suggestIndexNote,\n });\n }\n\n // Step 6: rank by size * folderDiversity * recency_ratio\n themes.sort(\n (a, b) =>\n b.size * b.folderDiversity * (b.avgRecency / now) -\n a.size * a.folderDiversity * (a.avgRecency / now),\n );\n\n return {\n themes: themes.slice(0, maxThemes),\n totalNotesAnalyzed,\n timeWindow: { from, to: now },\n };\n}\n"],"mappings":";;;;AAgCA,MAAM,aAAa;AAInB,SAAS,aAAa,WAA2B;CAC/C,MAAM,QAAQ,UAAU,MAAM,IAAI;AAClC,QAAO,MAAM,SAAS,IAAI,MAAM,KAAK;;AAGvC,SAAS,cAAc,QAAsC;CAC3D,MAAM,6BAAa,IAAI,KAAqB;AAC5C,MAAK,MAAM,SAAS,QAAQ;AAC1B,MAAI,CAAC,MAAO;EACZ,MAAM,QAAQ,MACX,aAAa,CACb,QAAQ,gBAAgB,IAAI,CAC5B,MAAM,MAAM,CACZ,QAAQ,MAAM,EAAE,SAAS,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC;AACpD,OAAK,MAAM,QAAQ,MACjB,YAAW,IAAI,OAAO,WAAW,IAAI,KAAK,IAAI,KAAK,EAAE;;AAIzD,QADe,CAAC,GAAG,WAAW,SAAS,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,GAAG,CAEjE,MAAM,GAAG,EAAE,CACX,KAAK,CAAC,OAAO,EAAE,CACf,KAAK,MAAM;;AAGhB,eAAe,mBAAmB,SAAyB,OAAkC;AAC3F,KAAI,MAAM,SAAS,EAAG,QAAO;CAC7B,MAAM,aAAc,MAAM,UAAU,MAAM,SAAS,KAAM;CACzD,MAAM,UAAU,IAAI,IAAI,MAAM;CAC9B,IAAI,cAAc;AAElB,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,QAAQ,MAAM,QAAQ,mBAAmB,KAAK;AACpD,OAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,cAAc,QAAQ,IAAI,KAAK,WAAW,CACjD;;CAMN,MAAM,cAAc,cAAc;AAClC,QAAO,KAAK,IAAI,GAAG,cAAc,WAAW;;AAU9C,SAAS,kBAAkB,YAA0C;AACnE,KAAI,WAAW,WAAW,EAAG,QAAO,IAAI,aAAa,EAAE;CACvD,MAAM,MAAM,WAAW,GAAG;CAC1B,MAAM,MAAM,IAAI,aAAa,IAAI;AACjC,MAAK,MAAM,OAAO,WAChB,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,IACvB,KAAI,MAAM,IAAI;CAGlB,MAAM,MAAM,IAAI,aAAa,IAAI;AACjC,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,IACvB,KAAI,KAAK,IAAI,KAAK,WAAW;AAE/B,QAAO;;;;;;AAOT,eAAsB,aACpB,SACA,MACsB;CACtB,MAAM,eAAe,KAAK,gBAAgB;CAC1C,MAAM,iBAAiB,KAAK,kBAAkB;CAC9C,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,sBAAsB,KAAK,uBAAuB;CAExD,MAAM,MAAM,KAAK,KAAK;CACtB,MAAM,OAAO,MAAM,eAAe;CAIlC,MAAM,eADc,MAAM,QAAQ,oBAAoB,KAAK,EAC3B,KAAI,OAAM;EAAE,YAAY,EAAE;EAAW,OAAO,EAAE;EAAO,YAAY,EAAE;EAAW,EAAE;CAGhH,MAAM,YAAY,MAAM,QAAQ,wBAAwB,KAAK,gBAAgB,WAAW;CAExF,MAAM,mCAAmB,IAAI,KAA6B;AAC1D,MAAK,MAAM,OAAO,WAAW;EAC3B,MAAM,MAAM,qBAAqB,IAAI,UAAU;EAC/C,MAAM,MAAM,iBAAiB,IAAI,IAAI,KAAK;AAC1C,MAAI,CAAC,IACH,kBAAiB,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC;MAErC,KAAI,KAAK,IAAI;;CAIjB,MAAM,iCAAiB,IAAI,KAA2B;AACtD,MAAK,MAAM,CAAC,MAAM,SAAS,iBACzB,gBAAe,IAAI,MAAM,kBAAkB,KAAK,CAAC;CAInD,MAAM,WAA0B,EAAE;AAClC,MAAK,MAAM,QAAQ,aAAa;EAC9B,MAAM,YAAY,eAAe,IAAI,KAAK,WAAW;AACrD,MAAI,CAAC,UAAW;AAChB,WAAS,KAAK;GACZ,OAAO,CAAC,KAAK,WAAW;GACxB,QAAQ,CAAC,KAAK,MAAM;GACpB,YAAY,CAAC,KAAK,WAAW;GAC7B,UAAU;GACX,CAAC;;CAGJ,MAAM,qBAAqB,SAAS;CAIpC,IAAI,SAAS;AACb,QAAO,UAAU,SAAS,SAAS,GAAG;AACpC,WAAS;EACT,IAAI,UAAU;EACd,IAAI,QAAQ;EACZ,IAAI,QAAQ;AAEZ,OAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,IACnC,MAAK,IAAI,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;GAC5C,MAAM,MAAM,iBAAiB,SAAS,GAAG,UAAU,SAAS,GAAG,SAAS;AACxE,OAAI,MAAM,SAAS;AACjB,cAAU;AACV,YAAQ;AACR,YAAQ;;;AAKd,MAAI,UAAU,GAAI;EAGlB,MAAM,KAAK,SAAS;EACpB,MAAM,KAAK,SAAS;EACpB,MAAM,cAAc,CAAC,GAAG,GAAG,OAAO,GAAG,GAAG,MAAM;EAC9C,MAAM,eAAe,CAAC,GAAG,GAAG,QAAQ,GAAG,GAAG,OAAO;EACjD,MAAM,mBAAmB,CAAC,GAAG,GAAG,YAAY,GAAG,GAAG,WAAW;EAG7D,MAAM,mBAAmC,EAAE;AAC3C,OAAK,MAAM,KAAK,aAAa;GAC3B,MAAM,MAAM,eAAe,IAAI,EAAE;AACjC,OAAI,IAAK,kBAAiB,KAAK,IAAI;;AAGrC,WAAS,SAAS;GAChB,OAAO;GACP,QAAQ;GACR,YAAY;GACZ,UAAU,kBAAkB,iBAAiB;GAC9C;AAED,WAAS,OAAO,OAAO,EAAE;AACzB,WAAS;;CAIX,MAAM,SAAyB,EAAE;CACjC,IAAI,eAAe;AAEnB,MAAK,MAAM,WAAW,UAAU;AAC9B,MAAI,QAAQ,MAAM,SAAS,eAAgB;EAE3C,MAAM,QAAQ,cAAc,QAAQ,OAAO,IAAI,SAAS,eAAe;EACvE,MAAM,aACJ,QAAQ,WAAW,QAAQ,KAAK,MAAM,MAAM,GAAG,EAAE,GAAG,QAAQ,WAAW;EAGzE,MAAM,kBADgB,IAAI,IAAI,QAAQ,MAAM,IAAI,aAAa,CAAC,CACxB,OAAO,QAAQ,MAAM;EAE3D,MAAM,cAAc,MAAM,mBAAmB,SAAS,QAAQ,MAAM;EACpE,MAAM,mBAAmB,cAAc,MAAO,QAAQ,MAAM,UAAU;AAEtE,SAAO,KAAK;GACV,IAAI;GACJ;GACA,OAAO,QAAQ,MAAM,KAAK,MAAM,SAAS;IACvC;IACA,OAAO,QAAQ,OAAO;IACvB,EAAE;GACH,MAAM,QAAQ,MAAM;GACpB;GACA;GACA;GACA;GACD,CAAC;;AAIJ,QAAO,MACJ,GAAG,MACF,EAAE,OAAO,EAAE,mBAAmB,EAAE,aAAa,OAC7C,EAAE,OAAO,EAAE,mBAAmB,EAAE,aAAa,KAChD;AAED,QAAO;EACL,QAAQ,OAAO,MAAM,GAAG,UAAU;EAClC;EACA,YAAY;GAAE;GAAM,IAAI;GAAK;EAC9B"}
|
|
1
|
+
{"version":3,"file":"themes-XPkj_bfP.mjs","names":[],"sources":["../src/zettelkasten/themes.ts"],"sourcesContent":["import type { StorageBackend } from \"../storage/interface.js\";\nimport { deserializeEmbedding, cosineSimilarity } from \"../memory/embeddings.js\";\nimport { STOP_WORDS } from \"../utils/stop-words.js\";\n\nexport interface ThemeOptions {\n vaultProjectId: number;\n lookbackDays?: number;\n minClusterSize?: number;\n maxThemes?: number;\n similarityThreshold?: number;\n}\n\nexport interface ThemeCluster {\n id: number;\n label: string;\n notes: Array<{\n path: string;\n title: string | null;\n }>;\n size: number;\n folderDiversity: number;\n avgRecency: number;\n linkedRatio: number;\n suggestIndexNote: boolean;\n}\n\nexport interface ThemeResult {\n themes: ThemeCluster[];\n totalNotesAnalyzed: number;\n timeWindow: { from: number; to: number };\n}\n\nconst MAX_CHUNKS = 5000;\n\n// STOP_WORDS imported from utils/stop-words.ts\n\nfunction getTopFolder(vaultPath: string): string {\n const parts = vaultPath.split(\"/\");\n return parts.length > 1 ? parts[0] : \"\";\n}\n\nfunction generateLabel(titles: Array<string | null>): string {\n const wordCounts = new Map<string, number>();\n for (const title of titles) {\n if (!title) continue;\n const words = title\n .toLowerCase()\n .replace(/[^a-z0-9\\s]/g, \" \")\n .split(/\\s+/)\n .filter((w) => w.length > 2 && !STOP_WORDS.has(w));\n for (const word of words) {\n wordCounts.set(word, (wordCounts.get(word) ?? 0) + 1);\n }\n }\n const sorted = [...wordCounts.entries()].sort((a, b) => b[1] - a[1]);\n return sorted\n .slice(0, 3)\n .map(([w]) => w)\n .join(\" / \");\n}\n\nasync function computeLinkedRatio(backend: StorageBackend, paths: string[]): Promise<number> {\n if (paths.length < 2) return 0;\n const totalPairs = (paths.length * (paths.length - 1)) / 2;\n const pathSet = new Set(paths);\n let linkedPairs = 0;\n\n for (const path of paths) {\n const links = await backend.getLinksFromSource(path);\n for (const link of links) {\n if (link.targetPath && pathSet.has(link.targetPath)) {\n linkedPairs++;\n }\n }\n }\n\n // Each bidirectional pair might be counted once per direction; divide by 2 to normalize\n const uniquePairs = linkedPairs / 2;\n return Math.min(1, uniquePairs / totalPairs);\n}\n\ntype ClusterNode = {\n paths: string[];\n titles: Array<string | null>;\n indexedAts: number[];\n centroid: Float32Array;\n};\n\nfunction averageEmbeddings(embeddings: Float32Array[]): Float32Array {\n if (embeddings.length === 0) return new Float32Array(0);\n const dim = embeddings[0].length;\n const sum = new Float32Array(dim);\n for (const vec of embeddings) {\n for (let i = 0; i < dim; i++) {\n sum[i] += vec[i];\n }\n }\n const avg = new Float32Array(dim);\n for (let i = 0; i < dim; i++) {\n avg[i] = sum[i] / embeddings.length;\n }\n return avg;\n}\n\n/**\n * Detect emerging themes in recently-modified notes using agglomerative single-linkage\n * clustering of note-level embeddings.\n */\nexport async function zettelThemes(\n backend: StorageBackend,\n opts: ThemeOptions,\n): Promise<ThemeResult> {\n const lookbackDays = opts.lookbackDays ?? 30;\n const minClusterSize = opts.minClusterSize ?? 3;\n const maxThemes = opts.maxThemes ?? 10;\n const similarityThreshold = opts.similarityThreshold ?? 0.65;\n\n const now = Date.now();\n const from = now - lookbackDays * 86400000;\n\n // Step 1: get recent notes\n const recentFiles = await backend.getRecentVaultFiles(from);\n const recentNotes = recentFiles.map(f => ({ vault_path: f.vaultPath, title: f.title, indexed_at: f.indexedAt }));\n\n // Step 2: get file-level embeddings from memory_chunks\n const chunkRows = await backend.getChunksWithEmbeddings(opts.vaultProjectId, MAX_CHUNKS);\n\n const embeddingsByPath = new Map<string, Float32Array[]>();\n for (const row of chunkRows) {\n const vec = deserializeEmbedding(row.embedding);\n const arr = embeddingsByPath.get(row.path);\n if (!arr) {\n embeddingsByPath.set(row.path, [vec]);\n } else {\n arr.push(vec);\n }\n }\n\n const fileEmbeddings = new Map<string, Float32Array>();\n for (const [path, vecs] of embeddingsByPath) {\n fileEmbeddings.set(path, averageEmbeddings(vecs));\n }\n\n // Step 3: build initial clusters — only include notes that have embeddings\n const clusters: ClusterNode[] = [];\n for (const note of recentNotes) {\n const embedding = fileEmbeddings.get(note.vault_path);\n if (!embedding) continue;\n clusters.push({\n paths: [note.vault_path],\n titles: [note.title],\n indexedAts: [note.indexed_at],\n centroid: embedding,\n });\n }\n\n const totalNotesAnalyzed = clusters.length;\n\n // Step 4: agglomerative single-linkage clustering\n // Stop when no two clusters have similarity >= threshold\n let merged = true;\n while (merged && clusters.length > 1) {\n merged = false;\n let bestSim = similarityThreshold;\n let bestI = -1;\n let bestJ = -1;\n\n for (let i = 0; i < clusters.length; i++) {\n for (let j = i + 1; j < clusters.length; j++) {\n const sim = cosineSimilarity(clusters[i].centroid, clusters[j].centroid);\n if (sim > bestSim) {\n bestSim = sim;\n bestI = i;\n bestJ = j;\n }\n }\n }\n\n if (bestI === -1) break;\n\n // Merge cluster j into cluster i\n const ci = clusters[bestI];\n const cj = clusters[bestJ];\n const mergedPaths = [...ci.paths, ...cj.paths];\n const mergedTitles = [...ci.titles, ...cj.titles];\n const mergedIndexedAts = [...ci.indexedAts, ...cj.indexedAts];\n\n // Recompute centroid from averaged embeddings of all member paths\n const memberEmbeddings: Float32Array[] = [];\n for (const p of mergedPaths) {\n const emb = fileEmbeddings.get(p);\n if (emb) memberEmbeddings.push(emb);\n }\n\n clusters[bestI] = {\n paths: mergedPaths,\n titles: mergedTitles,\n indexedAts: mergedIndexedAts,\n centroid: averageEmbeddings(memberEmbeddings),\n };\n\n clusters.splice(bestJ, 1);\n merged = true;\n }\n\n // Step 5: filter and annotate clusters\n const themes: ThemeCluster[] = [];\n let clusterIndex = 0;\n\n for (const cluster of clusters) {\n if (cluster.paths.length < minClusterSize) continue;\n\n const label = generateLabel(cluster.titles) || `Theme ${clusterIndex + 1}`;\n const avgRecency =\n cluster.indexedAts.reduce((sum, t) => sum + t, 0) / cluster.indexedAts.length;\n\n const uniqueFolders = new Set(cluster.paths.map(getTopFolder));\n const folderDiversity = uniqueFolders.size / cluster.paths.length;\n\n const linkedRatio = await computeLinkedRatio(backend, cluster.paths);\n const suggestIndexNote = linkedRatio < 0.3 && cluster.paths.length >= 5;\n\n themes.push({\n id: clusterIndex++,\n label,\n notes: cluster.paths.map((path, idx) => ({\n path,\n title: cluster.titles[idx],\n })),\n size: cluster.paths.length,\n folderDiversity,\n avgRecency,\n linkedRatio,\n suggestIndexNote,\n });\n }\n\n // Step 6: rank by size * folderDiversity * recency_ratio\n themes.sort(\n (a, b) =>\n b.size * b.folderDiversity * (b.avgRecency / now) -\n a.size * a.folderDiversity * (a.avgRecency / now),\n );\n\n return {\n themes: themes.slice(0, maxThemes),\n totalNotesAnalyzed,\n timeWindow: { from, to: now },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import "./embeddings-DOLZnT1X.mjs";
|
|
2
|
+
import "./search-Rpk1cSBC.mjs";
|
|
3
|
+
import { A as formatProject, C as toolProjectInfo, D as toolMemoryGet, E as combineHybridResults, O as toolMemorySearch, S as toolProjectHealth, T as toolProjectTodo, _ as toolZettelThemes, a as toolMemoryWakeup, b as toolSessionRoute, c as toolKgInvalidate, d as toolZettelConverse, f as toolZettelExplore, g as toolZettelSurprise, h as toolZettelSuggest, i as toolMemoryTaxonomy, j as lookupProjectId, k as detectProjectFromPath, l as toolKgQuery, m as toolZettelHealth, n as toolMemoryFeedback, o as toolKgAdd, p as toolZettelGodNotes, r as toolMemoryTunnels, s as toolKgContradictions, t as toolMemoryKgSearch, u as toolZettelCommunities, v as toolRegistrySearch, w as toolProjectList, x as toolProjectDetect, y as toolSessionList } from "./tools-ceiy7ANX.mjs";
|
|
4
|
+
|
|
5
|
+
export { toolKgAdd, toolKgContradictions, toolKgInvalidate, toolKgQuery, toolMemoryTunnels, toolZettelCommunities, toolZettelConverse, toolZettelExplore, toolZettelGodNotes, toolZettelHealth, toolZettelSuggest, toolZettelSurprise, toolZettelThemes };
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { a as kgContradictions, i as kgAdd, n as updateEntityFeedbackWeight, o as kgInvalidate, s as kgQuery, t as listKgEntities } from "./kg-entity-r8duqhi9.mjs";
|
|
1
|
+
import { n as cosineSimilarity } from "./embeddings-DOLZnT1X.mjs";
|
|
2
|
+
import { t as STOP_WORDS } from "./stop-words-Hfu8u22w.mjs";
|
|
3
|
+
import { c as touchChunksLastAccessed, i as populateSlugs, o as searchMemoryHybrid } from "./search-Rpk1cSBC.mjs";
|
|
4
|
+
import { r as formatDetectionJson, t as detectProject } from "./detect-CdaA48EI.mjs";
|
|
5
|
+
import { a as kgContradictions, i as kgAdd, n as updateEntityFeedbackWeight, o as kgInvalidate, s as kgQuery, t as listKgEntities } from "./kg-entity-DbOMPdF9.mjs";
|
|
7
6
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
8
7
|
import { homedir } from "node:os";
|
|
9
8
|
import { basename, isAbsolute, join, resolve } from "node:path";
|
|
@@ -80,7 +79,7 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
|
|
|
80
79
|
const isBackend = (x) => "backendType" in x;
|
|
81
80
|
if (isBackend(federation)) if (mode === "keyword") results = await federation.searchKeyword(params.query, searchOpts);
|
|
82
81
|
else if (mode === "semantic" || mode === "hybrid") {
|
|
83
|
-
const { generateEmbedding } = await import("./embeddings-
|
|
82
|
+
const { generateEmbedding } = await import("./embeddings-CEBGrzwu.mjs");
|
|
84
83
|
const queryEmbedding = await generateEmbedding(params.query, true);
|
|
85
84
|
if (mode === "semantic") results = await federation.searchSemantic(queryEmbedding, searchOpts);
|
|
86
85
|
else {
|
|
@@ -95,17 +94,17 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
|
|
|
95
94
|
}
|
|
96
95
|
} else results = await federation.searchKeyword(params.query, searchOpts);
|
|
97
96
|
else {
|
|
98
|
-
const { searchMemory, searchMemorySemantic } = await import("./search-
|
|
97
|
+
const { searchMemory, searchMemorySemantic } = await import("./search-CfPpJAWQ.mjs");
|
|
99
98
|
if (mode === "keyword") results = searchMemory(federation, params.query, searchOpts);
|
|
100
99
|
else if (mode === "semantic" || mode === "hybrid") {
|
|
101
|
-
const { generateEmbedding } = await import("./embeddings-
|
|
100
|
+
const { generateEmbedding } = await import("./embeddings-CEBGrzwu.mjs");
|
|
102
101
|
const queryEmbedding = await generateEmbedding(params.query, true);
|
|
103
102
|
if (mode === "semantic") results = searchMemorySemantic(federation, queryEmbedding, searchOpts);
|
|
104
103
|
else results = searchMemoryHybrid(federation, params.query, queryEmbedding, searchOpts);
|
|
105
104
|
} else results = searchMemory(federation, params.query, searchOpts);
|
|
106
105
|
}
|
|
107
106
|
try {
|
|
108
|
-
const { applyLinkBoost } = await import("./link-boost-
|
|
107
|
+
const { applyLinkBoost } = await import("./link-boost-fYjUnxCN.mjs");
|
|
109
108
|
const paths = [...new Set(results.map((r) => r.path))];
|
|
110
109
|
if (isBackend(federation) && paths.length > 1) {
|
|
111
110
|
const edges = [];
|
|
@@ -128,12 +127,12 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
|
|
|
128
127
|
} catch {}
|
|
129
128
|
const shouldRerank = params.rerank ?? searchDefaults?.rerank ?? true;
|
|
130
129
|
if (shouldRerank && results.length > 0) {
|
|
131
|
-
const { rerankResults } = await import("./reranker-
|
|
130
|
+
const { rerankResults } = await import("./reranker-CwTCNsgA.mjs");
|
|
132
131
|
results = await rerankResults(params.query, results, { topK: searchOpts.maxResults ?? 5 });
|
|
133
132
|
}
|
|
134
133
|
const recencyDays = params.recencyBoost ?? searchDefaults?.recencyBoostDays ?? 0;
|
|
135
134
|
if (recencyDays > 0 && results.length > 0) {
|
|
136
|
-
const { applyRecencyBoost } = await import("./search-
|
|
135
|
+
const { applyRecencyBoost } = await import("./search-CfPpJAWQ.mjs");
|
|
137
136
|
results = applyRecencyBoost(results, recencyDays);
|
|
138
137
|
}
|
|
139
138
|
const withSlugs = populateSlugs(results, registryDb);
|
|
@@ -152,7 +151,7 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
|
|
|
152
151
|
return `${header}\n${raw.length > snippetLength ? raw.slice(0, snippetLength) + "..." : raw}`;
|
|
153
152
|
}).join(useCompact ? "\n" : "\n\n---\n\n");
|
|
154
153
|
try {
|
|
155
|
-
const { saveQueryResult } = await import("./query-feedback-
|
|
154
|
+
const { saveQueryResult } = await import("./query-feedback-BBMBp96K.mjs");
|
|
156
155
|
saveQueryResult({
|
|
157
156
|
query: params.query,
|
|
158
157
|
timestamp: Date.now(),
|
|
@@ -377,7 +376,7 @@ async function toolProjectHealth(registryDb, params) {
|
|
|
377
376
|
const { existsSync: fsExists, readdirSync, statSync } = await import("node:fs");
|
|
378
377
|
const { join: pathJoin, basename: pathBasename } = await import("node:path");
|
|
379
378
|
const { homedir } = await import("node:os");
|
|
380
|
-
const { encodeDir: enc } = await import("./utils-
|
|
379
|
+
const { encodeDir: enc } = await import("./utils-DhMex3Ox.mjs");
|
|
381
380
|
const rows = registryDb.prepare(`SELECT p.id, p.slug, p.display_name, p.root_path, p.encoded_dir, p.status, p.type,
|
|
382
381
|
(SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count
|
|
383
382
|
FROM projects p
|
|
@@ -677,7 +676,7 @@ function toolSessionList(registryDb, params) {
|
|
|
677
676
|
*/
|
|
678
677
|
async function toolSessionRoute(registryDb, federation, params) {
|
|
679
678
|
try {
|
|
680
|
-
const { autoRoute, formatAutoRouteJson } = await import("./auto-route-
|
|
679
|
+
const { autoRoute, formatAutoRouteJson } = await import("./auto-route-Byf8ENXj.mjs");
|
|
681
680
|
const result = await autoRoute(registryDb, federation, params.cwd, params.context);
|
|
682
681
|
if (!result) return { content: [{
|
|
683
682
|
type: "text",
|
|
@@ -741,7 +740,7 @@ function toolRegistrySearch(registryDb, params) {
|
|
|
741
740
|
//#region src/mcp/tools/zettel.ts
|
|
742
741
|
async function toolZettelExplore(backend, params) {
|
|
743
742
|
try {
|
|
744
|
-
const { zettelExplore } = await import("./zettelkasten-
|
|
743
|
+
const { zettelExplore } = await import("./zettelkasten-CvjmMghT.mjs");
|
|
745
744
|
const result = await zettelExplore(backend, {
|
|
746
745
|
startNote: params.start_note,
|
|
747
746
|
depth: params.depth,
|
|
@@ -764,7 +763,7 @@ async function toolZettelExplore(backend, params) {
|
|
|
764
763
|
}
|
|
765
764
|
async function toolZettelHealth(backend, params) {
|
|
766
765
|
try {
|
|
767
|
-
const { zettelHealth } = await import("./zettelkasten-
|
|
766
|
+
const { zettelHealth } = await import("./zettelkasten-CvjmMghT.mjs");
|
|
768
767
|
const result = await zettelHealth(backend, {
|
|
769
768
|
scope: params.scope,
|
|
770
769
|
projectPath: params.project_path,
|
|
@@ -787,7 +786,7 @@ async function toolZettelHealth(backend, params) {
|
|
|
787
786
|
}
|
|
788
787
|
async function toolZettelSurprise(backend, params) {
|
|
789
788
|
try {
|
|
790
|
-
const { zettelSurprise } = await import("./zettelkasten-
|
|
789
|
+
const { zettelSurprise } = await import("./zettelkasten-CvjmMghT.mjs");
|
|
791
790
|
const results = await zettelSurprise(backend, {
|
|
792
791
|
referencePath: params.reference_path,
|
|
793
792
|
vaultProjectId: params.vault_project_id,
|
|
@@ -811,7 +810,7 @@ async function toolZettelSurprise(backend, params) {
|
|
|
811
810
|
}
|
|
812
811
|
async function toolZettelSuggest(backend, params) {
|
|
813
812
|
try {
|
|
814
|
-
const { zettelSuggest } = await import("./zettelkasten-
|
|
813
|
+
const { zettelSuggest } = await import("./zettelkasten-CvjmMghT.mjs");
|
|
815
814
|
const results = await zettelSuggest(backend, {
|
|
816
815
|
notePath: params.note_path,
|
|
817
816
|
vaultProjectId: params.vault_project_id,
|
|
@@ -834,7 +833,7 @@ async function toolZettelSuggest(backend, params) {
|
|
|
834
833
|
}
|
|
835
834
|
async function toolZettelConverse(backend, params) {
|
|
836
835
|
try {
|
|
837
|
-
const { zettelConverse } = await import("./zettelkasten-
|
|
836
|
+
const { zettelConverse } = await import("./zettelkasten-CvjmMghT.mjs");
|
|
838
837
|
const result = await zettelConverse(backend, {
|
|
839
838
|
question: params.question,
|
|
840
839
|
vaultProjectId: params.vault_project_id,
|
|
@@ -842,7 +841,7 @@ async function toolZettelConverse(backend, params) {
|
|
|
842
841
|
limit: params.limit
|
|
843
842
|
});
|
|
844
843
|
try {
|
|
845
|
-
const { saveQueryResult } = await import("./query-feedback-
|
|
844
|
+
const { saveQueryResult } = await import("./query-feedback-BBMBp96K.mjs");
|
|
846
845
|
saveQueryResult({
|
|
847
846
|
query: params.question,
|
|
848
847
|
timestamp: Date.now(),
|
|
@@ -870,7 +869,7 @@ async function toolZettelConverse(backend, params) {
|
|
|
870
869
|
}
|
|
871
870
|
async function toolZettelThemes(backend, params) {
|
|
872
871
|
try {
|
|
873
|
-
const { zettelThemes } = await import("./zettelkasten-
|
|
872
|
+
const { zettelThemes } = await import("./zettelkasten-CvjmMghT.mjs");
|
|
874
873
|
const result = await zettelThemes(backend, {
|
|
875
874
|
vaultProjectId: params.vault_project_id,
|
|
876
875
|
lookbackDays: params.lookback_days,
|
|
@@ -894,7 +893,7 @@ async function toolZettelThemes(backend, params) {
|
|
|
894
893
|
}
|
|
895
894
|
async function toolZettelGodNotes(backend, params) {
|
|
896
895
|
try {
|
|
897
|
-
const { zettelGodNotes } = await import("./zettelkasten-
|
|
896
|
+
const { zettelGodNotes } = await import("./zettelkasten-CvjmMghT.mjs");
|
|
898
897
|
const result = await zettelGodNotes(backend, {
|
|
899
898
|
limit: params.limit,
|
|
900
899
|
minInbound: params.min_inbound
|
|
@@ -915,7 +914,7 @@ async function toolZettelGodNotes(backend, params) {
|
|
|
915
914
|
}
|
|
916
915
|
async function toolZettelCommunities(backend, params) {
|
|
917
916
|
try {
|
|
918
|
-
const { zettelCommunities } = await import("./zettelkasten-
|
|
917
|
+
const { zettelCommunities } = await import("./zettelkasten-CvjmMghT.mjs");
|
|
919
918
|
const result = await zettelCommunities(backend, {
|
|
920
919
|
minSize: params.min_size,
|
|
921
920
|
maxCommunities: params.max_communities,
|
|
@@ -1782,7 +1781,7 @@ async function bfsExpand(pool, entityNames, hops, projectId) {
|
|
|
1782
1781
|
*/
|
|
1783
1782
|
async function rerankTriples(triples, queryVec, maxTriples) {
|
|
1784
1783
|
if (triples.length === 0) return [];
|
|
1785
|
-
const { generateEmbedding } = await import("./embeddings-
|
|
1784
|
+
const { generateEmbedding } = await import("./embeddings-CEBGrzwu.mjs");
|
|
1786
1785
|
return (await Promise.all(triples.map(async (triple) => {
|
|
1787
1786
|
const text = `${triple.subject} ${triple.predicate} ${triple.object}`;
|
|
1788
1787
|
try {
|
|
@@ -1816,7 +1815,7 @@ async function graphCompletionSearch(federationDb, pool, queryVec, opts = {}) {
|
|
|
1816
1815
|
};
|
|
1817
1816
|
let seedChunks = [];
|
|
1818
1817
|
try {
|
|
1819
|
-
const { searchMemorySemantic } = await import("./search-
|
|
1818
|
+
const { searchMemorySemantic } = await import("./search-CfPpJAWQ.mjs");
|
|
1820
1819
|
seedChunks = searchMemorySemantic(federationDb, queryVec, seedSearchOpts);
|
|
1821
1820
|
} catch (e) {
|
|
1822
1821
|
process.stderr.write(`[kg-search] Phase 1 seed search error: ${e}\n`);
|
|
@@ -1878,7 +1877,7 @@ async function toolMemoryKgSearch(federationDb, pool, params) {
|
|
|
1878
1877
|
}],
|
|
1879
1878
|
isError: true
|
|
1880
1879
|
};
|
|
1881
|
-
const { generateEmbedding } = await import("./embeddings-
|
|
1880
|
+
const { generateEmbedding } = await import("./embeddings-CEBGrzwu.mjs");
|
|
1882
1881
|
const result = await graphCompletionSearch(federationDb, pool, await generateEmbedding(params.query, true), {
|
|
1883
1882
|
seedCount: params.wide_k ?? 50,
|
|
1884
1883
|
hops: params.neighborhood_depth ?? 1,
|
|
@@ -1914,41 +1913,5 @@ async function toolMemoryKgSearch(federationDb, pool, params) {
|
|
|
1914
1913
|
}
|
|
1915
1914
|
|
|
1916
1915
|
//#endregion
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
combineHybridResults: () => combineHybridResults,
|
|
1920
|
-
detectProjectFromPath: () => detectProjectFromPath,
|
|
1921
|
-
formatProject: () => formatProject,
|
|
1922
|
-
lookupProjectId: () => lookupProjectId,
|
|
1923
|
-
toolKgAdd: () => toolKgAdd,
|
|
1924
|
-
toolKgContradictions: () => toolKgContradictions,
|
|
1925
|
-
toolKgInvalidate: () => toolKgInvalidate,
|
|
1926
|
-
toolKgQuery: () => toolKgQuery,
|
|
1927
|
-
toolMemoryFeedback: () => toolMemoryFeedback,
|
|
1928
|
-
toolMemoryGet: () => toolMemoryGet,
|
|
1929
|
-
toolMemoryKgSearch: () => toolMemoryKgSearch,
|
|
1930
|
-
toolMemorySearch: () => toolMemorySearch,
|
|
1931
|
-
toolMemoryTaxonomy: () => toolMemoryTaxonomy,
|
|
1932
|
-
toolMemoryTunnels: () => toolMemoryTunnels,
|
|
1933
|
-
toolMemoryWakeup: () => toolMemoryWakeup,
|
|
1934
|
-
toolProjectDetect: () => toolProjectDetect,
|
|
1935
|
-
toolProjectHealth: () => toolProjectHealth,
|
|
1936
|
-
toolProjectInfo: () => toolProjectInfo,
|
|
1937
|
-
toolProjectList: () => toolProjectList,
|
|
1938
|
-
toolProjectTodo: () => toolProjectTodo,
|
|
1939
|
-
toolRegistrySearch: () => toolRegistrySearch,
|
|
1940
|
-
toolSessionList: () => toolSessionList,
|
|
1941
|
-
toolSessionRoute: () => toolSessionRoute,
|
|
1942
|
-
toolZettelCommunities: () => toolZettelCommunities,
|
|
1943
|
-
toolZettelConverse: () => toolZettelConverse,
|
|
1944
|
-
toolZettelExplore: () => toolZettelExplore,
|
|
1945
|
-
toolZettelGodNotes: () => toolZettelGodNotes,
|
|
1946
|
-
toolZettelHealth: () => toolZettelHealth,
|
|
1947
|
-
toolZettelSuggest: () => toolZettelSuggest,
|
|
1948
|
-
toolZettelSurprise: () => toolZettelSurprise,
|
|
1949
|
-
toolZettelThemes: () => toolZettelThemes
|
|
1950
|
-
});
|
|
1951
|
-
|
|
1952
|
-
//#endregion
|
|
1953
|
-
export { toolMemoryWakeup as a, toolSessionRoute as c, toolProjectInfo as d, toolProjectList as f, toolMemorySearch as h, toolMemoryTaxonomy as i, toolProjectDetect as l, toolMemoryGet as m, toolMemoryKgSearch as n, toolRegistrySearch as o, toolProjectTodo as p, toolMemoryFeedback as r, toolSessionList as s, tools_exports as t, toolProjectHealth as u };
|
|
1954
|
-
//# sourceMappingURL=tools-C1lCHerL.mjs.map
|
|
1916
|
+
export { formatProject as A, toolProjectInfo as C, toolMemoryGet as D, combineHybridResults as E, toolMemorySearch as O, toolProjectHealth as S, toolProjectTodo as T, toolZettelThemes as _, toolMemoryWakeup as a, toolSessionRoute as b, toolKgInvalidate as c, toolZettelConverse as d, toolZettelExplore as f, toolZettelSurprise as g, toolZettelSuggest as h, toolMemoryTaxonomy as i, lookupProjectId as j, detectProjectFromPath as k, toolKgQuery as l, toolZettelHealth as m, toolMemoryFeedback as n, toolKgAdd as o, toolZettelGodNotes as p, toolMemoryTunnels as r, toolKgContradictions as s, toolMemoryKgSearch as t, toolZettelCommunities as u, toolRegistrySearch as v, toolProjectList as w, toolProjectDetect as x, toolSessionList as y };
|
|
1917
|
+
//# sourceMappingURL=tools-ceiy7ANX.mjs.map
|