@kinqs/brainrouter-mcp-server 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/.env.example +144 -0
  2. package/README.md +56 -0
  3. package/agents/README.md +120 -0
  4. package/agents/code-reviewer.md +97 -0
  5. package/agents/security-auditor.md +101 -0
  6. package/agents/test-engineer.md +95 -0
  7. package/dist/__tests__/agent_mode.test.d.ts +1 -0
  8. package/dist/__tests__/api-routes.test.d.ts +1 -0
  9. package/dist/__tests__/api-routes.test.js +170 -0
  10. package/dist/__tests__/crypto.test.d.ts +1 -0
  11. package/dist/__tests__/crypto.test.js +28 -0
  12. package/dist/__tests__/host-integrations.test.d.ts +1 -0
  13. package/dist/__tests__/host-integrations.test.js +82 -0
  14. package/dist/__tests__/integration.test.d.ts +1 -0
  15. package/dist/__tests__/integration.test.js +50 -0
  16. package/dist/__tests__/loader.test.d.ts +1 -0
  17. package/dist/__tests__/loader.test.js +89 -0
  18. package/dist/__tests__/neural-spark.test.d.ts +1 -0
  19. package/dist/__tests__/neural-spark.test.js +112 -0
  20. package/dist/__tests__/pagination.test.d.ts +1 -0
  21. package/dist/__tests__/pagination.test.js +23 -0
  22. package/dist/__tests__/redaction.test.d.ts +1 -0
  23. package/dist/__tests__/redaction.test.js +17 -0
  24. package/dist/__tests__/registry.test.d.ts +1 -0
  25. package/dist/__tests__/registry.test.js +56 -0
  26. package/dist/__tests__/retry.test.d.ts +1 -0
  27. package/dist/__tests__/retry.test.js +30 -0
  28. package/dist/__tests__/skill-activation.test.d.ts +1 -0
  29. package/dist/__tests__/skill-activation.test.js +112 -0
  30. package/dist/__tests__/working-memory.test.d.ts +1 -0
  31. package/dist/__tests__/working-memory.test.js +200 -0
  32. package/dist/__tests__/workspace-paths.test.d.ts +1 -0
  33. package/dist/__tests__/workspace-paths.test.js +56 -0
  34. package/dist/__tests__/writer.test.d.ts +1 -0
  35. package/dist/__tests__/writer.test.js +94 -0
  36. package/dist/api/auth/crypto.d.ts +4 -0
  37. package/dist/api/auth/crypto.js +54 -0
  38. package/dist/api/middleware/auth.d.ts +12 -0
  39. package/dist/api/middleware/auth.js +90 -0
  40. package/dist/api/pagination.d.ts +18 -0
  41. package/dist/api/pagination.js +32 -0
  42. package/dist/api/routes/auth.d.ts +1 -0
  43. package/dist/api/routes/auth.js +130 -0
  44. package/dist/api/routes/chat-completions.d.ts +7 -0
  45. package/dist/api/routes/chat-completions.js +474 -0
  46. package/dist/api/routes/contradictions.d.ts +1 -0
  47. package/dist/api/routes/contradictions.js +28 -0
  48. package/dist/api/routes/evidence.d.ts +1 -0
  49. package/dist/api/routes/evidence.js +59 -0
  50. package/dist/api/routes/governance.d.ts +1 -0
  51. package/dist/api/routes/governance.js +95 -0
  52. package/dist/api/routes/graph.d.ts +1 -0
  53. package/dist/api/routes/graph.js +25 -0
  54. package/dist/api/routes/hooks.d.ts +1 -0
  55. package/dist/api/routes/hooks.js +88 -0
  56. package/dist/api/routes/memories.d.ts +1 -0
  57. package/dist/api/routes/memories.js +92 -0
  58. package/dist/api/routes/persona.d.ts +1 -0
  59. package/dist/api/routes/persona.js +9 -0
  60. package/dist/api/routes/scenes.d.ts +1 -0
  61. package/dist/api/routes/scenes.js +35 -0
  62. package/dist/api/routes/skills.d.ts +1 -0
  63. package/dist/api/routes/skills.js +14 -0
  64. package/dist/api/routes/stats.d.ts +1 -0
  65. package/dist/api/routes/stats.js +8 -0
  66. package/dist/api/routes/users.d.ts +1 -0
  67. package/dist/api/routes/users.js +82 -0
  68. package/dist/api/routes/working.d.ts +1 -0
  69. package/dist/api/routes/working.js +88 -0
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.js +492 -0
  72. package/dist/integrations/claude-code.d.ts +12 -0
  73. package/dist/integrations/claude-code.js +35 -0
  74. package/dist/integrations/codex.d.ts +12 -0
  75. package/dist/integrations/codex.js +34 -0
  76. package/dist/integrations/generic-mcp.d.ts +52 -0
  77. package/dist/integrations/generic-mcp.js +118 -0
  78. package/dist/loader.d.ts +29 -0
  79. package/dist/loader.js +200 -0
  80. package/dist/memory/capture.d.ts +35 -0
  81. package/dist/memory/capture.js +230 -0
  82. package/dist/memory/config.d.ts +2 -0
  83. package/dist/memory/config.js +3 -0
  84. package/dist/memory/engine.d.ts +203 -0
  85. package/dist/memory/engine.js +626 -0
  86. package/dist/memory/llm-semaphore.d.ts +41 -0
  87. package/dist/memory/llm-semaphore.js +81 -0
  88. package/dist/memory/memory-type-config.d.ts +11 -0
  89. package/dist/memory/memory-type-config.js +65 -0
  90. package/dist/memory/pipeline/cognitive-contradiction.d.ts +7 -0
  91. package/dist/memory/pipeline/cognitive-contradiction.js +59 -0
  92. package/dist/memory/pipeline/cognitive-dedup.d.ts +23 -0
  93. package/dist/memory/pipeline/cognitive-dedup.js +38 -0
  94. package/dist/memory/pipeline/cognitive-extractor.d.ts +21 -0
  95. package/dist/memory/pipeline/cognitive-extractor.js +183 -0
  96. package/dist/memory/pipeline/contextual-focus-builder.d.ts +13 -0
  97. package/dist/memory/pipeline/contextual-focus-builder.js +135 -0
  98. package/dist/memory/pipeline/focus-direction-shift.d.ts +10 -0
  99. package/dist/memory/pipeline/focus-direction-shift.js +27 -0
  100. package/dist/memory/pipeline/graph-builder.d.ts +11 -0
  101. package/dist/memory/pipeline/graph-builder.js +88 -0
  102. package/dist/memory/pipeline/graph-recall.d.ts +13 -0
  103. package/dist/memory/pipeline/graph-recall.js +55 -0
  104. package/dist/memory/pipeline/identity-distiller.d.ts +15 -0
  105. package/dist/memory/pipeline/identity-distiller.js +40 -0
  106. package/dist/memory/pipeline/l1-contradiction.d.ts +7 -0
  107. package/dist/memory/pipeline/l1-contradiction.js +66 -0
  108. package/dist/memory/pipeline/l1-dedup.d.ts +23 -0
  109. package/dist/memory/pipeline/l1-dedup.js +39 -0
  110. package/dist/memory/pipeline/l1-extractor.d.ts +21 -0
  111. package/dist/memory/pipeline/l1-extractor.js +180 -0
  112. package/dist/memory/pipeline/l2-direction-shift.d.ts +10 -0
  113. package/dist/memory/pipeline/l2-direction-shift.js +27 -0
  114. package/dist/memory/pipeline/l2-scene.d.ts +15 -0
  115. package/dist/memory/pipeline/l2-scene.js +140 -0
  116. package/dist/memory/pipeline/l3-distiller.d.ts +15 -0
  117. package/dist/memory/pipeline/l3-distiller.js +40 -0
  118. package/dist/memory/pipeline/neural-spark.d.ts +27 -0
  119. package/dist/memory/pipeline/neural-spark.js +78 -0
  120. package/dist/memory/pipeline/skill-prewarm.d.ts +63 -0
  121. package/dist/memory/pipeline/skill-prewarm.js +127 -0
  122. package/dist/memory/pipeline/task-queue.d.ts +54 -0
  123. package/dist/memory/pipeline/task-queue.js +117 -0
  124. package/dist/memory/prompts/cognitive-contradiction.d.ts +1 -0
  125. package/dist/memory/prompts/cognitive-contradiction.js +25 -0
  126. package/dist/memory/prompts/cognitive-extraction.d.ts +10 -0
  127. package/dist/memory/prompts/cognitive-extraction.js +114 -0
  128. package/dist/memory/prompts/core-identity.d.ts +6 -0
  129. package/dist/memory/prompts/core-identity.js +60 -0
  130. package/dist/memory/prompts/focus-direction-shift.d.ts +5 -0
  131. package/dist/memory/prompts/focus-direction-shift.js +32 -0
  132. package/dist/memory/prompts/focus-scene-cluster.d.ts +2 -0
  133. package/dist/memory/prompts/focus-scene-cluster.js +33 -0
  134. package/dist/memory/prompts/focus-scene.d.ts +7 -0
  135. package/dist/memory/prompts/focus-scene.js +40 -0
  136. package/dist/memory/prompts/graph-extraction-batch.d.ts +14 -0
  137. package/dist/memory/prompts/graph-extraction-batch.js +54 -0
  138. package/dist/memory/prompts/graph-extraction.d.ts +2 -0
  139. package/dist/memory/prompts/graph-extraction.js +53 -0
  140. package/dist/memory/prompts/l1-contradiction-batch.d.ts +16 -0
  141. package/dist/memory/prompts/l1-contradiction-batch.js +47 -0
  142. package/dist/memory/prompts/l1-contradiction.d.ts +1 -0
  143. package/dist/memory/prompts/l1-contradiction.js +25 -0
  144. package/dist/memory/prompts/l1-extraction.d.ts +10 -0
  145. package/dist/memory/prompts/l1-extraction.js +114 -0
  146. package/dist/memory/prompts/l2-direction-shift.d.ts +5 -0
  147. package/dist/memory/prompts/l2-direction-shift.js +32 -0
  148. package/dist/memory/prompts/l2-scene-cluster.d.ts +2 -0
  149. package/dist/memory/prompts/l2-scene-cluster.js +33 -0
  150. package/dist/memory/prompts/l2-scene.d.ts +7 -0
  151. package/dist/memory/prompts/l2-scene.js +40 -0
  152. package/dist/memory/prompts/l3-persona.d.ts +6 -0
  153. package/dist/memory/prompts/l3-persona.js +60 -0
  154. package/dist/memory/recall.d.ts +47 -0
  155. package/dist/memory/recall.js +427 -0
  156. package/dist/memory/redaction.d.ts +1 -0
  157. package/dist/memory/redaction.js +24 -0
  158. package/dist/memory/retry.d.ts +13 -0
  159. package/dist/memory/retry.js +53 -0
  160. package/dist/memory/scheduler.d.ts +9 -0
  161. package/dist/memory/scheduler.js +16 -0
  162. package/dist/memory/skill-hints-loader.d.ts +30 -0
  163. package/dist/memory/skill-hints-loader.js +100 -0
  164. package/dist/memory/store/embedding.d.ts +16 -0
  165. package/dist/memory/store/embedding.js +68 -0
  166. package/dist/memory/store/reranker.d.ts +24 -0
  167. package/dist/memory/store/reranker.js +83 -0
  168. package/dist/memory/store/sqlite.d.ts +167 -0
  169. package/dist/memory/store/sqlite.js +1816 -0
  170. package/dist/memory/store/types.d.ts +101 -0
  171. package/dist/memory/store/types.js +1 -0
  172. package/dist/memory/types.d.ts +207 -0
  173. package/dist/memory/types.js +7 -0
  174. package/dist/memory/validation.d.ts +441 -0
  175. package/dist/memory/validation.js +129 -0
  176. package/dist/memory/working/canvas.d.ts +5 -0
  177. package/dist/memory/working/canvas.js +43 -0
  178. package/dist/memory/working/offload.d.ts +71 -0
  179. package/dist/memory/working/offload.js +211 -0
  180. package/dist/memory/working/step-log.d.ts +16 -0
  181. package/dist/memory/working/step-log.js +35 -0
  182. package/dist/registry.d.ts +34 -0
  183. package/dist/registry.js +305 -0
  184. package/dist/resolver.d.ts +17 -0
  185. package/dist/resolver.js +126 -0
  186. package/dist/scripts/validate-foreign-workspace-path.d.ts +1 -0
  187. package/dist/scripts/validate-foreign-workspace-path.js +39 -0
  188. package/dist/tools/agent_memory_tools.d.ts +485 -0
  189. package/dist/tools/agent_memory_tools.js +793 -0
  190. package/dist/tools/create_skill.d.ts +46 -0
  191. package/dist/tools/create_skill.js +46 -0
  192. package/dist/tools/get_doc.d.ts +21 -0
  193. package/dist/tools/get_doc.js +24 -0
  194. package/dist/tools/get_persona.d.ts +15 -0
  195. package/dist/tools/get_persona.js +20 -0
  196. package/dist/tools/get_reference.d.ts +15 -0
  197. package/dist/tools/get_reference.js +20 -0
  198. package/dist/tools/get_skill.d.ts +34 -0
  199. package/dist/tools/get_skill.js +65 -0
  200. package/dist/tools/get_template_doc.d.ts +21 -0
  201. package/dist/tools/get_template_doc.js +24 -0
  202. package/dist/tools/list_docs.d.ts +15 -0
  203. package/dist/tools/list_docs.js +16 -0
  204. package/dist/tools/list_skills.d.ts +18 -0
  205. package/dist/tools/list_skills.js +17 -0
  206. package/dist/tools/list_template_docs.d.ts +15 -0
  207. package/dist/tools/list_template_docs.js +16 -0
  208. package/dist/tools/memory-engineering.d.ts +225 -0
  209. package/dist/tools/memory-engineering.js +284 -0
  210. package/dist/tools/memory-explain.d.ts +34 -0
  211. package/dist/tools/memory-explain.js +109 -0
  212. package/dist/tools/memory-governance.d.ts +171 -0
  213. package/dist/tools/memory-governance.js +224 -0
  214. package/dist/tools/memory-hooks.d.ts +67 -0
  215. package/dist/tools/memory-hooks.js +102 -0
  216. package/dist/tools/memory-working.d.ts +98 -0
  217. package/dist/tools/memory-working.js +101 -0
  218. package/dist/tools/memory_capture_turn.d.ts +66 -0
  219. package/dist/tools/memory_capture_turn.js +85 -0
  220. package/dist/tools/memory_consolidate.d.ts +55 -0
  221. package/dist/tools/memory_consolidate.js +176 -0
  222. package/dist/tools/memory_contradictions.d.ts +53 -0
  223. package/dist/tools/memory_contradictions.js +52 -0
  224. package/dist/tools/memory_graph_query.d.ts +51 -0
  225. package/dist/tools/memory_graph_query.js +35 -0
  226. package/dist/tools/memory_mark_cited.d.ts +43 -0
  227. package/dist/tools/memory_mark_cited.js +63 -0
  228. package/dist/tools/memory_recall.d.ts +77 -0
  229. package/dist/tools/memory_recall.js +81 -0
  230. package/dist/tools/memory_register_skill_hints.d.ts +49 -0
  231. package/dist/tools/memory_register_skill_hints.js +55 -0
  232. package/dist/tools/memory_resolve_session.d.ts +24 -0
  233. package/dist/tools/memory_resolve_session.js +133 -0
  234. package/dist/tools/memory_search.d.ts +146 -0
  235. package/dist/tools/memory_search.js +84 -0
  236. package/dist/tools/search_skills.d.ts +18 -0
  237. package/dist/tools/search_skills.js +17 -0
  238. package/dist/tools/update_doc.d.ts +24 -0
  239. package/dist/tools/update_doc.js +35 -0
  240. package/dist/tools/update_skill.d.ts +30 -0
  241. package/dist/tools/update_skill.js +80 -0
  242. package/dist/types.d.ts +81 -0
  243. package/dist/types.js +4 -0
  244. package/dist/writer.d.ts +30 -0
  245. package/dist/writer.js +220 -0
  246. package/docs/TEMPLATE ONLY +1 -0
  247. package/docs/api/API.md +64 -0
  248. package/docs/api/security/SECURITY.md +58 -0
  249. package/docs/deployment/DockerDeployment.md +30 -0
  250. package/docs/design/Design.md +59 -0
  251. package/docs/design/themes/apple.md +101 -0
  252. package/docs/design/themes/dieter-grid.md +100 -0
  253. package/docs/design/themes/gallery-white.md +100 -0
  254. package/docs/design/themes/pinterest.md +101 -0
  255. package/docs/design/themes/realty-open-house.md +101 -0
  256. package/docs/design/themes/vodafone.md +101 -0
  257. package/docs/hooks/Hooks.md +30 -0
  258. package/docs/schema/Schema.md +35 -0
  259. package/docs/strategy/ScalingStrategy.md +19 -0
  260. package/package.json +88 -0
  261. package/references/accessibility-checklist.md +160 -0
  262. package/references/orchestration-patterns.md +370 -0
  263. package/references/performance-checklist.md +153 -0
  264. package/references/security-checklist.md +134 -0
  265. package/references/testing-patterns.md +236 -0
  266. package/skills/agent/adr-skill/SKILL.md +299 -0
  267. package/skills/agent/agentic-engineering-workflow/SKILL.md +95 -0
  268. package/skills/agent/bootstrap-skill/SKILL.md +103 -0
  269. package/skills/agent/context-engineering/SKILL.md +307 -0
  270. package/skills/agent/debugging-and-error-recovery/SKILL.md +308 -0
  271. package/skills/agent/developer-growth-analysis/SKILL.md +328 -0
  272. package/skills/agent/doubt-driven-skill/SKILL.md +249 -0
  273. package/skills/agent/handover-skill/SKILL.md +112 -0
  274. package/skills/agent/idea-refine-skill/SKILL.md +185 -0
  275. package/skills/agent/idea-refine-skill/examples.md +238 -0
  276. package/skills/agent/idea-refine-skill/frameworks.md +99 -0
  277. package/skills/agent/idea-refine-skill/refinement-criteria.md +113 -0
  278. package/skills/agent/interview-skill/SKILL.md +226 -0
  279. package/skills/agent/planning-skill/SKILL.md +270 -0
  280. package/skills/agent/skill-authoring/SKILL.md +189 -0
  281. package/skills/agent/source-driven-skill/SKILL.md +197 -0
  282. package/skills/agent/spec-driven-skill/SKILL.md +221 -0
  283. package/skills/agent/sync-skill/SKILL.md +92 -0
  284. package/skills/agent/using-agent-skills/SKILL.md +189 -0
  285. package/skills/api/a11y-skill/SKILL.md +88 -0
  286. package/skills/api/api-skill/SKILL.md +123 -0
  287. package/skills/api/auth-skill/SKILL.md +80 -0
  288. package/skills/api/debug-skill/SKILL.md +535 -0
  289. package/skills/api/performance-skill/SKILL.md +100 -0
  290. package/skills/api/testing-skill/SKILL.md +100 -0
  291. package/skills/codebase/code-review-and-quality/SKILL.md +228 -0
  292. package/skills/codebase/code-simplification/SKILL.md +352 -0
  293. package/skills/codebase/code-structure-cleanup/SKILL.md +142 -0
  294. package/skills/codebase/concerns-skill/SKILL.md +89 -0
  295. package/skills/codebase/conventions-skill/SKILL.md +95 -0
  296. package/skills/codebase/doc-management-skill/SKILL.md +47 -0
  297. package/skills/codebase/git-workflow-skill/SKILL.md +312 -0
  298. package/skills/communication/1-3-1-rule/SKILL.md +120 -0
  299. package/skills/design/brutalist-skill/SKILL.md +131 -0
  300. package/skills/design/concept-diagrams/SKILL.md +387 -0
  301. package/skills/design/concept-diagrams/examples/apartment-floor-plan-conversion.md +244 -0
  302. package/skills/design/concept-diagrams/examples/automated-password-reset-flow.md +276 -0
  303. package/skills/design/concept-diagrams/examples/autonomous-llm-research-agent-flow.md +240 -0
  304. package/skills/design/concept-diagrams/examples/banana-journey-tree-to-smoothie.md +161 -0
  305. package/skills/design/concept-diagrams/examples/commercial-aircraft-structure.md +209 -0
  306. package/skills/design/concept-diagrams/examples/cpu-ooo-microarchitecture.md +236 -0
  307. package/skills/design/concept-diagrams/examples/electricity-grid-flow.md +182 -0
  308. package/skills/design/concept-diagrams/examples/feature-film-production-pipeline.md +172 -0
  309. package/skills/design/concept-diagrams/examples/hospital-emergency-department-flow.md +165 -0
  310. package/skills/design/concept-diagrams/examples/ml-benchmark-grouped-bar-chart.md +114 -0
  311. package/skills/design/concept-diagrams/examples/place-order-uml-sequence.md +325 -0
  312. package/skills/design/concept-diagrams/examples/smart-city-infrastructure.md +173 -0
  313. package/skills/design/concept-diagrams/examples/smartphone-layer-anatomy.md +154 -0
  314. package/skills/design/concept-diagrams/examples/sn2-reaction-mechanism.md +247 -0
  315. package/skills/design/concept-diagrams/examples/wind-turbine-structure.md +338 -0
  316. package/skills/design/concept-diagrams/references/dashboard-patterns.md +43 -0
  317. package/skills/design/concept-diagrams/references/infrastructure-patterns.md +144 -0
  318. package/skills/design/concept-diagrams/references/physical-shape-cookbook.md +42 -0
  319. package/skills/design/concept-diagrams/templates/template.html +174 -0
  320. package/skills/design/gpt-tasteskill/SKILL.md +114 -0
  321. package/skills/design/minimalist-skill/SKILL.md +116 -0
  322. package/skills/design/output-skill/SKILL.md +87 -0
  323. package/skills/design/redesign-skill/SKILL.md +213 -0
  324. package/skills/design/soft-skill/SKILL.md +132 -0
  325. package/skills/design/stitch-skill/EXAMPLE.md +121 -0
  326. package/skills/design/stitch-skill/SKILL.md +222 -0
  327. package/skills/design/taste-skill/SKILL.md +269 -0
  328. package/skills/devops/ci-cd-skill/SKILL.md +402 -0
  329. package/skills/devops/docker-skill/SKILL.md +297 -0
  330. package/skills/devops/domain-skill/SKILL.md +234 -0
  331. package/skills/lifecycle/changelog-generator/SKILL.md +135 -0
  332. package/skills/lifecycle/incremental-skill/SKILL.md +257 -0
  333. package/skills/lifecycle/migration-skill/SKILL.md +218 -0
  334. package/skills/lifecycle/shipping-skill/SKILL.md +321 -0
  335. package/skills/memory/agent-memory/SKILL.md +122 -0
  336. package/skills/qa/browser-testing-skill/SKILL.md +314 -0
  337. package/skills/ux/adversarial-ux-skill/SKILL.md +168 -0
