shieldcortex 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +282 -0
  3. package/dashboard/components.json +22 -0
  4. package/dashboard/eslint.config.mjs +42 -0
  5. package/dashboard/next.config.ts +7 -0
  6. package/dashboard/package-lock.json +8053 -0
  7. package/dashboard/package.json +44 -0
  8. package/dashboard/postcss.config.mjs +7 -0
  9. package/dashboard/public/file.svg +1 -0
  10. package/dashboard/public/globe.svg +1 -0
  11. package/dashboard/public/next.svg +1 -0
  12. package/dashboard/public/vercel.svg +1 -0
  13. package/dashboard/public/window.svg +1 -0
  14. package/dashboard/scripts/ensure-api.mjs +76 -0
  15. package/dashboard/src/app/error.tsx +49 -0
  16. package/dashboard/src/app/favicon.ico +0 -0
  17. package/dashboard/src/app/globals.css +130 -0
  18. package/dashboard/src/app/layout.tsx +35 -0
  19. package/dashboard/src/app/page.tsx +364 -0
  20. package/dashboard/src/components/Providers.tsx +27 -0
  21. package/dashboard/src/components/brain/ActivityPulseSystem.tsx +229 -0
  22. package/dashboard/src/components/brain/BrainMesh.tsx +133 -0
  23. package/dashboard/src/components/brain/BrainRegions.tsx +254 -0
  24. package/dashboard/src/components/brain/BrainScene.tsx +255 -0
  25. package/dashboard/src/components/brain/CategoryLabels.tsx +103 -0
  26. package/dashboard/src/components/brain/CoreSphere.tsx +215 -0
  27. package/dashboard/src/components/brain/DataFlowParticles.tsx +123 -0
  28. package/dashboard/src/components/brain/DataStreamRings.tsx +161 -0
  29. package/dashboard/src/components/brain/ElectronFlow.tsx +323 -0
  30. package/dashboard/src/components/brain/HolographicGrid.tsx +235 -0
  31. package/dashboard/src/components/brain/MemoryLinks.tsx +271 -0
  32. package/dashboard/src/components/brain/MemoryNode.tsx +245 -0
  33. package/dashboard/src/components/brain/NeuralPathways.tsx +441 -0
  34. package/dashboard/src/components/brain/SynapseNodes.tsx +312 -0
  35. package/dashboard/src/components/brain/TimelineControls.tsx +205 -0
  36. package/dashboard/src/components/chip/ChipScene.tsx +497 -0
  37. package/dashboard/src/components/chip/ChipSubstrate.tsx +238 -0
  38. package/dashboard/src/components/chip/CortexCore.tsx +210 -0
  39. package/dashboard/src/components/chip/DataBus.tsx +416 -0
  40. package/dashboard/src/components/chip/MemoryCell.tsx +225 -0
  41. package/dashboard/src/components/chip/MemoryGrid.tsx +328 -0
  42. package/dashboard/src/components/chip/QuantumCell.tsx +316 -0
  43. package/dashboard/src/components/chip/SectionLabel.tsx +113 -0
  44. package/dashboard/src/components/chip/index.ts +14 -0
  45. package/dashboard/src/components/controls/ControlPanel.tsx +106 -0
  46. package/dashboard/src/components/controls/VersionPanel.tsx +185 -0
  47. package/dashboard/src/components/dashboard/StatsPanel.tsx +164 -0
  48. package/dashboard/src/components/debug/ActivityLog.tsx +250 -0
  49. package/dashboard/src/components/debug/DebugPanel.tsx +101 -0
  50. package/dashboard/src/components/debug/QueryTester.tsx +192 -0
  51. package/dashboard/src/components/debug/RelationshipGraph.tsx +403 -0
  52. package/dashboard/src/components/debug/SqlConsole.tsx +319 -0
  53. package/dashboard/src/components/graph/KnowledgeGraph.tsx +230 -0
  54. package/dashboard/src/components/graph/OntologyGraph.tsx +631 -0
  55. package/dashboard/src/components/insights/ActivityHeatmap.tsx +131 -0
  56. package/dashboard/src/components/insights/InsightsView.tsx +46 -0
  57. package/dashboard/src/components/insights/KnowledgeMapPanel.tsx +80 -0
  58. package/dashboard/src/components/insights/QualityPanel.tsx +116 -0
  59. package/dashboard/src/components/memories/MemoriesView.tsx +150 -0
  60. package/dashboard/src/components/memories/MemoryCard.tsx +103 -0
  61. package/dashboard/src/components/memory/MemoryDetail.tsx +325 -0
  62. package/dashboard/src/components/nav/NavRail.tsx +54 -0
  63. package/dashboard/src/components/ui/button.tsx +62 -0
  64. package/dashboard/src/components/ui/card.tsx +92 -0
  65. package/dashboard/src/components/ui/input.tsx +21 -0
  66. package/dashboard/src/hooks/useDebouncedValue.ts +24 -0
  67. package/dashboard/src/hooks/useMemories.ts +458 -0
  68. package/dashboard/src/hooks/useSuggestions.ts +46 -0
  69. package/dashboard/src/lib/category-colors.ts +84 -0
  70. package/dashboard/src/lib/position-algorithm.ts +177 -0
  71. package/dashboard/src/lib/simplex-noise.ts +217 -0
  72. package/dashboard/src/lib/store.ts +88 -0
  73. package/dashboard/src/lib/utils.ts +6 -0
  74. package/dashboard/src/lib/websocket.ts +249 -0
  75. package/dashboard/src/types/memory.ts +73 -0
  76. package/dashboard/tsconfig.json +34 -0
  77. package/dist/__tests__/consolidation-merge.test.d.ts +9 -0
  78. package/dist/__tests__/consolidation-merge.test.d.ts.map +1 -0
  79. package/dist/__tests__/consolidation-merge.test.js +137 -0
  80. package/dist/__tests__/consolidation-merge.test.js.map +1 -0
  81. package/dist/__tests__/contradictions.test.d.ts +8 -0
  82. package/dist/__tests__/contradictions.test.d.ts.map +1 -0
  83. package/dist/__tests__/contradictions.test.js +78 -0
  84. package/dist/__tests__/contradictions.test.js.map +1 -0
  85. package/dist/__tests__/salience-evolution.test.d.ts +7 -0
  86. package/dist/__tests__/salience-evolution.test.d.ts.map +1 -0
  87. package/dist/__tests__/salience-evolution.test.js +151 -0
  88. package/dist/__tests__/salience-evolution.test.js.map +1 -0
  89. package/dist/__tests__/store.test.d.ts +7 -0
  90. package/dist/__tests__/store.test.d.ts.map +1 -0
  91. package/dist/__tests__/store.test.js +582 -0
  92. package/dist/__tests__/store.test.js.map +1 -0
  93. package/dist/api/control.d.ts +27 -0
  94. package/dist/api/control.d.ts.map +1 -0
  95. package/dist/api/control.js +60 -0
  96. package/dist/api/control.js.map +1 -0
  97. package/dist/api/events.d.ts +159 -0
  98. package/dist/api/events.d.ts.map +1 -0
  99. package/dist/api/events.js +155 -0
  100. package/dist/api/events.js.map +1 -0
  101. package/dist/api/version.d.ts +36 -0
  102. package/dist/api/version.d.ts.map +1 -0
  103. package/dist/api/version.js +146 -0
  104. package/dist/api/version.js.map +1 -0
  105. package/dist/api/visualization-server.d.ts +11 -0
  106. package/dist/api/visualization-server.d.ts.map +1 -0
  107. package/dist/api/visualization-server.js +1186 -0
  108. package/dist/api/visualization-server.js.map +1 -0
  109. package/dist/context/project-context.d.ts +57 -0
  110. package/dist/context/project-context.d.ts.map +1 -0
  111. package/dist/context/project-context.js +135 -0
  112. package/dist/context/project-context.js.map +1 -0
  113. package/dist/database/init.d.ts +49 -0
  114. package/dist/database/init.d.ts.map +1 -0
  115. package/dist/database/init.js +567 -0
  116. package/dist/database/init.js.map +1 -0
  117. package/dist/defence/__tests__/firewall.test.d.ts +8 -0
  118. package/dist/defence/__tests__/firewall.test.d.ts.map +1 -0
  119. package/dist/defence/__tests__/firewall.test.js +123 -0
  120. package/dist/defence/__tests__/firewall.test.js.map +1 -0
  121. package/dist/defence/__tests__/fragmentation.test.d.ts +7 -0
  122. package/dist/defence/__tests__/fragmentation.test.d.ts.map +1 -0
  123. package/dist/defence/__tests__/fragmentation.test.js +51 -0
  124. package/dist/defence/__tests__/fragmentation.test.js.map +1 -0
  125. package/dist/defence/__tests__/pipeline.test.d.ts +8 -0
  126. package/dist/defence/__tests__/pipeline.test.d.ts.map +1 -0
  127. package/dist/defence/__tests__/pipeline.test.js +61 -0
  128. package/dist/defence/__tests__/pipeline.test.js.map +1 -0
  129. package/dist/defence/__tests__/sensitivity.test.d.ts +7 -0
  130. package/dist/defence/__tests__/sensitivity.test.d.ts.map +1 -0
  131. package/dist/defence/__tests__/sensitivity.test.js +61 -0
  132. package/dist/defence/__tests__/sensitivity.test.js.map +1 -0
  133. package/dist/defence/__tests__/trust.test.d.ts +7 -0
  134. package/dist/defence/__tests__/trust.test.d.ts.map +1 -0
  135. package/dist/defence/__tests__/trust.test.js +49 -0
  136. package/dist/defence/__tests__/trust.test.js.map +1 -0
  137. package/dist/defence/audit/index.d.ts +4 -0
  138. package/dist/defence/audit/index.d.ts.map +1 -0
  139. package/dist/defence/audit/index.js +3 -0
  140. package/dist/defence/audit/index.js.map +1 -0
  141. package/dist/defence/audit/logger.d.ts +14 -0
  142. package/dist/defence/audit/logger.d.ts.map +1 -0
  143. package/dist/defence/audit/logger.js +54 -0
  144. package/dist/defence/audit/logger.js.map +1 -0
  145. package/dist/defence/audit/queries.d.ts +33 -0
  146. package/dist/defence/audit/queries.d.ts.map +1 -0
  147. package/dist/defence/audit/queries.js +103 -0
  148. package/dist/defence/audit/queries.js.map +1 -0
  149. package/dist/defence/firewall/anomaly-scorer.d.ts +8 -0
  150. package/dist/defence/firewall/anomaly-scorer.d.ts.map +1 -0
  151. package/dist/defence/firewall/anomaly-scorer.js +58 -0
  152. package/dist/defence/firewall/anomaly-scorer.js.map +1 -0
  153. package/dist/defence/firewall/encoding-detector.d.ts +13 -0
  154. package/dist/defence/firewall/encoding-detector.d.ts.map +1 -0
  155. package/dist/defence/firewall/encoding-detector.js +120 -0
  156. package/dist/defence/firewall/encoding-detector.js.map +1 -0
  157. package/dist/defence/firewall/index.d.ts +21 -0
  158. package/dist/defence/firewall/index.d.ts.map +1 -0
  159. package/dist/defence/firewall/index.js +133 -0
  160. package/dist/defence/firewall/index.js.map +1 -0
  161. package/dist/defence/firewall/instruction-detector.d.ts +12 -0
  162. package/dist/defence/firewall/instruction-detector.d.ts.map +1 -0
  163. package/dist/defence/firewall/instruction-detector.js +99 -0
  164. package/dist/defence/firewall/instruction-detector.js.map +1 -0
  165. package/dist/defence/firewall/privilege-detector.d.ts +13 -0
  166. package/dist/defence/firewall/privilege-detector.d.ts.map +1 -0
  167. package/dist/defence/firewall/privilege-detector.js +89 -0
  168. package/dist/defence/firewall/privilege-detector.js.map +1 -0
  169. package/dist/defence/fragmentation/assembly-detector.d.ts +18 -0
  170. package/dist/defence/fragmentation/assembly-detector.d.ts.map +1 -0
  171. package/dist/defence/fragmentation/assembly-detector.js +72 -0
  172. package/dist/defence/fragmentation/assembly-detector.js.map +1 -0
  173. package/dist/defence/fragmentation/entity-extractor.d.ts +19 -0
  174. package/dist/defence/fragmentation/entity-extractor.d.ts.map +1 -0
  175. package/dist/defence/fragmentation/entity-extractor.js +86 -0
  176. package/dist/defence/fragmentation/entity-extractor.js.map +1 -0
  177. package/dist/defence/fragmentation/index.d.ts +23 -0
  178. package/dist/defence/fragmentation/index.d.ts.map +1 -0
  179. package/dist/defence/fragmentation/index.js +49 -0
  180. package/dist/defence/fragmentation/index.js.map +1 -0
  181. package/dist/defence/fragmentation/temporal-analyzer.d.ts +28 -0
  182. package/dist/defence/fragmentation/temporal-analyzer.d.ts.map +1 -0
  183. package/dist/defence/fragmentation/temporal-analyzer.js +41 -0
  184. package/dist/defence/fragmentation/temporal-analyzer.js.map +1 -0
  185. package/dist/defence/index.d.ts +12 -0
  186. package/dist/defence/index.d.ts.map +1 -0
  187. package/dist/defence/index.js +18 -0
  188. package/dist/defence/index.js.map +1 -0
  189. package/dist/defence/pipeline.d.ts +9 -0
  190. package/dist/defence/pipeline.d.ts.map +1 -0
  191. package/dist/defence/pipeline.js +115 -0
  192. package/dist/defence/pipeline.js.map +1 -0
  193. package/dist/defence/scanner/index.d.ts +5 -0
  194. package/dist/defence/scanner/index.d.ts.map +1 -0
  195. package/dist/defence/scanner/index.js +5 -0
  196. package/dist/defence/scanner/index.js.map +1 -0
  197. package/dist/defence/scanner/scan-existing.d.ts +34 -0
  198. package/dist/defence/scanner/scan-existing.d.ts.map +1 -0
  199. package/dist/defence/scanner/scan-existing.js +136 -0
  200. package/dist/defence/scanner/scan-existing.js.map +1 -0
  201. package/dist/defence/sensitivity/classifier.d.ts +6 -0
  202. package/dist/defence/sensitivity/classifier.d.ts.map +1 -0
  203. package/dist/defence/sensitivity/classifier.js +50 -0
  204. package/dist/defence/sensitivity/classifier.js.map +1 -0
  205. package/dist/defence/sensitivity/index.d.ts +11 -0
  206. package/dist/defence/sensitivity/index.d.ts.map +1 -0
  207. package/dist/defence/sensitivity/index.js +13 -0
  208. package/dist/defence/sensitivity/index.js.map +1 -0
  209. package/dist/defence/sensitivity/patterns.d.ts +14 -0
  210. package/dist/defence/sensitivity/patterns.d.ts.map +1 -0
  211. package/dist/defence/sensitivity/patterns.js +67 -0
  212. package/dist/defence/sensitivity/patterns.js.map +1 -0
  213. package/dist/defence/sensitivity/redaction.d.ts +17 -0
  214. package/dist/defence/sensitivity/redaction.d.ts.map +1 -0
  215. package/dist/defence/sensitivity/redaction.js +47 -0
  216. package/dist/defence/sensitivity/redaction.js.map +1 -0
  217. package/dist/defence/trust/index.d.ts +3 -0
  218. package/dist/defence/trust/index.d.ts.map +1 -0
  219. package/dist/defence/trust/index.js +3 -0
  220. package/dist/defence/trust/index.js.map +1 -0
  221. package/dist/defence/trust/recall-filter.d.ts +10 -0
  222. package/dist/defence/trust/recall-filter.d.ts.map +1 -0
  223. package/dist/defence/trust/recall-filter.js +38 -0
  224. package/dist/defence/trust/recall-filter.js.map +1 -0
  225. package/dist/defence/trust/source-scorer.d.ts +6 -0
  226. package/dist/defence/trust/source-scorer.d.ts.map +1 -0
  227. package/dist/defence/trust/source-scorer.js +34 -0
  228. package/dist/defence/trust/source-scorer.js.map +1 -0
  229. package/dist/defence/types.d.ts +88 -0
  230. package/dist/defence/types.d.ts.map +1 -0
  231. package/dist/defence/types.js +15 -0
  232. package/dist/defence/types.js.map +1 -0
  233. package/dist/embeddings/generator.d.ts +20 -0
  234. package/dist/embeddings/generator.d.ts.map +1 -0
  235. package/dist/embeddings/generator.js +83 -0
  236. package/dist/embeddings/generator.js.map +1 -0
  237. package/dist/embeddings/index.d.ts +2 -0
  238. package/dist/embeddings/index.d.ts.map +1 -0
  239. package/dist/embeddings/index.js +2 -0
  240. package/dist/embeddings/index.js.map +1 -0
  241. package/dist/errors.d.ts +74 -0
  242. package/dist/errors.d.ts.map +1 -0
  243. package/dist/errors.js +131 -0
  244. package/dist/errors.js.map +1 -0
  245. package/dist/graph/backfill.d.ts +6 -0
  246. package/dist/graph/backfill.d.ts.map +1 -0
  247. package/dist/graph/backfill.js +33 -0
  248. package/dist/graph/backfill.js.map +1 -0
  249. package/dist/graph/extract.d.ts +21 -0
  250. package/dist/graph/extract.d.ts.map +1 -0
  251. package/dist/graph/extract.js +231 -0
  252. package/dist/graph/extract.js.map +1 -0
  253. package/dist/graph/resolve.d.ts +6 -0
  254. package/dist/graph/resolve.d.ts.map +1 -0
  255. package/dist/graph/resolve.js +126 -0
  256. package/dist/graph/resolve.js.map +1 -0
  257. package/dist/index.d.ts +31 -0
  258. package/dist/index.d.ts.map +1 -0
  259. package/dist/index.js +248 -0
  260. package/dist/index.js.map +1 -0
  261. package/dist/memory/activation.d.ts +69 -0
  262. package/dist/memory/activation.d.ts.map +1 -0
  263. package/dist/memory/activation.js +168 -0
  264. package/dist/memory/activation.js.map +1 -0
  265. package/dist/memory/consolidate.d.ts +98 -0
  266. package/dist/memory/consolidate.d.ts.map +1 -0
  267. package/dist/memory/consolidate.js +511 -0
  268. package/dist/memory/consolidate.js.map +1 -0
  269. package/dist/memory/contradiction.d.ts +69 -0
  270. package/dist/memory/contradiction.d.ts.map +1 -0
  271. package/dist/memory/contradiction.js +286 -0
  272. package/dist/memory/contradiction.js.map +1 -0
  273. package/dist/memory/decay.d.ts +62 -0
  274. package/dist/memory/decay.d.ts.map +1 -0
  275. package/dist/memory/decay.js +184 -0
  276. package/dist/memory/decay.js.map +1 -0
  277. package/dist/memory/salience.d.ts +36 -0
  278. package/dist/memory/salience.d.ts.map +1 -0
  279. package/dist/memory/salience.js +216 -0
  280. package/dist/memory/salience.js.map +1 -0
  281. package/dist/memory/similarity.d.ts +57 -0
  282. package/dist/memory/similarity.d.ts.map +1 -0
  283. package/dist/memory/similarity.js +114 -0
  284. package/dist/memory/similarity.js.map +1 -0
  285. package/dist/memory/store.d.ts +179 -0
  286. package/dist/memory/store.d.ts.map +1 -0
  287. package/dist/memory/store.js +1184 -0
  288. package/dist/memory/store.js.map +1 -0
  289. package/dist/memory/types.d.ts +97 -0
  290. package/dist/memory/types.d.ts.map +1 -0
  291. package/dist/memory/types.js +30 -0
  292. package/dist/memory/types.js.map +1 -0
  293. package/dist/server.d.ts +12 -0
  294. package/dist/server.d.ts.map +1 -0
  295. package/dist/server.js +568 -0
  296. package/dist/server.js.map +1 -0
  297. package/dist/service/install.d.ts +15 -0
  298. package/dist/service/install.d.ts.map +1 -0
  299. package/dist/service/install.js +178 -0
  300. package/dist/service/install.js.map +1 -0
  301. package/dist/service/templates.d.ts +13 -0
  302. package/dist/service/templates.d.ts.map +1 -0
  303. package/dist/service/templates.js +58 -0
  304. package/dist/service/templates.js.map +1 -0
  305. package/dist/setup/claude-md.d.ts +12 -0
  306. package/dist/setup/claude-md.d.ts.map +1 -0
  307. package/dist/setup/claude-md.js +68 -0
  308. package/dist/setup/claude-md.js.map +1 -0
  309. package/dist/setup/clawdbot.d.ts +15 -0
  310. package/dist/setup/clawdbot.d.ts.map +1 -0
  311. package/dist/setup/clawdbot.js +118 -0
  312. package/dist/setup/clawdbot.js.map +1 -0
  313. package/dist/setup/doctor.d.ts +5 -0
  314. package/dist/setup/doctor.d.ts.map +1 -0
  315. package/dist/setup/doctor.js +141 -0
  316. package/dist/setup/doctor.js.map +1 -0
  317. package/dist/setup/hooks.d.ts +6 -0
  318. package/dist/setup/hooks.d.ts.map +1 -0
  319. package/dist/setup/hooks.js +36 -0
  320. package/dist/setup/hooks.js.map +1 -0
  321. package/dist/setup/migrate.d.ts +16 -0
  322. package/dist/setup/migrate.d.ts.map +1 -0
  323. package/dist/setup/migrate.js +164 -0
  324. package/dist/setup/migrate.js.map +1 -0
  325. package/dist/setup/settings-hooks.d.ts +7 -0
  326. package/dist/setup/settings-hooks.d.ts.map +1 -0
  327. package/dist/setup/settings-hooks.js +83 -0
  328. package/dist/setup/settings-hooks.js.map +1 -0
  329. package/dist/setup/uninstall.d.ts +12 -0
  330. package/dist/setup/uninstall.d.ts.map +1 -0
  331. package/dist/setup/uninstall.js +125 -0
  332. package/dist/setup/uninstall.js.map +1 -0
  333. package/dist/tools/context.d.ts +135 -0
  334. package/dist/tools/context.d.ts.map +1 -0
  335. package/dist/tools/context.js +273 -0
  336. package/dist/tools/context.js.map +1 -0
  337. package/dist/tools/forget.d.ts +53 -0
  338. package/dist/tools/forget.d.ts.map +1 -0
  339. package/dist/tools/forget.js +179 -0
  340. package/dist/tools/forget.js.map +1 -0
  341. package/dist/tools/graph.d.ts +46 -0
  342. package/dist/tools/graph.d.ts.map +1 -0
  343. package/dist/tools/graph.js +206 -0
  344. package/dist/tools/graph.js.map +1 -0
  345. package/dist/tools/recall.d.ts +79 -0
  346. package/dist/tools/recall.d.ts.map +1 -0
  347. package/dist/tools/recall.js +156 -0
  348. package/dist/tools/recall.js.map +1 -0
  349. package/dist/tools/remember.d.ts +83 -0
  350. package/dist/tools/remember.d.ts.map +1 -0
  351. package/dist/tools/remember.js +151 -0
  352. package/dist/tools/remember.js.map +1 -0
  353. package/dist/worker/brain-worker.d.ts +100 -0
  354. package/dist/worker/brain-worker.d.ts.map +1 -0
  355. package/dist/worker/brain-worker.js +283 -0
  356. package/dist/worker/brain-worker.js.map +1 -0
  357. package/dist/worker/link-discovery.d.ts +47 -0
  358. package/dist/worker/link-discovery.d.ts.map +1 -0
  359. package/dist/worker/link-discovery.js +103 -0
  360. package/dist/worker/link-discovery.js.map +1 -0
  361. package/dist/worker/predictive-consolidation.d.ts +46 -0
  362. package/dist/worker/predictive-consolidation.d.ts.map +1 -0
  363. package/dist/worker/predictive-consolidation.js +110 -0
  364. package/dist/worker/predictive-consolidation.js.map +1 -0
  365. package/dist/worker/types.d.ts +91 -0
  366. package/dist/worker/types.d.ts.map +1 -0
  367. package/dist/worker/types.js +22 -0
  368. package/dist/worker/types.js.map +1 -0
  369. package/hooks/clawdbot/cortex-memory/HOOK.md +71 -0
  370. package/hooks/clawdbot/cortex-memory/handler.js +279 -0
  371. package/package.json +73 -0
  372. package/scripts/pre-compact-hook.mjs +716 -0
  373. package/scripts/session-end-hook.mjs +548 -0
  374. package/scripts/session-start-hook.mjs +221 -0
  375. package/scripts/start-dashboard.sh +41 -0
  376. package/scripts/stop-dashboard.sh +21 -0
  377. package/scripts/stop-hook.mjs +163 -0
