@tekmidian/pai 0.54.0 → 0.55.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/{aibroker-client-CNfIBrl5.mjs → aibroker-client-B8Wtasez.mjs} +1 -1
- package/dist/{aibroker-client-DzkTpVjx.mjs → aibroker-client-BK1h--W_.mjs} +3 -3
- package/dist/{aibroker-client-DzkTpVjx.mjs.map → aibroker-client-BK1h--W_.mjs.map} +1 -1
- package/dist/{indexer-backend-CdvCJCkD.mjs → async-CLvuY7b-.mjs} +7 -7
- package/dist/async-CLvuY7b-.mjs.map +1 -0
- package/dist/auto-route-CF8WNaoy.mjs +100 -0
- package/dist/auto-route-CF8WNaoy.mjs.map +1 -0
- package/dist/{chain-BcBDJ1Id.mjs → chain-DogidBji.mjs} +4 -4
- package/dist/{chain-BcBDJ1Id.mjs.map → chain-DogidBji.mjs.map} +1 -1
- package/dist/cli/index.mjs +22 -23
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/program.d.mts.map +1 -1
- package/dist/cli/program.mjs +22 -23
- package/dist/{clusters-Cw_P6lO-.mjs → clusters-Ci1mseh2.mjs} +4 -36
- package/dist/clusters-Ci1mseh2.mjs.map +1 -0
- package/dist/{config-B3EPzRjf.mjs → config-B93YS8tP.mjs} +3 -3
- package/dist/{config-B3EPzRjf.mjs.map → config-B93YS8tP.mjs.map} +1 -1
- package/dist/{config-Bn9jpblO.mjs → config-D1omG_UU.mjs} +3 -3
- package/dist/{config-Bn9jpblO.mjs.map → config-D1omG_UU.mjs.map} +1 -1
- package/dist/{config-RjeVGJ7K.mjs → config-DCYWFaEi.mjs} +1 -1
- package/dist/{context-handover-cache-CXyH6QsC.mjs → context-handover-cache-k2kWbE6G.mjs} +82 -99
- package/dist/context-handover-cache-k2kWbE6G.mjs.map +1 -0
- package/dist/daemon/index.mjs +18 -24
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/daemon-B7Y8GQnt.mjs +19 -0
- package/dist/{daemon-BkJNFWr9.mjs → daemon-Cgrdvdo6.mjs} +97 -466
- package/dist/daemon-Cgrdvdo6.mjs.map +1 -0
- package/dist/daemon-mcp/index.mjs +10 -10
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/{detector-DWXkeTlk.mjs → detector-3g7XuU1r.mjs} +5 -12
- package/dist/detector-3g7XuU1r.mjs.map +1 -0
- package/dist/detector-DgDn56j-.mjs +3 -0
- package/dist/{embeddings-ClqKtLZ8.mjs → embeddings-BNv3TQC5.mjs} +1 -1
- package/dist/{embeddings-DvRKcQyt.mjs → embeddings-DfrYNIng.mjs} +1 -1
- package/dist/{embeddings-DvRKcQyt.mjs.map → embeddings-DfrYNIng.mjs.map} +1 -1
- package/dist/factory-C9wGUe30.mjs +3 -0
- package/dist/factory-DiuntvwG.mjs +250 -0
- package/dist/factory-DiuntvwG.mjs.map +1 -0
- package/dist/{fallback-CPfyTSTx.mjs → fallback-DS6AEJsU.mjs} +190 -132
- package/dist/fallback-DS6AEJsU.mjs.map +1 -0
- package/dist/federation-db-CJDw_Pwq.mjs +3 -0
- package/dist/{db-DJ8rk0VM.mjs → federation-db-w8e-sKjp.mjs} +5 -5
- package/dist/federation-db-w8e-sKjp.mjs.map +1 -0
- package/dist/{helpers-crDEr6S2.mjs → helpers-D9WkIf9R.mjs} +1 -1
- package/dist/{helpers-crDEr6S2.mjs.map → helpers-D9WkIf9R.mjs.map} +1 -1
- package/dist/hooks/capture-all-events.mjs.map +1 -1
- package/dist/hooks/capture-session-summary.mjs.map +1 -1
- package/dist/hooks/capture-tool-output.mjs.map +1 -1
- package/dist/hooks/context-compression-hook.mjs.map +1 -1
- package/dist/hooks/load-core-context.mjs +5 -0
- package/dist/hooks/load-core-context.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs.map +1 -1
- package/dist/hooks/post-compact-inject.mjs.map +1 -1
- package/dist/hooks/route-agents-to-worker.mjs.map +1 -1
- package/dist/hooks/route-edits-to-worker.mjs +1 -1
- package/dist/hooks/route-edits-to-worker.mjs.map +2 -2
- package/dist/hooks/security-validator.mjs +8367 -19
- package/dist/hooks/security-validator.mjs.map +4 -4
- package/dist/hooks/stop-hook.mjs.map +2 -2
- package/dist/hooks/whisper-rules.mjs.map +2 -2
- package/dist/hooks/worker-proxy.mjs.map +1 -1
- package/dist/hooks/worker-status-line.mjs.map +2 -2
- package/dist/hooks/worker-supervision.mjs.map +1 -1
- package/dist/index.d.mts +327 -100
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +7 -11
- package/dist/indexer-backend-Cc-_NK3q.mjs +1 -0
- package/dist/indexer-backend-DcDtin1O.mjs +5 -0
- package/dist/{ipc-client-C6NryMMb.mjs → ipc-client-Dasm515c.mjs} +2 -2
- package/dist/{ipc-client-C6NryMMb.mjs.map → ipc-client-Dasm515c.mjs.map} +1 -1
- package/dist/kg-entity-Cj8Q6YmU.mjs +29 -0
- package/dist/kg-entity-Cj8Q6YmU.mjs.map +1 -0
- package/dist/{latent-ideas-BfL4nCQ3.mjs → latent-ideas-BRy6HNwg.mjs} +4 -4
- package/dist/{latent-ideas-BfL4nCQ3.mjs.map → latent-ideas-BRy6HNwg.mjs.map} +1 -1
- package/dist/{link-boost-C5PaNdzm.mjs → link-boost-BptbAPuv.mjs} +1 -1
- package/dist/{link-boost-C5PaNdzm.mjs.map → link-boost-BptbAPuv.mjs.map} +1 -1
- package/dist/{main-resolver-Bt1f6jJc.mjs → main-resolver-Dx-pxic1.mjs} +56 -31
- package/dist/main-resolver-Dx-pxic1.mjs.map +1 -0
- package/dist/main-resolver-uDtK9p6e.mjs +7 -0
- package/dist/merge-BncjJTfb.mjs +3 -0
- package/dist/merge-xoyfZQ4W.mjs +6 -0
- package/dist/merge-xoyfZQ4W.mjs.map +1 -0
- package/dist/{migrate-CfERwXhY.mjs → migrate-BD7D8EEh.mjs} +21 -54
- package/dist/migrate-BD7D8EEh.mjs.map +1 -0
- package/dist/{neighborhood-CETR_P4y.mjs → neighborhood-D8uscnGy.mjs} +4 -25
- package/dist/neighborhood-D8uscnGy.mjs.map +1 -0
- package/dist/{note-context-DCcRWBoN.mjs → note-context-CgffaUMh.mjs} +3 -23
- package/dist/note-context-CgffaUMh.mjs.map +1 -0
- package/dist/{pai-home-ChAOupZc.mjs → pai-home-CeOIYltV.mjs} +2 -2
- package/dist/pai-home-CeOIYltV.mjs.map +1 -0
- package/dist/{pai-marker-B20KqhA8.mjs → pai-marker-D1MMswkz.mjs} +1 -1
- package/dist/{pai-marker-B20KqhA8.mjs.map → pai-marker-D1MMswkz.mjs.map} +1 -1
- package/dist/{planner-BLbCtBUa.mjs → planner-C2wWnZrZ.mjs} +7 -7
- package/dist/{planner-BLbCtBUa.mjs.map → planner-C2wWnZrZ.mjs.map} +1 -1
- package/dist/{postgres-CUSXWgTG.mjs → postgres-nvU5pShF.mjs} +863 -4
- package/dist/postgres-nvU5pShF.mjs.map +1 -0
- package/dist/{program-DYux0CW2.mjs → program-Cb-9J5Ie.mjs} +2260 -1399
- package/dist/program-Cb-9J5Ie.mjs.map +1 -0
- package/dist/query-feedback-COYCnvHQ.mjs +3 -0
- package/dist/{query-feedback-CTs_jx6i.mjs → query-feedback-xz5axTj3.mjs} +3 -3
- package/dist/query-feedback-xz5axTj3.mjs.map +1 -0
- package/dist/{db-Ddns80yf.mjs → registry-db-Bkpbp7Gf.mjs} +5 -5
- package/dist/registry-db-Bkpbp7Gf.mjs.map +1 -0
- package/dist/registry-db-DwXpf7yF.mjs +3 -0
- package/dist/registry-postgres-B07-hmn8.mjs +795 -0
- package/dist/registry-postgres-B07-hmn8.mjs.map +1 -0
- package/dist/registry-sqlite-CkGND_tm.mjs +593 -0
- package/dist/registry-sqlite-CkGND_tm.mjs.map +1 -0
- package/dist/{reranker-B5qUTamW.mjs → reranker-BS5gqGeF.mjs} +1 -1
- package/dist/{reranker-HHmhZk6v.mjs → reranker-CRVawoEh.mjs} +1 -1
- package/dist/{reranker-HHmhZk6v.mjs.map → reranker-CRVawoEh.mjs.map} +1 -1
- package/dist/router-BXOf_e9e.mjs +3 -0
- package/dist/{router-DBAZfE7X.mjs → router-BdxdsAvs.mjs} +2 -2
- package/dist/{router-DBAZfE7X.mjs.map → router-BdxdsAvs.mjs.map} +1 -1
- package/dist/{run-C7EDdVAB.mjs → run-DkTrZejF.mjs} +268 -53
- package/dist/run-DkTrZejF.mjs.map +1 -0
- package/dist/{runtime-paths-B0P1TvUr.mjs → runtime-paths-BRwDeBEg.mjs} +1 -1
- package/dist/{runtime-paths-B0P1TvUr.mjs.map → runtime-paths-BRwDeBEg.mjs.map} +1 -1
- package/dist/search-BQHL5wDr.mjs +3 -0
- package/dist/search-BaTbyWjK.mjs +107 -0
- package/dist/search-BaTbyWjK.mjs.map +1 -0
- package/dist/{server-CGiHdaUp.mjs → server-CGuZFOeR.mjs} +4 -4
- package/dist/{server-CGiHdaUp.mjs.map → server-CGuZFOeR.mjs.map} +1 -1
- package/dist/{session-keepalive-DC2JGggK.mjs → session-keepalive-C5_69Av6.mjs} +7 -7
- package/dist/{session-keepalive-DC2JGggK.mjs.map → session-keepalive-C5_69Av6.mjs.map} +1 -1
- package/dist/skills/Plan/SKILL.md +1 -1
- package/dist/{sources-KBGEA_E6.mjs → sources-BBe3AZ-W.mjs} +8 -27
- package/dist/sources-BBe3AZ-W.mjs.map +1 -0
- package/dist/sqlite-D9p5a4qe.mjs +935 -0
- package/dist/sqlite-D9p5a4qe.mjs.map +1 -0
- package/dist/state-CwlGAKgF.mjs +3 -0
- package/dist/{state-DwrsOoMi.mjs → state-n13uPK5B.mjs} +6 -5
- package/dist/state-n13uPK5B.mjs.map +1 -0
- package/dist/{stop-words-BaMEGVeY.mjs → stop-words-ClCwO8mZ.mjs} +1 -1
- package/dist/{stop-words-BaMEGVeY.mjs.map → stop-words-ClCwO8mZ.mjs.map} +1 -1
- package/dist/{themes-BVoAnLZj.mjs → themes-B2v7RNnA.mjs} +3 -3
- package/dist/{themes-BVoAnLZj.mjs.map → themes-B2v7RNnA.mjs.map} +1 -1
- package/dist/{tools-BEqiJt_j.mjs → tools-Diro2s8J.mjs} +157 -398
- package/dist/tools-Diro2s8J.mjs.map +1 -0
- package/dist/{tools-BwsOTmjh.mjs → tools-KgL1rkPD.mjs} +4 -4
- package/dist/{trace-Cy8quQTm.mjs → trace-BRflVyBi.mjs} +1 -1
- package/dist/{trace-Cy8quQTm.mjs.map → trace-BRflVyBi.mjs.map} +1 -1
- package/dist/{utils-BD8g8oSf.mjs → utils-BrX9FLeK.mjs} +1 -1
- package/dist/{utils-CJymHkYm.mjs → utils-C9HsYDpO.mjs} +1 -1
- package/dist/{utils-CJymHkYm.mjs.map → utils-C9HsYDpO.mjs.map} +1 -1
- package/dist/{vault-indexer-C5i-OAyr.mjs → vault-indexer-DHYBpRZ5.mjs} +2 -2
- package/dist/{vault-indexer-C5i-OAyr.mjs.map → vault-indexer-DHYBpRZ5.mjs.map} +1 -1
- package/dist/{wakeup-Yk5lQ0jb.mjs → wakeup-D0uvqLsE.mjs} +95 -4
- package/dist/wakeup-D0uvqLsE.mjs.map +1 -0
- package/dist/work-queue-worker-DEbWsrDQ.mjs +11 -0
- package/dist/{work-queue-worker-utPp4hUv.mjs → work-queue-worker-DufMOtrL.mjs} +9 -9
- package/dist/work-queue-worker-DufMOtrL.mjs.map +1 -0
- package/dist/{zettelkasten-DezbXghl.mjs → zettelkasten-BPxSOalU.mjs} +4 -4
- package/dist/{zettelkasten-DezbXghl.mjs.map → zettelkasten-BPxSOalU.mjs.map} +1 -1
- package/docker/init.sql +5 -1
- package/docs/commands/README.md +5 -0
- package/docs/commands/audit.md +1 -1
- package/docs/commands/backup.md +1 -1
- package/docs/commands/clear-names.md +1 -1
- package/docs/commands/config.md +4 -4
- package/docs/commands/daemon.md +1 -1
- package/docs/commands/db.md +15 -1
- package/docs/commands/end.md +1 -1
- package/docs/commands/help.md +1 -1
- package/docs/commands/hooks-db.md +47 -0
- package/docs/commands/identity.md +1 -1
- package/docs/commands/kg.md +1 -1
- package/docs/commands/launch.md +1 -1
- package/docs/commands/mcp.md +1 -1
- package/docs/commands/memory.md +1 -1
- package/docs/commands/notify.md +1 -1
- package/docs/commands/observation.md +1 -1
- package/docs/commands/obsidian.md +1 -1
- package/docs/commands/pause.md +1 -1
- package/docs/commands/project.md +1 -1
- package/docs/commands/projects.md +1 -1
- package/docs/commands/registry.md +1 -1
- package/docs/commands/restore.md +1 -1
- package/docs/commands/session.md +2 -2
- package/docs/commands/sessions.md +1 -1
- package/docs/commands/setup.md +1 -1
- package/docs/commands/shell-init.md +1 -1
- package/docs/commands/skill.md +1 -1
- package/docs/commands/task.md +1 -1
- package/docs/commands/topic.md +1 -1
- package/docs/commands/update.md +1 -1
- package/docs/commands/worker.md +1 -1
- package/docs/commands/zettel.md +1 -1
- package/docs/design/postgres-only.md +438 -0
- package/docs/design/registry-method-map.md +174 -0
- package/package.json +1 -1
- package/src/hooks/pre-compact.sh +2 -40
- package/src/hooks/session-hooks-db.test.ts +93 -0
- package/src/hooks/session-stop.sh +2 -26
- package/src/hooks/ts/lib/edit-gate.test.ts +9 -0
- package/src/hooks/ts/lib/edit-gate.ts +5 -2
- package/src/hooks/ts/lib/plutil-guard.test.ts +54 -0
- package/src/hooks/ts/lib/plutil-guard.ts +43 -0
- package/src/hooks/ts/lib/worker-git-guard.test.ts +134 -0
- package/src/hooks/ts/lib/worker-git-guard.ts +103 -0
- package/src/hooks/ts/pre-tool-use/security-validator.test.ts +68 -0
- package/src/hooks/ts/pre-tool-use/security-validator.ts +69 -21
- package/src/hooks/ts/session-start/load-core-context.ts +13 -0
- package/src/hooks/ts/session-start/session-start-worker-guard.test.ts +13 -0
- package/dist/auto-route-CcD0OWWB.mjs +0 -86
- package/dist/auto-route-CcD0OWWB.mjs.map +0 -1
- package/dist/clusters-Cw_P6lO-.mjs.map +0 -1
- package/dist/context-handover-cache-CXyH6QsC.mjs.map +0 -1
- package/dist/daemon-BkJNFWr9.mjs.map +0 -1
- package/dist/daemon-CiythMfR.mjs +0 -25
- package/dist/db-BOBfewFH.mjs +0 -3
- package/dist/db-DJ8rk0VM.mjs.map +0 -1
- package/dist/db-DbY2O8QO.mjs +0 -3
- package/dist/db-Ddns80yf.mjs.map +0 -1
- package/dist/detect-Bf2z-oKB.mjs +0 -86
- package/dist/detect-Bf2z-oKB.mjs.map +0 -1
- package/dist/detector-CkwkXuGh.mjs +0 -5
- package/dist/detector-DWXkeTlk.mjs.map +0 -1
- package/dist/factory-Ba8j9K7q.mjs +0 -3
- package/dist/factory-Bt7TWQje.mjs +0 -150
- package/dist/factory-Bt7TWQje.mjs.map +0 -1
- package/dist/fallback-CPfyTSTx.mjs.map +0 -1
- package/dist/indexer-backend-CdvCJCkD.mjs.map +0 -1
- package/dist/kg-entity-BSW7ra8i.mjs +0 -176
- package/dist/kg-entity-BSW7ra8i.mjs.map +0 -1
- package/dist/main-resolver-Bt1f6jJc.mjs.map +0 -1
- package/dist/main-resolver-DYHhw5Ig.mjs +0 -7
- package/dist/migrate-CfERwXhY.mjs.map +0 -1
- package/dist/neighborhood-CETR_P4y.mjs.map +0 -1
- package/dist/note-context-DCcRWBoN.mjs.map +0 -1
- package/dist/pai-home-ChAOupZc.mjs.map +0 -1
- package/dist/postgres-CUSXWgTG.mjs.map +0 -1
- package/dist/program-DYux0CW2.mjs.map +0 -1
- package/dist/query-feedback-Bm6XvCmg.mjs +0 -3
- package/dist/query-feedback-CTs_jx6i.mjs.map +0 -1
- package/dist/router-CremHKTI.mjs +0 -3
- package/dist/run-C7EDdVAB.mjs.map +0 -1
- package/dist/search-B69cQwDB.mjs +0 -303
- package/dist/search-B69cQwDB.mjs.map +0 -1
- package/dist/search-fb-5ovj4.mjs +0 -4
- package/dist/sources-KBGEA_E6.mjs.map +0 -1
- package/dist/sqlite-By6r2YhZ.mjs +0 -271
- package/dist/sqlite-By6r2YhZ.mjs.map +0 -1
- package/dist/state-Ca0ltLgI.mjs +0 -3
- package/dist/state-DwrsOoMi.mjs.map +0 -1
- package/dist/sync-Btcb6xA4.mjs +0 -310
- package/dist/sync-Btcb6xA4.mjs.map +0 -1
- package/dist/tools-BEqiJt_j.mjs.map +0 -1
- package/dist/wakeup-Yk5lQ0jb.mjs.map +0 -1
- package/dist/work-queue-worker-DsdJx-jn.mjs +0 -14
- package/dist/work-queue-worker-utPp4hUv.mjs.map +0 -1
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as
|
|
3
|
-
import {
|
|
4
|
-
import { r as formatDetectionJson, t as detectProject } from "./detect-Bf2z-oKB.mjs";
|
|
5
|
-
import { t as buildWakeupContext } from "./wakeup-Yk5lQ0jb.mjs";
|
|
6
|
-
import { a as kgContradictions, i as kgAdd, n as updateEntityFeedbackWeight, o as kgInvalidate, s as kgQuery, t as listKgEntities } from "./kg-entity-BSW7ra8i.mjs";
|
|
1
|
+
import { i as populateSlugs } from "./search-BaTbyWjK.mjs";
|
|
2
|
+
import { a as formatDetectionJson, r as detectProject, t as buildWakeupContext } from "./wakeup-D0uvqLsE.mjs";
|
|
3
|
+
import { n as cosineSimilarity } from "./embeddings-DfrYNIng.mjs";
|
|
7
4
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
8
5
|
import { isAbsolute, join, resolve } from "node:path";
|
|
9
6
|
|
|
@@ -11,29 +8,24 @@ import { isAbsolute, join, resolve } from "node:path";
|
|
|
11
8
|
/**
|
|
12
9
|
* Shared types and project-row helpers used across all MCP tool handler modules.
|
|
13
10
|
*/
|
|
14
|
-
function lookupProjectId(
|
|
15
|
-
const bySlug =
|
|
11
|
+
async function lookupProjectId(registry, slug) {
|
|
12
|
+
const bySlug = await registry.getProjectBySlug(slug);
|
|
16
13
|
if (bySlug) return bySlug.id;
|
|
17
|
-
|
|
18
|
-
if (byAlias) return byAlias.project_id;
|
|
19
|
-
return null;
|
|
14
|
+
return await registry.resolveAlias(slug) ?? null;
|
|
20
15
|
}
|
|
21
|
-
function detectProjectFromPath(
|
|
16
|
+
async function detectProjectFromPath(registry, fsPath) {
|
|
22
17
|
const resolved = resolve(fsPath);
|
|
23
|
-
const exact =
|
|
18
|
+
const exact = await registry.getProjectByRootPath(resolved);
|
|
24
19
|
if (exact) return exact;
|
|
25
|
-
const all =
|
|
20
|
+
const all = await registry.listProjectsByPathLengthDesc();
|
|
26
21
|
for (const project of all) if (resolved.startsWith(project.root_path + "/") || resolved === project.root_path) return project;
|
|
27
22
|
return null;
|
|
28
23
|
}
|
|
29
|
-
function formatProject(
|
|
30
|
-
const sessionCount =
|
|
31
|
-
const
|
|
32
|
-
const tags =
|
|
33
|
-
|
|
34
|
-
WHERE pt.project_id = ?
|
|
35
|
-
ORDER BY t.name`).all(project.id).map((r) => r.name);
|
|
36
|
-
const aliases = registryDb.prepare("SELECT alias FROM aliases WHERE project_id = ? ORDER BY alias").all(project.id).map((r) => r.alias);
|
|
24
|
+
async function formatProject(registry, project) {
|
|
25
|
+
const sessionCount = await registry.countSessionsForProject(project.id);
|
|
26
|
+
const lastSessionDate = await registry.getMostRecentSessionDate(project.id);
|
|
27
|
+
const tags = await registry.listTagsForProject(project.id);
|
|
28
|
+
const aliases = await registry.listAliasesForProject(project.id);
|
|
37
29
|
const lines = [
|
|
38
30
|
`slug: ${project.slug}`,
|
|
39
31
|
`display_name: ${project.display_name}`,
|
|
@@ -42,7 +34,7 @@ function formatProject(registryDb, project) {
|
|
|
42
34
|
`status: ${project.status}`,
|
|
43
35
|
`sessions: ${sessionCount}`
|
|
44
36
|
];
|
|
45
|
-
if (
|
|
37
|
+
if (lastSessionDate) lines.push(`last_session: ${lastSessionDate}`);
|
|
46
38
|
if (tags.length) lines.push(`tags: ${tags.join(", ")}`);
|
|
47
39
|
if (aliases.length) lines.push(`aliases: ${aliases.join(", ")}`);
|
|
48
40
|
if (project.obsidian_link) lines.push(`obsidian_link: ${project.obsidian_link}`);
|
|
@@ -55,10 +47,10 @@ function formatProject(registryDb, project) {
|
|
|
55
47
|
/**
|
|
56
48
|
* MCP tool handlers: memory_search, memory_get
|
|
57
49
|
*/
|
|
58
|
-
async function toolMemorySearch(
|
|
50
|
+
async function toolMemorySearch(registry, federation, params, searchDefaults) {
|
|
59
51
|
try {
|
|
60
|
-
const projectIds = params.project ? (() => {
|
|
61
|
-
const id = lookupProjectId(
|
|
52
|
+
const projectIds = params.project ? await (async () => {
|
|
53
|
+
const id = await lookupProjectId(registry, params.project);
|
|
62
54
|
return id != null ? [id] : [];
|
|
63
55
|
})() : void 0;
|
|
64
56
|
if (params.project && (!projectIds || projectIds.length === 0)) return {
|
|
@@ -76,10 +68,9 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
|
|
|
76
68
|
maxResults: params.limit ?? searchDefaults?.defaultLimit ?? 5
|
|
77
69
|
};
|
|
78
70
|
let results;
|
|
79
|
-
|
|
80
|
-
if (isBackend(federation)) if (mode === "keyword") results = await federation.searchKeyword(params.query, searchOpts);
|
|
71
|
+
if (mode === "keyword") results = await federation.searchKeyword(params.query, searchOpts);
|
|
81
72
|
else if (mode === "semantic" || mode === "hybrid") {
|
|
82
|
-
const { generateEmbedding } = await import("./embeddings-
|
|
73
|
+
const { generateEmbedding } = await import("./embeddings-BNv3TQC5.mjs");
|
|
83
74
|
const queryEmbedding = await generateEmbedding(params.query, true);
|
|
84
75
|
if (mode === "semantic") results = await federation.searchSemantic(queryEmbedding, searchOpts);
|
|
85
76
|
else {
|
|
@@ -93,20 +84,10 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
|
|
|
93
84
|
results = combineHybridResults(kwResults, semResults, searchOpts.maxResults ?? 10);
|
|
94
85
|
}
|
|
95
86
|
} else results = await federation.searchKeyword(params.query, searchOpts);
|
|
96
|
-
else {
|
|
97
|
-
const { searchMemory, searchMemorySemantic } = await import("./search-fb-5ovj4.mjs");
|
|
98
|
-
if (mode === "keyword") results = searchMemory(federation, params.query, searchOpts);
|
|
99
|
-
else if (mode === "semantic" || mode === "hybrid") {
|
|
100
|
-
const { generateEmbedding } = await import("./embeddings-ClqKtLZ8.mjs");
|
|
101
|
-
const queryEmbedding = await generateEmbedding(params.query, true);
|
|
102
|
-
if (mode === "semantic") results = searchMemorySemantic(federation, queryEmbedding, searchOpts);
|
|
103
|
-
else results = searchMemoryHybrid(federation, params.query, queryEmbedding, searchOpts);
|
|
104
|
-
} else results = searchMemory(federation, params.query, searchOpts);
|
|
105
|
-
}
|
|
106
87
|
try {
|
|
107
|
-
const { applyLinkBoost } = await import("./link-boost-
|
|
88
|
+
const { applyLinkBoost } = await import("./link-boost-BptbAPuv.mjs");
|
|
108
89
|
const paths = [...new Set(results.map((r) => r.path))];
|
|
109
|
-
if (
|
|
90
|
+
if (paths.length > 1) {
|
|
110
91
|
const edges = [];
|
|
111
92
|
for (const p of paths) for (const l of await federation.getLinksToTarget(p)) {
|
|
112
93
|
if (l.confidence && l.confidence !== "EXTRACTED") continue;
|
|
@@ -120,22 +101,19 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
|
|
|
120
101
|
} catch {}
|
|
121
102
|
try {
|
|
122
103
|
const chunkIds = results.map((r) => r.chunkId).filter((id) => id != null);
|
|
123
|
-
if (chunkIds.length > 0)
|
|
124
|
-
const rawDb = !isBackend(federation) ? federation : federation.getSqliteDb?.() ?? null;
|
|
125
|
-
if (rawDb) touchChunksLastAccessed(rawDb, chunkIds);
|
|
126
|
-
}
|
|
104
|
+
if (chunkIds.length > 0) await federation.touchChunksLastAccessed(chunkIds);
|
|
127
105
|
} catch {}
|
|
128
106
|
const shouldRerank = params.rerank ?? searchDefaults?.rerank ?? true;
|
|
129
107
|
if (shouldRerank && results.length > 0) {
|
|
130
|
-
const { rerankResults } = await import("./reranker-
|
|
108
|
+
const { rerankResults } = await import("./reranker-BS5gqGeF.mjs");
|
|
131
109
|
results = await rerankResults(params.query, results, { topK: searchOpts.maxResults ?? 5 });
|
|
132
110
|
}
|
|
133
111
|
const recencyDays = params.recencyBoost ?? searchDefaults?.recencyBoostDays ?? 0;
|
|
134
112
|
if (recencyDays > 0 && results.length > 0) {
|
|
135
|
-
const { applyRecencyBoost } = await import("./search-
|
|
113
|
+
const { applyRecencyBoost } = await import("./search-BQHL5wDr.mjs");
|
|
136
114
|
results = applyRecencyBoost(results, recencyDays);
|
|
137
115
|
}
|
|
138
|
-
const withSlugs = populateSlugs(results,
|
|
116
|
+
const withSlugs = await populateSlugs(results, registry);
|
|
139
117
|
if (withSlugs.length === 0) return { content: [{
|
|
140
118
|
type: "text",
|
|
141
119
|
text: `No results found for query: "${params.query}" (mode: ${mode})`
|
|
@@ -151,7 +129,7 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
|
|
|
151
129
|
return `${header}\n${raw.length > snippetLength ? raw.slice(0, snippetLength) + "..." : raw}`;
|
|
152
130
|
}).join(useCompact ? "\n" : "\n\n---\n\n");
|
|
153
131
|
try {
|
|
154
|
-
const { saveQueryResult } = await import("./query-feedback-
|
|
132
|
+
const { saveQueryResult } = await import("./query-feedback-COYCnvHQ.mjs");
|
|
155
133
|
saveQueryResult({
|
|
156
134
|
query: params.query,
|
|
157
135
|
timestamp: Date.now(),
|
|
@@ -175,9 +153,9 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
|
|
|
175
153
|
};
|
|
176
154
|
}
|
|
177
155
|
}
|
|
178
|
-
function toolMemoryGet(
|
|
156
|
+
async function toolMemoryGet(registry, params) {
|
|
179
157
|
try {
|
|
180
|
-
const projectId = lookupProjectId(
|
|
158
|
+
const projectId = await lookupProjectId(registry, params.project);
|
|
181
159
|
if (projectId == null) return {
|
|
182
160
|
content: [{
|
|
183
161
|
type: "text",
|
|
@@ -185,7 +163,7 @@ function toolMemoryGet(registryDb, params) {
|
|
|
185
163
|
}],
|
|
186
164
|
isError: true
|
|
187
165
|
};
|
|
188
|
-
const project =
|
|
166
|
+
const project = await registry.getProjectById(projectId);
|
|
189
167
|
if (!project) return {
|
|
190
168
|
content: [{
|
|
191
169
|
type: "text",
|
|
@@ -283,13 +261,13 @@ function combineHybridResults(keywordResults, semanticResults, maxResults, keywo
|
|
|
283
261
|
* MCP tool handlers: project_info, project_list, project_detect,
|
|
284
262
|
* project_health, project_todo
|
|
285
263
|
*/
|
|
286
|
-
function toolProjectInfo(
|
|
264
|
+
async function toolProjectInfo(registry, params) {
|
|
287
265
|
try {
|
|
288
266
|
let project = null;
|
|
289
267
|
if (params.slug) {
|
|
290
|
-
const projectId = lookupProjectId(
|
|
291
|
-
if (projectId != null) project =
|
|
292
|
-
} else project = detectProjectFromPath(
|
|
268
|
+
const projectId = await lookupProjectId(registry, params.slug);
|
|
269
|
+
if (projectId != null) project = await registry.getProjectById(projectId);
|
|
270
|
+
} else project = await detectProjectFromPath(registry, process.cwd());
|
|
293
271
|
if (!project) return {
|
|
294
272
|
content: [{
|
|
295
273
|
type: "text",
|
|
@@ -299,7 +277,7 @@ function toolProjectInfo(registryDb, params) {
|
|
|
299
277
|
};
|
|
300
278
|
return { content: [{
|
|
301
279
|
type: "text",
|
|
302
|
-
text: formatProject(
|
|
280
|
+
text: await formatProject(registry, project)
|
|
303
281
|
}] };
|
|
304
282
|
} catch (e) {
|
|
305
283
|
return {
|
|
@@ -311,26 +289,15 @@ function toolProjectInfo(registryDb, params) {
|
|
|
311
289
|
};
|
|
312
290
|
}
|
|
313
291
|
}
|
|
314
|
-
function toolProjectList(
|
|
292
|
+
async function toolProjectList(registry, params) {
|
|
315
293
|
try {
|
|
316
|
-
const conditions = [];
|
|
317
|
-
const queryParams = [];
|
|
318
|
-
if (params.status) {
|
|
319
|
-
conditions.push("p.status = ?");
|
|
320
|
-
queryParams.push(params.status);
|
|
321
|
-
}
|
|
322
|
-
if (params.tag) {
|
|
323
|
-
conditions.push("p.id IN (SELECT pt.project_id FROM project_tags pt JOIN tags t ON pt.tag_id = t.id WHERE t.name = ?)");
|
|
324
|
-
queryParams.push(params.tag);
|
|
325
|
-
}
|
|
326
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
327
294
|
const limit = params.limit ?? 50;
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
295
|
+
const projects = await registry.listProjects({
|
|
296
|
+
status: params.status,
|
|
297
|
+
tagName: params.tag,
|
|
298
|
+
orderBy: "updated_desc",
|
|
299
|
+
limit
|
|
300
|
+
});
|
|
334
301
|
if (projects.length === 0) return { content: [{
|
|
335
302
|
type: "text",
|
|
336
303
|
text: "No projects found matching the given filters."
|
|
@@ -350,9 +317,9 @@ function toolProjectList(registryDb, params) {
|
|
|
350
317
|
};
|
|
351
318
|
}
|
|
352
319
|
}
|
|
353
|
-
function toolProjectDetect(
|
|
320
|
+
async function toolProjectDetect(params) {
|
|
354
321
|
try {
|
|
355
|
-
const detection = detectProject(
|
|
322
|
+
const detection = await detectProject(params.cwd);
|
|
356
323
|
if (!detection) return { content: [{
|
|
357
324
|
type: "text",
|
|
358
325
|
text: `No registered project found for path: ${params.cwd ?? process.cwd()}\n\nRun 'pai project add .' to register this directory.`
|
|
@@ -371,16 +338,13 @@ function toolProjectDetect(registryDb, params) {
|
|
|
371
338
|
};
|
|
372
339
|
}
|
|
373
340
|
}
|
|
374
|
-
async function toolProjectHealth(
|
|
341
|
+
async function toolProjectHealth(registry, params) {
|
|
375
342
|
try {
|
|
376
343
|
const { existsSync: fsExists, readdirSync, statSync } = await import("node:fs");
|
|
377
344
|
const { join: pathJoin, basename: pathBasename } = await import("node:path");
|
|
378
345
|
const { homedir } = await import("node:os");
|
|
379
|
-
const { encodeDir: enc } = await import("./utils-
|
|
380
|
-
const rows =
|
|
381
|
-
(SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count
|
|
382
|
-
FROM projects p
|
|
383
|
-
ORDER BY p.slug ASC`).all();
|
|
346
|
+
const { encodeDir: enc } = await import("./utils-BrX9FLeK.mjs");
|
|
347
|
+
const rows = (await registry.listProjectsWithSessionStats()).slice().sort((a, b) => a.slug.localeCompare(b.slug));
|
|
384
348
|
const home = homedir();
|
|
385
349
|
const claudeProjects = pathJoin(home, ".claude", "projects");
|
|
386
350
|
function suggestMoved(rootPath) {
|
|
@@ -539,12 +503,12 @@ function parseTodoContent(raw) {
|
|
|
539
503
|
hasContinue: true
|
|
540
504
|
};
|
|
541
505
|
}
|
|
542
|
-
function toolProjectTodo(
|
|
506
|
+
async function toolProjectTodo(registry, params) {
|
|
543
507
|
try {
|
|
544
508
|
let rootPath;
|
|
545
509
|
let projectSlug;
|
|
546
510
|
if (params.project) {
|
|
547
|
-
const projectId = lookupProjectId(
|
|
511
|
+
const projectId = await lookupProjectId(registry, params.project);
|
|
548
512
|
if (projectId == null) return {
|
|
549
513
|
content: [{
|
|
550
514
|
type: "text",
|
|
@@ -552,7 +516,7 @@ function toolProjectTodo(registryDb, params) {
|
|
|
552
516
|
}],
|
|
553
517
|
isError: true
|
|
554
518
|
};
|
|
555
|
-
const row =
|
|
519
|
+
const row = await registry.getProjectById(projectId);
|
|
556
520
|
if (!row) return {
|
|
557
521
|
content: [{
|
|
558
522
|
type: "text",
|
|
@@ -563,7 +527,7 @@ function toolProjectTodo(registryDb, params) {
|
|
|
563
527
|
rootPath = row.root_path;
|
|
564
528
|
projectSlug = row.slug;
|
|
565
529
|
} else {
|
|
566
|
-
const project = detectProjectFromPath(
|
|
530
|
+
const project = await detectProjectFromPath(registry, process.cwd());
|
|
567
531
|
if (!project) return { content: [{
|
|
568
532
|
type: "text",
|
|
569
533
|
text: `No PAI project found matching the current directory: ${process.cwd()}\n\nProvide a project slug or run 'pai project add .' to register this directory.`
|
|
@@ -621,9 +585,9 @@ function toolProjectTodo(registryDb, params) {
|
|
|
621
585
|
|
|
622
586
|
//#endregion
|
|
623
587
|
//#region src/mcp/tools/sessions.ts
|
|
624
|
-
function toolSessionList(
|
|
588
|
+
async function toolSessionList(registry, params) {
|
|
625
589
|
try {
|
|
626
|
-
const projectId = lookupProjectId(
|
|
590
|
+
const projectId = await lookupProjectId(registry, params.project);
|
|
627
591
|
if (projectId == null) return {
|
|
628
592
|
content: [{
|
|
629
593
|
type: "text",
|
|
@@ -631,19 +595,12 @@ function toolSessionList(registryDb, params) {
|
|
|
631
595
|
}],
|
|
632
596
|
isError: true
|
|
633
597
|
};
|
|
634
|
-
const conditions = ["project_id = ?"];
|
|
635
|
-
const queryParams = [projectId];
|
|
636
|
-
if (params.status) {
|
|
637
|
-
conditions.push("status = ?");
|
|
638
|
-
queryParams.push(params.status);
|
|
639
|
-
}
|
|
640
598
|
const limit = params.limit ?? 10;
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
LIMIT ?`).all(...queryParams);
|
|
599
|
+
const sessions = await registry.listSessions({
|
|
600
|
+
projectId,
|
|
601
|
+
status: params.status,
|
|
602
|
+
limit
|
|
603
|
+
});
|
|
647
604
|
if (sessions.length === 0) return { content: [{
|
|
648
605
|
type: "text",
|
|
649
606
|
text: `No sessions found for project: ${params.project}`
|
|
@@ -674,10 +631,10 @@ function toolSessionList(registryDb, params) {
|
|
|
674
631
|
* Call this at session start (e.g., from CLAUDE.md or a session-start hook)
|
|
675
632
|
* to automatically route the session to the correct project.
|
|
676
633
|
*/
|
|
677
|
-
async function toolSessionRoute(
|
|
634
|
+
async function toolSessionRoute(registry, federation, params) {
|
|
678
635
|
try {
|
|
679
|
-
const { autoRoute, formatAutoRouteJson } = await import("./auto-route-
|
|
680
|
-
const result = await autoRoute(
|
|
636
|
+
const { autoRoute, formatAutoRouteJson } = await import("./auto-route-CF8WNaoy.mjs");
|
|
637
|
+
const result = await autoRoute(registry, federation, params.cwd, params.context);
|
|
681
638
|
if (!result) return { content: [{
|
|
682
639
|
type: "text",
|
|
683
640
|
text: [
|
|
@@ -706,16 +663,9 @@ async function toolSessionRoute(registryDb, federation, params) {
|
|
|
706
663
|
|
|
707
664
|
//#endregion
|
|
708
665
|
//#region src/mcp/tools/registry.ts
|
|
709
|
-
function toolRegistrySearch(
|
|
666
|
+
async function toolRegistrySearch(registry, params) {
|
|
710
667
|
try {
|
|
711
|
-
const
|
|
712
|
-
const projects = registryDb.prepare(`SELECT id, slug, display_name, root_path, type, status, updated_at
|
|
713
|
-
FROM projects
|
|
714
|
-
WHERE slug LIKE ?
|
|
715
|
-
OR display_name LIKE ?
|
|
716
|
-
OR root_path LIKE ?
|
|
717
|
-
ORDER BY updated_at DESC
|
|
718
|
-
LIMIT 20`).all(q, q, q);
|
|
668
|
+
const projects = await registry.searchProjects(params.query, 20);
|
|
719
669
|
if (projects.length === 0) return { content: [{
|
|
720
670
|
type: "text",
|
|
721
671
|
text: `No projects found matching: "${params.query}"`
|
|
@@ -740,7 +690,7 @@ function toolRegistrySearch(registryDb, params) {
|
|
|
740
690
|
//#region src/mcp/tools/zettel.ts
|
|
741
691
|
async function toolZettelExplore(backend, params) {
|
|
742
692
|
try {
|
|
743
|
-
const { zettelExplore } = await import("./zettelkasten-
|
|
693
|
+
const { zettelExplore } = await import("./zettelkasten-BPxSOalU.mjs");
|
|
744
694
|
const result = await zettelExplore(backend, {
|
|
745
695
|
startNote: params.start_note,
|
|
746
696
|
depth: params.depth,
|
|
@@ -763,7 +713,7 @@ async function toolZettelExplore(backend, params) {
|
|
|
763
713
|
}
|
|
764
714
|
async function toolZettelHealth(backend, params) {
|
|
765
715
|
try {
|
|
766
|
-
const { zettelHealth } = await import("./zettelkasten-
|
|
716
|
+
const { zettelHealth } = await import("./zettelkasten-BPxSOalU.mjs");
|
|
767
717
|
const result = await zettelHealth(backend, {
|
|
768
718
|
scope: params.scope,
|
|
769
719
|
projectPath: params.project_path,
|
|
@@ -786,7 +736,7 @@ async function toolZettelHealth(backend, params) {
|
|
|
786
736
|
}
|
|
787
737
|
async function toolZettelSurprise(backend, params) {
|
|
788
738
|
try {
|
|
789
|
-
const { zettelSurprise } = await import("./zettelkasten-
|
|
739
|
+
const { zettelSurprise } = await import("./zettelkasten-BPxSOalU.mjs");
|
|
790
740
|
const results = await zettelSurprise(backend, {
|
|
791
741
|
referencePath: params.reference_path,
|
|
792
742
|
vaultProjectId: params.vault_project_id,
|
|
@@ -810,7 +760,7 @@ async function toolZettelSurprise(backend, params) {
|
|
|
810
760
|
}
|
|
811
761
|
async function toolZettelSuggest(backend, params) {
|
|
812
762
|
try {
|
|
813
|
-
const { zettelSuggest } = await import("./zettelkasten-
|
|
763
|
+
const { zettelSuggest } = await import("./zettelkasten-BPxSOalU.mjs");
|
|
814
764
|
const results = await zettelSuggest(backend, {
|
|
815
765
|
notePath: params.note_path,
|
|
816
766
|
vaultProjectId: params.vault_project_id,
|
|
@@ -833,7 +783,7 @@ async function toolZettelSuggest(backend, params) {
|
|
|
833
783
|
}
|
|
834
784
|
async function toolZettelConverse(backend, params) {
|
|
835
785
|
try {
|
|
836
|
-
const { zettelConverse } = await import("./zettelkasten-
|
|
786
|
+
const { zettelConverse } = await import("./zettelkasten-BPxSOalU.mjs");
|
|
837
787
|
const result = await zettelConverse(backend, {
|
|
838
788
|
question: params.question,
|
|
839
789
|
vaultProjectId: params.vault_project_id,
|
|
@@ -841,7 +791,7 @@ async function toolZettelConverse(backend, params) {
|
|
|
841
791
|
limit: params.limit
|
|
842
792
|
});
|
|
843
793
|
try {
|
|
844
|
-
const { saveQueryResult } = await import("./query-feedback-
|
|
794
|
+
const { saveQueryResult } = await import("./query-feedback-COYCnvHQ.mjs");
|
|
845
795
|
saveQueryResult({
|
|
846
796
|
query: params.question,
|
|
847
797
|
timestamp: Date.now(),
|
|
@@ -869,7 +819,7 @@ async function toolZettelConverse(backend, params) {
|
|
|
869
819
|
}
|
|
870
820
|
async function toolZettelThemes(backend, params) {
|
|
871
821
|
try {
|
|
872
|
-
const { zettelThemes } = await import("./zettelkasten-
|
|
822
|
+
const { zettelThemes } = await import("./zettelkasten-BPxSOalU.mjs");
|
|
873
823
|
const result = await zettelThemes(backend, {
|
|
874
824
|
vaultProjectId: params.vault_project_id,
|
|
875
825
|
lookbackDays: params.lookback_days,
|
|
@@ -893,7 +843,7 @@ async function toolZettelThemes(backend, params) {
|
|
|
893
843
|
}
|
|
894
844
|
async function toolZettelGodNotes(backend, params) {
|
|
895
845
|
try {
|
|
896
|
-
const { zettelGodNotes } = await import("./zettelkasten-
|
|
846
|
+
const { zettelGodNotes } = await import("./zettelkasten-BPxSOalU.mjs");
|
|
897
847
|
const result = await zettelGodNotes(backend, {
|
|
898
848
|
limit: params.limit,
|
|
899
849
|
minInbound: params.min_inbound
|
|
@@ -914,7 +864,7 @@ async function toolZettelGodNotes(backend, params) {
|
|
|
914
864
|
}
|
|
915
865
|
async function toolZettelCommunities(backend, params) {
|
|
916
866
|
try {
|
|
917
|
-
const { zettelCommunities } = await import("./zettelkasten-
|
|
867
|
+
const { zettelCommunities } = await import("./zettelkasten-BPxSOalU.mjs");
|
|
918
868
|
const result = await zettelCommunities(backend, {
|
|
919
869
|
minSize: params.min_size,
|
|
920
870
|
maxCommunities: params.max_communities,
|
|
@@ -937,7 +887,7 @@ async function toolZettelCommunities(backend, params) {
|
|
|
937
887
|
|
|
938
888
|
//#endregion
|
|
939
889
|
//#region src/mcp/tools/kg.ts
|
|
940
|
-
async function toolKgAdd(
|
|
890
|
+
async function toolKgAdd(backend, params) {
|
|
941
891
|
try {
|
|
942
892
|
if (!params.subject || !params.predicate || !params.object) return {
|
|
943
893
|
content: [{
|
|
@@ -946,7 +896,7 @@ async function toolKgAdd(pool, params) {
|
|
|
946
896
|
}],
|
|
947
897
|
isError: true
|
|
948
898
|
};
|
|
949
|
-
const triple = await
|
|
899
|
+
const triple = await backend.addKgTriple(params);
|
|
950
900
|
return { content: [{
|
|
951
901
|
type: "text",
|
|
952
902
|
text: JSON.stringify(triple, null, 2)
|
|
@@ -961,7 +911,7 @@ async function toolKgAdd(pool, params) {
|
|
|
961
911
|
};
|
|
962
912
|
}
|
|
963
913
|
}
|
|
964
|
-
async function toolKgQuery(
|
|
914
|
+
async function toolKgQuery(backend, params) {
|
|
965
915
|
try {
|
|
966
916
|
const asOf = params.as_of ? new Date(params.as_of) : void 0;
|
|
967
917
|
if (asOf && isNaN(asOf.getTime())) return {
|
|
@@ -971,7 +921,7 @@ async function toolKgQuery(pool, params) {
|
|
|
971
921
|
}],
|
|
972
922
|
isError: true
|
|
973
923
|
};
|
|
974
|
-
const triples = await
|
|
924
|
+
const triples = await backend.queryKgTriples({
|
|
975
925
|
subject: params.subject,
|
|
976
926
|
predicate: params.predicate,
|
|
977
927
|
object: params.object,
|
|
@@ -993,7 +943,7 @@ async function toolKgQuery(pool, params) {
|
|
|
993
943
|
};
|
|
994
944
|
}
|
|
995
945
|
}
|
|
996
|
-
async function toolKgInvalidate(
|
|
946
|
+
async function toolKgInvalidate(backend, params) {
|
|
997
947
|
try {
|
|
998
948
|
if (params.triple_id === void 0 || params.triple_id === null) return {
|
|
999
949
|
content: [{
|
|
@@ -1002,7 +952,7 @@ async function toolKgInvalidate(pool, params) {
|
|
|
1002
952
|
}],
|
|
1003
953
|
isError: true
|
|
1004
954
|
};
|
|
1005
|
-
await
|
|
955
|
+
await backend.invalidateKgTriple(params.triple_id);
|
|
1006
956
|
return { content: [{
|
|
1007
957
|
type: "text",
|
|
1008
958
|
text: JSON.stringify({
|
|
@@ -1020,7 +970,7 @@ async function toolKgInvalidate(pool, params) {
|
|
|
1020
970
|
};
|
|
1021
971
|
}
|
|
1022
972
|
}
|
|
1023
|
-
async function toolKgContradictions(
|
|
973
|
+
async function toolKgContradictions(backend, params) {
|
|
1024
974
|
try {
|
|
1025
975
|
if (!params.subject) return {
|
|
1026
976
|
content: [{
|
|
@@ -1029,7 +979,7 @@ async function toolKgContradictions(pool, params) {
|
|
|
1029
979
|
}],
|
|
1030
980
|
isError: true
|
|
1031
981
|
};
|
|
1032
|
-
const contradictions = await
|
|
982
|
+
const contradictions = await backend.getKgContradictions(params.subject);
|
|
1033
983
|
return { content: [{
|
|
1034
984
|
type: "text",
|
|
1035
985
|
text: JSON.stringify(contradictions, null, 2)
|
|
@@ -1048,19 +998,19 @@ async function toolKgContradictions(pool, params) {
|
|
|
1048
998
|
//#endregion
|
|
1049
999
|
//#region src/mcp/tools/wakeup.ts
|
|
1050
1000
|
const DEFAULT_TOKEN_BUDGET = 800;
|
|
1051
|
-
function toolMemoryWakeup(
|
|
1001
|
+
async function toolMemoryWakeup(registry, params) {
|
|
1052
1002
|
try {
|
|
1053
1003
|
const tokenBudget = params.token_budget ?? DEFAULT_TOKEN_BUDGET;
|
|
1054
1004
|
let rootPath;
|
|
1055
1005
|
if (params.project) {
|
|
1056
|
-
const bySlug =
|
|
1006
|
+
const bySlug = await registry.getProjectBySlug(params.project);
|
|
1057
1007
|
if (bySlug) rootPath = bySlug.root_path;
|
|
1058
1008
|
else {
|
|
1059
|
-
const detected = detectProjectFromPath(
|
|
1009
|
+
const detected = await detectProjectFromPath(registry, params.project);
|
|
1060
1010
|
if (detected) rootPath = detected.root_path;
|
|
1061
1011
|
}
|
|
1062
1012
|
} else {
|
|
1063
|
-
const detected = detectProjectFromPath(
|
|
1013
|
+
const detected = await detectProjectFromPath(registry, process.cwd());
|
|
1064
1014
|
if (detected) rootPath = detected.root_path;
|
|
1065
1015
|
}
|
|
1066
1016
|
const wakeupBlock = buildWakeupContext(rootPath, tokenBudget);
|
|
@@ -1089,18 +1039,20 @@ function toolMemoryWakeup(registryDb, params) {
|
|
|
1089
1039
|
* Build a taxonomy of stored memory — what projects exist, how much is stored,
|
|
1090
1040
|
* and what has been active recently.
|
|
1091
1041
|
*
|
|
1092
|
-
* Registry queries (projects, sessions)
|
|
1042
|
+
* Registry queries (projects, sessions) go through RegistryBackend (async).
|
|
1093
1043
|
* Storage backend queries (files, chunks) are async.
|
|
1094
1044
|
*/
|
|
1095
|
-
async function getTaxonomy(
|
|
1045
|
+
async function getTaxonomy(registry, storage, options = {}) {
|
|
1096
1046
|
const includeArchived = options.include_archived ?? false;
|
|
1097
1047
|
const limit = options.limit ?? 50;
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1048
|
+
const projectRows = includeArchived ? await registry.listProjects({
|
|
1049
|
+
orderBy: "updated_desc",
|
|
1050
|
+
limit
|
|
1051
|
+
}) : await registry.listProjects({
|
|
1052
|
+
status: "active",
|
|
1053
|
+
orderBy: "updated_desc",
|
|
1054
|
+
limit
|
|
1055
|
+
});
|
|
1104
1056
|
if (projectRows.length === 0) return {
|
|
1105
1057
|
projects: [],
|
|
1106
1058
|
totals: {
|
|
@@ -1112,48 +1064,27 @@ async function getTaxonomy(registryDb, storage, options = {}) {
|
|
|
1112
1064
|
recent_activity: []
|
|
1113
1065
|
};
|
|
1114
1066
|
const projectIds = projectRows.map((p) => p.id);
|
|
1067
|
+
const projectIdSet = new Set(projectIds);
|
|
1115
1068
|
const sessionCountsByProject = /* @__PURE__ */ new Map();
|
|
1116
1069
|
const lastSessionDateByProject = /* @__PURE__ */ new Map();
|
|
1117
1070
|
for (const projectId of projectIds) {
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
const lastRow = registryDb.prepare("SELECT date FROM sessions WHERE project_id = ? ORDER BY number DESC LIMIT 1").get(projectId);
|
|
1121
|
-
lastSessionDateByProject.set(projectId, lastRow?.date ?? null);
|
|
1071
|
+
sessionCountsByProject.set(projectId, await registry.countSessionsForProject(projectId));
|
|
1072
|
+
lastSessionDateByProject.set(projectId, await registry.getMostRecentSessionDate(projectId));
|
|
1122
1073
|
}
|
|
1123
1074
|
const tagsByProject = /* @__PURE__ */ new Map();
|
|
1124
|
-
for (const projectId of projectIds)
|
|
1125
|
-
const tags = registryDb.prepare(`SELECT t.name
|
|
1126
|
-
FROM tags t
|
|
1127
|
-
JOIN project_tags pt ON pt.tag_id = t.id
|
|
1128
|
-
WHERE pt.project_id = ?
|
|
1129
|
-
ORDER BY t.name`).all(projectId);
|
|
1130
|
-
tagsByProject.set(projectId, tags.map((t) => t.name));
|
|
1131
|
-
}
|
|
1075
|
+
for (const projectId of projectIds) tagsByProject.set(projectId, await registry.listTagsForProject(projectId));
|
|
1132
1076
|
const noteCountsByProject = /* @__PURE__ */ new Map();
|
|
1133
1077
|
const chunkCountsByProject = /* @__PURE__ */ new Map();
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
const noteRow = rawDb.prepare("SELECT COUNT(*) AS n FROM memory_files WHERE project_id = ?").get(projectId);
|
|
1139
|
-
noteCountsByProject.set(projectId, noteRow.n);
|
|
1140
|
-
const chunkRow = rawDb.prepare("SELECT COUNT(*) AS n FROM memory_chunks WHERE project_id = ?").get(projectId);
|
|
1141
|
-
chunkCountsByProject.set(projectId, chunkRow.n);
|
|
1142
|
-
}
|
|
1143
|
-
} else for (const projectId of projectIds) {
|
|
1144
|
-
noteCountsByProject.set(projectId, 0);
|
|
1145
|
-
chunkCountsByProject.set(projectId, 0);
|
|
1078
|
+
for (const projectId of projectIds) {
|
|
1079
|
+
const projectStats = await storage.getProjectStats(projectId);
|
|
1080
|
+
noteCountsByProject.set(projectId, projectStats.files);
|
|
1081
|
+
chunkCountsByProject.set(projectId, projectStats.chunks);
|
|
1146
1082
|
}
|
|
1147
1083
|
const stats = await storage.getStats();
|
|
1148
|
-
const totalProjects =
|
|
1149
|
-
const totalSessions =
|
|
1150
|
-
const recentActivity =
|
|
1151
|
-
|
|
1152
|
-
JOIN projects p ON p.id = s.project_id
|
|
1153
|
-
WHERE p.${statusFilter.replace("status", "p.status")}
|
|
1154
|
-
ORDER BY s.created_at DESC
|
|
1155
|
-
LIMIT 10`).all().map((row) => ({
|
|
1156
|
-
project_slug: row.slug,
|
|
1084
|
+
const totalProjects = includeArchived ? await registry.countProjects() : await registry.countProjects({ status: "active" });
|
|
1085
|
+
const totalSessions = [...sessionCountsByProject.values()].reduce((a, b) => a + b, 0);
|
|
1086
|
+
const recentActivity = (await registry.listSessions({ limit: Math.max(50, limit) })).filter((s) => projectIdSet.has(s.project_id)).slice(0, 10).map((row) => ({
|
|
1087
|
+
project_slug: row.project_slug,
|
|
1157
1088
|
action: `session: ${row.title || "(untitled)"}`,
|
|
1158
1089
|
timestamp: row.date
|
|
1159
1090
|
}));
|
|
@@ -1178,9 +1109,9 @@ async function getTaxonomy(registryDb, storage, options = {}) {
|
|
|
1178
1109
|
|
|
1179
1110
|
//#endregion
|
|
1180
1111
|
//#region src/mcp/tools/taxonomy.ts
|
|
1181
|
-
async function toolMemoryTaxonomy(
|
|
1112
|
+
async function toolMemoryTaxonomy(registry, storage, params = {}) {
|
|
1182
1113
|
try {
|
|
1183
|
-
const result = await getTaxonomy(
|
|
1114
|
+
const result = await getTaxonomy(registry, storage, {
|
|
1184
1115
|
include_archived: params.include_archived,
|
|
1185
1116
|
limit: params.limit
|
|
1186
1117
|
});
|
|
@@ -1219,209 +1150,24 @@ async function toolMemoryTaxonomy(registryDb, storage, params = {}) {
|
|
|
1219
1150
|
//#endregion
|
|
1220
1151
|
//#region src/memory/tunnels.ts
|
|
1221
1152
|
/**
|
|
1222
|
-
* tunnels.ts — cross-project concept detection ("palace graph / tunnel detection")
|
|
1223
|
-
*
|
|
1224
|
-
* A "tunnel" is a concept (word or short phrase) that appears in chunks from
|
|
1225
|
-
* at least two distinct projects. These serendipitous cross-project connections
|
|
1226
|
-
* are surfaced so the user can discover unexpected relationships between their
|
|
1227
|
-
* work streams.
|
|
1228
|
-
*
|
|
1229
|
-
* Algorithm:
|
|
1230
|
-
* 1. Pull the top-N most frequent significant terms from memory_chunks via BM25 FTS.
|
|
1231
|
-
* We use the FTS5 vocab table (if available) or fall back to term frequency
|
|
1232
|
-
* aggregation over the raw text via a trigram approach.
|
|
1233
|
-
* 2. For each candidate term, count how many distinct projects have at least one
|
|
1234
|
-
* chunk containing it and aggregate occurrence stats.
|
|
1235
|
-
* 3. Filter by min_projects and min_occurrences, sort by project breadth then
|
|
1236
|
-
* frequency, return top limit results.
|
|
1237
|
-
*
|
|
1238
|
-
* Backend support:
|
|
1239
|
-
* - SQLite — uses `memory_fts` MATCH to count per-project occurrences.
|
|
1240
|
-
* - Postgres — uses `memory_chunks` tsvector + ts_stat for term extraction and
|
|
1241
|
-
* per-project term frequency counting via plainto_tsquery.
|
|
1242
|
-
*/
|
|
1243
|
-
/**
|
|
1244
|
-
* Extract candidate terms from the SQLite FTS5 index using the vocabulary
|
|
1245
|
-
* approach: iterate the fts5vocab table (if it exists) for the most common
|
|
1246
|
-
* terms, then per-term count distinct projects.
|
|
1247
|
-
*/
|
|
1248
|
-
async function findTunnelsSqlite(db, slugMap, opts) {
|
|
1249
|
-
const projectIds = Object.keys(slugMap).map(Number);
|
|
1250
|
-
if (projectIds.length < 2) return {
|
|
1251
|
-
tunnels: [],
|
|
1252
|
-
projects_analyzed: projectIds.length,
|
|
1253
|
-
total_concepts_evaluated: 0
|
|
1254
|
-
};
|
|
1255
|
-
let candidateTerms = [];
|
|
1256
|
-
try {
|
|
1257
|
-
candidateTerms = db.prepare(`SELECT term, SUM(doc) AS doc_count, SUM(cnt) AS total_cnt
|
|
1258
|
-
FROM memory_fts_v
|
|
1259
|
-
GROUP BY term
|
|
1260
|
-
HAVING SUM(cnt) >= ?
|
|
1261
|
-
ORDER BY SUM(doc) DESC
|
|
1262
|
-
LIMIT 500`).all(opts.min_occurrences).map((r) => r.term).filter((t) => t.length >= 3 && !STOP_WORDS.has(t));
|
|
1263
|
-
} catch {
|
|
1264
|
-
const sampleRows = db.prepare(`SELECT LOWER(text) AS text FROM memory_chunks
|
|
1265
|
-
WHERE LENGTH(text) > 20
|
|
1266
|
-
ORDER BY RANDOM()
|
|
1267
|
-
LIMIT 2000`).all();
|
|
1268
|
-
const freq = /* @__PURE__ */ new Map();
|
|
1269
|
-
for (const { text } of sampleRows) {
|
|
1270
|
-
const tokens = text.split(/[\s\p{P}]+/u).filter(Boolean).filter((t) => t.length >= 3 && !STOP_WORDS.has(t));
|
|
1271
|
-
for (const t of tokens) freq.set(t, (freq.get(t) ?? 0) + 1);
|
|
1272
|
-
}
|
|
1273
|
-
candidateTerms = [...freq.entries()].filter(([, n]) => n >= opts.min_occurrences).sort((a, b) => b[1] - a[1]).slice(0, 200).map(([t]) => t);
|
|
1274
|
-
}
|
|
1275
|
-
if (candidateTerms.length === 0) return {
|
|
1276
|
-
tunnels: [],
|
|
1277
|
-
projects_analyzed: projectIds.length,
|
|
1278
|
-
total_concepts_evaluated: 0
|
|
1279
|
-
};
|
|
1280
|
-
const tunnels = [];
|
|
1281
|
-
for (const term of candidateTerms) try {
|
|
1282
|
-
const rows = db.prepare(`SELECT c.project_id, COUNT(*) AS cnt,
|
|
1283
|
-
MIN(c.updated_at) AS first_seen,
|
|
1284
|
-
MAX(c.updated_at) AS last_seen
|
|
1285
|
-
FROM memory_fts f
|
|
1286
|
-
JOIN memory_chunks c ON c.id = f.id
|
|
1287
|
-
WHERE memory_fts MATCH ?
|
|
1288
|
-
AND c.project_id IN (${projectIds.map(() => "?").join(", ")})
|
|
1289
|
-
GROUP BY c.project_id`).all(`"${term.replace(/"/g, "\"\"")}"`, ...projectIds);
|
|
1290
|
-
if (rows.length < opts.min_projects) continue;
|
|
1291
|
-
const totalOccurrences = rows.reduce((s, r) => s + Number(r.cnt), 0);
|
|
1292
|
-
if (totalOccurrences < opts.min_occurrences) continue;
|
|
1293
|
-
const projects = rows.map((r) => slugMap[r.project_id] ?? String(r.project_id)).filter(Boolean);
|
|
1294
|
-
const firstSeen = Math.min(...rows.map((r) => r.first_seen));
|
|
1295
|
-
const lastSeen = Math.max(...rows.map((r) => r.last_seen));
|
|
1296
|
-
tunnels.push({
|
|
1297
|
-
concept: term,
|
|
1298
|
-
projects,
|
|
1299
|
-
occurrences: totalOccurrences,
|
|
1300
|
-
first_seen: firstSeen,
|
|
1301
|
-
last_seen: lastSeen
|
|
1302
|
-
});
|
|
1303
|
-
} catch {
|
|
1304
|
-
continue;
|
|
1305
|
-
}
|
|
1306
|
-
tunnels.sort((a, b) => {
|
|
1307
|
-
const byProjects = b.projects.length - a.projects.length;
|
|
1308
|
-
if (byProjects !== 0) return byProjects;
|
|
1309
|
-
return b.occurrences - a.occurrences;
|
|
1310
|
-
});
|
|
1311
|
-
return {
|
|
1312
|
-
tunnels: tunnels.slice(0, opts.limit),
|
|
1313
|
-
projects_analyzed: projectIds.length,
|
|
1314
|
-
total_concepts_evaluated: candidateTerms.length
|
|
1315
|
-
};
|
|
1316
|
-
}
|
|
1317
|
-
/**
|
|
1318
|
-
* Use Postgres ts_stat() + plainto_tsquery to efficiently find terms that
|
|
1319
|
-
* appear across multiple projects.
|
|
1320
|
-
*/
|
|
1321
|
-
async function findTunnelsPostgres(pool, slugMap, opts) {
|
|
1322
|
-
const projectIds = Object.keys(slugMap).map(Number);
|
|
1323
|
-
if (projectIds.length < 2) return {
|
|
1324
|
-
tunnels: [],
|
|
1325
|
-
projects_analyzed: projectIds.length,
|
|
1326
|
-
total_concepts_evaluated: 0
|
|
1327
|
-
};
|
|
1328
|
-
let candidateTerms = (await pool.query(`SELECT word, ndoc, nentry
|
|
1329
|
-
FROM ts_stat(
|
|
1330
|
-
'SELECT to_tsvector(''simple'', text) FROM memory_chunks WHERE project_id = ANY($1)'
|
|
1331
|
-
)
|
|
1332
|
-
WHERE length(word) >= 3
|
|
1333
|
-
AND nentry >= $2
|
|
1334
|
-
ORDER BY ndoc DESC
|
|
1335
|
-
LIMIT 500`, [projectIds, opts.min_occurrences])).rows.map((r) => r.word).filter((t) => !STOP_WORDS.has(t));
|
|
1336
|
-
if (candidateTerms.length === 0) return {
|
|
1337
|
-
tunnels: [],
|
|
1338
|
-
projects_analyzed: projectIds.length,
|
|
1339
|
-
total_concepts_evaluated: 0
|
|
1340
|
-
};
|
|
1341
|
-
candidateTerms = candidateTerms.slice(0, 200);
|
|
1342
|
-
const valuesClause = candidateTerms.map((t, i) => `($${i + 2}::text)`).join(", ");
|
|
1343
|
-
const batchResult = await pool.query(`SELECT v.concept, c.project_id::text, COUNT(*) AS cnt,
|
|
1344
|
-
MIN(c.updated_at) AS first_seen,
|
|
1345
|
-
MAX(c.updated_at) AS last_seen
|
|
1346
|
-
FROM (VALUES ${valuesClause}) AS v(concept)
|
|
1347
|
-
JOIN memory_chunks c
|
|
1348
|
-
ON to_tsvector('simple', c.text) @@ plainto_tsquery('simple', v.concept)
|
|
1349
|
-
AND c.project_id = ANY($1)
|
|
1350
|
-
GROUP BY v.concept, c.project_id`, [projectIds, ...candidateTerms]);
|
|
1351
|
-
const byConceptMap = /* @__PURE__ */ new Map();
|
|
1352
|
-
for (const row of batchResult.rows) {
|
|
1353
|
-
const existing = byConceptMap.get(row.concept) ?? {
|
|
1354
|
-
projects: /* @__PURE__ */ new Set(),
|
|
1355
|
-
occurrences: 0,
|
|
1356
|
-
firstSeen: Infinity,
|
|
1357
|
-
lastSeen: -Infinity
|
|
1358
|
-
};
|
|
1359
|
-
existing.projects.add(parseInt(row.project_id, 10));
|
|
1360
|
-
existing.occurrences += parseInt(row.cnt, 10);
|
|
1361
|
-
const fs = parseInt(row.first_seen, 10);
|
|
1362
|
-
const ls = parseInt(row.last_seen, 10);
|
|
1363
|
-
if (fs < existing.firstSeen) existing.firstSeen = fs;
|
|
1364
|
-
if (ls > existing.lastSeen) existing.lastSeen = ls;
|
|
1365
|
-
byConceptMap.set(row.concept, existing);
|
|
1366
|
-
}
|
|
1367
|
-
const tunnels = [];
|
|
1368
|
-
for (const [concept, data] of byConceptMap) {
|
|
1369
|
-
if (data.projects.size < opts.min_projects) continue;
|
|
1370
|
-
if (data.occurrences < opts.min_occurrences) continue;
|
|
1371
|
-
const projects = [...data.projects].map((id) => slugMap[id] ?? String(id)).filter(Boolean);
|
|
1372
|
-
tunnels.push({
|
|
1373
|
-
concept,
|
|
1374
|
-
projects,
|
|
1375
|
-
occurrences: data.occurrences,
|
|
1376
|
-
first_seen: data.firstSeen === Infinity ? 0 : data.firstSeen,
|
|
1377
|
-
last_seen: data.lastSeen === -Infinity ? 0 : data.lastSeen
|
|
1378
|
-
});
|
|
1379
|
-
}
|
|
1380
|
-
tunnels.sort((a, b) => {
|
|
1381
|
-
const byProjects = b.projects.length - a.projects.length;
|
|
1382
|
-
if (byProjects !== 0) return byProjects;
|
|
1383
|
-
return b.occurrences - a.occurrences;
|
|
1384
|
-
});
|
|
1385
|
-
return {
|
|
1386
|
-
tunnels: tunnels.slice(0, opts.limit),
|
|
1387
|
-
projects_analyzed: projectIds.length,
|
|
1388
|
-
total_concepts_evaluated: candidateTerms.length
|
|
1389
|
-
};
|
|
1390
|
-
}
|
|
1391
|
-
/**
|
|
1392
1153
|
* Find cross-project concept tunnels.
|
|
1393
1154
|
*
|
|
1394
1155
|
* Works with both SQLite and Postgres storage backends.
|
|
1395
|
-
* Requires the
|
|
1156
|
+
* Requires the RegistryBackend for project slug resolution.
|
|
1396
1157
|
*
|
|
1397
1158
|
* @param backend Active PAI storage backend.
|
|
1398
|
-
* @param
|
|
1159
|
+
* @param registry Registry backend for project slug resolution.
|
|
1399
1160
|
* @param options Filter and limit options.
|
|
1400
1161
|
*/
|
|
1401
|
-
async function findTunnels(backend,
|
|
1402
|
-
|
|
1403
|
-
min_projects: options?.min_projects ?? 2,
|
|
1404
|
-
min_occurrences: options?.min_occurrences ?? 3,
|
|
1405
|
-
limit: options?.limit ?? 20
|
|
1406
|
-
};
|
|
1407
|
-
const projectRows = registryDb.prepare("SELECT id, slug FROM projects WHERE status != 'archived'").all();
|
|
1408
|
-
const slugMap = {};
|
|
1409
|
-
for (const { id, slug } of projectRows) slugMap[id] = slug;
|
|
1410
|
-
if (backend.backendType === "postgres") {
|
|
1411
|
-
const pool = backend.getPool?.();
|
|
1412
|
-
if (!pool) throw new Error("findTunnels: Postgres backend does not expose getPool()");
|
|
1413
|
-
return findTunnelsPostgres(pool, slugMap, opts);
|
|
1414
|
-
}
|
|
1415
|
-
const rawDb = backend.getRawDb?.();
|
|
1416
|
-
if (!rawDb) throw new Error("findTunnels: SQLite backend does not expose getRawDb()");
|
|
1417
|
-
return findTunnelsSqlite(rawDb, slugMap, opts);
|
|
1162
|
+
async function findTunnels(backend, registry, options) {
|
|
1163
|
+
return backend.findTunnels(registry, options);
|
|
1418
1164
|
}
|
|
1419
1165
|
|
|
1420
1166
|
//#endregion
|
|
1421
1167
|
//#region src/mcp/tools/tunnels.ts
|
|
1422
|
-
async function toolMemoryTunnels(
|
|
1168
|
+
async function toolMemoryTunnels(registry, backend, params) {
|
|
1423
1169
|
try {
|
|
1424
|
-
const result = await findTunnels(backend,
|
|
1170
|
+
const result = await findTunnels(backend, registry, {
|
|
1425
1171
|
min_projects: params.min_projects,
|
|
1426
1172
|
min_occurrences: params.min_occurrences,
|
|
1427
1173
|
limit: params.limit
|
|
@@ -1446,11 +1192,13 @@ async function toolMemoryTunnels(registryDb, backend, params) {
|
|
|
1446
1192
|
const FEEDBACK_ALPHA = .1;
|
|
1447
1193
|
/**
|
|
1448
1194
|
* Apply relevance feedback to memory chunks and associated KG entities.
|
|
1195
|
+
* Both memory_chunks.relevance_score and kg_entities.feedback_weight go
|
|
1196
|
+
* through the passed StorageBackend, so this works on either backend.
|
|
1449
1197
|
*
|
|
1450
|
-
* @param
|
|
1451
|
-
* @param params
|
|
1198
|
+
* @param backend StorageBackend for both the chunk and kg_entities portions
|
|
1199
|
+
* @param params Feedback parameters
|
|
1452
1200
|
*/
|
|
1453
|
-
function toolMemoryFeedback(
|
|
1201
|
+
async function toolMemoryFeedback(backend, params) {
|
|
1454
1202
|
try {
|
|
1455
1203
|
if (!Array.isArray(params.chunk_ids) || params.chunk_ids.length === 0) return {
|
|
1456
1204
|
content: [{
|
|
@@ -1468,8 +1216,7 @@ function toolMemoryFeedback(db, params) {
|
|
|
1468
1216
|
isError: true
|
|
1469
1217
|
};
|
|
1470
1218
|
const normalizedRating = (rating - 1) / 4;
|
|
1471
|
-
const
|
|
1472
|
-
const chunks = db.prepare(`SELECT id, text, relevance_score FROM memory_chunks WHERE id IN (${placeholders})`).all(...params.chunk_ids);
|
|
1219
|
+
const chunks = await backend.getChunksForFeedback(params.chunk_ids);
|
|
1473
1220
|
if (chunks.length === 0) return {
|
|
1474
1221
|
content: [{
|
|
1475
1222
|
type: "text",
|
|
@@ -1479,19 +1226,19 @@ function toolMemoryFeedback(db, params) {
|
|
|
1479
1226
|
};
|
|
1480
1227
|
let updatedChunks = 0;
|
|
1481
1228
|
const combinedText = [];
|
|
1482
|
-
const updateStmt = db.prepare("UPDATE memory_chunks SET relevance_score = ? WHERE id = ?");
|
|
1483
1229
|
for (const chunk of chunks) {
|
|
1484
|
-
const oldScore = chunk.
|
|
1230
|
+
const oldScore = chunk.relevanceScore ?? .5;
|
|
1485
1231
|
const newScore = oldScore + FEEDBACK_ALPHA * (normalizedRating - oldScore);
|
|
1486
|
-
|
|
1232
|
+
await backend.updateChunkRelevanceScore(chunk.id, newScore);
|
|
1487
1233
|
updatedChunks++;
|
|
1488
1234
|
combinedText.push(chunk.text);
|
|
1489
1235
|
}
|
|
1490
|
-
const
|
|
1236
|
+
const tenantId = params.tenant_id ?? "default";
|
|
1237
|
+
const entities = await backend.listKgEntities(tenantId, void 0, 500);
|
|
1491
1238
|
const text = combinedText.join("\n").toLowerCase();
|
|
1492
1239
|
let updatedEntities = 0;
|
|
1493
1240
|
for (const entity of entities) if (text.includes(entity.name.toLowerCase())) {
|
|
1494
|
-
updateEntityFeedbackWeight(
|
|
1241
|
+
await backend.updateEntityFeedbackWeight(entity.entity_id, normalizedRating, FEEDBACK_ALPHA);
|
|
1495
1242
|
updatedEntities++;
|
|
1496
1243
|
}
|
|
1497
1244
|
return { content: [{
|
|
@@ -1517,13 +1264,28 @@ function toolMemoryFeedback(db, params) {
|
|
|
1517
1264
|
//#endregion
|
|
1518
1265
|
//#region src/memory/kg-search.ts
|
|
1519
1266
|
/**
|
|
1520
|
-
*
|
|
1267
|
+
* kg-search.ts — Graph-completion retrieval (MR1).
|
|
1268
|
+
*
|
|
1269
|
+
* Implements graphCompletionSearch: a multi-phase retrieval strategy that
|
|
1270
|
+
* combines vector search with knowledge-graph neighborhood expansion.
|
|
1271
|
+
*
|
|
1272
|
+
* Algorithm:
|
|
1273
|
+
* Phase 1: Wide vector search (50 candidates) using queryVec
|
|
1274
|
+
* Phase 2: Extract entity mentions from retrieved chunks (substring match vs kg_entities)
|
|
1275
|
+
* Phase 3: BFS neighborhood expansion in kg_triples (1-2 hops from matched entities)
|
|
1276
|
+
* Phase 4: Re-rank all collected triples against query embedding using cosine similarity
|
|
1277
|
+
*
|
|
1278
|
+
* The result is a ranked list of KgTriple objects most relevant to the query,
|
|
1279
|
+
* augmented with graph-derived context that pure vector search would miss.
|
|
1280
|
+
*/
|
|
1281
|
+
/**
|
|
1282
|
+
* Find entity names (from the kg_entities table) that appear
|
|
1521
1283
|
* as substrings in the given text chunks.
|
|
1522
1284
|
*
|
|
1523
1285
|
* Returns a deduplicated list of matching entity names.
|
|
1524
1286
|
*/
|
|
1525
|
-
function extractEntityMentions(
|
|
1526
|
-
const entities = listKgEntities(
|
|
1287
|
+
async function extractEntityMentions(federation, chunks, tenantId = "default") {
|
|
1288
|
+
const entities = await federation.listKgEntities(tenantId, void 0, 500);
|
|
1527
1289
|
if (entities.length === 0) return [];
|
|
1528
1290
|
const combinedText = chunks.map((c) => c.snippet).join("\n").toLowerCase();
|
|
1529
1291
|
const matched = /* @__PURE__ */ new Set();
|
|
@@ -1538,18 +1300,18 @@ function extractEntityMentions(federationDb, chunks, tenantId = "default") {
|
|
|
1538
1300
|
*
|
|
1539
1301
|
* Returns all unique triples found in the BFS expansion.
|
|
1540
1302
|
*/
|
|
1541
|
-
async function bfsExpand(
|
|
1303
|
+
async function bfsExpand(federation, entityNames, hops, projectId) {
|
|
1542
1304
|
const seen = /* @__PURE__ */ new Set();
|
|
1543
1305
|
const tripleMap = /* @__PURE__ */ new Map();
|
|
1544
1306
|
const frontier = new Set(entityNames);
|
|
1545
1307
|
for (let hop = 0; hop < hops; hop++) {
|
|
1546
1308
|
const nextFrontier = /* @__PURE__ */ new Set();
|
|
1547
1309
|
for (const name of frontier) {
|
|
1548
|
-
const asSubject = await
|
|
1310
|
+
const asSubject = await federation.queryKgTriples({
|
|
1549
1311
|
subject: name,
|
|
1550
1312
|
project_id: projectId
|
|
1551
1313
|
});
|
|
1552
|
-
const asObject = await
|
|
1314
|
+
const asObject = await federation.queryKgTriples({
|
|
1553
1315
|
object: name,
|
|
1554
1316
|
project_id: projectId
|
|
1555
1317
|
});
|
|
@@ -1578,7 +1340,7 @@ async function bfsExpand(pool, entityNames, hops, projectId) {
|
|
|
1578
1340
|
*/
|
|
1579
1341
|
async function rerankTriples(triples, queryVec, maxTriples) {
|
|
1580
1342
|
if (triples.length === 0) return [];
|
|
1581
|
-
const { generateEmbedding } = await import("./embeddings-
|
|
1343
|
+
const { generateEmbedding } = await import("./embeddings-BNv3TQC5.mjs");
|
|
1582
1344
|
return (await Promise.all(triples.map(async (triple) => {
|
|
1583
1345
|
const text = `${triple.subject} ${triple.predicate} ${triple.object}`;
|
|
1584
1346
|
try {
|
|
@@ -1598,12 +1360,11 @@ async function rerankTriples(triples, queryVec, maxTriples) {
|
|
|
1598
1360
|
/**
|
|
1599
1361
|
* Graph-completion retrieval combining vector search with KG neighborhood BFS.
|
|
1600
1362
|
*
|
|
1601
|
-
* @param
|
|
1602
|
-
* @param pool Postgres connection pool (for kg_triples queries)
|
|
1363
|
+
* @param federation Active StorageBackend (for chunk/entity/kg_triples lookup)
|
|
1603
1364
|
* @param queryVec Pre-computed embedding for the search query
|
|
1604
1365
|
* @param opts Configuration options
|
|
1605
1366
|
*/
|
|
1606
|
-
async function graphCompletionSearch(
|
|
1367
|
+
async function graphCompletionSearch(federation, queryVec, opts = {}) {
|
|
1607
1368
|
const { seedCount = 50, hops = 1, maxTriples = 20, projectId, tenantId = "default", searchOpts } = opts;
|
|
1608
1369
|
const seedSearchOpts = {
|
|
1609
1370
|
...searchOpts,
|
|
@@ -1612,8 +1373,7 @@ async function graphCompletionSearch(federationDb, pool, queryVec, opts = {}) {
|
|
|
1612
1373
|
};
|
|
1613
1374
|
let seedChunks = [];
|
|
1614
1375
|
try {
|
|
1615
|
-
|
|
1616
|
-
seedChunks = searchMemorySemantic(federationDb, queryVec, seedSearchOpts);
|
|
1376
|
+
seedChunks = await federation.searchSemantic(queryVec, seedSearchOpts);
|
|
1617
1377
|
} catch (e) {
|
|
1618
1378
|
process.stderr.write(`[kg-search] Phase 1 seed search error: ${e}\n`);
|
|
1619
1379
|
}
|
|
@@ -1622,7 +1382,7 @@ async function graphCompletionSearch(federationDb, pool, queryVec, opts = {}) {
|
|
|
1622
1382
|
seedChunks: [],
|
|
1623
1383
|
expandedEntities: []
|
|
1624
1384
|
};
|
|
1625
|
-
const expandedEntities = extractEntityMentions(
|
|
1385
|
+
const expandedEntities = await extractEntityMentions(federation, seedChunks, tenantId);
|
|
1626
1386
|
if (expandedEntities.length === 0) return {
|
|
1627
1387
|
triples: [],
|
|
1628
1388
|
seedChunks,
|
|
@@ -1630,7 +1390,7 @@ async function graphCompletionSearch(federationDb, pool, queryVec, opts = {}) {
|
|
|
1630
1390
|
};
|
|
1631
1391
|
let expandedTriples = [];
|
|
1632
1392
|
try {
|
|
1633
|
-
expandedTriples = await bfsExpand(
|
|
1393
|
+
expandedTriples = await bfsExpand(federation, [...expandedEntities], hops, projectId);
|
|
1634
1394
|
} catch (e) {
|
|
1635
1395
|
process.stderr.write(`[kg-search] Phase 3 BFS expansion error: ${e}\n`);
|
|
1636
1396
|
}
|
|
@@ -1661,11 +1421,10 @@ async function graphCompletionSearch(federationDb, pool, queryVec, opts = {}) {
|
|
|
1661
1421
|
/**
|
|
1662
1422
|
* Graph-completion search: vector search + KG neighborhood BFS.
|
|
1663
1423
|
*
|
|
1664
|
-
* @param
|
|
1665
|
-
* @param pool Postgres pool for kg_triples BFS expansion
|
|
1424
|
+
* @param federation Active StorageBackend for chunk/entity/kg_triples operations
|
|
1666
1425
|
* @param params Tool parameters
|
|
1667
1426
|
*/
|
|
1668
|
-
async function toolMemoryKgSearch(
|
|
1427
|
+
async function toolMemoryKgSearch(federation, params) {
|
|
1669
1428
|
try {
|
|
1670
1429
|
if (!params.query || typeof params.query !== "string" || params.query.trim() === "") return {
|
|
1671
1430
|
content: [{
|
|
@@ -1674,8 +1433,8 @@ async function toolMemoryKgSearch(federationDb, pool, params) {
|
|
|
1674
1433
|
}],
|
|
1675
1434
|
isError: true
|
|
1676
1435
|
};
|
|
1677
|
-
const { generateEmbedding } = await import("./embeddings-
|
|
1678
|
-
const result = await graphCompletionSearch(
|
|
1436
|
+
const { generateEmbedding } = await import("./embeddings-BNv3TQC5.mjs");
|
|
1437
|
+
const result = await graphCompletionSearch(federation, await generateEmbedding(params.query, true), {
|
|
1679
1438
|
seedCount: params.wide_k ?? 50,
|
|
1680
1439
|
hops: params.neighborhood_depth ?? 1,
|
|
1681
1440
|
maxTriples: params.top_k ?? 20,
|
|
@@ -1711,4 +1470,4 @@ async function toolMemoryKgSearch(federationDb, pool, params) {
|
|
|
1711
1470
|
|
|
1712
1471
|
//#endregion
|
|
1713
1472
|
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 };
|
|
1714
|
-
//# sourceMappingURL=tools-
|
|
1473
|
+
//# sourceMappingURL=tools-Diro2s8J.mjs.map
|