@@ -0,0 +1,1816 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { randomUUID } from "node:crypto";
3
+ import * as sqliteVec from "sqlite-vec";
4
+ // Ensure Node version has node:sqlite (v22+)
5
+ const DB_VERSION_ERROR = "Memory Engine requires Node.js v22+ with node:sqlite built-in.";
6
+ // A minimal BM25 search ranking helper (for simple text split)
7
+ function bm25RankToScore(rank) {
8
+ if (!Number.isFinite(rank))
9
+ return 1 / (1 + 999);
10
+ if (rank < 0) {
11
+ const relevance = -rank;
12
+ return relevance / (1 + relevance);
13
+ }
14
+ return 1 / (1 + rank);
15
+ }
16
+ function buildFtsQuery(raw) {
17
+ // Simple Unicode regex split for English + general tokens
18
+ const tokens = raw
19
+ .match(/[\p{L}\p{N}_]+/gu)
20
+ ?.map((t) => t.trim())
21
+ .filter(Boolean) ?? [];
22
+ if (tokens.length === 0)
23
+ return null;
24
+ const quoted = tokens.map((t) => `"${t.replaceAll('"', "")}"`);
25
+ return quoted.join(" OR ");
26
+ }
27
+ function parseJsonObject(raw) {
28
+ if (!raw)
29
+ return {};
30
+ try {
31
+ const parsed = JSON.parse(raw);
32
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
33
+ }
34
+ catch {
35
+ return {};
36
+ }
37
+ }
38
+ function parseJsonArray(raw) {
39
+ if (!raw)
40
+ return [];
41
+ try {
42
+ const parsed = JSON.parse(raw);
43
+ return Array.isArray(parsed) ? parsed.map(String) : [];
44
+ }
45
+ catch {
46
+ return [];
47
+ }
48
+ }
49
+ function cognitiveRowToRecord(row) {
50
+ return {
51
+ id: row.record_id,
52
+ userId: row.user_id,
53
+ sessionKey: row.session_key ?? "",
54
+ sessionId: row.session_id ?? "",
55
+ content: row.content,
56
+ type: row.type || "episodic",
57
+ priority: row.priority ?? 50,
58
+ sceneName: row.scene_name ?? "",
59
+ skillTag: row.skill_tag ?? "",
60
+ halfLifeDays: row.half_life_days ?? null,
61
+ supersededBy: row.superseded_by ?? null,
62
+ invalidAt: row.invalid_at ?? null,
63
+ timestampStr: row.timestamp_str ?? "",
64
+ timestampStart: row.timestamp_start ?? "",
65
+ timestampEnd: row.timestamp_end ?? "",
66
+ createdTime: row.created_time ?? "",
67
+ updatedTime: row.updated_time ?? "",
68
+ metadata: parseJsonObject(row.metadata_json),
69
+ confidence: typeof row.confidence === "number" ? row.confidence : 0.65,
70
+ status: row.status ?? (row.archived ? "archived" : "active"),
71
+ sourceKind: row.source_kind ?? "",
72
+ verificationStatus: row.verification_status ?? "",
73
+ repoPaths: parseJsonArray(row.repo_paths_json),
74
+ filePaths: parseJsonArray(row.file_paths_json),
75
+ commands: parseJsonArray(row.commands_json),
76
+ citationCount: row.citation_count ?? 0,
77
+ lastCitedAt: row.last_cited_at ?? null,
78
+ neverCitedCount: row.never_cited_count ?? 0,
79
+ archived: Boolean(row.archived),
80
+ };
81
+ }
82
+ function evidenceRowToRecord(row) {
83
+ return {
84
+ id: row.id,
85
+ userId: row.user_id,
86
+ recordId: row.record_id,
87
+ kind: row.kind,
88
+ ref: row.ref,
89
+ excerpt: row.excerpt ?? "",
90
+ observedAt: row.observed_at ?? "",
91
+ metadata: parseJsonObject(row.metadata_json),
92
+ };
93
+ }
94
+ function operationRowToRecord(row) {
95
+ return {
96
+ id: row.id,
97
+ userId: row.user_id,
98
+ recordId: row.record_id ?? null,
99
+ operation: row.operation,
100
+ actor: row.actor ?? "",
101
+ sessionKey: row.session_key ?? "",
102
+ reason: row.reason ?? "",
103
+ createdAt: row.created_at ?? "",
104
+ metadata: parseJsonObject(row.metadata_json),
105
+ };
106
+ }
107
+ export class SqliteMemoryStore {
108
+ db;
109
+ // Sensory statements
110
+ stmtSensoryUpsertMeta;
111
+ stmtSensoryQueryAfter;
112
+ // Cognitive statements
113
+ stmtCognitiveUpsertMeta;
114
+ stmtCognitiveGetMeta;
115
+ // FTS statements
116
+ stmtCognitiveFtsInsert;
117
+ stmtCognitiveFtsSearch;
118
+ // Vector statements
119
+ stmtCognitiveVecInsert;
120
+ stmtCognitiveVecDelete;
121
+ vecLoaded = false;
122
+ vecDimensions = 0;
123
+ constructor(dbPath) {
124
+ try {
125
+ this.db = new DatabaseSync(dbPath, { allowExtension: true });
126
+ }
127
+ catch (e) {
128
+ throw new Error(`${DB_VERSION_ERROR}\n${e}`);
129
+ }
130
+ this.db.exec("PRAGMA busy_timeout = 5000");
131
+ this.db.exec("PRAGMA journal_mode = WAL");
132
+ }
133
+ init() {
134
+ this.initSchema();
135
+ }
136
+ initVec(dimensions) {
137
+ if (dimensions <= 0)
138
+ return;
139
+ try {
140
+ sqliteVec.load(this.db);
141
+ this.vecLoaded = true;
142
+ }
143
+ catch (e) {
144
+ console.error("[BrainRouter] Failed to load sqlite-vec. Vector search disabled.", e);
145
+ return;
146
+ }
147
+ this.vecDimensions = dimensions;
148
+ this.db.exec(`
149
+ CREATE TABLE IF NOT EXISTS embedding_meta (
150
+ id INTEGER PRIMARY KEY CHECK (id = 1),
151
+ dimensions INTEGER NOT NULL,
152
+ created_at TEXT NOT NULL
153
+ )
154
+ `);
155
+ // Robust dimension check: extract actual dimension from the virtual table schema
156
+ const tableInfo = this.db.prepare("SELECT sql FROM sqlite_master WHERE name = 'cognitive_vec'").get();
157
+ let actualDimensions = -1;
158
+ if (tableInfo && tableInfo.sql) {
159
+ const match = tableInfo.sql.match(/float\[(\d+)\]/i);
160
+ if (match)
161
+ actualDimensions = parseInt(match[1], 10);
162
+ }
163
+ if (actualDimensions !== -1 && actualDimensions !== dimensions) {
164
+ console.error(`[BrainRouter] Embedding dimensions changed (${actualDimensions} -> ${dimensions}). Recreating vector tables.`);
165
+ try {
166
+ this.db.exec("DROP TABLE IF EXISTS cognitive_vec");
167
+ }
168
+ catch (e) {
169
+ console.warn("[BrainRouter] Error dropping cognitive_vec:", e);
170
+ }
171
+ this.db.prepare("UPDATE embedding_meta SET dimensions = ?, created_at = ? WHERE id = 1")
172
+ .run(dimensions, new Date().toISOString());
173
+ }
174
+ else {
175
+ const metaRow = this.db.prepare("SELECT dimensions FROM embedding_meta WHERE id = 1").get();
176
+ if (!metaRow) {
177
+ this.db.prepare("INSERT INTO embedding_meta (id, dimensions, created_at) VALUES (1, ?, ?)")
178
+ .run(dimensions, new Date().toISOString());
179
+ }
180
+ }
181
+ this.db.exec(`
182
+ CREATE VIRTUAL TABLE IF NOT EXISTS cognitive_vec USING vec0(
183
+ record_id TEXT PRIMARY KEY,
184
+ embedding float[${dimensions}] distance_metric=cosine
185
+ )
186
+ `);
187
+ this.stmtCognitiveVecInsert = this.db.prepare("INSERT INTO cognitive_vec (record_id, embedding) VALUES (?, ?)");
188
+ this.stmtCognitiveVecDelete = this.db.prepare("DELETE FROM cognitive_vec WHERE record_id = ?");
189
+ }
190
+ isVecAvailable() {
191
+ return this.vecLoaded && this.vecDimensions > 0;
192
+ }
193
+ async reembedStaleRecords(embedder) {
194
+ if (!this.vecLoaded)
195
+ return 0;
196
+ const rows = this.db.prepare(`
197
+ SELECT r.record_id, r.content
198
+ FROM cognitive_records r
199
+ LEFT JOIN cognitive_vec v ON r.record_id = v.record_id
200
+ WHERE r.invalid_at IS NULL
201
+ AND r.archived = 0
202
+ AND v.record_id IS NULL
203
+ ORDER BY r.created_time ASC, r.record_id ASC
204
+ `).all();
205
+ let successCount = 0;
206
+ for (const row of rows) {
207
+ try {
208
+ const embedding = await embedder(row.content);
209
+ this.upsertCognitiveVec(row.record_id, embedding);
210
+ successCount += 1;
211
+ }
212
+ catch (error) {
213
+ console.error(`[BrainRouter] Failed to re-embed record ${row.record_id}:`, error instanceof Error ? error.message : error);
214
+ }
215
+ }
216
+ return successCount;
217
+ }
218
+ getSqliteVersion() {
219
+ try {
220
+ const row = this.db.prepare("SELECT sqlite_version() AS version").get();
221
+ return row?.version ?? "unknown";
222
+ }
223
+ catch {
224
+ return "unknown";
225
+ }
226
+ }
227
+ initSchema() {
228
+ // ── Sensory Schema ──
229
+ this.db.exec(`
230
+ CREATE TABLE IF NOT EXISTS sensory_stream (
231
+ record_id TEXT PRIMARY KEY,
232
+ user_id TEXT NOT NULL,
233
+ session_key TEXT NOT NULL,
234
+ session_id TEXT DEFAULT '',
235
+ role TEXT NOT NULL DEFAULT '',
236
+ message_text TEXT NOT NULL,
237
+ recorded_at TEXT DEFAULT '',
238
+ timestamp INTEGER DEFAULT 0,
239
+ skill_tag TEXT DEFAULT '',
240
+ extracted_at TEXT DEFAULT NULL
241
+ )
242
+ `);
243
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_sensory_user_session ON sensory_stream(user_id, session_key)");
244
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_sensory_recorded ON sensory_stream(recorded_at)");
245
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_sensory_extracted ON sensory_stream(user_id, session_key, extracted_at)");
246
+ this.db.exec(`
247
+ CREATE TABLE IF NOT EXISTS users (
248
+ user_id TEXT PRIMARY KEY,
249
+ api_key TEXT NOT NULL UNIQUE,
250
+ password_hash TEXT DEFAULT NULL,
251
+ display_name TEXT DEFAULT '',
252
+ email TEXT DEFAULT '',
253
+ is_admin INTEGER DEFAULT 0,
254
+ status TEXT DEFAULT 'active',
255
+ created_at TEXT NOT NULL
256
+ )
257
+ `);
258
+ this.stmtSensoryUpsertMeta = this.db.prepare(`
259
+ INSERT INTO sensory_stream (
260
+ record_id, user_id, session_key, session_id, role, message_text, recorded_at, timestamp, skill_tag
261
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
262
+ ON CONFLICT(record_id) DO UPDATE SET
263
+ message_text=excluded.message_text,
264
+ recorded_at=excluded.recorded_at,
265
+ timestamp=excluded.timestamp
266
+ `);
267
+ this.stmtSensoryQueryAfter = this.db.prepare(`
268
+ SELECT record_id as id, user_id as userId, session_key as sessionKey, session_id as sessionId,
269
+ role, message_text as messageText, recorded_at as recordedAt, timestamp, skill_tag as skillTag
270
+ FROM sensory_stream
271
+ WHERE user_id = ? AND session_key = ? AND recorded_at > ? AND extracted_at IS NULL
272
+ ORDER BY recorded_at DESC
273
+ LIMIT ?
274
+ `);
275
+ // ── Cognitive Schema ──
276
+ this.db.exec(`
277
+ CREATE TABLE IF NOT EXISTS cognitive_records (
278
+ record_id TEXT PRIMARY KEY,
279
+ user_id TEXT NOT NULL,
280
+ session_key TEXT DEFAULT '',
281
+ session_id TEXT DEFAULT '',
282
+ content TEXT NOT NULL,
283
+ type TEXT DEFAULT '',
284
+ priority INTEGER DEFAULT 50,
285
+ scene_name TEXT DEFAULT '',
286
+ skill_tag TEXT DEFAULT '',
287
+ half_life_days INTEGER,
288
+ superseded_by TEXT,
289
+ invalid_at TEXT DEFAULT NULL,
290
+ timestamp_str TEXT DEFAULT '',
291
+ timestamp_start TEXT DEFAULT '',
292
+ timestamp_end TEXT DEFAULT '',
293
+ created_time TEXT DEFAULT '',
294
+ updated_time TEXT DEFAULT '',
295
+ metadata_json TEXT DEFAULT '{}',
296
+ citation_count INTEGER DEFAULT 0,
297
+ last_cited_at TEXT,
298
+ never_cited_count INTEGER DEFAULT 0,
299
+ archived INTEGER DEFAULT 0,
300
+ confidence REAL DEFAULT 0.65,
301
+ status TEXT DEFAULT 'active',
302
+ source_kind TEXT DEFAULT '',
303
+ verification_status TEXT DEFAULT '',
304
+ repo_paths_json TEXT DEFAULT '[]',
305
+ file_paths_json TEXT DEFAULT '[]',
306
+ commands_json TEXT DEFAULT '[]'
307
+ )
308
+ `);
309
+ this.db.exec(`
310
+ CREATE TABLE IF NOT EXISTS memory_evidence (
311
+ id TEXT PRIMARY KEY,
312
+ user_id TEXT NOT NULL,
313
+ record_id TEXT NOT NULL,
314
+ kind TEXT NOT NULL,
315
+ ref TEXT NOT NULL,
316
+ excerpt TEXT DEFAULT '',
317
+ observed_at TEXT NOT NULL,
318
+ metadata_json TEXT DEFAULT '{}'
319
+ )
320
+ `);
321
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_memory_evidence_record ON memory_evidence(user_id, record_id)");
322
+ this.db.exec(`
323
+ CREATE TABLE IF NOT EXISTS memory_operations (
324
+ id TEXT PRIMARY KEY,
325
+ user_id TEXT NOT NULL,
326
+ record_id TEXT DEFAULT NULL,
327
+ operation TEXT NOT NULL,
328
+ actor TEXT DEFAULT '',
329
+ session_key TEXT DEFAULT '',
330
+ reason TEXT DEFAULT '',
331
+ created_at TEXT NOT NULL,
332
+ metadata_json TEXT DEFAULT '{}'
333
+ )
334
+ `);
335
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_memory_operations_user_time ON memory_operations(user_id, created_at DESC, id ASC)");
336
+ this.db.exec(`
337
+ CREATE TABLE IF NOT EXISTS memory_file_index (
338
+ id TEXT PRIMARY KEY,
339
+ user_id TEXT NOT NULL,
340
+ record_id TEXT NOT NULL,
341
+ file_path TEXT NOT NULL,
342
+ symbol TEXT DEFAULT '',
343
+ created_time TEXT NOT NULL
344
+ )
345
+ `);
346
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_memory_file_index_path ON memory_file_index(user_id, file_path, created_time DESC)");
347
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_memory_file_index_record ON memory_file_index(user_id, record_id)");
348
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_cognitive_user_type ON cognitive_records(user_id, type)");
349
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_cognitive_user_session ON cognitive_records(user_id, session_key)");
350
+ this.stmtCognitiveUpsertMeta = this.db.prepare(`
351
+ INSERT INTO cognitive_records (
352
+ record_id, user_id, session_key, session_id, content, type, priority, scene_name, skill_tag,
353
+ half_life_days, superseded_by, invalid_at, timestamp_str, timestamp_start, timestamp_end,
354
+ created_time, updated_time, metadata_json, confidence, status, source_kind, verification_status,
355
+ repo_paths_json, file_paths_json, commands_json
356
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
357
+ ON CONFLICT(record_id) DO UPDATE SET
358
+ content=excluded.content,
359
+ type=excluded.type,
360
+ priority=excluded.priority,
361
+ scene_name=excluded.scene_name,
362
+ skill_tag=excluded.skill_tag,
363
+ half_life_days=excluded.half_life_days,
364
+ superseded_by=excluded.superseded_by,
365
+ invalid_at=excluded.invalid_at,
366
+ timestamp_str=excluded.timestamp_str,
367
+ timestamp_start=excluded.timestamp_start,
368
+ timestamp_end=excluded.timestamp_end,
369
+ updated_time=excluded.updated_time,
370
+ metadata_json=excluded.metadata_json,
371
+ confidence=excluded.confidence,
372
+ status=excluded.status,
373
+ source_kind=excluded.source_kind,
374
+ verification_status=excluded.verification_status,
375
+ repo_paths_json=excluded.repo_paths_json,
376
+ file_paths_json=excluded.file_paths_json,
377
+ commands_json=excluded.commands_json
378
+ `);
379
+ this.stmtCognitiveGetMeta = this.db.prepare(`
380
+ SELECT * FROM cognitive_records WHERE record_id = ? AND user_id = ?
381
+ `);
382
+ // ── Cognitive FTS5 Schema ──
383
+ this.db.exec(`
384
+ CREATE VIRTUAL TABLE IF NOT EXISTS cognitive_fts USING fts5(
385
+ content,
386
+ content_original UNINDEXED,
387
+ record_id UNINDEXED,
388
+ user_id UNINDEXED,
389
+ type UNINDEXED,
390
+ priority UNINDEXED,
391
+ scene_name UNINDEXED,
392
+ skill_tag UNINDEXED,
393
+ session_key UNINDEXED,
394
+ timestamp_str UNINDEXED,
395
+ created_time UNINDEXED
396
+ )
397
+ `);
398
+ this.stmtCognitiveFtsInsert = this.db.prepare(`
399
+ INSERT INTO cognitive_fts (
400
+ content, content_original, record_id, user_id, type, priority, scene_name,
401
+ skill_tag, session_key, timestamp_str, created_time
402
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
403
+ `);
404
+ this.stmtCognitiveFtsSearch = this.db.prepare(`
405
+ SELECT
406
+ f.record_id, f.user_id, f.content_original as content, f.type, f.priority, f.scene_name,
407
+ f.skill_tag, f.session_key, f.timestamp_str, f.created_time,
408
+ f.rank, r.citation_count
409
+ FROM cognitive_fts f
410
+ JOIN cognitive_records r ON f.record_id = r.record_id
411
+ WHERE f.user_id = ? AND cognitive_fts MATCH ? AND r.invalid_at IS NULL AND r.archived = 0
412
+ ORDER BY rank
413
+ LIMIT ?
414
+ `);
415
+ this.db.exec(`
416
+ CREATE TABLE IF NOT EXISTS contradictions (
417
+ id TEXT PRIMARY KEY,
418
+ user_id TEXT NOT NULL,
419
+ record_id_a TEXT NOT NULL,
420
+ record_id_b TEXT NOT NULL,
421
+ reason TEXT,
422
+ confidence REAL,
423
+ status TEXT DEFAULT 'pending', -- pending, resolved, dismissed
424
+ created_time TEXT DEFAULT ''
425
+ )
426
+ `);
427
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_contradictions_user ON contradictions(user_id, status)");
428
+ // ── Skill Extraction Hints Schema ──
429
+ this.db.exec(`
430
+ CREATE TABLE IF NOT EXISTS skill_extraction_hints (
431
+ skill_name TEXT PRIMARY KEY,
432
+ hints TEXT NOT NULL,
433
+ source_file TEXT DEFAULT '',
434
+ registered_at TEXT DEFAULT ''
435
+ )
436
+ `);
437
+ this.db.exec(`
438
+ CREATE TABLE IF NOT EXISTS skill_activations (
439
+ user_id TEXT NOT NULL,
440
+ skill_name TEXT NOT NULL,
441
+ potential REAL DEFAULT 0.0,
442
+ last_decay_time TEXT NOT NULL,
443
+ PRIMARY KEY (user_id, skill_name)
444
+ )
445
+ `);
446
+ // ── Contextual Focus Scenes ──
447
+ this.db.exec(`
448
+ CREATE TABLE IF NOT EXISTS contextual_focus (
449
+ id TEXT PRIMARY KEY,
450
+ user_id TEXT NOT NULL,
451
+ scene_name TEXT NOT NULL,
452
+ summary_md TEXT NOT NULL,
453
+ heat_score REAL DEFAULT 100.0,
454
+ last_active_time TEXT DEFAULT '',
455
+ created_time TEXT DEFAULT '',
456
+ updated_time TEXT DEFAULT '',
457
+ UNIQUE(user_id, scene_name)
458
+ )
459
+ `);
460
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_focus_user_heat ON contextual_focus(user_id, heat_score DESC)");
461
+ // ── Core Identity ──
462
+ this.db.exec(`
463
+ CREATE TABLE IF NOT EXISTS core_identity (
464
+ user_id TEXT PRIMARY KEY,
465
+ persona_md TEXT NOT NULL,
466
+ cognitive_count_at_generation INTEGER DEFAULT 0,
467
+ created_time TEXT DEFAULT '',
468
+ updated_time TEXT DEFAULT ''
469
+ )
470
+ `);
471
+ // ── Scheduler State ──
472
+ this.db.exec(`
473
+ CREATE TABLE IF NOT EXISTS scheduler_state (
474
+ user_id TEXT PRIMARY KEY,
475
+ cognitive_count_since_last_focus INTEGER DEFAULT 0,
476
+ cognitive_count_since_last_identity INTEGER DEFAULT 0,
477
+ total_cognitive_count INTEGER DEFAULT 0,
478
+ extraction_errors INTEGER DEFAULT 0,
479
+ last_error_message TEXT DEFAULT NULL,
480
+ last_error_at TEXT DEFAULT NULL
481
+ )
482
+ `);
483
+ // ── GraphRAG Nodes ──
484
+ this.db.exec(`
485
+ CREATE TABLE IF NOT EXISTS graph_nodes (
486
+ id TEXT PRIMARY KEY,
487
+ user_id TEXT NOT NULL,
488
+ entity TEXT NOT NULL,
489
+ entity_type TEXT NOT NULL,
490
+ skill_tag TEXT DEFAULT '',
491
+ confidence REAL DEFAULT 1.0,
492
+ source_record_id TEXT,
493
+ created_time TEXT DEFAULT '',
494
+ UNIQUE(user_id, entity)
495
+ )
496
+ `);
497
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_graph_nodes_user ON graph_nodes(user_id)");
498
+ // ── GraphRAG Edges ──
499
+ this.db.exec(`
500
+ CREATE TABLE IF NOT EXISTS graph_edges (
501
+ id TEXT PRIMARY KEY,
502
+ user_id TEXT NOT NULL,
503
+ from_node_id TEXT NOT NULL,
504
+ to_node_id TEXT NOT NULL,
505
+ relation TEXT NOT NULL,
506
+ skill_tag TEXT DEFAULT '',
507
+ confidence REAL DEFAULT 1.0,
508
+ source_record_id TEXT,
509
+ created_time TEXT DEFAULT '',
510
+ UNIQUE(user_id, from_node_id, to_node_id, relation)
511
+ )
512
+ `);
513
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_graph_edges_from ON graph_edges(from_node_id)");
514
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_graph_edges_to ON graph_edges(to_node_id)");
515
+ // ── Dendritic Spines (Cognitive Connections) ──
516
+ this.db.exec(`
517
+ CREATE TABLE IF NOT EXISTS cognitive_connections (
518
+ user_id TEXT NOT NULL,
519
+ source_id TEXT NOT NULL,
520
+ target_id TEXT NOT NULL,
521
+ weight REAL DEFAULT 0.5,
522
+ last_activated_at TEXT DEFAULT '',
523
+ PRIMARY KEY (user_id, source_id, target_id),
524
+ FOREIGN KEY (source_id) REFERENCES cognitive_records(record_id) ON DELETE CASCADE,
525
+ FOREIGN KEY (target_id) REFERENCES cognitive_records(record_id) ON DELETE CASCADE
526
+ )
527
+ `);
528
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_cognitive_conn_user_src ON cognitive_connections(user_id, source_id)");
529
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_cognitive_conn_user_tgt ON cognitive_connections(user_id, target_id)");
530
+ }
531
+ // ============================
532
+ // Sensory Stream Methods
533
+ // ============================
534
+ upsertSensory(record) {
535
+ this.db.exec("BEGIN");
536
+ try {
537
+ this.stmtSensoryUpsertMeta.run(record.id, record.userId, record.sessionKey, record.sessionId, record.role, record.messageText, record.recordedAt, record.timestamp, record.skillTag);
538
+ this.db.exec("COMMIT");
539
+ }
540
+ catch (e) {
541
+ this.db.exec("ROLLBACK");
542
+ throw e;
543
+ }
544
+ }
545
+ getRecentSensoryMessages(userId, sessionKey, limit, afterIsoTime = "") {
546
+ const rows = this.stmtSensoryQueryAfter.all(userId, sessionKey, afterIsoTime, limit);
547
+ // Reverse so chronologically they are oldest first
548
+ return rows.reverse().map(r => ({
549
+ id: r.id,
550
+ userId: r.userId,
551
+ sessionKey: r.sessionKey,
552
+ sessionId: r.sessionId,
553
+ role: r.role,
554
+ messageText: r.messageText,
555
+ recordedAt: r.recordedAt,
556
+ timestamp: r.timestamp,
557
+ skillTag: r.skillTag
558
+ }));
559
+ }
560
+ getUnextractedSensoryCount(userId, sessionKey) {
561
+ const stmtCount = this.db.prepare("SELECT COUNT(*) as count FROM sensory_stream WHERE user_id = ? AND session_key = ? AND extracted_at IS NULL");
562
+ const row = stmtCount.get(userId, sessionKey);
563
+ return row?.count || 0;
564
+ }
565
+ markSensoryExtracted(userId, sessionKey, recordIds, extractedAt = new Date().toISOString()) {
566
+ if (recordIds.length === 0)
567
+ return;
568
+ this.db.exec("BEGIN");
569
+ try {
570
+ const stmt = this.db.prepare("UPDATE sensory_stream SET extracted_at = ? WHERE user_id = ? AND session_key = ? AND record_id = ?");
571
+ for (const recordId of recordIds) {
572
+ stmt.run(extractedAt, userId, sessionKey, recordId);
573
+ }
574
+ this.db.exec("COMMIT");
575
+ }
576
+ catch (e) {
577
+ this.db.exec("ROLLBACK");
578
+ throw e;
579
+ }
580
+ }
581
+ // ============================
582
+ // Cognitive Methods
583
+ // ============================
584
+ upsertCognitiveBatch(entries, options) {
585
+ this.db.exec("BEGIN");
586
+ try {
587
+ const deleteFts = this.db.prepare("DELETE FROM cognitive_fts WHERE record_id = ? AND user_id = ?");
588
+ for (const entry of entries) {
589
+ const record = entry.record;
590
+ this.stmtCognitiveUpsertMeta.run(record.id, record.userId, record.sessionKey, record.sessionId, record.content, record.type, record.priority, record.sceneName, record.skillTag, record.halfLifeDays, record.supersededBy, record.invalidAt || null, record.timestampStr, record.timestampStart, record.timestampEnd, record.createdTime, record.updatedTime, JSON.stringify(record.metadata), record.confidence ?? 0.65, record.status ?? "active", record.sourceKind ?? "", record.verificationStatus ?? "", JSON.stringify(record.repoPaths ?? []), JSON.stringify(record.filePaths ?? []), JSON.stringify(record.commands ?? []));
591
+ // FTS5 Insert
592
+ deleteFts.run(record.id, record.userId);
593
+ this.stmtCognitiveFtsInsert.run(record.content, record.content, record.id, record.userId, record.type, record.priority, record.sceneName, record.skillTag, record.sessionKey, record.timestampStr, record.createdTime);
594
+ // Vector Insert
595
+ if (entry.embedding && this.vecLoaded && this.stmtCognitiveVecInsert && this.stmtCognitiveVecDelete) {
596
+ this.stmtCognitiveVecDelete.run(record.id);
597
+ this.stmtCognitiveVecInsert.run(record.id, entry.embedding);
598
+ }
599
+ this.replaceFileIndex(record);
600
+ if (!options?.skipAudit) {
601
+ this.insertOperation({
602
+ id: randomUUID(),
603
+ userId: record.userId,
604
+ recordId: record.id,
605
+ operation: "cognitive_upsert",
606
+ actor: "system",
607
+ sessionKey: record.sessionKey,
608
+ reason: "",
609
+ createdAt: new Date().toISOString(),
610
+ metadata: { batch: true, type: record.type },
611
+ });
612
+ }
613
+ }
614
+ this.db.exec("COMMIT");
615
+ }
616
+ catch (e) {
617
+ this.db.exec("ROLLBACK");
618
+ throw e;
619
+ }
620
+ }
621
+ upsertCognitive(record, options) {
622
+ this.db.exec("BEGIN");
623
+ try {
624
+ this.stmtCognitiveUpsertMeta.run(record.id, record.userId, record.sessionKey, record.sessionId, record.content, record.type, record.priority, record.sceneName, record.skillTag, record.halfLifeDays, record.supersededBy, record.invalidAt || null, record.timestampStr, record.timestampStart, record.timestampEnd, record.createdTime, record.updatedTime, JSON.stringify(record.metadata), record.confidence ?? 0.65, record.status ?? "active", record.sourceKind ?? "", record.verificationStatus ?? "", JSON.stringify(record.repoPaths ?? []), JSON.stringify(record.filePaths ?? []), JSON.stringify(record.commands ?? []));
625
+ // FTS5 Insert (delete old first if it exists to emulate UPSERT)
626
+ const deleteFts = this.db.prepare("DELETE FROM cognitive_fts WHERE record_id = ? AND user_id = ?");
627
+ deleteFts.run(record.id, record.userId);
628
+ this.stmtCognitiveFtsInsert.run(record.content, record.content, record.id, record.userId, record.type, record.priority, record.sceneName, record.skillTag, record.sessionKey, record.timestampStr, record.createdTime);
629
+ this.replaceFileIndex(record);
630
+ if (!options?.skipAudit) {
631
+ this.insertOperation({
632
+ id: randomUUID(),
633
+ userId: record.userId,
634
+ recordId: record.id,
635
+ operation: "cognitive_upsert",
636
+ actor: "system",
637
+ sessionKey: record.sessionKey,
638
+ reason: "",
639
+ createdAt: new Date().toISOString(),
640
+ metadata: { type: record.type },
641
+ });
642
+ }
643
+ this.db.exec("COMMIT");
644
+ }
645
+ catch (e) {
646
+ this.db.exec("ROLLBACK");
647
+ throw e;
648
+ }
649
+ }
650
+ invalidateCognitiveRecord(userId, recordId, supersededById) {
651
+ const stmt = this.db.prepare("UPDATE cognitive_records SET invalid_at = ?, superseded_by = ?, status = 'superseded' WHERE user_id = ? AND record_id = ?");
652
+ const now = new Date().toISOString();
653
+ stmt.run(now, supersededById, userId, recordId);
654
+ this.insertOperation({
655
+ id: randomUUID(),
656
+ userId,
657
+ recordId,
658
+ operation: "cognitive_supersede",
659
+ actor: "system",
660
+ sessionKey: "",
661
+ reason: `Superseded by ${supersededById}`,
662
+ createdAt: now,
663
+ metadata: { supersededById },
664
+ });
665
+ }
666
+ getMemoryById(userId, recordId) {
667
+ const row = this.stmtCognitiveGetMeta.get(recordId, userId);
668
+ return row ? cognitiveRowToRecord(row) : null;
669
+ }
670
+ getMemoriesByFilePath(userId, filePath, limit) {
671
+ const rows = this.db.prepare(`
672
+ SELECT r.*
673
+ FROM memory_file_index i
674
+ JOIN cognitive_records r ON r.user_id = i.user_id AND r.record_id = i.record_id
675
+ WHERE i.user_id = ?
676
+ AND (i.file_path = ? OR i.file_path LIKE ?)
677
+ AND r.invalid_at IS NULL
678
+ AND r.archived = 0
679
+ ORDER BY i.created_time DESC, r.priority DESC
680
+ LIMIT ?
681
+ `).all(userId, filePath, `%${filePath}%`, limit);
682
+ return rows.map(cognitiveRowToRecord);
683
+ }
684
+ updateCognitiveConfidence(userId, recordId, confidence, status) {
685
+ const now = new Date().toISOString();
686
+ this.db.prepare("UPDATE cognitive_records SET confidence = ?, status = ?, archived = CASE WHEN ? = 'archived' THEN 1 ELSE archived END, updated_time = ? WHERE user_id = ? AND record_id = ?").run(confidence, status, status, now, userId, recordId);
687
+ this.insertOperation({
688
+ id: randomUUID(),
689
+ userId,
690
+ recordId,
691
+ operation: "cognitive_status_update",
692
+ actor: "system",
693
+ sessionKey: "",
694
+ reason: "",
695
+ createdAt: now,
696
+ metadata: { confidence, status },
697
+ });
698
+ }
699
+ insertEvidence(ev) {
700
+ this.db.prepare(`
701
+ INSERT INTO memory_evidence (id, user_id, record_id, kind, ref, excerpt, observed_at, metadata_json)
702
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
703
+ ON CONFLICT(id) DO UPDATE SET
704
+ kind=excluded.kind,
705
+ ref=excluded.ref,
706
+ excerpt=excluded.excerpt,
707
+ observed_at=excluded.observed_at,
708
+ metadata_json=excluded.metadata_json
709
+ `).run(ev.id, ev.userId, ev.recordId, ev.kind, ev.ref, ev.excerpt, ev.observedAt, JSON.stringify(ev.metadata ?? {}));
710
+ this.insertOperation({
711
+ id: randomUUID(),
712
+ userId: ev.userId,
713
+ recordId: ev.recordId,
714
+ operation: "evidence_add",
715
+ actor: "system",
716
+ sessionKey: "",
717
+ reason: "",
718
+ createdAt: new Date().toISOString(),
719
+ metadata: { evidenceId: ev.id, kind: ev.kind, ref: ev.ref },
720
+ });
721
+ }
722
+ getEvidenceByRecord(userId, recordId) {
723
+ const rows = this.db.prepare(`
724
+ SELECT id, user_id, record_id, kind, ref, excerpt, observed_at, metadata_json
725
+ FROM memory_evidence
726
+ WHERE user_id = ? AND record_id = ?
727
+ ORDER BY observed_at DESC, id ASC
728
+ `).all(userId, recordId);
729
+ return rows.map(evidenceRowToRecord);
730
+ }
731
+ listEvidence(userId, filters, pagination) {
732
+ const where = ["user_id = ?"];
733
+ const args = [userId];
734
+ if (filters?.recordId) {
735
+ where.push("record_id = ?");
736
+ args.push(filters.recordId);
737
+ }
738
+ if (filters?.kind) {
739
+ where.push("kind = ?");
740
+ args.push(filters.kind);
741
+ }
742
+ if (pagination?.cursor) {
743
+ where.push("(observed_at < ? OR (observed_at = ? AND id > ?))");
744
+ args.push(pagination.cursor.observedAt, pagination.cursor.observedAt, pagination.cursor.id);
745
+ }
746
+ args.push(pagination?.limit ?? 100);
747
+ const rows = this.db.prepare(`
748
+ SELECT id, user_id, record_id, kind, ref, excerpt, observed_at, metadata_json
749
+ FROM memory_evidence
750
+ WHERE ${where.join(" AND ")}
751
+ ORDER BY observed_at DESC, id ASC
752
+ LIMIT ?
753
+ `).all(...args);
754
+ return rows.map(evidenceRowToRecord);
755
+ }
756
+ insertOperation(op) {
757
+ this.db.prepare(`
758
+ INSERT INTO memory_operations (id, user_id, record_id, operation, actor, session_key, reason, created_at, metadata_json)
759
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
760
+ ON CONFLICT(id) DO UPDATE SET
761
+ operation=excluded.operation,
762
+ actor=excluded.actor,
763
+ session_key=excluded.session_key,
764
+ reason=excluded.reason,
765
+ metadata_json=excluded.metadata_json
766
+ `).run(op.id, op.userId, op.recordId, op.operation, op.actor, op.sessionKey, op.reason, op.createdAt, JSON.stringify(op.metadata ?? {}));
767
+ }
768
+ getOperationLog(userId, options, filters) {
769
+ const where = ["user_id = ?"];
770
+ const args = [userId];
771
+ if (filters?.operation) {
772
+ where.push("operation = ?");
773
+ args.push(filters.operation);
774
+ }
775
+ if (filters?.sessionKey) {
776
+ where.push("session_key = ?");
777
+ args.push(filters.sessionKey);
778
+ }
779
+ if (filters?.createdAfter) {
780
+ where.push("created_at >= ?");
781
+ args.push(filters.createdAfter);
782
+ }
783
+ if (filters?.createdBefore) {
784
+ where.push("created_at <= ?");
785
+ args.push(filters.createdBefore);
786
+ }
787
+ if (options?.cursor) {
788
+ where.push("(created_at < ? OR (created_at = ? AND id > ?))");
789
+ args.push(options.cursor.createdAt, options.cursor.createdAt, options.cursor.id);
790
+ }
791
+ args.push(options?.limit ?? 100);
792
+ const rows = this.db.prepare(`
793
+ SELECT id, user_id, record_id, operation, actor, session_key, reason, created_at, metadata_json
794
+ FROM memory_operations
795
+ WHERE ${where.join(" AND ")}
796
+ ORDER BY created_at DESC, id ASC
797
+ LIMIT ?
798
+ `).all(...args);
799
+ return rows.map(operationRowToRecord);
800
+ }
801
+ exportMemories(userId) {
802
+ const memoryRows = this.db.prepare("SELECT * FROM cognitive_records WHERE user_id = ? ORDER BY created_time ASC, record_id ASC").all(userId);
803
+ const evidenceRows = this.db.prepare("SELECT * FROM memory_evidence WHERE user_id = ? ORDER BY observed_at ASC, id ASC").all(userId);
804
+ const operationRows = this.db.prepare("SELECT * FROM memory_operations WHERE user_id = ? ORDER BY created_at ASC, id ASC").all(userId);
805
+ return {
806
+ version: 1,
807
+ exportedAt: new Date().toISOString(),
808
+ userId,
809
+ memories: memoryRows.map(cognitiveRowToRecord),
810
+ evidence: evidenceRows.map(evidenceRowToRecord),
811
+ operations: operationRows.map(operationRowToRecord),
812
+ };
813
+ }
814
+ importMemories(userId, data) {
815
+ let importedMemories = 0;
816
+ let importedEvidence = 0;
817
+ let importedOperations = 0;
818
+ this.db.exec("BEGIN");
819
+ try {
820
+ for (const record of data.memories ?? []) {
821
+ this.stmtCognitiveUpsertMeta.run(record.id, userId, record.sessionKey, record.sessionId, record.content, record.type, record.priority, record.sceneName, record.skillTag, record.halfLifeDays, record.supersededBy, record.invalidAt || null, record.timestampStr, record.timestampStart, record.timestampEnd, record.createdTime, record.updatedTime, JSON.stringify(record.metadata ?? {}), record.confidence ?? 0.65, record.status ?? "active", record.sourceKind ?? "", record.verificationStatus ?? "", JSON.stringify(record.repoPaths ?? []), JSON.stringify(record.filePaths ?? []), JSON.stringify(record.commands ?? []));
822
+ this.db.prepare("DELETE FROM cognitive_fts WHERE record_id = ? AND user_id = ?").run(record.id, userId);
823
+ this.stmtCognitiveFtsInsert.run(record.content, record.content, record.id, userId, record.type, record.priority, record.sceneName, record.skillTag, record.sessionKey, record.timestampStr, record.createdTime);
824
+ this.replaceFileIndex({ ...record, userId });
825
+ importedMemories++;
826
+ }
827
+ for (const ev of data.evidence ?? []) {
828
+ this.db.prepare(`
829
+ INSERT INTO memory_evidence (id, user_id, record_id, kind, ref, excerpt, observed_at, metadata_json)
830
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
831
+ ON CONFLICT(id) DO UPDATE SET
832
+ kind=excluded.kind,
833
+ ref=excluded.ref,
834
+ excerpt=excluded.excerpt,
835
+ observed_at=excluded.observed_at,
836
+ metadata_json=excluded.metadata_json
837
+ `).run(ev.id, userId, ev.recordId, ev.kind, ev.ref, ev.excerpt, ev.observedAt, JSON.stringify(ev.metadata ?? {}));
838
+ importedEvidence++;
839
+ }
840
+ for (const op of data.operations ?? []) {
841
+ this.db.prepare(`
842
+ INSERT INTO memory_operations (id, user_id, record_id, operation, actor, session_key, reason, created_at, metadata_json)
843
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
844
+ ON CONFLICT(id) DO NOTHING
845
+ `).run(op.id, userId, op.recordId, op.operation, op.actor, op.sessionKey, op.reason, op.createdAt, JSON.stringify(op.metadata ?? {}));
846
+ importedOperations++;
847
+ }
848
+ const now = new Date().toISOString();
849
+ this.db.prepare(`
850
+ INSERT INTO memory_operations (id, user_id, record_id, operation, actor, session_key, reason, created_at, metadata_json)
851
+ VALUES (?, ?, NULL, 'import', 'system', '', '', ?, ?)
852
+ `).run(randomUUID(), userId, now, JSON.stringify({ importedMemories, importedEvidence, importedOperations }));
853
+ this.db.exec("COMMIT");
854
+ }
855
+ catch (e) {
856
+ this.db.exec("ROLLBACK");
857
+ throw e;
858
+ }
859
+ return { importedMemories, importedEvidence, importedOperations };
860
+ }
861
+ hardDeleteMemory(userId, recordId, reason) {
862
+ const now = new Date().toISOString();
863
+ this.db.exec("BEGIN");
864
+ try {
865
+ this.db.prepare("DELETE FROM memory_evidence WHERE user_id = ? AND record_id = ?").run(userId, recordId);
866
+ this.db.prepare("DELETE FROM memory_file_index WHERE user_id = ? AND record_id = ?").run(userId, recordId);
867
+ this.db.prepare("DELETE FROM cognitive_fts WHERE user_id = ? AND record_id = ?").run(userId, recordId);
868
+ if (this.stmtCognitiveVecDelete) {
869
+ this.stmtCognitiveVecDelete.run(recordId);
870
+ }
871
+ this.db.prepare("DELETE FROM cognitive_records WHERE user_id = ? AND record_id = ?").run(userId, recordId);
872
+ this.db.prepare(`
873
+ INSERT INTO memory_operations (id, user_id, record_id, operation, actor, session_key, reason, created_at, metadata_json)
874
+ VALUES (?, ?, ?, 'governance_delete', 'system', '', ?, ?, '{}')
875
+ `).run(randomUUID(), userId, recordId, reason, now);
876
+ this.db.exec("COMMIT");
877
+ }
878
+ catch (e) {
879
+ this.db.exec("ROLLBACK");
880
+ throw e;
881
+ }
882
+ }
883
+ searchCognitiveFts(userId, query, limit) {
884
+ const ftsQuery = buildFtsQuery(query);
885
+ if (!ftsQuery)
886
+ return [];
887
+ const rows = this.stmtCognitiveFtsSearch.all(userId, ftsQuery, limit);
888
+ return rows.map(r => ({
889
+ record_id: r.record_id,
890
+ user_id: r.user_id,
891
+ content: r.content,
892
+ type: r.type,
893
+ priority: r.priority,
894
+ scene_name: r.scene_name,
895
+ skill_tag: r.skill_tag,
896
+ score: bm25RankToScore(r.rank),
897
+ timestamp_str: r.timestamp_str,
898
+ timestamp_start: "",
899
+ timestamp_end: "",
900
+ session_key: r.session_key,
901
+ session_id: "",
902
+ metadata_json: "{}",
903
+ created_time: r.created_time,
904
+ citation_count: r.citation_count ?? 0
905
+ }));
906
+ }
907
+ searchCognitiveFtsAsOf(userId, query, limit, asOf) {
908
+ const ftsQuery = buildFtsQuery(query);
909
+ if (!ftsQuery)
910
+ return [];
911
+ const stmt = this.db.prepare(`
912
+ SELECT
913
+ f.record_id, f.user_id, f.content_original as content, f.type, f.priority, f.scene_name,
914
+ f.skill_tag, f.session_key, f.timestamp_str, f.created_time,
915
+ f.rank
916
+ FROM cognitive_fts f
917
+ JOIN cognitive_records r ON f.record_id = r.record_id
918
+ WHERE f.user_id = ?
919
+ AND cognitive_fts MATCH ?
920
+ AND r.created_time <= ? -- memory must have existed at asOf
921
+ AND (r.invalid_at IS NULL OR r.invalid_at > ?) -- must have been valid at asOf
922
+ AND r.archived = 0
923
+ ORDER BY rank
924
+ LIMIT ?
925
+ `);
926
+ const rows = stmt.all(userId, ftsQuery, asOf, asOf, limit);
927
+ return rows.map(r => ({
928
+ record_id: r.record_id,
929
+ user_id: r.user_id,
930
+ content: r.content,
931
+ type: r.type,
932
+ priority: r.priority,
933
+ scene_name: r.scene_name,
934
+ skill_tag: r.skill_tag,
935
+ score: bm25RankToScore(r.rank),
936
+ timestamp_str: r.timestamp_str,
937
+ timestamp_start: "",
938
+ timestamp_end: "",
939
+ session_key: r.session_key,
940
+ session_id: "",
941
+ metadata_json: "{}",
942
+ created_time: r.created_time
943
+ }));
944
+ }
945
+ upsertCognitiveVec(recordId, embedding) {
946
+ if (!this.vecLoaded)
947
+ return;
948
+ if (this.vecDimensions !== embedding.length) {
949
+ this.initVec(embedding.length);
950
+ }
951
+ if (!this.stmtCognitiveVecInsert || !this.stmtCognitiveVecDelete)
952
+ return;
953
+ this.db.exec("BEGIN");
954
+ try {
955
+ this.stmtCognitiveVecDelete.run(recordId);
956
+ this.stmtCognitiveVecInsert.run(recordId, embedding);
957
+ this.db.exec("COMMIT");
958
+ }
959
+ catch (e) {
960
+ this.db.exec("ROLLBACK");
961
+ throw e;
962
+ }
963
+ }
964
+ searchCognitiveVec(userId, queryEmbedding, limit) {
965
+ if (!this.vecLoaded)
966
+ return [];
967
+ if (this.vecDimensions !== queryEmbedding.length) {
968
+ this.initVec(queryEmbedding.length);
969
+ }
970
+ if (!this.vecDimensions)
971
+ return [];
972
+ const stmt = this.db.prepare(`
973
+ SELECT
974
+ v.record_id, v.distance,
975
+ r.user_id, r.content, r.type, r.priority, r.scene_name, r.skill_tag,
976
+ r.session_key, r.timestamp_str, r.created_time
977
+ FROM cognitive_vec v
978
+ JOIN cognitive_records r ON v.record_id = r.record_id
979
+ WHERE v.embedding MATCH ? AND k = ? AND r.user_id = ? AND r.invalid_at IS NULL AND r.archived = 0
980
+ ORDER BY distance
981
+ `);
982
+ try {
983
+ const rows = stmt.all(queryEmbedding, limit, userId);
984
+ return rows.map(r => ({
985
+ record_id: r.record_id,
986
+ user_id: r.user_id,
987
+ content: r.content,
988
+ type: r.type,
989
+ priority: r.priority,
990
+ scene_name: r.scene_name,
991
+ skill_tag: r.skill_tag,
992
+ score: 1 - r.distance,
993
+ timestamp_str: r.timestamp_str,
994
+ timestamp_start: "",
995
+ timestamp_end: "",
996
+ session_key: r.session_key,
997
+ session_id: "",
998
+ metadata_json: "{}",
999
+ created_time: r.created_time
1000
+ }));
1001
+ }
1002
+ catch (e) {
1003
+ console.error("[BrainRouter] Vector search failed:", e);
1004
+ return [];
1005
+ }
1006
+ }
1007
+ upsertContradiction(data) {
1008
+ const stmt = this.db.prepare(`
1009
+ INSERT INTO contradictions (id, user_id, record_id_a, record_id_b, reason, confidence, created_time)
1010
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1011
+ ON CONFLICT(id) DO UPDATE SET
1012
+ reason=excluded.reason,
1013
+ confidence=excluded.confidence
1014
+ `);
1015
+ stmt.run(data.id, data.userId, data.recordIdA, data.recordIdB, data.reason, data.confidence, new Date().toISOString());
1016
+ }
1017
+ getPendingContradictions(userId, pagination) {
1018
+ const where = ["c.user_id = ?", "c.status = 'pending'"];
1019
+ const args = [userId];
1020
+ if (pagination?.cursor) {
1021
+ where.push("(c.confidence < ? OR (c.confidence = ? AND c.id > ?))");
1022
+ args.push(pagination.cursor.confidence, pagination.cursor.confidence, pagination.cursor.id);
1023
+ }
1024
+ args.push(pagination?.limit ?? 20);
1025
+ const stmt = this.db.prepare(`
1026
+ SELECT c.*, r1.content as content_a, r2.content as content_b
1027
+ FROM contradictions c
1028
+ JOIN cognitive_records r1 ON c.record_id_a = r1.record_id
1029
+ JOIN cognitive_records r2 ON c.record_id_b = r2.record_id
1030
+ WHERE ${where.join(" AND ")}
1031
+ ORDER BY c.confidence DESC, c.id ASC
1032
+ LIMIT ?
1033
+ `);
1034
+ return stmt.all(...args);
1035
+ }
1036
+ resolveContradiction(id, userId, status) {
1037
+ const stmt = this.db.prepare("UPDATE contradictions SET status = ? WHERE id = ? AND user_id = ?");
1038
+ stmt.run(status, id, userId);
1039
+ this.insertOperation({
1040
+ id: randomUUID(),
1041
+ userId,
1042
+ recordId: id,
1043
+ operation: "contradiction_resolve",
1044
+ actor: "system",
1045
+ sessionKey: "",
1046
+ reason: status,
1047
+ createdAt: new Date().toISOString(),
1048
+ metadata: { contradictionId: id, status },
1049
+ });
1050
+ }
1051
+ // ============================
1052
+ // Skill Hints Methods
1053
+ // ============================
1054
+ upsertSkillHints(skillName, hints, sourceFile = "") {
1055
+ const stmt = this.db.prepare(`
1056
+ INSERT INTO skill_extraction_hints (skill_name, hints, source_file, registered_at)
1057
+ VALUES (?, ?, ?, ?)
1058
+ ON CONFLICT(skill_name) DO UPDATE SET
1059
+ hints=excluded.hints,
1060
+ source_file=excluded.source_file,
1061
+ registered_at=excluded.registered_at
1062
+ `);
1063
+ stmt.run(skillName, hints, sourceFile, new Date().toISOString());
1064
+ }
1065
+ listSkillHints() {
1066
+ const stmt = this.db.prepare("SELECT skill_name, hints, source_file, registered_at FROM skill_extraction_hints ORDER BY registered_at DESC");
1067
+ const rows = stmt.all();
1068
+ return rows.map(r => ({
1069
+ skillName: r.skill_name,
1070
+ hints: r.hints,
1071
+ sourceFile: r.source_file,
1072
+ registeredAt: r.registered_at
1073
+ }));
1074
+ }
1075
+ // ============================
1076
+ // Contextual Focus Methods
1077
+ // ============================
1078
+ upsertContextualFocus(record) {
1079
+ const stmt = this.db.prepare(`
1080
+ INSERT INTO contextual_focus (id, user_id, scene_name, summary_md, heat_score, last_active_time, created_time, updated_time)
1081
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1082
+ ON CONFLICT(user_id, scene_name) DO UPDATE SET
1083
+ summary_md=excluded.summary_md,
1084
+ heat_score=excluded.heat_score,
1085
+ last_active_time=excluded.last_active_time,
1086
+ updated_time=excluded.updated_time
1087
+ `);
1088
+ stmt.run(record.id, record.userId, record.sceneName, record.summaryMd, record.heatScore, record.lastActiveTime, record.createdTime, record.updatedTime);
1089
+ }
1090
+ getTopContextualFocus(userId, limit = 3, cursor) {
1091
+ const where = ["user_id = ?"];
1092
+ const args = [userId];
1093
+ if (cursor) {
1094
+ where.push("(heat_score < ? OR (heat_score = ? AND id > ?))");
1095
+ args.push(cursor.heatScore, cursor.heatScore, cursor.id);
1096
+ }
1097
+ args.push(limit);
1098
+ const stmt = this.db.prepare(`SELECT id, user_id, scene_name, summary_md, heat_score, last_active_time, created_time, updated_time
1099
+ FROM contextual_focus
1100
+ WHERE ${where.join(" AND ")}
1101
+ ORDER BY heat_score DESC, id ASC
1102
+ LIMIT ?`);
1103
+ const rows = stmt.all(...args);
1104
+ return rows.map(r => ({
1105
+ id: r.id, userId: r.user_id, sceneName: r.scene_name,
1106
+ summaryMd: r.summary_md, heatScore: r.heat_score,
1107
+ lastActiveTime: r.last_active_time, createdTime: r.created_time, updatedTime: r.updated_time
1108
+ }));
1109
+ }
1110
+ decayContextualFocusHeatScores(userId, decayFactor = 0.95) {
1111
+ const stmt = this.db.prepare("UPDATE contextual_focus SET heat_score = heat_score * ? WHERE user_id = ?");
1112
+ stmt.run(decayFactor, userId);
1113
+ }
1114
+ boostContextualFocusHeatScore(userId, sceneName, boost = 20) {
1115
+ const stmt = this.db.prepare("UPDATE contextual_focus SET heat_score = MIN(100.0, heat_score + ?), last_active_time = ? WHERE user_id = ? AND scene_name = ?");
1116
+ stmt.run(boost, new Date().toISOString(), userId, sceneName);
1117
+ }
1118
+ getCognitivesByFocus(userId, sceneName, limit = 30) {
1119
+ const stmt = this.db.prepare("SELECT record_id, content, type, priority, skill_tag, created_time FROM cognitive_records WHERE user_id = ? AND scene_name = ? AND invalid_at IS NULL ORDER BY priority DESC LIMIT ?");
1120
+ return stmt.all(userId, sceneName, limit);
1121
+ }
1122
+ getContextualFocusCount(userId) {
1123
+ const stmt = this.db.prepare("SELECT COUNT(*) as count FROM contextual_focus WHERE user_id = ?");
1124
+ const row = stmt.get(userId);
1125
+ return row?.count || 0;
1126
+ }
1127
+ getColdContextualFocus(userId, limit) {
1128
+ const stmt = this.db.prepare("SELECT id, user_id, scene_name, summary_md, heat_score, last_active_time, created_time, updated_time FROM contextual_focus WHERE user_id = ? ORDER BY heat_score ASC LIMIT ?");
1129
+ const rows = stmt.all(userId, limit);
1130
+ return rows.map(r => ({
1131
+ id: r.id, userId: r.user_id, sceneName: r.scene_name,
1132
+ summaryMd: r.summary_md, heatScore: r.heat_score,
1133
+ lastActiveTime: r.last_active_time, createdTime: r.created_time, updatedTime: r.updated_time
1134
+ }));
1135
+ }
1136
+ deleteContextualFocus(userId, sceneIds) {
1137
+ if (sceneIds.length === 0)
1138
+ return;
1139
+ const placeholders = sceneIds.map(() => "?").join(",");
1140
+ const stmt = this.db.prepare(`DELETE FROM contextual_focus WHERE user_id = ? AND id IN (${placeholders})`);
1141
+ stmt.run(userId, ...sceneIds);
1142
+ }
1143
+ getContextualFocusByName(userId, sceneName) {
1144
+ const stmt = this.db.prepare("SELECT id, user_id, scene_name, summary_md, heat_score, last_active_time, created_time, updated_time FROM contextual_focus WHERE user_id = ? AND scene_name = ?");
1145
+ const row = stmt.get(userId, sceneName);
1146
+ if (!row)
1147
+ return null;
1148
+ return {
1149
+ id: row.id, userId: row.user_id, sceneName: row.scene_name,
1150
+ summaryMd: row.summary_md, heatScore: row.heat_score,
1151
+ lastActiveTime: row.last_active_time, createdTime: row.created_time, updatedTime: row.updated_time
1152
+ };
1153
+ }
1154
+ getDistinctSceneNames(userId) {
1155
+ const stmt = this.db.prepare("SELECT DISTINCT scene_name FROM cognitive_records WHERE user_id = ? AND scene_name != ''");
1156
+ const rows = stmt.all(userId);
1157
+ return rows.map(r => r.scene_name);
1158
+ }
1159
+ renameFocusInCognitiveRecords(userId, oldName, canonicalName) {
1160
+ this.db.exec("BEGIN");
1161
+ try {
1162
+ const stmtUpdate = this.db.prepare("UPDATE cognitive_records SET scene_name = ?, updated_time = ? WHERE user_id = ? AND scene_name = ?");
1163
+ stmtUpdate.run(canonicalName, new Date().toISOString(), userId, oldName);
1164
+ const stmtDelete = this.db.prepare("DELETE FROM contextual_focus WHERE user_id = ? AND scene_name = ?");
1165
+ stmtDelete.run(userId, oldName);
1166
+ this.db.exec("COMMIT");
1167
+ }
1168
+ catch (e) {
1169
+ this.db.exec("ROLLBACK");
1170
+ throw e;
1171
+ }
1172
+ }
1173
+ // ============================
1174
+ // Core Identity Methods
1175
+ // ============================
1176
+ upsertCoreIdentity(record) {
1177
+ const stmt = this.db.prepare(`
1178
+ INSERT INTO core_identity (user_id, persona_md, cognitive_count_at_generation, created_time, updated_time)
1179
+ VALUES (?, ?, ?, ?, ?)
1180
+ ON CONFLICT(user_id) DO UPDATE SET
1181
+ persona_md=excluded.persona_md,
1182
+ cognitive_count_at_generation=excluded.cognitive_count_at_generation,
1183
+ updated_time=excluded.updated_time
1184
+ `);
1185
+ stmt.run(record.userId, record.personaMd, record.cognitiveCountAtGeneration, record.createdTime, record.updatedTime);
1186
+ }
1187
+ getCoreIdentity(userId) {
1188
+ const stmt = this.db.prepare("SELECT user_id, persona_md, cognitive_count_at_generation, created_time, updated_time FROM core_identity WHERE user_id = ?");
1189
+ const row = stmt.get(userId);
1190
+ if (!row)
1191
+ return null;
1192
+ return {
1193
+ userId: row.user_id, personaMd: row.persona_md,
1194
+ cognitiveCountAtGeneration: row.cognitive_count_at_generation,
1195
+ createdTime: row.created_time, updatedTime: row.updated_time
1196
+ };
1197
+ }
1198
+ getIdentityAndInstructionCognitives(userId, limit = 100) {
1199
+ const stmt = this.db.prepare("SELECT record_id, content, type, priority, skill_tag, created_time FROM cognitive_records WHERE user_id = ? AND type IN ('persona','instruction') AND invalid_at IS NULL ORDER BY priority DESC, created_time DESC LIMIT ?");
1200
+ return stmt.all(userId, limit);
1201
+ }
1202
+ // ============================
1203
+ // Scheduler State Methods
1204
+ // ============================
1205
+ getSchedulerState(userId) {
1206
+ const stmt = this.db.prepare("SELECT cognitive_count_since_last_focus, cognitive_count_since_last_identity, total_cognitive_count, extraction_errors, last_error_message, last_error_at FROM scheduler_state WHERE user_id = ?");
1207
+ const row = stmt.get(userId);
1208
+ if (!row) {
1209
+ return {
1210
+ cognitiveCountSinceLastFocus: 0,
1211
+ cognitiveCountSinceLastIdentity: 0,
1212
+ totalCognitiveCount: 0,
1213
+ extractionErrors: 0,
1214
+ lastErrorMessage: null,
1215
+ lastErrorAt: null,
1216
+ };
1217
+ }
1218
+ return {
1219
+ cognitiveCountSinceLastFocus: row.cognitive_count_since_last_focus,
1220
+ cognitiveCountSinceLastIdentity: row.cognitive_count_since_last_identity,
1221
+ totalCognitiveCount: row.total_cognitive_count,
1222
+ extractionErrors: row.extraction_errors ?? 0,
1223
+ lastErrorMessage: row.last_error_message ?? null,
1224
+ lastErrorAt: row.last_error_at ?? null,
1225
+ };
1226
+ }
1227
+ incrementSchedulerCognitiveCount(userId, count) {
1228
+ const stmt = this.db.prepare(`
1229
+ INSERT INTO scheduler_state (user_id, cognitive_count_since_last_focus, cognitive_count_since_last_identity, total_cognitive_count)
1230
+ VALUES (?, ?, ?, ?)
1231
+ ON CONFLICT(user_id) DO UPDATE SET
1232
+ cognitive_count_since_last_focus = cognitive_count_since_last_focus + excluded.cognitive_count_since_last_focus,
1233
+ cognitive_count_since_last_identity = cognitive_count_since_last_identity + excluded.cognitive_count_since_last_identity,
1234
+ total_cognitive_count = total_cognitive_count + excluded.total_cognitive_count
1235
+ `);
1236
+ stmt.run(userId, count, count, count);
1237
+ }
1238
+ resetSchedulerFocusCount(userId) {
1239
+ const stmt = this.db.prepare("UPDATE scheduler_state SET cognitive_count_since_last_focus = 0 WHERE user_id = ?");
1240
+ stmt.run(userId);
1241
+ }
1242
+ resetSchedulerIdentityCount(userId) {
1243
+ const stmt = this.db.prepare("UPDATE scheduler_state SET cognitive_count_since_last_identity = 0 WHERE user_id = ?");
1244
+ stmt.run(userId);
1245
+ }
1246
+ recordExtractionFailure(userId, message) {
1247
+ const now = new Date().toISOString();
1248
+ const stmt = this.db.prepare(`
1249
+ INSERT INTO scheduler_state (user_id, extraction_errors, last_error_message, last_error_at)
1250
+ VALUES (?, 1, ?, ?)
1251
+ ON CONFLICT(user_id) DO UPDATE SET
1252
+ extraction_errors = COALESCE(extraction_errors, 0) + 1,
1253
+ last_error_message = excluded.last_error_message,
1254
+ last_error_at = excluded.last_error_at
1255
+ `);
1256
+ stmt.run(userId, message.slice(0, 1000), now);
1257
+ }
1258
+ resetExtractionFailures(userId) {
1259
+ const stmt = this.db.prepare(`
1260
+ INSERT INTO scheduler_state (user_id, extraction_errors, last_error_message, last_error_at)
1261
+ VALUES (?, 0, NULL, NULL)
1262
+ ON CONFLICT(user_id) DO UPDATE SET
1263
+ extraction_errors = 0,
1264
+ last_error_message = NULL,
1265
+ last_error_at = NULL
1266
+ `);
1267
+ stmt.run(userId);
1268
+ }
1269
+ getExtractionStatus(userId) {
1270
+ const state = this.getSchedulerState(userId);
1271
+ return {
1272
+ extractionErrors: state.extractionErrors,
1273
+ lastErrorMessage: state.lastErrorMessage,
1274
+ lastErrorAt: state.lastErrorAt,
1275
+ syncPaused: state.extractionErrors >= 5,
1276
+ };
1277
+ }
1278
+ sweepUnextractedBacklog(options) {
1279
+ const cutoff = new Date(Date.now() - options.olderThanMs).toISOString();
1280
+ const minUnextracted = options.minUnextracted ?? 1;
1281
+ const maxFailures = options.maxFailures ?? 5;
1282
+ const limit = options.limit ?? 20;
1283
+ const rows = this.db.prepare(`
1284
+ SELECT
1285
+ l0.user_id,
1286
+ l0.session_key,
1287
+ COALESCE(MAX(l0.session_id), '') AS session_id,
1288
+ COUNT(*) AS unextracted_count,
1289
+ MAX(l0.recorded_at) AS latest_recorded_at,
1290
+ COALESCE(ss.extraction_errors, 0) AS extraction_errors,
1291
+ ss.last_error_message
1292
+ FROM sensory_stream l0
1293
+ LEFT JOIN scheduler_state ss ON ss.user_id = l0.user_id
1294
+ WHERE l0.extracted_at IS NULL
1295
+ GROUP BY l0.user_id, l0.session_key
1296
+ HAVING
1297
+ COUNT(*) >= ?
1298
+ AND MAX(l0.recorded_at) <= ?
1299
+ AND COALESCE(ss.extraction_errors, 0) < ?
1300
+ ORDER BY MAX(l0.recorded_at) ASC
1301
+ LIMIT ?
1302
+ `).all(minUnextracted, cutoff, maxFailures, limit);
1303
+ return rows.map((row) => ({
1304
+ userId: row.user_id,
1305
+ sessionKey: row.session_key,
1306
+ sessionId: row.session_id ?? "",
1307
+ unextractedCount: row.unextracted_count ?? 0,
1308
+ latestRecordedAt: row.latest_recorded_at ?? "",
1309
+ extractionErrors: row.extraction_errors ?? 0,
1310
+ lastErrorMessage: row.last_error_message ?? null,
1311
+ }));
1312
+ }
1313
+ // ============================
1314
+ // GraphRAG Methods
1315
+ // ============================
1316
+ getAllGraphNodes(userId) {
1317
+ const stmt = this.db.prepare("SELECT id, user_id, entity, entity_type, skill_tag, confidence, source_record_id, created_time FROM graph_nodes WHERE user_id = ?");
1318
+ const rows = stmt.all(userId);
1319
+ return rows.map(r => ({
1320
+ id: r.id, userId: r.user_id, entity: r.entity,
1321
+ entityType: r.entity_type, skillTag: r.skill_tag,
1322
+ confidence: r.confidence, sourceRecordId: r.source_record_id,
1323
+ createdTime: r.created_time
1324
+ }));
1325
+ }
1326
+ upsertGraphNode(node) {
1327
+ const stmt = this.db.prepare(`
1328
+ INSERT INTO graph_nodes (id, user_id, entity, entity_type, skill_tag, confidence, source_record_id, created_time)
1329
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1330
+ ON CONFLICT(id) DO UPDATE SET
1331
+ entity_type=excluded.entity_type,
1332
+ skill_tag=excluded.skill_tag,
1333
+ confidence=excluded.confidence,
1334
+ source_record_id=excluded.source_record_id
1335
+ `);
1336
+ stmt.run(node.id, node.userId, node.entity, node.entityType, node.skillTag || "", node.confidence, node.sourceRecordId, node.createdTime);
1337
+ }
1338
+ upsertGraphEdge(edge) {
1339
+ const stmt = this.db.prepare(`
1340
+ INSERT INTO graph_edges (id, user_id, from_node_id, to_node_id, relation, skill_tag, confidence, source_record_id, created_time)
1341
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
1342
+ ON CONFLICT(user_id, from_node_id, to_node_id, relation) DO UPDATE SET
1343
+ skill_tag=excluded.skill_tag,
1344
+ confidence=excluded.confidence,
1345
+ source_record_id=excluded.source_record_id,
1346
+ created_time=excluded.created_time
1347
+ `);
1348
+ stmt.run(edge.id, edge.userId, edge.fromNodeId, edge.toNodeId, edge.relation, edge.skillTag || "", edge.confidence, edge.sourceRecordId, edge.createdTime);
1349
+ }
1350
+ getGraphNodeByEntity(userId, entity) {
1351
+ const stmt = this.db.prepare("SELECT id, user_id, entity, entity_type, skill_tag, confidence, source_record_id, created_time FROM graph_nodes WHERE user_id = ? AND LOWER(entity) = LOWER(?)");
1352
+ const row = stmt.get(userId, entity);
1353
+ if (!row)
1354
+ return null;
1355
+ return {
1356
+ id: row.id,
1357
+ userId: row.user_id,
1358
+ entity: row.entity,
1359
+ entityType: row.entity_type,
1360
+ skillTag: row.skill_tag,
1361
+ confidence: row.confidence,
1362
+ sourceRecordId: row.source_record_id,
1363
+ createdTime: row.created_time
1364
+ };
1365
+ }
1366
+ getGraphNeighbors(userId, entityId, skillTag, maxHops = 2) {
1367
+ const visitedNodes = new Map();
1368
+ const visitedEdges = new Map();
1369
+ const stmtNodeById = this.db.prepare("SELECT id, user_id, entity, entity_type, skill_tag, confidence, source_record_id, created_time FROM graph_nodes WHERE user_id = ? AND id = ?");
1370
+ const startRow = stmtNodeById.get(userId, entityId);
1371
+ if (!startRow)
1372
+ return { nodes: [], edges: [] };
1373
+ const startNode = {
1374
+ id: startRow.id,
1375
+ userId: startRow.user_id,
1376
+ entity: startRow.entity,
1377
+ entityType: startRow.entity_type,
1378
+ skillTag: startRow.skill_tag,
1379
+ confidence: startRow.confidence,
1380
+ sourceRecordId: startRow.source_record_id,
1381
+ createdTime: startRow.created_time
1382
+ };
1383
+ visitedNodes.set(startNode.id, startNode);
1384
+ let queue = [startNode.id];
1385
+ let currentHop = 0;
1386
+ while (queue.length > 0 && currentHop < maxHops) {
1387
+ const nextQueue = [];
1388
+ for (const nodeId of queue) {
1389
+ const queryParams = [userId, nodeId, nodeId];
1390
+ let edgeSql = `
1391
+ SELECT id, user_id, from_node_id, to_node_id, relation, skill_tag, confidence, source_record_id, created_time
1392
+ FROM graph_edges
1393
+ WHERE user_id = ? AND (from_node_id = ? OR to_node_id = ?)
1394
+ `;
1395
+ if (skillTag) {
1396
+ edgeSql += " AND (skill_tag = ? OR skill_tag = '')";
1397
+ queryParams.push(skillTag);
1398
+ }
1399
+ const stmtEdges = this.db.prepare(edgeSql);
1400
+ const edgeRows = stmtEdges.all(...queryParams);
1401
+ for (const row of edgeRows) {
1402
+ const edge = {
1403
+ id: row.id,
1404
+ userId: row.user_id,
1405
+ fromNodeId: row.from_node_id,
1406
+ toNodeId: row.to_node_id,
1407
+ relation: row.relation,
1408
+ skillTag: row.skill_tag,
1409
+ confidence: row.confidence,
1410
+ sourceRecordId: row.source_record_id,
1411
+ createdTime: row.created_time
1412
+ };
1413
+ visitedEdges.set(edge.id, edge);
1414
+ const neighborId = edge.fromNodeId === nodeId ? edge.toNodeId : edge.fromNodeId;
1415
+ if (!visitedNodes.has(neighborId)) {
1416
+ const neighborRow = stmtNodeById.get(userId, neighborId);
1417
+ if (neighborRow) {
1418
+ const neighborNode = {
1419
+ id: neighborRow.id,
1420
+ userId: neighborRow.user_id,
1421
+ entity: neighborRow.entity,
1422
+ entityType: neighborRow.entity_type,
1423
+ skillTag: neighborRow.skill_tag,
1424
+ confidence: neighborRow.confidence,
1425
+ sourceRecordId: neighborRow.source_record_id,
1426
+ createdTime: neighborRow.created_time
1427
+ };
1428
+ visitedNodes.set(neighborId, neighborNode);
1429
+ nextQueue.push(neighborId);
1430
+ }
1431
+ }
1432
+ }
1433
+ }
1434
+ queue = nextQueue;
1435
+ currentHop++;
1436
+ }
1437
+ return {
1438
+ nodes: Array.from(visitedNodes.values()),
1439
+ edges: Array.from(visitedEdges.values())
1440
+ };
1441
+ }
1442
+ // ============================
1443
+ // ACE Feedback Loop Methods
1444
+ // ============================
1445
+ markCited(userId, recordIds) {
1446
+ if (recordIds.length === 0)
1447
+ return;
1448
+ const now = new Date().toISOString();
1449
+ const placeholders = recordIds.map(() => "?").join(",");
1450
+ const stmt = this.db.prepare(`
1451
+ UPDATE cognitive_records
1452
+ SET citation_count = citation_count + 1,
1453
+ last_cited_at = ?,
1454
+ never_cited_count = 0,
1455
+ updated_time = ?
1456
+ WHERE user_id = ? AND record_id IN (${placeholders})
1457
+ `);
1458
+ stmt.run(now, now, userId, ...recordIds);
1459
+ }
1460
+ incrementNeverCited(userId, recordIds) {
1461
+ if (recordIds.length === 0)
1462
+ return [];
1463
+ const now = new Date().toISOString();
1464
+ const placeholders = recordIds.map(() => "?").join(",");
1465
+ this.db.prepare(`
1466
+ UPDATE cognitive_records
1467
+ SET never_cited_count = never_cited_count + 1, updated_time = ?
1468
+ WHERE user_id = ? AND record_id IN (${placeholders})
1469
+ `).run(now, userId, ...recordIds);
1470
+ const rows = this.db.prepare(`
1471
+ SELECT record_id, never_cited_count FROM cognitive_records
1472
+ WHERE user_id = ? AND record_id IN (${placeholders})
1473
+ `).all(userId, ...recordIds);
1474
+ return rows.map(r => ({ recordId: r.record_id, neverCitedCount: r.never_cited_count }));
1475
+ }
1476
+ archiveCognitiveRecord(userId, recordId) {
1477
+ const now = new Date().toISOString();
1478
+ this.db.prepare("UPDATE cognitive_records SET archived = 1, status = 'archived', updated_time = ? WHERE user_id = ? AND record_id = ?").run(now, userId, recordId);
1479
+ this.insertOperation({
1480
+ id: randomUUID(),
1481
+ userId,
1482
+ recordId,
1483
+ operation: "archive",
1484
+ actor: "system",
1485
+ sessionKey: "",
1486
+ reason: "",
1487
+ createdAt: now,
1488
+ metadata: {},
1489
+ });
1490
+ }
1491
+ // ============================
1492
+ // Skill Pre-warming Helpers
1493
+ // ============================
1494
+ getRecentSkillContextCognitives(userId, limit) {
1495
+ const rows = this.db.prepare(`
1496
+ SELECT skill_tag, created_time FROM cognitive_records
1497
+ WHERE user_id = ? AND type = 'skill_context' AND skill_tag != '' AND invalid_at IS NULL AND archived = 0
1498
+ ORDER BY created_time DESC
1499
+ LIMIT ?
1500
+ `).all(userId, limit);
1501
+ return rows.map(r => ({ skillTag: r.skill_tag, createdTime: r.created_time }));
1502
+ }
1503
+ getSkillHints(skillName) {
1504
+ const row = this.db.prepare("SELECT hints FROM skill_extraction_hints WHERE skill_name = ?").get(skillName);
1505
+ return row?.hints ?? null;
1506
+ }
1507
+ getSkillActivations(userId) {
1508
+ const rows = this.db.prepare(`
1509
+ SELECT skill_name, potential, last_decay_time
1510
+ FROM skill_activations
1511
+ WHERE user_id = ?
1512
+ ORDER BY potential DESC, skill_name ASC
1513
+ `).all(userId);
1514
+ return rows.map((row) => ({
1515
+ skillName: row.skill_name,
1516
+ potential: row.potential,
1517
+ lastDecayTime: row.last_decay_time,
1518
+ }));
1519
+ }
1520
+ upsertSkillActivations(userId, activations) {
1521
+ if (activations.length === 0)
1522
+ return;
1523
+ const stmt = this.db.prepare(`
1524
+ INSERT INTO skill_activations (user_id, skill_name, potential, last_decay_time)
1525
+ VALUES (?, ?, ?, ?)
1526
+ ON CONFLICT(user_id, skill_name) DO UPDATE SET
1527
+ potential=excluded.potential,
1528
+ last_decay_time=excluded.last_decay_time
1529
+ `);
1530
+ this.db.exec("BEGIN");
1531
+ try {
1532
+ for (const record of activations) {
1533
+ stmt.run(userId, record.skillName, record.potential, record.lastDecayTime);
1534
+ }
1535
+ this.db.exec("COMMIT");
1536
+ }
1537
+ catch (error) {
1538
+ this.db.exec("ROLLBACK");
1539
+ throw error;
1540
+ }
1541
+ }
1542
+ createUser(userId, apiKey, displayName = "", isAdmin = false) {
1543
+ const createdAt = new Date().toISOString();
1544
+ this.db.prepare(`
1545
+ INSERT INTO users (user_id, api_key, password_hash, display_name, email, is_admin, status, created_at)
1546
+ VALUES (?, ?, NULL, ?, '', ?, 'active', ?)
1547
+ `).run(userId, apiKey, displayName, isAdmin ? 1 : 0, createdAt);
1548
+ return {
1549
+ userId,
1550
+ apiKey,
1551
+ passwordHash: null,
1552
+ displayName,
1553
+ email: "",
1554
+ isAdmin,
1555
+ status: "active",
1556
+ createdAt,
1557
+ };
1558
+ }
1559
+ getUserByApiKey(apiKey) {
1560
+ const row = this.db.prepare("SELECT user_id, api_key, password_hash, display_name, email, is_admin, status, created_at FROM users WHERE api_key = ?").get(apiKey);
1561
+ if (!row)
1562
+ return null;
1563
+ return {
1564
+ userId: row.user_id,
1565
+ apiKey: row.api_key,
1566
+ passwordHash: row.password_hash ?? null,
1567
+ displayName: row.display_name ?? "",
1568
+ email: row.email ?? "",
1569
+ isAdmin: Boolean(row.is_admin),
1570
+ status: row.status === "disabled" ? "disabled" : "active",
1571
+ createdAt: row.created_at,
1572
+ };
1573
+ }
1574
+ getUserByEmail(email) {
1575
+ const row = this.db.prepare("SELECT user_id, api_key, password_hash, display_name, email, is_admin, status, created_at FROM users WHERE lower(email) = lower(?)").get(email);
1576
+ if (!row)
1577
+ return null;
1578
+ return {
1579
+ userId: row.user_id,
1580
+ apiKey: row.api_key,
1581
+ passwordHash: row.password_hash ?? null,
1582
+ displayName: row.display_name ?? "",
1583
+ email: row.email ?? "",
1584
+ isAdmin: Boolean(row.is_admin),
1585
+ status: row.status === "disabled" ? "disabled" : "active",
1586
+ createdAt: row.created_at,
1587
+ };
1588
+ }
1589
+ getUserById(userId) {
1590
+ const row = this.db.prepare("SELECT user_id, api_key, password_hash, display_name, email, is_admin, status, created_at FROM users WHERE user_id = ?").get(userId);
1591
+ if (!row)
1592
+ return null;
1593
+ return {
1594
+ userId: row.user_id,
1595
+ apiKey: row.api_key,
1596
+ passwordHash: row.password_hash ?? null,
1597
+ displayName: row.display_name ?? "",
1598
+ email: row.email ?? "",
1599
+ isAdmin: Boolean(row.is_admin),
1600
+ status: row.status === "disabled" ? "disabled" : "active",
1601
+ createdAt: row.created_at,
1602
+ };
1603
+ }
1604
+ updateUserPassword(userId, passwordHash) {
1605
+ this.db.prepare("UPDATE users SET password_hash = ? WHERE user_id = ?").run(passwordHash, userId);
1606
+ }
1607
+ updateUserEmail(userId, email) {
1608
+ this.db.prepare("UPDATE users SET email = ? WHERE user_id = ?").run(email, userId);
1609
+ }
1610
+ updateUserDisplayName(userId, displayName) {
1611
+ this.db.prepare("UPDATE users SET display_name = ? WHERE user_id = ?").run(displayName, userId);
1612
+ }
1613
+ updateUserStatus(userId, status) {
1614
+ this.db.prepare("UPDATE users SET status = ? WHERE user_id = ?").run(status, userId);
1615
+ }
1616
+ updateUserApiKey(userId, apiKey) {
1617
+ this.db.prepare("UPDATE users SET api_key = ? WHERE user_id = ?").run(apiKey, userId);
1618
+ }
1619
+ listUsers(pagination) {
1620
+ const where = [];
1621
+ const args = [];
1622
+ if (pagination?.cursor) {
1623
+ where.push("(created_at < ? OR (created_at = ? AND user_id > ?))");
1624
+ args.push(pagination.cursor.createdAt, pagination.cursor.createdAt, pagination.cursor.userId);
1625
+ }
1626
+ args.push(pagination?.limit ?? 500);
1627
+ const rows = this.db.prepare(`SELECT user_id, api_key, password_hash, display_name, email, is_admin, status, created_at
1628
+ FROM users
1629
+ ${where.length ? `WHERE ${where.join(" AND ")}` : ""}
1630
+ ORDER BY created_at DESC, user_id ASC
1631
+ LIMIT ?`).all(...args);
1632
+ return rows.map((row) => ({
1633
+ userId: row.user_id,
1634
+ apiKey: row.api_key,
1635
+ passwordHash: row.password_hash ?? null,
1636
+ displayName: row.display_name ?? "",
1637
+ email: row.email ?? "",
1638
+ isAdmin: Boolean(row.is_admin),
1639
+ status: row.status === "disabled" ? "disabled" : "active",
1640
+ createdAt: row.created_at,
1641
+ }));
1642
+ }
1643
+ deleteUser(userId) {
1644
+ this.db.exec("BEGIN");
1645
+ try {
1646
+ this.db.prepare("DELETE FROM users WHERE user_id = ?").run(userId);
1647
+ this.db.prepare("DELETE FROM sensory_stream WHERE user_id = ?").run(userId);
1648
+ this.db.prepare("DELETE FROM cognitive_fts WHERE user_id = ?").run(userId);
1649
+ this.db.prepare("DELETE FROM cognitive_records WHERE user_id = ?").run(userId);
1650
+ this.db.prepare("DELETE FROM contradictions WHERE user_id = ?").run(userId);
1651
+ this.db.prepare("DELETE FROM contextual_focus WHERE user_id = ?").run(userId);
1652
+ this.db.prepare("DELETE FROM core_identity WHERE user_id = ?").run(userId);
1653
+ this.db.prepare("DELETE FROM scheduler_state WHERE user_id = ?").run(userId);
1654
+ this.db.prepare("DELETE FROM graph_nodes WHERE user_id = ?").run(userId);
1655
+ this.db.prepare("DELETE FROM graph_edges WHERE user_id = ?").run(userId);
1656
+ this.db.prepare("DELETE FROM cognitive_connections WHERE user_id = ?").run(userId);
1657
+ this.db.prepare("DELETE FROM memory_evidence WHERE user_id = ?").run(userId);
1658
+ this.db.prepare("DELETE FROM memory_operations WHERE user_id = ?").run(userId);
1659
+ this.db.prepare("DELETE FROM memory_file_index WHERE user_id = ?").run(userId);
1660
+ this.db.exec("COMMIT");
1661
+ }
1662
+ catch (e) {
1663
+ this.db.exec("ROLLBACK");
1664
+ throw e;
1665
+ }
1666
+ }
1667
+ listMemories(userId, filters, pagination) {
1668
+ const where = ["user_id = ?"];
1669
+ const args = [userId];
1670
+ if (filters?.query) {
1671
+ where.push("content LIKE ?");
1672
+ args.push(`%${filters.query}%`);
1673
+ }
1674
+ if (filters?.type) {
1675
+ where.push("type = ?");
1676
+ args.push(filters.type);
1677
+ }
1678
+ if (filters?.scene) {
1679
+ where.push("scene_name = ?");
1680
+ args.push(filters.scene);
1681
+ }
1682
+ if (filters?.skill) {
1683
+ where.push("skill_tag = ?");
1684
+ args.push(filters.skill);
1685
+ }
1686
+ if (typeof filters?.archived === "boolean") {
1687
+ where.push("archived = ?");
1688
+ args.push(filters.archived ? 1 : 0);
1689
+ }
1690
+ if (pagination?.cursor) {
1691
+ where.push("(created_time < ? OR (created_time = ? AND record_id > ?))");
1692
+ args.push(pagination.cursor.createdTime, pagination.cursor.createdTime, pagination.cursor.recordId);
1693
+ }
1694
+ args.push(pagination?.limit ?? 500);
1695
+ const rows = this.db.prepare(`
1696
+ SELECT record_id, content, type, priority, scene_name, skill_tag, created_time, citation_count, never_cited_count, archived
1697
+ FROM cognitive_records
1698
+ WHERE ${where.join(" AND ")}
1699
+ ORDER BY created_time DESC, record_id ASC
1700
+ LIMIT ?
1701
+ `).all(...args);
1702
+ return rows.map((row) => ({
1703
+ recordId: row.record_id,
1704
+ content: row.content,
1705
+ type: row.type,
1706
+ priority: row.priority,
1707
+ sceneName: row.scene_name ?? "",
1708
+ skillTag: row.skill_tag ?? "",
1709
+ createdTime: row.created_time,
1710
+ citationCount: row.citation_count ?? 0,
1711
+ neverCitedCount: row.never_cited_count ?? 0,
1712
+ archived: Boolean(row.archived),
1713
+ }));
1714
+ }
1715
+ getMemoryStats(userId) {
1716
+ const totalRow = this.db.prepare("SELECT COUNT(*) as c FROM cognitive_records WHERE user_id = ?").get(userId);
1717
+ const archivedRow = this.db.prepare("SELECT COUNT(*) as c FROM cognitive_records WHERE user_id = ? AND archived = 1").get(userId);
1718
+ const typeRows = this.db.prepare("SELECT type, COUNT(*) as c FROM cognitive_records WHERE user_id = ? GROUP BY type").all(userId);
1719
+ const citationRows = this.db.prepare("SELECT SUM(citation_count) as cited, COUNT(*) as total FROM cognitive_records WHERE user_id = ?").get(userId);
1720
+ const lastRecall = this.db.prepare("SELECT MAX(recorded_at) as last_at FROM sensory_stream WHERE user_id = ?").get(userId);
1721
+ const byType = {};
1722
+ for (const row of typeRows)
1723
+ byType[row.type] = row.c;
1724
+ const totalRecords = totalRow?.c ?? 0;
1725
+ const cited = citationRows?.cited ?? 0;
1726
+ return {
1727
+ total: totalRecords,
1728
+ archived: archivedRow?.c ?? 0,
1729
+ byType,
1730
+ citationRate: totalRecords > 0 ? cited / totalRecords : 0,
1731
+ lastRecallAt: lastRecall?.last_at ?? null,
1732
+ extraction: this.getExtractionStatus(userId),
1733
+ };
1734
+ }
1735
+ replaceFileIndex(record) {
1736
+ this.db.prepare("DELETE FROM memory_file_index WHERE user_id = ? AND record_id = ?").run(record.userId, record.id);
1737
+ const filePaths = [...new Set((record.filePaths ?? []).map((filePath) => filePath.trim()).filter(Boolean))];
1738
+ if (filePaths.length === 0)
1739
+ return;
1740
+ const stmt = this.db.prepare(`
1741
+ INSERT INTO memory_file_index (id, user_id, record_id, file_path, symbol, created_time)
1742
+ VALUES (?, ?, ?, ?, '', ?)
1743
+ `);
1744
+ for (const filePath of filePaths) {
1745
+ stmt.run(randomUUID(), record.userId, record.id, filePath, record.createdTime);
1746
+ }
1747
+ }
1748
+ upsertConnection(userId, sourceId, targetId, weight) {
1749
+ const stmt = this.db.prepare(`
1750
+ INSERT INTO cognitive_connections (user_id, source_id, target_id, weight, last_activated_at)
1751
+ VALUES (?, ?, ?, ?, datetime('now'))
1752
+ ON CONFLICT(user_id, source_id, target_id) DO UPDATE SET
1753
+ weight = excluded.weight,
1754
+ last_activated_at = datetime('now')
1755
+ `);
1756
+ stmt.run(userId, sourceId, targetId, weight);
1757
+ }
1758
+ getConnectionsForSource(userId, sourceId) {
1759
+ const rows = this.db.prepare(`
1760
+ SELECT target_id, weight FROM cognitive_connections
1761
+ WHERE user_id = ? AND source_id = ? AND weight >= 0.1
1762
+ `).all(userId, sourceId);
1763
+ return rows.map(r => ({ targetId: r.target_id, weight: r.weight }));
1764
+ }
1765
+ strengthenConnectionsBatch(userId, pairs, delta) {
1766
+ if (pairs.length === 0)
1767
+ return;
1768
+ this.db.exec("BEGIN");
1769
+ try {
1770
+ const stmt = this.db.prepare(`
1771
+ INSERT INTO cognitive_connections (user_id, source_id, target_id, weight, last_activated_at)
1772
+ VALUES (?, ?, ?, ?, datetime('now'))
1773
+ ON CONFLICT(user_id, source_id, target_id) DO UPDATE SET
1774
+ weight = MIN(1.0, weight + ?),
1775
+ last_activated_at = datetime('now')
1776
+ `);
1777
+ for (const pair of pairs) {
1778
+ stmt.run(userId, pair.source, pair.target, delta, delta);
1779
+ stmt.run(userId, pair.target, pair.source, delta, delta);
1780
+ }
1781
+ this.db.exec("COMMIT");
1782
+ }
1783
+ catch (e) {
1784
+ this.db.exec("ROLLBACK");
1785
+ throw e;
1786
+ }
1787
+ }
1788
+ decayConnections(userId, decayFactor) {
1789
+ const stmt = this.db.prepare(`
1790
+ UPDATE cognitive_connections
1791
+ SET weight = MAX(0.0, weight * ?)
1792
+ WHERE user_id = ?
1793
+ `);
1794
+ stmt.run(decayFactor, userId);
1795
+ }
1796
+ pruneConnections(userId, threshold) {
1797
+ const stmt = this.db.prepare(`
1798
+ DELETE FROM cognitive_connections
1799
+ WHERE user_id = ? AND weight < ?
1800
+ `);
1801
+ stmt.run(userId, threshold);
1802
+ }
1803
+ getAllConnections(userId) {
1804
+ const rows = this.db.prepare(`
1805
+ SELECT source_id, target_id, weight, last_activated_at
1806
+ FROM cognitive_connections
1807
+ WHERE user_id = ?
1808
+ `).all(userId);
1809
+ return rows.map(r => ({
1810
+ sourceId: r.source_id,
1811
+ targetId: r.target_id,
1812
+ weight: r.weight,
1813
+ lastActivatedAt: r.last_activated_at ?? "",
1814
+ }));
1815
+ }
1816
+ }