@@ -0,0 +1,1186 @@
1
+ /**
2
+ * Visualization API Server
3
+ *
4
+ * Provides REST endpoints and WebSocket for the Brain Dashboard.
5
+ * Runs alongside or instead of the MCP server.
6
+ */
7
+ import express from 'express';
8
+ import cors from 'cors';
9
+ import { createServer } from 'http';
10
+ import { WebSocketServer, WebSocket } from 'ws';
11
+ import { getDatabase, initDatabase, checkpointWal } from '../database/init.js';
12
+ import { DEFAULT_CONFIG } from '../memory/types.js';
13
+ import { searchMemories, getRecentMemories, getHighPriorityMemories, getMemoryStats, getMemoryById, addMemory, deleteMemory, accessMemory, updateDecayScores, rowToMemory, } from '../memory/store.js';
14
+ import { consolidate, generateContextSummary, formatContextSummary, } from '../memory/consolidate.js';
15
+ import { calculateDecayedScore } from '../memory/decay.js';
16
+ import { getActivationStats, getActiveMemories } from '../memory/activation.js';
17
+ import { detectContradictions, getContradictionsFor } from '../memory/contradiction.js';
18
+ import { enrichMemory } from '../memory/store.js';
19
+ import { memoryEvents, emitDecayTick, emitConsolidation, getUnprocessedEvents, markEventsProcessed, cleanupOldEvents, } from './events.js';
20
+ import { BrainWorker } from '../worker/brain-worker.js';
21
+ import { pause, resume, getControlStatus } from './control.js';
22
+ import { getCurrentVersion, checkForUpdates, performUpdate, scheduleRestart } from './version.js';
23
+ const PORT = process.env.PORT || 3001;
24
+ // Track connected WebSocket clients
25
+ const clients = new Set();
26
+ /**
27
+ * Start the visualization API server
28
+ */
29
+ export function startVisualizationServer(dbPath) {
30
+ // Initialize database
31
+ initDatabase(dbPath || DEFAULT_CONFIG.dbPath);
32
+ const app = express();
33
+ const server = createServer(app);
34
+ // Middleware — CORS restricted to localhost by default
35
+ const allowedOrigins = process.env.CORTEX_CORS_ORIGINS
36
+ ? process.env.CORTEX_CORS_ORIGINS.split(',').map(s => s.trim())
37
+ : ['http://localhost:3030', 'http://localhost:3000', 'http://127.0.0.1:3030', 'http://127.0.0.1:3000'];
38
+ app.use(cors({
39
+ origin: (origin, callback) => {
40
+ // Allow requests with no origin (curl, server-to-server, same-origin)
41
+ if (!origin || allowedOrigins.includes(origin)) {
42
+ callback(null, true);
43
+ }
44
+ else {
45
+ callback(new Error(`Origin ${origin} not allowed by CORS`));
46
+ }
47
+ },
48
+ }));
49
+ app.use(express.json());
50
+ // ============================================
51
+ // REST API ENDPOINTS
52
+ // ============================================
53
+ // Health check
54
+ app.get('/api/health', (_req, res) => {
55
+ res.json({ status: 'ok', timestamp: new Date().toISOString() });
56
+ });
57
+ // Get all memories with filters and pagination
58
+ app.get('/api/memories', async (req, res) => {
59
+ try {
60
+ // Extract query params as strings
61
+ const project = typeof req.query.project === 'string' ? req.query.project : undefined;
62
+ const type = typeof req.query.type === 'string' ? req.query.type : undefined;
63
+ const category = typeof req.query.category === 'string' ? req.query.category : undefined;
64
+ const limitStr = typeof req.query.limit === 'string' ? req.query.limit : '50';
65
+ const offsetStr = typeof req.query.offset === 'string' ? req.query.offset : '0';
66
+ const mode = typeof req.query.mode === 'string' ? req.query.mode : 'recent';
67
+ const query = typeof req.query.query === 'string' ? req.query.query : undefined;
68
+ const limit = Math.min(parseInt(limitStr), 200); // Cap at 200
69
+ const offset = parseInt(offsetStr);
70
+ let memories;
71
+ if (mode === 'search' && query) {
72
+ const results = await searchMemories({
73
+ query,
74
+ project,
75
+ type: type,
76
+ category: category,
77
+ limit: limit + offset + 1, // Fetch extra to check hasMore
78
+ });
79
+ memories = results.map(r => r.memory);
80
+ }
81
+ else if (mode === 'important') {
82
+ memories = getHighPriorityMemories(limit + offset + 1, project);
83
+ }
84
+ else {
85
+ memories = getRecentMemories(limit + offset + 1, project);
86
+ }
87
+ // Filter by type and category if provided
88
+ if (type) {
89
+ memories = memories.filter(m => m.type === type);
90
+ }
91
+ if (category) {
92
+ memories = memories.filter(m => m.category === category);
93
+ }
94
+ // Get total count for pagination
95
+ const stats = getMemoryStats(project);
96
+ const total = stats.total;
97
+ // Apply pagination
98
+ const hasMore = memories.length > offset + limit;
99
+ const paginatedMemories = memories.slice(offset, offset + limit);
100
+ // Add computed decayed score to each memory
101
+ const memoriesWithDecay = paginatedMemories.map(m => ({
102
+ ...m,
103
+ decayedScore: calculateDecayedScore(m),
104
+ }));
105
+ res.json({
106
+ memories: memoriesWithDecay,
107
+ pagination: {
108
+ offset,
109
+ limit,
110
+ total,
111
+ hasMore,
112
+ },
113
+ });
114
+ }
115
+ catch (error) {
116
+ res.status(500).json({ error: error.message });
117
+ }
118
+ });
119
+ // Activity data for heatmap (must be before :id route)
120
+ app.get('/api/memories/activity', (req, res) => {
121
+ try {
122
+ const project = typeof req.query.project === 'string' ? req.query.project : undefined;
123
+ const db = getDatabase();
124
+ const query = project
125
+ ? `SELECT date(created_at) as date, COUNT(*) as count
126
+ FROM memories WHERE project = ?
127
+ GROUP BY date(created_at)
128
+ ORDER BY date DESC
129
+ LIMIT 365`
130
+ : `SELECT date(created_at) as date, COUNT(*) as count
131
+ FROM memories
132
+ GROUP BY date(created_at)
133
+ ORDER BY date DESC
134
+ LIMIT 365`;
135
+ const rows = project
136
+ ? db.prepare(query).all(project)
137
+ : db.prepare(query).all();
138
+ res.json({ activity: rows });
139
+ }
140
+ catch (error) {
141
+ res.status(500).json({ error: error.message });
142
+ }
143
+ });
144
+ // Memory quality analysis (must be before :id route)
145
+ app.get('/api/memories/quality', (req, res) => {
146
+ try {
147
+ const project = typeof req.query.project === 'string' ? req.query.project : undefined;
148
+ const db = getDatabase();
149
+ const projectFilter = project ? 'AND project = ?' : '';
150
+ const params = project ? [project] : [];
151
+ const neverAccessed = db.prepare(`
152
+ SELECT id, title, category, type, created_at, salience
153
+ FROM memories WHERE access_count = 0 ${projectFilter}
154
+ AND created_at < datetime('now', '-1 day')
155
+ ORDER BY created_at DESC LIMIT 50
156
+ `).all(...params);
157
+ const stale = db.prepare(`
158
+ SELECT id, title, category, type, last_accessed, decayed_score, salience
159
+ FROM memories WHERE decayed_score < 0.3 ${projectFilter}
160
+ AND last_accessed < datetime('now', '-30 days')
161
+ ORDER BY decayed_score ASC LIMIT 50
162
+ `).all(...params);
163
+ const duplicates = db.prepare(`
164
+ SELECT m1.id as id1, m1.title as title_a, m2.id as id2, m2.title as title_b
165
+ FROM memories m1
166
+ JOIN memories m2 ON m1.title = m2.title AND m1.id < m2.id
167
+ ${project ? 'WHERE m1.project = ?' : ''}
168
+ LIMIT 50
169
+ `).all(...params);
170
+ res.json({
171
+ neverAccessed: { count: neverAccessed.length, items: neverAccessed },
172
+ stale: { count: stale.length, items: stale },
173
+ duplicates: { count: duplicates.length, items: duplicates },
174
+ });
175
+ }
176
+ catch (error) {
177
+ res.status(500).json({ error: error.message });
178
+ }
179
+ });
180
+ // Get single memory by ID
181
+ app.get('/api/memories/:id', (req, res) => {
182
+ try {
183
+ const id = parseInt(req.params.id);
184
+ const memory = getMemoryById(id);
185
+ if (!memory) {
186
+ return res.status(404).json({ error: 'Memory not found' });
187
+ }
188
+ res.json({
189
+ ...memory,
190
+ decayedScore: calculateDecayedScore(memory),
191
+ });
192
+ }
193
+ catch (error) {
194
+ res.status(500).json({ error: error.message });
195
+ }
196
+ });
197
+ // Create memory
198
+ app.post('/api/memories', (req, res) => {
199
+ try {
200
+ const { title, content, type, category, project, tags, salience } = req.body;
201
+ if (!title || !content) {
202
+ return res.status(400).json({ error: 'Title and content required' });
203
+ }
204
+ const memory = addMemory({
205
+ title,
206
+ content,
207
+ type: type || 'short_term',
208
+ category: category || 'note',
209
+ project,
210
+ tags: tags || [],
211
+ salience,
212
+ });
213
+ res.status(201).json(memory);
214
+ }
215
+ catch (error) {
216
+ // Handle paused state gracefully
217
+ if (error.name === 'MemoryPausedError') {
218
+ return res.status(503).json({
219
+ error: 'Memory creation is paused',
220
+ paused: true,
221
+ message: 'Use the dashboard control panel to resume memory creation.',
222
+ });
223
+ }
224
+ res.status(500).json({ error: error.message });
225
+ }
226
+ });
227
+ // Delete memory
228
+ app.delete('/api/memories/:id', (req, res) => {
229
+ try {
230
+ const id = parseInt(req.params.id);
231
+ const success = deleteMemory(id);
232
+ if (!success) {
233
+ return res.status(404).json({ error: 'Memory not found' });
234
+ }
235
+ res.json({ success: true });
236
+ }
237
+ catch (error) {
238
+ res.status(500).json({ error: error.message });
239
+ }
240
+ });
241
+ // Access/reinforce memory
242
+ app.post('/api/memories/:id/access', (req, res) => {
243
+ try {
244
+ const id = parseInt(req.params.id);
245
+ const memory = accessMemory(id);
246
+ if (!memory) {
247
+ return res.status(404).json({ error: 'Memory not found' });
248
+ }
249
+ res.json({
250
+ ...memory,
251
+ decayedScore: calculateDecayedScore(memory),
252
+ });
253
+ }
254
+ catch (error) {
255
+ res.status(500).json({ error: error.message });
256
+ }
257
+ });
258
+ // Get statistics
259
+ app.get('/api/stats', (req, res) => {
260
+ try {
261
+ const project = typeof req.query.project === 'string' ? req.query.project : undefined;
262
+ const stats = getMemoryStats(project);
263
+ // Add decay distribution
264
+ const db = getDatabase();
265
+ const rawRows = db.prepare(project
266
+ ? 'SELECT * FROM memories WHERE project = ?'
267
+ : 'SELECT * FROM memories').all(project ? [project] : []);
268
+ // Convert raw DB rows to Memory objects (snake_case -> camelCase)
269
+ const allMemories = rawRows.map(rowToMemory);
270
+ const decayDistribution = {
271
+ healthy: 0, // > 0.35 (realistic given base salience 0.25 + access bonus)
272
+ fading: 0, // 0.2 - 0.35
273
+ critical: 0, // < 0.2 (approaching deletion threshold)
274
+ };
275
+ for (const m of allMemories) {
276
+ const score = calculateDecayedScore(m);
277
+ if (score > 0.35)
278
+ decayDistribution.healthy++;
279
+ else if (score > 0.2)
280
+ decayDistribution.fading++;
281
+ else
282
+ decayDistribution.critical++;
283
+ }
284
+ // Get spreading activation stats (Phase 2 organic feature)
285
+ const activationStats = getActivationStats();
286
+ res.json({
287
+ ...stats,
288
+ decayDistribution,
289
+ activation: activationStats,
290
+ timestamp: new Date().toISOString(),
291
+ });
292
+ }
293
+ catch (error) {
294
+ res.status(500).json({ error: error.message });
295
+ }
296
+ });
297
+ // Get currently activated memories (spreading activation)
298
+ app.get('/api/activation', (_req, res) => {
299
+ try {
300
+ const activeMemories = getActiveMemories();
301
+ const stats = getActivationStats();
302
+ res.json({
303
+ activeMemories,
304
+ stats,
305
+ timestamp: new Date().toISOString(),
306
+ });
307
+ }
308
+ catch (error) {
309
+ res.status(500).json({ error: error.message });
310
+ }
311
+ });
312
+ // ============================================
313
+ // ORGANIC BRAIN ENDPOINTS (Phase 3)
314
+ // ============================================
315
+ // Get detected contradictions
316
+ app.get('/api/contradictions', (req, res) => {
317
+ try {
318
+ const project = typeof req.query.project === 'string' ? req.query.project : undefined;
319
+ const category = typeof req.query.category === 'string' ? req.query.category : undefined;
320
+ const minScoreStr = typeof req.query.minScore === 'string' ? req.query.minScore : '0.4';
321
+ const limitStr = typeof req.query.limit === 'string' ? req.query.limit : '20';
322
+ const minScore = parseFloat(minScoreStr);
323
+ const limit = parseInt(limitStr);
324
+ const contradictions = detectContradictions({
325
+ project,
326
+ category: category,
327
+ minScore,
328
+ limit,
329
+ });
330
+ res.json({
331
+ contradictions: contradictions.map(c => ({
332
+ memoryAId: c.memoryA.id,
333
+ memoryATitle: c.memoryA.title,
334
+ memoryBId: c.memoryB.id,
335
+ memoryBTitle: c.memoryB.title,
336
+ score: c.score,
337
+ reason: c.reason,
338
+ sharedTopics: c.sharedTopics,
339
+ })),
340
+ count: contradictions.length,
341
+ timestamp: new Date().toISOString(),
342
+ });
343
+ }
344
+ catch (error) {
345
+ res.status(500).json({ error: error.message });
346
+ }
347
+ });
348
+ // Get contradictions for a specific memory
349
+ app.get('/api/memories/:id/contradictions', (req, res) => {
350
+ try {
351
+ const id = parseInt(req.params.id);
352
+ if (isNaN(id)) {
353
+ return res.status(400).json({ error: 'Invalid memory ID' });
354
+ }
355
+ const contradictions = getContradictionsFor(id);
356
+ res.json({
357
+ memoryId: id,
358
+ contradictions: contradictions.map(c => ({
359
+ contradictingMemoryId: c.memoryB.id,
360
+ contradictingMemoryTitle: c.memoryB.title,
361
+ score: c.score,
362
+ reason: c.reason,
363
+ sharedTopics: c.sharedTopics,
364
+ })),
365
+ count: contradictions.length,
366
+ });
367
+ }
368
+ catch (error) {
369
+ res.status(500).json({ error: error.message });
370
+ }
371
+ });
372
+ // Manually enrich a memory with new context
373
+ app.post('/api/memories/:id/enrich', (req, res) => {
374
+ try {
375
+ const id = parseInt(req.params.id);
376
+ if (isNaN(id)) {
377
+ return res.status(400).json({ error: 'Invalid memory ID' });
378
+ }
379
+ const { context, contextType } = req.body;
380
+ if (!context || typeof context !== 'string') {
381
+ return res.status(400).json({ error: 'Context string required in request body' });
382
+ }
383
+ const validTypes = ['search', 'access', 'related'];
384
+ const type = validTypes.includes(contextType) ? contextType : 'access';
385
+ const result = enrichMemory(id, context, type);
386
+ res.json(result);
387
+ }
388
+ catch (error) {
389
+ res.status(500).json({ error: error.message });
390
+ }
391
+ });
392
+ // Get list of all projects
393
+ app.get('/api/projects', (_req, res) => {
394
+ try {
395
+ const db = getDatabase();
396
+ const projects = db.prepare(`
397
+ SELECT DISTINCT project, COUNT(*) as memory_count
398
+ FROM memories
399
+ WHERE project IS NOT NULL AND project != ''
400
+ GROUP BY project
401
+ ORDER BY memory_count DESC
402
+ `).all();
403
+ // Add "All Projects" option with total count
404
+ const totalCount = db.prepare('SELECT COUNT(*) as count FROM memories').get();
405
+ res.json({
406
+ projects: [
407
+ { project: null, memory_count: totalCount.count, label: 'All Projects' },
408
+ ...projects.map(p => ({ ...p, label: p.project })),
409
+ ],
410
+ });
411
+ }
412
+ catch (error) {
413
+ res.status(500).json({ error: error.message });
414
+ }
415
+ });
416
+ // ============================================
417
+ // CONTROL ENDPOINTS
418
+ // ============================================
419
+ // Get control status
420
+ app.get('/api/control/status', (_req, res) => {
421
+ try {
422
+ const status = getControlStatus();
423
+ res.json(status);
424
+ }
425
+ catch (error) {
426
+ res.status(500).json({ error: error.message });
427
+ }
428
+ });
429
+ // Pause memory creation
430
+ app.post('/api/control/pause', (_req, res) => {
431
+ try {
432
+ pause();
433
+ res.json({ paused: true, message: 'Memory creation paused' });
434
+ }
435
+ catch (error) {
436
+ res.status(500).json({ error: error.message });
437
+ }
438
+ });
439
+ // Resume memory creation
440
+ app.post('/api/control/resume', (_req, res) => {
441
+ try {
442
+ resume();
443
+ res.json({ paused: false, message: 'Memory creation resumed' });
444
+ }
445
+ catch (error) {
446
+ res.status(500).json({ error: error.message });
447
+ }
448
+ });
449
+ // ============================================
450
+ // VERSION ENDPOINTS
451
+ // ============================================
452
+ // Get current version
453
+ app.get('/api/version', (_req, res) => {
454
+ try {
455
+ const version = getCurrentVersion();
456
+ res.json({ version });
457
+ }
458
+ catch (error) {
459
+ res.status(500).json({ error: error.message });
460
+ }
461
+ });
462
+ // Check for updates
463
+ app.get('/api/version/check', async (req, res) => {
464
+ try {
465
+ const forceRefresh = req.query.force === 'true';
466
+ const versionInfo = await checkForUpdates(forceRefresh);
467
+ res.json(versionInfo);
468
+ }
469
+ catch (error) {
470
+ res.status(500).json({ error: error.message });
471
+ }
472
+ });
473
+ // Perform update
474
+ app.post('/api/version/update', async (_req, res) => {
475
+ try {
476
+ // Notify clients that update is starting
477
+ broadcast({
478
+ type: 'update_started',
479
+ timestamp: new Date().toISOString(),
480
+ data: { message: 'Update in progress...' },
481
+ });
482
+ const result = await performUpdate();
483
+ // Notify clients of result
484
+ broadcast({
485
+ type: result.success ? 'update_complete' : 'update_failed',
486
+ timestamp: new Date().toISOString(),
487
+ data: result,
488
+ });
489
+ res.json(result);
490
+ }
491
+ catch (error) {
492
+ res.status(500).json({ error: error.message });
493
+ }
494
+ });
495
+ // Restart server
496
+ app.post('/api/version/restart', (_req, res) => {
497
+ try {
498
+ // Notify all WebSocket clients
499
+ broadcast({
500
+ type: 'server_restarting',
501
+ timestamp: new Date().toISOString(),
502
+ data: { message: 'Server restarting in 3 seconds...' },
503
+ });
504
+ // Close WebSocket connections gracefully
505
+ for (const client of clients) {
506
+ client.send(JSON.stringify({
507
+ type: 'server_restarting',
508
+ timestamp: new Date().toISOString(),
509
+ data: { reconnectIn: 5000 },
510
+ }));
511
+ }
512
+ // Schedule restart after response is sent
513
+ res.json({ success: true, message: 'Server will restart in 3 seconds' });
514
+ scheduleRestart(3000);
515
+ }
516
+ catch (error) {
517
+ res.status(500).json({ error: error.message });
518
+ }
519
+ });
520
+ // Get memory links/relationships
521
+ app.get('/api/links', (req, res) => {
522
+ try {
523
+ const project = typeof req.query.project === 'string' ? req.query.project : undefined;
524
+ const db = getDatabase();
525
+ const query = project
526
+ ? `
527
+ SELECT
528
+ ml.*,
529
+ m1.title as source_title,
530
+ m1.category as source_category,
531
+ m1.type as source_type,
532
+ m2.title as target_title,
533
+ m2.category as target_category,
534
+ m2.type as target_type
535
+ FROM memory_links ml
536
+ JOIN memories m1 ON ml.source_id = m1.id
537
+ JOIN memories m2 ON ml.target_id = m2.id
538
+ WHERE m1.project = ? OR m2.project = ?
539
+ ORDER BY ml.created_at DESC
540
+ LIMIT 500
541
+ `
542
+ : `
543
+ SELECT
544
+ ml.*,
545
+ m1.title as source_title,
546
+ m1.category as source_category,
547
+ m1.type as source_type,
548
+ m2.title as target_title,
549
+ m2.category as target_category,
550
+ m2.type as target_type
551
+ FROM memory_links ml
552
+ JOIN memories m1 ON ml.source_id = m1.id
553
+ JOIN memories m2 ON ml.target_id = m2.id
554
+ ORDER BY ml.created_at DESC
555
+ LIMIT 500
556
+ `;
557
+ const links = project
558
+ ? db.prepare(query).all(project, project)
559
+ : db.prepare(query).all();
560
+ res.json(links);
561
+ }
562
+ catch (error) {
563
+ res.status(500).json({ error: error.message });
564
+ }
565
+ });
566
+ // ============================================
567
+ // INSIGHTS ENDPOINTS
568
+ // ============================================
569
+ // (activity and quality routes moved above :id route)
570
+ // ============================================
571
+ // SQL CONSOLE ENDPOINT
572
+ // ============================================
573
+ // Execute SQL query (with safety restrictions)
574
+ app.post('/api/sql', (req, res) => {
575
+ try {
576
+ const { query, allowWrite } = req.body;
577
+ if (!query || typeof query !== 'string') {
578
+ return res.status(400).json({ error: 'Query string required' });
579
+ }
580
+ const upperQuery = query.toUpperCase().trim();
581
+ // Always block DROP and TRUNCATE
582
+ if (/\bDROP\b/.test(upperQuery) || /\bTRUNCATE\b/.test(upperQuery)) {
583
+ return res.status(403).json({
584
+ error: 'DROP and TRUNCATE operations are blocked for safety',
585
+ });
586
+ }
587
+ // Block writes unless explicitly allowed
588
+ const isWriteOperation = upperQuery.startsWith('INSERT') ||
589
+ upperQuery.startsWith('UPDATE') ||
590
+ upperQuery.startsWith('DELETE') ||
591
+ upperQuery.startsWith('ALTER') ||
592
+ upperQuery.startsWith('CREATE');
593
+ if (isWriteOperation && !allowWrite) {
594
+ return res.status(403).json({
595
+ error: 'Write operations are disabled. Enable allowWrite to execute.',
596
+ });
597
+ }
598
+ const db = getDatabase();
599
+ const startTime = Date.now();
600
+ // Execute query
601
+ const isSelect = upperQuery.startsWith('SELECT') || upperQuery.startsWith('PRAGMA');
602
+ if (isSelect) {
603
+ const rows = db.prepare(query).all();
604
+ const executionTime = Date.now() - startTime;
605
+ // Get column names from first row or empty
606
+ const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
607
+ res.json({
608
+ columns,
609
+ rows,
610
+ rowCount: rows.length,
611
+ executionTime,
612
+ });
613
+ }
614
+ else {
615
+ // Write operation
616
+ const result = db.prepare(query).run();
617
+ const executionTime = Date.now() - startTime;
618
+ res.json({
619
+ columns: ['changes', 'lastInsertRowid'],
620
+ rows: [{ changes: result.changes, lastInsertRowid: result.lastInsertRowid }],
621
+ rowCount: 1,
622
+ executionTime,
623
+ });
624
+ }
625
+ }
626
+ catch (error) {
627
+ res.status(500).json({ error: error.message });
628
+ }
629
+ });
630
+ // Trigger consolidation
631
+ app.post('/api/consolidate', (_req, res) => {
632
+ try {
633
+ const result = consolidate();
634
+ // Emit event for Activity log
635
+ emitConsolidation(result);
636
+ res.json({
637
+ success: true,
638
+ ...result,
639
+ });
640
+ }
641
+ catch (error) {
642
+ res.status(500).json({ error: error.message });
643
+ }
644
+ });
645
+ // Get context summary
646
+ app.get('/api/context', async (req, res) => {
647
+ try {
648
+ const project = typeof req.query.project === 'string' ? req.query.project : undefined;
649
+ const summary = await generateContextSummary(project);
650
+ const formatted = formatContextSummary(summary);
651
+ res.json({
652
+ summary,
653
+ formatted,
654
+ });
655
+ }
656
+ catch (error) {
657
+ res.status(500).json({ error: error.message });
658
+ }
659
+ });
660
+ // Get search suggestions (for autocomplete)
661
+ app.get('/api/suggestions', (req, res) => {
662
+ try {
663
+ const query = typeof req.query.q === 'string' ? req.query.q : '';
664
+ const limit = typeof req.query.limit === 'string' ? parseInt(req.query.limit) : 10;
665
+ if (!query || query.length < 2) {
666
+ return res.json({ suggestions: [] });
667
+ }
668
+ const db = getDatabase();
669
+ // Get suggestions from memory titles, categories, tags, and projects
670
+ const suggestions = [];
671
+ // Search titles that contain the query
672
+ const titleMatches = db.prepare(`
673
+ SELECT DISTINCT title, COUNT(*) as count
674
+ FROM memories
675
+ WHERE title LIKE ?
676
+ GROUP BY title
677
+ ORDER BY count DESC, last_accessed DESC
678
+ LIMIT ?
679
+ `).all(`%${query}%`, limit);
680
+ for (const match of titleMatches) {
681
+ suggestions.push({ text: match.title, type: 'title', count: match.count });
682
+ }
683
+ // Get matching categories
684
+ const categoryMatches = db.prepare(`
685
+ SELECT DISTINCT category, COUNT(*) as count
686
+ FROM memories
687
+ WHERE category LIKE ?
688
+ GROUP BY category
689
+ ORDER BY count DESC
690
+ LIMIT 5
691
+ `).all(`%${query}%`);
692
+ for (const match of categoryMatches) {
693
+ suggestions.push({ text: match.category, type: 'category', count: match.count });
694
+ }
695
+ // Get matching projects
696
+ const projectMatches = db.prepare(`
697
+ SELECT DISTINCT project, COUNT(*) as count
698
+ FROM memories
699
+ WHERE project IS NOT NULL AND project LIKE ?
700
+ GROUP BY project
701
+ ORDER BY count DESC
702
+ LIMIT 5
703
+ `).all(`%${query}%`);
704
+ for (const match of projectMatches) {
705
+ suggestions.push({ text: match.project, type: 'project', count: match.count });
706
+ }
707
+ // Sort by count and limit total results
708
+ suggestions.sort((a, b) => b.count - a.count);
709
+ const limitedSuggestions = suggestions.slice(0, limit);
710
+ res.json({ suggestions: limitedSuggestions });
711
+ }
712
+ catch (error) {
713
+ res.status(500).json({ error: error.message });
714
+ }
715
+ });
716
+ // ============================================
717
+ // GRAPH / ONTOLOGY ENDPOINTS
718
+ // ============================================
719
+ // List entities with optional filters and pagination
720
+ app.get('/api/graph/entities', (req, res) => {
721
+ try {
722
+ const db = getDatabase();
723
+ const type = typeof req.query.type === 'string' ? req.query.type : undefined;
724
+ const minMentions = typeof req.query.minMentions === 'string' ? parseInt(req.query.minMentions) : 0;
725
+ const limit = typeof req.query.limit === 'string' ? Math.min(parseInt(req.query.limit), 500) : 100;
726
+ const offset = typeof req.query.offset === 'string' ? parseInt(req.query.offset) : 0;
727
+ let whereClause = 'WHERE 1=1';
728
+ const params = [];
729
+ if (type) {
730
+ whereClause += ' AND type = ?';
731
+ params.push(type);
732
+ }
733
+ if (minMentions > 0) {
734
+ whereClause += ' AND memory_count >= ?';
735
+ params.push(minMentions);
736
+ }
737
+ const totalRow = db.prepare(`SELECT COUNT(*) as count FROM entities ${whereClause}`).get(...params);
738
+ const total = totalRow.count;
739
+ const rows = db.prepare(`SELECT * FROM entities ${whereClause} ORDER BY memory_count DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
740
+ const entities = rows.map((r) => {
741
+ let aliases = [];
742
+ try {
743
+ aliases = JSON.parse(r.aliases || '[]');
744
+ }
745
+ catch {
746
+ aliases = [];
747
+ }
748
+ return {
749
+ id: r.id,
750
+ name: r.name,
751
+ type: r.type,
752
+ memoryCount: r.memory_count ?? 0,
753
+ aliases,
754
+ createdAt: r.created_at,
755
+ updatedAt: r.updated_at,
756
+ };
757
+ });
758
+ res.json({ entities, total, offset, limit, hasMore: offset + limit < total });
759
+ }
760
+ catch (error) {
761
+ res.status(500).json({ error: error.message });
762
+ }
763
+ });
764
+ // Get triples for a specific entity
765
+ app.get('/api/graph/entities/:id/triples', (req, res) => {
766
+ try {
767
+ const db = getDatabase();
768
+ const id = parseInt(req.params.id);
769
+ if (isNaN(id)) {
770
+ return res.status(400).json({ error: 'Invalid entity ID' });
771
+ }
772
+ const rows = db.prepare(`
773
+ SELECT t.*, s.name as subject_name, s.type as subject_type,
774
+ o.name as object_name, o.type as object_type
775
+ FROM triples t
776
+ JOIN entities s ON s.id = t.subject_id
777
+ JOIN entities o ON o.id = t.object_id
778
+ WHERE t.subject_id = ? OR t.object_id = ?
779
+ ORDER BY t.created_at DESC
780
+ `).all(id, id);
781
+ res.json({ triples: rows });
782
+ }
783
+ catch (error) {
784
+ res.status(500).json({ error: error.message });
785
+ }
786
+ });
787
+ // Get memories linked to a specific entity
788
+ app.get('/api/graph/entities/:id/memories', (req, res) => {
789
+ try {
790
+ const db = getDatabase();
791
+ const id = parseInt(req.params.id);
792
+ if (isNaN(id)) {
793
+ return res.status(400).json({ error: 'Invalid entity ID' });
794
+ }
795
+ const rows = db.prepare(`
796
+ SELECT m.id, m.title, m.type, m.category, m.salience, m.created_at
797
+ FROM memories m
798
+ JOIN memory_entities me ON me.memory_id = m.id
799
+ WHERE me.entity_id = ?
800
+ ORDER BY m.salience DESC, m.created_at DESC
801
+ LIMIT 50
802
+ `).all(id);
803
+ res.json({ memories: rows });
804
+ }
805
+ catch (error) {
806
+ res.status(500).json({ error: error.message });
807
+ }
808
+ });
809
+ // List triples with optional predicate filter and pagination
810
+ app.get('/api/graph/triples', (req, res) => {
811
+ try {
812
+ const db = getDatabase();
813
+ const predicate = typeof req.query.predicate === 'string' ? req.query.predicate : undefined;
814
+ const limit = typeof req.query.limit === 'string' ? Math.min(parseInt(req.query.limit), 500) : 100;
815
+ const offset = typeof req.query.offset === 'string' ? parseInt(req.query.offset) : 0;
816
+ let whereClause = '';
817
+ const params = [];
818
+ if (predicate) {
819
+ whereClause = 'WHERE t.predicate = ?';
820
+ params.push(predicate);
821
+ }
822
+ const totalRow = db.prepare(`SELECT COUNT(*) as count FROM triples t ${whereClause}`).get(...params);
823
+ const total = totalRow.count;
824
+ const rows = db.prepare(`
825
+ SELECT t.*, s.name as subject_name, s.type as subject_type,
826
+ o.name as object_name, o.type as object_type
827
+ FROM triples t
828
+ JOIN entities s ON s.id = t.subject_id
829
+ JOIN entities o ON o.id = t.object_id
830
+ ${whereClause}
831
+ ORDER BY t.created_at DESC
832
+ LIMIT ? OFFSET ?
833
+ `).all(...params, limit, offset);
834
+ res.json({ triples: rows, total, offset, limit, hasMore: offset + limit < total });
835
+ }
836
+ catch (error) {
837
+ res.status(500).json({ error: error.message });
838
+ }
839
+ });
840
+ // Search entities by name
841
+ app.get('/api/graph/search', (req, res) => {
842
+ try {
843
+ const db = getDatabase();
844
+ const q = typeof req.query.q === 'string' ? req.query.q : '';
845
+ if (!q) {
846
+ return res.status(400).json({ error: 'Query parameter "q" is required' });
847
+ }
848
+ const rows = db.prepare(`SELECT * FROM entities WHERE LOWER(name) LIKE ? ORDER BY memory_count DESC LIMIT 20`).all(`%${q.toLowerCase()}%`);
849
+ const entities = rows.map((r) => {
850
+ let aliases = [];
851
+ try {
852
+ aliases = JSON.parse(r.aliases || '[]');
853
+ }
854
+ catch {
855
+ aliases = [];
856
+ }
857
+ return {
858
+ id: r.id,
859
+ name: r.name,
860
+ type: r.type,
861
+ memoryCount: r.memory_count ?? 0,
862
+ aliases,
863
+ };
864
+ });
865
+ res.json({ entities });
866
+ }
867
+ catch (error) {
868
+ res.status(500).json({ error: error.message });
869
+ }
870
+ });
871
+ // Find path between two entities using BFS
872
+ app.get('/api/graph/paths', (req, res) => {
873
+ try {
874
+ const db = getDatabase();
875
+ const fromName = typeof req.query.from === 'string' ? req.query.from : '';
876
+ const toName = typeof req.query.to === 'string' ? req.query.to : '';
877
+ if (!fromName || !toName) {
878
+ return res.status(400).json({ error: 'Both "from" and "to" query parameters are required' });
879
+ }
880
+ const fromRow = db.prepare('SELECT * FROM entities WHERE LOWER(name) = LOWER(?)').get(fromName);
881
+ if (!fromRow) {
882
+ return res.status(404).json({ error: `Entity "${fromName}" not found` });
883
+ }
884
+ const toRow = db.prepare('SELECT * FROM entities WHERE LOWER(name) = LOWER(?)').get(toName);
885
+ if (!toRow) {
886
+ return res.status(404).json({ error: `Entity "${toName}" not found` });
887
+ }
888
+ if (fromRow.id === toRow.id) {
889
+ return res.json({ path: [{ entity: fromRow.name, predicate: '(self)' }], sourceMemories: [] });
890
+ }
891
+ // BFS
892
+ const maxDepth = 4;
893
+ const visited = new Map();
894
+ visited.set(fromRow.id, { id: fromRow.id, name: fromRow.name, parentId: null, predicate: '', sourceMemoryId: null });
895
+ let frontier = [fromRow.id];
896
+ let found = false;
897
+ for (let d = 0; d < maxDepth && !found; d++) {
898
+ const nextFrontier = [];
899
+ for (const nodeId of frontier) {
900
+ const outgoing = db.prepare('SELECT t.object_id as next_id, t.predicate, t.source_memory_id, e.name FROM triples t JOIN entities e ON e.id = t.object_id WHERE t.subject_id = ?').all(nodeId);
901
+ for (const row of outgoing) {
902
+ if (!visited.has(row.next_id)) {
903
+ visited.set(row.next_id, { id: row.next_id, name: row.name, parentId: nodeId, predicate: row.predicate, sourceMemoryId: row.source_memory_id });
904
+ nextFrontier.push(row.next_id);
905
+ if (row.next_id === toRow.id) {
906
+ found = true;
907
+ break;
908
+ }
909
+ }
910
+ }
911
+ if (found)
912
+ break;
913
+ const incoming = db.prepare('SELECT t.subject_id as next_id, t.predicate, t.source_memory_id, e.name FROM triples t JOIN entities e ON e.id = t.subject_id WHERE t.object_id = ?').all(nodeId);
914
+ for (const row of incoming) {
915
+ if (!visited.has(row.next_id)) {
916
+ visited.set(row.next_id, { id: row.next_id, name: row.name, parentId: nodeId, predicate: `~${row.predicate}`, sourceMemoryId: row.source_memory_id });
917
+ nextFrontier.push(row.next_id);
918
+ if (row.next_id === toRow.id) {
919
+ found = true;
920
+ break;
921
+ }
922
+ }
923
+ }
924
+ if (found)
925
+ break;
926
+ }
927
+ frontier = nextFrontier;
928
+ if (frontier.length === 0)
929
+ break;
930
+ }
931
+ if (!found) {
932
+ return res.json({ path: [], sourceMemories: [], message: 'No path found' });
933
+ }
934
+ // Reconstruct path
935
+ const path = [];
936
+ const sourceMemoryIds = [];
937
+ let current = visited.get(toRow.id);
938
+ while (current) {
939
+ path.unshift({ entity: current.name, predicate: current.predicate });
940
+ if (current.sourceMemoryId)
941
+ sourceMemoryIds.push(current.sourceMemoryId);
942
+ current = current.parentId !== null ? visited.get(current.parentId) : undefined;
943
+ }
944
+ // Fetch source memories
945
+ const sourceMemories = sourceMemoryIds.length > 0
946
+ ? db.prepare(`SELECT id, title FROM memories WHERE id IN (${sourceMemoryIds.map(() => '?').join(',')})`).all(...sourceMemoryIds)
947
+ : [];
948
+ res.json({ path, sourceMemories });
949
+ }
950
+ catch (error) {
951
+ res.status(500).json({ error: error.message });
952
+ }
953
+ });
954
+ // ============================================
955
+ // BRAIN WORKER (Phase 4)
956
+ // ============================================
957
+ // Create and start the background brain worker
958
+ const brainWorker = new BrainWorker();
959
+ // Worker status endpoint
960
+ app.get('/api/worker/status', (_req, res) => {
961
+ try {
962
+ res.json(brainWorker.getStatus());
963
+ }
964
+ catch (error) {
965
+ res.status(500).json({ error: error.message });
966
+ }
967
+ });
968
+ // Manually trigger light tick (for testing)
969
+ app.post('/api/worker/trigger-light', async (_req, res) => {
970
+ try {
971
+ const result = await brainWorker.triggerLightTick();
972
+ res.json({
973
+ success: true,
974
+ ...result,
975
+ timestamp: result.timestamp.toISOString(),
976
+ });
977
+ }
978
+ catch (error) {
979
+ res.status(500).json({ error: error.message });
980
+ }
981
+ });
982
+ // Manually trigger medium tick (for testing)
983
+ app.post('/api/worker/trigger-medium', async (_req, res) => {
984
+ try {
985
+ const result = await brainWorker.triggerMediumTick();
986
+ res.json({
987
+ success: true,
988
+ ...result,
989
+ timestamp: result.timestamp.toISOString(),
990
+ });
991
+ }
992
+ catch (error) {
993
+ res.status(500).json({ error: error.message });
994
+ }
995
+ });
996
+ // ============================================
997
+ // WEBSOCKET SERVER
998
+ // ============================================
999
+ const wss = new WebSocketServer({ server, path: '/ws/events' });
1000
+ wss.on('connection', (ws) => {
1001
+ clients.add(ws);
1002
+ console.log(`[WS] Client connected. Total: ${clients.size}`);
1003
+ // Send initial state
1004
+ const stats = getMemoryStats();
1005
+ const memories = getRecentMemories(100);
1006
+ const memoriesWithDecay = memories.map(m => ({
1007
+ ...m,
1008
+ decayedScore: calculateDecayedScore(m),
1009
+ }));
1010
+ ws.send(JSON.stringify({
1011
+ type: 'initial_state',
1012
+ timestamp: new Date().toISOString(),
1013
+ data: {
1014
+ stats,
1015
+ memories: memoriesWithDecay,
1016
+ },
1017
+ }));
1018
+ ws.on('close', () => {
1019
+ clients.delete(ws);
1020
+ console.log(`[WS] Client disconnected. Total: ${clients.size}`);
1021
+ });
1022
+ ws.on('error', (error) => {
1023
+ console.error('[WS] Error:', error);
1024
+ clients.delete(ws);
1025
+ });
1026
+ });
1027
+ // Broadcast events to all connected clients
1028
+ function broadcast(event) {
1029
+ const message = JSON.stringify(event);
1030
+ for (const client of clients) {
1031
+ if (client.readyState === WebSocket.OPEN) {
1032
+ client.send(message);
1033
+ }
1034
+ }
1035
+ }
1036
+ // Subscribe to memory events
1037
+ memoryEvents.onMemoryEvent((event) => {
1038
+ broadcast(event);
1039
+ });
1040
+ // Decay tick - update clients with decay changes every 30 seconds
1041
+ let decayTickCount = 0;
1042
+ setInterval(() => {
1043
+ const db = getDatabase();
1044
+ const rawRows = db.prepare('SELECT * FROM memories ORDER BY last_accessed DESC LIMIT 200').all();
1045
+ // Convert raw DB rows to Memory objects (snake_case -> camelCase)
1046
+ const memories = rawRows.map(rowToMemory);
1047
+ const updates = [];
1048
+ for (const memory of memories) {
1049
+ const newScore = calculateDecayedScore(memory);
1050
+ // Only include memories that have decayed significantly since last update
1051
+ // Compare to decayedScore (not salience) to detect actual changes
1052
+ if (Math.abs(newScore - memory.decayedScore) > 0.01) {
1053
+ updates.push({
1054
+ memoryId: memory.id,
1055
+ oldScore: memory.decayedScore,
1056
+ newScore,
1057
+ });
1058
+ }
1059
+ }
1060
+ if (updates.length > 0) {
1061
+ emitDecayTick(updates);
1062
+ }
1063
+ // Persist decay scores and checkpoint WAL every 5 minutes (10 ticks)
1064
+ decayTickCount++;
1065
+ if (decayTickCount >= 10) {
1066
+ decayTickCount = 0;
1067
+ try {
1068
+ updateDecayScores();
1069
+ // Checkpoint WAL to prevent file bloat and reduce contention
1070
+ const checkpoint = checkpointWal();
1071
+ if (checkpoint.walPages > 0) {
1072
+ console.log(`[WAL] Checkpointed ${checkpoint.checkpointed}/${checkpoint.walPages} pages`);
1073
+ }
1074
+ }
1075
+ catch (error) {
1076
+ console.error('[Maintenance] Failed to persist decay scores or checkpoint:', error);
1077
+ }
1078
+ }
1079
+ }, 30000);
1080
+ // ============================================
1081
+ // CROSS-PROCESS EVENT POLLING (IPC)
1082
+ // ============================================
1083
+ // Poll database for events from MCP process every 500ms
1084
+ const eventPollInterval = setInterval(() => {
1085
+ try {
1086
+ const events = getUnprocessedEvents(50);
1087
+ if (events.length > 0) {
1088
+ const ids = [];
1089
+ for (const event of events) {
1090
+ broadcast({ type: event.type, data: event.data, timestamp: event.timestamp });
1091
+ ids.push(event.id);
1092
+ }
1093
+ markEventsProcessed(ids);
1094
+ console.log(`[Events] Processed ${events.length} cross-process events`);
1095
+ }
1096
+ }
1097
+ catch (error) {
1098
+ // Don't spam logs on transient errors
1099
+ if (Math.random() < 0.1) {
1100
+ console.error('[Events] Event polling error:', error);
1101
+ }
1102
+ }
1103
+ }, 500);
1104
+ // Cleanup old processed events every hour
1105
+ const cleanupInterval = setInterval(() => {
1106
+ try {
1107
+ cleanupOldEvents();
1108
+ }
1109
+ catch (error) {
1110
+ console.error('[Events] Cleanup error:', error);
1111
+ }
1112
+ }, 60 * 60 * 1000);
1113
+ // ============================================
1114
+ // START SERVER
1115
+ // ============================================
1116
+ // Start brain worker before starting server
1117
+ brainWorker.start();
1118
+ // Graceful shutdown handler
1119
+ function gracefulShutdown(signal) {
1120
+ console.log(`\n[Server] Received ${signal}, shutting down gracefully...`);
1121
+ // Stop the brain worker
1122
+ brainWorker.stop();
1123
+ // Clear polling intervals
1124
+ clearInterval(eventPollInterval);
1125
+ clearInterval(cleanupInterval);
1126
+ // Close WebSocket connections
1127
+ for (const client of clients) {
1128
+ client.close();
1129
+ }
1130
+ clients.clear();
1131
+ // Close the HTTP server
1132
+ server.close(() => {
1133
+ console.log('[Server] HTTP server closed');
1134
+ // Checkpoint WAL before exit
1135
+ try {
1136
+ checkpointWal();
1137
+ console.log('[Server] WAL checkpointed');
1138
+ }
1139
+ catch (e) {
1140
+ console.error('[Server] Failed to checkpoint WAL:', e);
1141
+ }
1142
+ process.exit(0);
1143
+ });
1144
+ // Force exit after 10 seconds
1145
+ setTimeout(() => {
1146
+ console.error('[Server] Forced exit after timeout');
1147
+ process.exit(1);
1148
+ }, 10000);
1149
+ }
1150
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
1151
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
1152
+ server.listen(PORT, () => {
1153
+ console.log(`
1154
+ ╔══════════════════════════════════════════════════════════════╗
1155
+ ║ 🧠 ShieldCortex API Server ║
1156
+ ╠══════════════════════════════════════════════════════════════╣
1157
+ ║ REST API: http://localhost:${PORT}/api ║
1158
+ ║ WebSocket: ws://localhost:${PORT}/ws/events ║
1159
+ ║ ║
1160
+ ║ Endpoints: ║
1161
+ ║ GET /api/health - Health check ║
1162
+ ║ GET /api/memories - List memories ║
1163
+ ║ GET /api/memories/:id - Get memory ║
1164
+ ║ POST /api/memories - Create memory ║
1165
+ ║ DEL /api/memories/:id - Delete memory ║
1166
+ ║ POST /api/memories/:id/access - Reinforce memory ║
1167
+ ║ GET /api/stats - Memory statistics ║
1168
+ ║ GET /api/links - Memory relationships ║
1169
+ ║ POST /api/consolidate - Trigger consolidation ║
1170
+ ║ GET /api/context - Context summary ║
1171
+ ║ GET /api/suggestions - Search autocomplete ║
1172
+ ║ ║
1173
+ ║ Control: ║
1174
+ ║ GET /api/control/status - Get pause state & uptime ║
1175
+ ║ POST /api/control/pause - Pause memory creation ║
1176
+ ║ POST /api/control/resume - Resume memory creation ║
1177
+ ║ ║
1178
+ ║ Brain Worker: ║
1179
+ ║ GET /api/worker/status - Worker status ║
1180
+ ║ POST /api/worker/trigger-light - Trigger light tick ║
1181
+ ║ POST /api/worker/trigger-medium - Trigger medium tick ║
1182
+ ╚══════════════════════════════════════════════════════════════╝
1183
+ `);
1184
+ });
1185
+ }
1186
+ //# sourceMappingURL=visualization-server.js.map