specmem-hardwicksoftware 3.5.99

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 (1473) hide show
  1. package/CHANGELOG.md +299 -0
  2. package/LICENSE.md +6406 -0
  3. package/README.md +539 -0
  4. package/bin/AegisTheme.cjs +1022 -0
  5. package/bin/AnsiRenderer.cjs +1055 -0
  6. package/bin/BoxRenderer.cjs +605 -0
  7. package/bin/ClaudeLiveScreen.cjs +1299 -0
  8. package/bin/DashboardModules.cjs +733 -0
  9. package/bin/LiveScreenCapture.cjs +1012 -0
  10. package/bin/MemoryBrowserScreen.cjs +1595 -0
  11. package/bin/TabManager.cjs +1414 -0
  12. package/bin/checkAgentStatus-fix.patch +30 -0
  13. package/bin/mcp-socket-client.cjs +462 -0
  14. package/bin/screen-utils.cjs +106 -0
  15. package/bin/specmem-autoclaude.cjs +663 -0
  16. package/bin/specmem-cleanup.cjs +421 -0
  17. package/bin/specmem-cli.cjs +794 -0
  18. package/bin/specmem-console-teamcomms-class.cjs +428 -0
  19. package/bin/specmem-console.cjs +8104 -0
  20. package/bin/specmem-statusbar.cjs +530 -0
  21. package/bootstrap.cjs +5065 -0
  22. package/claude-hooks/agent-chooser-hook.js +179 -0
  23. package/claude-hooks/agent-chooser-inject.js +121 -0
  24. package/claude-hooks/agent-loading-hook.js +990 -0
  25. package/claude-hooks/agent-output-fader.cjs +542 -0
  26. package/claude-hooks/agent-output-interceptor.js +193 -0
  27. package/claude-hooks/agent-type-matcher.js +419 -0
  28. package/claude-hooks/auto-bypass.py +74 -0
  29. package/claude-hooks/background-completion-silencer.js +134 -0
  30. package/claude-hooks/bash-auto-background.js +182 -0
  31. package/claude-hooks/build-cedict-dictionary.mjs +167 -0
  32. package/claude-hooks/bullshit-radar.cjs +323 -0
  33. package/claude-hooks/cedict-codes.json +270 -0
  34. package/claude-hooks/cedict-extracted.json +22632 -0
  35. package/claude-hooks/claude-watchdog.sh +401 -0
  36. package/claude-hooks/context-dedup.cjs +144 -0
  37. package/claude-hooks/context-yeeter.cjs +244 -0
  38. package/claude-hooks/debug-suffix.cjs +15 -0
  39. package/claude-hooks/debug2.cjs +7 -0
  40. package/claude-hooks/drilldown-enforcer.js +242 -0
  41. package/claude-hooks/english-morphology-standalone.cjs +149 -0
  42. package/claude-hooks/english-morphology.cjs +152 -0
  43. package/claude-hooks/extract-translations.mjs +193 -0
  44. package/claude-hooks/file-claim-enforcer.cjs +293 -0
  45. package/claude-hooks/file-claim-enforcer.js +293 -0
  46. package/claude-hooks/find-collisions.cjs +39 -0
  47. package/claude-hooks/fix-abbreviations.cjs +60 -0
  48. package/claude-hooks/fix-collisions.cjs +60 -0
  49. package/claude-hooks/fix-decompressor.cjs +79 -0
  50. package/claude-hooks/fix-suffixes.cjs +66 -0
  51. package/claude-hooks/grammar-engine.cjs +159 -0
  52. package/claude-hooks/input-aware-improver.js +231 -0
  53. package/claude-hooks/is-agent.cjs +64 -0
  54. package/claude-hooks/mega-test.cjs +213 -0
  55. package/claude-hooks/merge-dictionaries.mjs +207 -0
  56. package/claude-hooks/merged-codes.cjs +22675 -0
  57. package/claude-hooks/merged-codes.json +22676 -0
  58. package/claude-hooks/output-cleaner.cjs +388 -0
  59. package/claude-hooks/post-write-memory-hook.cjs +430 -0
  60. package/claude-hooks/quick-test.cjs +24 -0
  61. package/claude-hooks/quick-test2.cjs +24 -0
  62. package/claude-hooks/remove-bad-codes.cjs +23 -0
  63. package/claude-hooks/search-reminder-hook.js +90 -0
  64. package/claude-hooks/semantic-test.cjs +93 -0
  65. package/claude-hooks/settings.json +445 -0
  66. package/claude-hooks/smart-context-hook.cjs +547 -0
  67. package/claude-hooks/smart-context-hook.js +539 -0
  68. package/claude-hooks/smart-search-interceptor.js +364 -0
  69. package/claude-hooks/socket-connect-helper.cjs +235 -0
  70. package/claude-hooks/specmem/sockets/session-start.lock +1 -0
  71. package/claude-hooks/specmem-context-hook.cjs +357 -0
  72. package/claude-hooks/specmem-context-hook.js +357 -0
  73. package/claude-hooks/specmem-drilldown-hook.cjs +480 -0
  74. package/claude-hooks/specmem-drilldown-hook.js +480 -0
  75. package/claude-hooks/specmem-drilldown-setter.js +210 -0
  76. package/claude-hooks/specmem-paths.cjs +213 -0
  77. package/claude-hooks/specmem-precompact.js +183 -0
  78. package/claude-hooks/specmem-session-init.sh +33 -0
  79. package/claude-hooks/specmem-session-start.cjs +498 -0
  80. package/claude-hooks/specmem-stop-hook.cjs +73 -0
  81. package/claude-hooks/specmem-stop-hook.js +5 -0
  82. package/claude-hooks/specmem-team-comms.cjs +434 -0
  83. package/claude-hooks/specmem-team-member-inject.js +271 -0
  84. package/claude-hooks/specmem-unified-hook.py +670 -0
  85. package/claude-hooks/subagent-loading-hook.js +194 -0
  86. package/claude-hooks/sysprompt-squisher.cjs +167 -0
  87. package/claude-hooks/task-progress-hook.js +204 -0
  88. package/claude-hooks/team-comms-enforcer.cjs +585 -0
  89. package/claude-hooks/test-accuracy.cjs +27 -0
  90. package/claude-hooks/test-big.cjs +28 -0
  91. package/claude-hooks/test-inflectors.cjs +39 -0
  92. package/claude-hooks/test-pluralize.cjs +37 -0
  93. package/claude-hooks/test-quick.cjs +8 -0
  94. package/claude-hooks/test-wink.cjs +20 -0
  95. package/claude-hooks/token-compressor.cjs +940 -0
  96. package/claude-hooks/use-code-pointers.cjs +279 -0
  97. package/commands/COMMAND_TOOL_MAP.md +299 -0
  98. package/commands/specmem-agents.md +412 -0
  99. package/commands/specmem-autoclaude.md +295 -0
  100. package/commands/specmem-changes.md +247 -0
  101. package/commands/specmem-code.md +103 -0
  102. package/commands/specmem-configteammembercomms.md +322 -0
  103. package/commands/specmem-drilldown.md +208 -0
  104. package/commands/specmem-find.md +195 -0
  105. package/commands/specmem-getdashboard.md +243 -0
  106. package/commands/specmem-hooks.md +219 -0
  107. package/commands/specmem-pointers.md +149 -0
  108. package/commands/specmem-progress.md +287 -0
  109. package/commands/specmem-remember.md +123 -0
  110. package/commands/specmem-service.md +349 -0
  111. package/commands/specmem-stats.md +189 -0
  112. package/commands/specmem-team-member.md +409 -0
  113. package/commands/specmem-webdev.md +583 -0
  114. package/commands/specmem.md +363 -0
  115. package/dist/autoStart/index.d.ts +214 -0
  116. package/dist/autoStart/index.d.ts.map +1 -0
  117. package/dist/autoStart/index.js +883 -0
  118. package/dist/autoStart/index.js.map +1 -0
  119. package/dist/claude-sessions/contextRestorationParser.d.ts +74 -0
  120. package/dist/claude-sessions/contextRestorationParser.d.ts.map +1 -0
  121. package/dist/claude-sessions/contextRestorationParser.js +570 -0
  122. package/dist/claude-sessions/contextRestorationParser.js.map +1 -0
  123. package/dist/claude-sessions/index.d.ts +13 -0
  124. package/dist/claude-sessions/index.d.ts.map +1 -0
  125. package/dist/claude-sessions/index.js +11 -0
  126. package/dist/claude-sessions/index.js.map +1 -0
  127. package/dist/claude-sessions/sessionIntegration.d.ts +48 -0
  128. package/dist/claude-sessions/sessionIntegration.d.ts.map +1 -0
  129. package/dist/claude-sessions/sessionIntegration.js +146 -0
  130. package/dist/claude-sessions/sessionIntegration.js.map +1 -0
  131. package/dist/claude-sessions/sessionParser.d.ts +293 -0
  132. package/dist/claude-sessions/sessionParser.d.ts.map +1 -0
  133. package/dist/claude-sessions/sessionParser.js +1028 -0
  134. package/dist/claude-sessions/sessionParser.js.map +1 -0
  135. package/dist/claude-sessions/sessionWatcher.d.ts +139 -0
  136. package/dist/claude-sessions/sessionWatcher.d.ts.map +1 -0
  137. package/dist/claude-sessions/sessionWatcher.js +722 -0
  138. package/dist/claude-sessions/sessionWatcher.js.map +1 -0
  139. package/dist/cli/deploy-to-claude.d.ts +56 -0
  140. package/dist/cli/deploy-to-claude.d.ts.map +1 -0
  141. package/dist/cli/deploy-to-claude.js +576 -0
  142. package/dist/cli/deploy-to-claude.js.map +1 -0
  143. package/dist/code-explanations/explainCode.d.ts +86 -0
  144. package/dist/code-explanations/explainCode.d.ts.map +1 -0
  145. package/dist/code-explanations/explainCode.js +286 -0
  146. package/dist/code-explanations/explainCode.js.map +1 -0
  147. package/dist/code-explanations/feedback.d.ts +87 -0
  148. package/dist/code-explanations/feedback.d.ts.map +1 -0
  149. package/dist/code-explanations/feedback.js +212 -0
  150. package/dist/code-explanations/feedback.js.map +1 -0
  151. package/dist/code-explanations/getRelatedCode.d.ts +80 -0
  152. package/dist/code-explanations/getRelatedCode.d.ts.map +1 -0
  153. package/dist/code-explanations/getRelatedCode.js +262 -0
  154. package/dist/code-explanations/getRelatedCode.js.map +1 -0
  155. package/dist/code-explanations/index.d.ts +284 -0
  156. package/dist/code-explanations/index.d.ts.map +1 -0
  157. package/dist/code-explanations/index.js +249 -0
  158. package/dist/code-explanations/index.js.map +1 -0
  159. package/dist/code-explanations/linkCodeToPrompt.d.ts +79 -0
  160. package/dist/code-explanations/linkCodeToPrompt.d.ts.map +1 -0
  161. package/dist/code-explanations/linkCodeToPrompt.js +213 -0
  162. package/dist/code-explanations/linkCodeToPrompt.js.map +1 -0
  163. package/dist/code-explanations/recallExplanation.d.ts +88 -0
  164. package/dist/code-explanations/recallExplanation.d.ts.map +1 -0
  165. package/dist/code-explanations/recallExplanation.js +218 -0
  166. package/dist/code-explanations/recallExplanation.js.map +1 -0
  167. package/dist/code-explanations/schema.d.ts +32 -0
  168. package/dist/code-explanations/schema.d.ts.map +1 -0
  169. package/dist/code-explanations/schema.js +221 -0
  170. package/dist/code-explanations/schema.js.map +1 -0
  171. package/dist/code-explanations/semanticSearch.d.ts +75 -0
  172. package/dist/code-explanations/semanticSearch.d.ts.map +1 -0
  173. package/dist/code-explanations/semanticSearch.js +203 -0
  174. package/dist/code-explanations/semanticSearch.js.map +1 -0
  175. package/dist/code-explanations/types.d.ts +328 -0
  176. package/dist/code-explanations/types.d.ts.map +1 -0
  177. package/dist/code-explanations/types.js +122 -0
  178. package/dist/code-explanations/types.js.map +1 -0
  179. package/dist/codebase/codeAnalyzer.d.ts +272 -0
  180. package/dist/codebase/codeAnalyzer.d.ts.map +1 -0
  181. package/dist/codebase/codeAnalyzer.js +1353 -0
  182. package/dist/codebase/codeAnalyzer.js.map +1 -0
  183. package/dist/codebase/codebaseIndexer.d.ts +360 -0
  184. package/dist/codebase/codebaseIndexer.d.ts.map +1 -0
  185. package/dist/codebase/codebaseIndexer.js +1735 -0
  186. package/dist/codebase/codebaseIndexer.js.map +1 -0
  187. package/dist/codebase/codebaseTools.d.ts +853 -0
  188. package/dist/codebase/codebaseTools.d.ts.map +1 -0
  189. package/dist/codebase/codebaseTools.js +1279 -0
  190. package/dist/codebase/codebaseTools.js.map +1 -0
  191. package/dist/codebase/exclusions.d.ts +111 -0
  192. package/dist/codebase/exclusions.d.ts.map +1 -0
  193. package/dist/codebase/exclusions.js +771 -0
  194. package/dist/codebase/exclusions.js.map +1 -0
  195. package/dist/codebase/fileWatcher.d.ts +135 -0
  196. package/dist/codebase/fileWatcher.d.ts.map +1 -0
  197. package/dist/codebase/fileWatcher.js +309 -0
  198. package/dist/codebase/fileWatcher.js.map +1 -0
  199. package/dist/codebase/index.d.ts +33 -0
  200. package/dist/codebase/index.d.ts.map +1 -0
  201. package/dist/codebase/index.js +77 -0
  202. package/dist/codebase/index.js.map +1 -0
  203. package/dist/codebase/ingestion.d.ts +177 -0
  204. package/dist/codebase/ingestion.d.ts.map +1 -0
  205. package/dist/codebase/ingestion.js +690 -0
  206. package/dist/codebase/ingestion.js.map +1 -0
  207. package/dist/codebase/languageDetection.d.ts +75 -0
  208. package/dist/codebase/languageDetection.d.ts.map +1 -0
  209. package/dist/codebase/languageDetection.js +768 -0
  210. package/dist/codebase/languageDetection.js.map +1 -0
  211. package/dist/commands/codebaseCommands.d.ts +101 -0
  212. package/dist/commands/codebaseCommands.d.ts.map +1 -0
  213. package/dist/commands/codebaseCommands.js +911 -0
  214. package/dist/commands/codebaseCommands.js.map +1 -0
  215. package/dist/commands/commandHandler.d.ts +126 -0
  216. package/dist/commands/commandHandler.d.ts.map +1 -0
  217. package/dist/commands/commandHandler.js +296 -0
  218. package/dist/commands/commandHandler.js.map +1 -0
  219. package/dist/commands/commandLoader.d.ts +103 -0
  220. package/dist/commands/commandLoader.d.ts.map +1 -0
  221. package/dist/commands/commandLoader.js +223 -0
  222. package/dist/commands/commandLoader.js.map +1 -0
  223. package/dist/commands/contextCommands.d.ts +83 -0
  224. package/dist/commands/contextCommands.d.ts.map +1 -0
  225. package/dist/commands/contextCommands.js +512 -0
  226. package/dist/commands/contextCommands.js.map +1 -0
  227. package/dist/commands/index.d.ts +24 -0
  228. package/dist/commands/index.d.ts.map +1 -0
  229. package/dist/commands/index.js +28 -0
  230. package/dist/commands/index.js.map +1 -0
  231. package/dist/commands/mcpResources.d.ts +50 -0
  232. package/dist/commands/mcpResources.d.ts.map +1 -0
  233. package/dist/commands/mcpResources.js +372 -0
  234. package/dist/commands/mcpResources.js.map +1 -0
  235. package/dist/commands/memoryCommands.d.ts +74 -0
  236. package/dist/commands/memoryCommands.d.ts.map +1 -0
  237. package/dist/commands/memoryCommands.js +609 -0
  238. package/dist/commands/memoryCommands.js.map +1 -0
  239. package/dist/commands/promptCommands.d.ts +91 -0
  240. package/dist/commands/promptCommands.d.ts.map +1 -0
  241. package/dist/commands/promptCommands.js +801 -0
  242. package/dist/commands/promptCommands.js.map +1 -0
  243. package/dist/commands/teamMemberCommands.d.ts +21 -0
  244. package/dist/commands/teamMemberCommands.d.ts.map +1 -0
  245. package/dist/commands/teamMemberCommands.js +137 -0
  246. package/dist/commands/teamMemberCommands.js.map +1 -0
  247. package/dist/comms/fileCommsTransport.d.ts +91 -0
  248. package/dist/comms/fileCommsTransport.d.ts.map +1 -0
  249. package/dist/comms/fileCommsTransport.js +244 -0
  250. package/dist/comms/fileCommsTransport.js.map +1 -0
  251. package/dist/comms/index.d.ts +7 -0
  252. package/dist/comms/index.d.ts.map +1 -0
  253. package/dist/comms/index.js +7 -0
  254. package/dist/comms/index.js.map +1 -0
  255. package/dist/config/apiKeyDetection.d.ts +41 -0
  256. package/dist/config/apiKeyDetection.d.ts.map +1 -0
  257. package/dist/config/apiKeyDetection.js +211 -0
  258. package/dist/config/apiKeyDetection.js.map +1 -0
  259. package/dist/config/autoConfig.d.ts +188 -0
  260. package/dist/config/autoConfig.d.ts.map +1 -0
  261. package/dist/config/autoConfig.js +850 -0
  262. package/dist/config/autoConfig.js.map +1 -0
  263. package/dist/config/configSync.d.ts +119 -0
  264. package/dist/config/configSync.d.ts.map +1 -0
  265. package/dist/config/configSync.js +878 -0
  266. package/dist/config/configSync.js.map +1 -0
  267. package/dist/config/embeddingTimeouts.d.ts +145 -0
  268. package/dist/config/embeddingTimeouts.d.ts.map +1 -0
  269. package/dist/config/embeddingTimeouts.js +255 -0
  270. package/dist/config/embeddingTimeouts.js.map +1 -0
  271. package/dist/config/index.d.ts +5 -0
  272. package/dist/config/index.d.ts.map +1 -0
  273. package/dist/config/index.js +7 -0
  274. package/dist/config/index.js.map +1 -0
  275. package/dist/config/languageConfig.d.ts +68 -0
  276. package/dist/config/languageConfig.d.ts.map +1 -0
  277. package/dist/config/languageConfig.js +473 -0
  278. package/dist/config/languageConfig.js.map +1 -0
  279. package/dist/config/password.d.ts +145 -0
  280. package/dist/config/password.d.ts.map +1 -0
  281. package/dist/config/password.js +428 -0
  282. package/dist/config/password.js.map +1 -0
  283. package/dist/config.d.ts +338 -0
  284. package/dist/config.d.ts.map +1 -0
  285. package/dist/config.js +1177 -0
  286. package/dist/config.js.map +1 -0
  287. package/dist/consolidation.d.ts +44 -0
  288. package/dist/consolidation.d.ts.map +1 -0
  289. package/dist/consolidation.js +447 -0
  290. package/dist/consolidation.js.map +1 -0
  291. package/dist/constants.d.ts +371 -0
  292. package/dist/constants.d.ts.map +1 -0
  293. package/dist/constants.js +552 -0
  294. package/dist/constants.js.map +1 -0
  295. package/dist/coordination/TeamMemberRegistry.d.ts +192 -0
  296. package/dist/coordination/TeamMemberRegistry.d.ts.map +1 -0
  297. package/dist/coordination/TeamMemberRegistry.js +415 -0
  298. package/dist/coordination/TeamMemberRegistry.js.map +1 -0
  299. package/dist/coordination/events.d.ts +369 -0
  300. package/dist/coordination/events.d.ts.map +1 -0
  301. package/dist/coordination/events.js +232 -0
  302. package/dist/coordination/events.js.map +1 -0
  303. package/dist/coordination/handlers.d.ts +116 -0
  304. package/dist/coordination/handlers.d.ts.map +1 -0
  305. package/dist/coordination/handlers.js +400 -0
  306. package/dist/coordination/handlers.js.map +1 -0
  307. package/dist/coordination/index.d.ts +14 -0
  308. package/dist/coordination/index.d.ts.map +1 -0
  309. package/dist/coordination/index.js +31 -0
  310. package/dist/coordination/index.js.map +1 -0
  311. package/dist/coordination/integration.d.ts +260 -0
  312. package/dist/coordination/integration.d.ts.map +1 -0
  313. package/dist/coordination/integration.js +472 -0
  314. package/dist/coordination/integration.js.map +1 -0
  315. package/dist/coordination/server.d.ts +266 -0
  316. package/dist/coordination/server.d.ts.map +1 -0
  317. package/dist/coordination/server.js +995 -0
  318. package/dist/coordination/server.js.map +1 -0
  319. package/dist/coordination/serviceProvider.d.ts +70 -0
  320. package/dist/coordination/serviceProvider.d.ts.map +1 -0
  321. package/dist/coordination/serviceProvider.js +273 -0
  322. package/dist/coordination/serviceProvider.js.map +1 -0
  323. package/dist/dashboard/api/claudeControl.d.ts +44 -0
  324. package/dist/dashboard/api/claudeControl.d.ts.map +1 -0
  325. package/dist/dashboard/api/claudeControl.js +650 -0
  326. package/dist/dashboard/api/claudeControl.js.map +1 -0
  327. package/dist/dashboard/api/claudeHistory.d.ts +4 -0
  328. package/dist/dashboard/api/claudeHistory.d.ts.map +1 -0
  329. package/dist/dashboard/api/claudeHistory.js +319 -0
  330. package/dist/dashboard/api/claudeHistory.js.map +1 -0
  331. package/dist/dashboard/api/dataExport.d.ts +23 -0
  332. package/dist/dashboard/api/dataExport.d.ts.map +1 -0
  333. package/dist/dashboard/api/dataExport.js +509 -0
  334. package/dist/dashboard/api/dataExport.js.map +1 -0
  335. package/dist/dashboard/api/fileManager.d.ts +39 -0
  336. package/dist/dashboard/api/fileManager.d.ts.map +1 -0
  337. package/dist/dashboard/api/fileManager.js +814 -0
  338. package/dist/dashboard/api/fileManager.js.map +1 -0
  339. package/dist/dashboard/api/hooks.d.ts +16 -0
  340. package/dist/dashboard/api/hooks.d.ts.map +1 -0
  341. package/dist/dashboard/api/hooks.js +342 -0
  342. package/dist/dashboard/api/hooks.js.map +1 -0
  343. package/dist/dashboard/api/hotReload.d.ts +14 -0
  344. package/dist/dashboard/api/hotReload.d.ts.map +1 -0
  345. package/dist/dashboard/api/hotReload.js +219 -0
  346. package/dist/dashboard/api/hotReload.js.map +1 -0
  347. package/dist/dashboard/api/liveSessionStream.d.ts +19 -0
  348. package/dist/dashboard/api/liveSessionStream.d.ts.map +1 -0
  349. package/dist/dashboard/api/liveSessionStream.js +430 -0
  350. package/dist/dashboard/api/liveSessionStream.js.map +1 -0
  351. package/dist/dashboard/api/memoryRecall.d.ts +20 -0
  352. package/dist/dashboard/api/memoryRecall.d.ts.map +1 -0
  353. package/dist/dashboard/api/memoryRecall.js +524 -0
  354. package/dist/dashboard/api/memoryRecall.js.map +1 -0
  355. package/dist/dashboard/api/promptSend.d.ts +33 -0
  356. package/dist/dashboard/api/promptSend.d.ts.map +1 -0
  357. package/dist/dashboard/api/promptSend.js +544 -0
  358. package/dist/dashboard/api/promptSend.js.map +1 -0
  359. package/dist/dashboard/api/settings.d.ts +10 -0
  360. package/dist/dashboard/api/settings.d.ts.map +1 -0
  361. package/dist/dashboard/api/settings.js +656 -0
  362. package/dist/dashboard/api/settings.js.map +1 -0
  363. package/dist/dashboard/api/setup.d.ts +21 -0
  364. package/dist/dashboard/api/setup.d.ts.map +1 -0
  365. package/dist/dashboard/api/setup.js +663 -0
  366. package/dist/dashboard/api/setup.js.map +1 -0
  367. package/dist/dashboard/api/specmemTools.d.ts +14 -0
  368. package/dist/dashboard/api/specmemTools.d.ts.map +1 -0
  369. package/dist/dashboard/api/specmemTools.js +1059 -0
  370. package/dist/dashboard/api/specmemTools.js.map +1 -0
  371. package/dist/dashboard/api/taskTeamMembers.d.ts +8 -0
  372. package/dist/dashboard/api/taskTeamMembers.d.ts.map +1 -0
  373. package/dist/dashboard/api/taskTeamMembers.js +136 -0
  374. package/dist/dashboard/api/taskTeamMembers.js.map +1 -0
  375. package/dist/dashboard/api/teamMemberDeploy.d.ts +15 -0
  376. package/dist/dashboard/api/teamMemberDeploy.d.ts.map +1 -0
  377. package/dist/dashboard/api/teamMemberDeploy.js +421 -0
  378. package/dist/dashboard/api/teamMemberDeploy.js.map +1 -0
  379. package/dist/dashboard/api/teamMemberHistory.d.ts +38 -0
  380. package/dist/dashboard/api/teamMemberHistory.d.ts.map +1 -0
  381. package/dist/dashboard/api/teamMemberHistory.js +583 -0
  382. package/dist/dashboard/api/teamMemberHistory.js.map +1 -0
  383. package/dist/dashboard/api/terminal.d.ts +12 -0
  384. package/dist/dashboard/api/terminal.d.ts.map +1 -0
  385. package/dist/dashboard/api/terminal.js +344 -0
  386. package/dist/dashboard/api/terminal.js.map +1 -0
  387. package/dist/dashboard/api/terminalInject.d.ts +17 -0
  388. package/dist/dashboard/api/terminalInject.d.ts.map +1 -0
  389. package/dist/dashboard/api/terminalInject.js +322 -0
  390. package/dist/dashboard/api/terminalInject.js.map +1 -0
  391. package/dist/dashboard/api/terminalStream.d.ts +12 -0
  392. package/dist/dashboard/api/terminalStream.d.ts.map +1 -0
  393. package/dist/dashboard/api/terminalStream.js +482 -0
  394. package/dist/dashboard/api/terminalStream.js.map +1 -0
  395. package/dist/dashboard/index.d.ts +7 -0
  396. package/dist/dashboard/index.d.ts.map +1 -0
  397. package/dist/dashboard/index.js +7 -0
  398. package/dist/dashboard/index.js.map +1 -0
  399. package/dist/dashboard/ptyStreamer.d.ts +173 -0
  400. package/dist/dashboard/ptyStreamer.d.ts.map +1 -0
  401. package/dist/dashboard/ptyStreamer.js +661 -0
  402. package/dist/dashboard/ptyStreamer.js.map +1 -0
  403. package/dist/dashboard/public/DASHBOARD-README.md +378 -0
  404. package/dist/dashboard/public/INTEGRATION-GUIDE.md +395 -0
  405. package/dist/dashboard/public/codebase-config.html +1247 -0
  406. package/dist/dashboard/public/dashboard-v2.html +1942 -0
  407. package/dist/dashboard/public/data-export.html +819 -0
  408. package/dist/dashboard/public/example-page.html +164 -0
  409. package/dist/dashboard/public/file-explorer.html +1023 -0
  410. package/dist/dashboard/public/hooks.html +1103 -0
  411. package/dist/dashboard/public/index-improvements.css +499 -0
  412. package/dist/dashboard/public/index.html +5534 -0
  413. package/dist/dashboard/public/memory-controls.html +1959 -0
  414. package/dist/dashboard/public/memory-recall.html +1495 -0
  415. package/dist/dashboard/public/previews/skeleton-memory-graph.html +361 -0
  416. package/dist/dashboard/public/previews/skeleton-memory-list.html +366 -0
  417. package/dist/dashboard/public/previews/skeleton-search-results.html +609 -0
  418. package/dist/dashboard/public/previews/skeleton-stats-dashboard.html +556 -0
  419. package/dist/dashboard/public/prompt-console.html +2763 -0
  420. package/dist/dashboard/public/react-dist/assets/index-CkjobT5B.js +871 -0
  421. package/dist/dashboard/public/react-dist/assets/index-iRclxMst.css +1 -0
  422. package/dist/dashboard/public/react-dist/index.html +16 -0
  423. package/dist/dashboard/public/shared-header.js +325 -0
  424. package/dist/dashboard/public/shared-language-selector.js +626 -0
  425. package/dist/dashboard/public/shared-logger.js +66 -0
  426. package/dist/dashboard/public/shared-nav.js +325 -0
  427. package/dist/dashboard/public/shared-theme-blue.css +331 -0
  428. package/dist/dashboard/public/shared-theme.css +813 -0
  429. package/dist/dashboard/public/shared-toast.js +415 -0
  430. package/dist/dashboard/public/team-member-history.html +1291 -0
  431. package/dist/dashboard/public/team-member-spy.html +1199 -0
  432. package/dist/dashboard/public/team-members.html +3756 -0
  433. package/dist/dashboard/public/terminal-output.html +1013 -0
  434. package/dist/dashboard/public/terminal.html +372 -0
  435. package/dist/dashboard/sessionStore.d.ts +86 -0
  436. package/dist/dashboard/sessionStore.d.ts.map +1 -0
  437. package/dist/dashboard/sessionStore.js +262 -0
  438. package/dist/dashboard/sessionStore.js.map +1 -0
  439. package/dist/dashboard/standalone.d.ts +27 -0
  440. package/dist/dashboard/standalone.d.ts.map +1 -0
  441. package/dist/dashboard/standalone.js +380 -0
  442. package/dist/dashboard/standalone.js.map +1 -0
  443. package/dist/dashboard/webServer.d.ts +390 -0
  444. package/dist/dashboard/webServer.d.ts.map +1 -0
  445. package/dist/dashboard/webServer.js +4297 -0
  446. package/dist/dashboard/webServer.js.map +1 -0
  447. package/dist/dashboard/websocket/teamMemberStream.d.ts +87 -0
  448. package/dist/dashboard/websocket/teamMemberStream.d.ts.map +1 -0
  449. package/dist/dashboard/websocket/teamMemberStream.js +366 -0
  450. package/dist/dashboard/websocket/teamMemberStream.js.map +1 -0
  451. package/dist/dashboard/websocket/terminalStream.d.ts +130 -0
  452. package/dist/dashboard/websocket/terminalStream.d.ts.map +1 -0
  453. package/dist/dashboard/websocket/terminalStream.js +456 -0
  454. package/dist/dashboard/websocket/terminalStream.js.map +1 -0
  455. package/dist/database/embeddedPostgres.d.ts +187 -0
  456. package/dist/database/embeddedPostgres.d.ts.map +1 -0
  457. package/dist/database/embeddedPostgres.js +763 -0
  458. package/dist/database/embeddedPostgres.js.map +1 -0
  459. package/dist/database/index.d.ts +12 -0
  460. package/dist/database/index.d.ts.map +1 -0
  461. package/dist/database/index.js +20 -0
  462. package/dist/database/index.js.map +1 -0
  463. package/dist/database/initEmbeddedPostgres.d.ts +124 -0
  464. package/dist/database/initEmbeddedPostgres.d.ts.map +1 -0
  465. package/dist/database/initEmbeddedPostgres.js +855 -0
  466. package/dist/database/initEmbeddedPostgres.js.map +1 -0
  467. package/dist/database.d.ts +256 -0
  468. package/dist/database.d.ts.map +1 -0
  469. package/dist/database.js +1209 -0
  470. package/dist/database.js.map +1 -0
  471. package/dist/db/apiDataManager.d.ts +334 -0
  472. package/dist/db/apiDataManager.d.ts.map +1 -0
  473. package/dist/db/apiDataManager.js +631 -0
  474. package/dist/db/apiDataManager.js.map +1 -0
  475. package/dist/db/batchOperations.d.ts +154 -0
  476. package/dist/db/batchOperations.d.ts.map +1 -0
  477. package/dist/db/batchOperations.js +564 -0
  478. package/dist/db/batchOperations.js.map +1 -0
  479. package/dist/db/bigBrainMigrations.d.ts +48 -0
  480. package/dist/db/bigBrainMigrations.d.ts.map +1 -0
  481. package/dist/db/bigBrainMigrations.js +4266 -0
  482. package/dist/db/bigBrainMigrations.js.map +1 -0
  483. package/dist/db/connectionPoolGoBrrr.d.ts +94 -0
  484. package/dist/db/connectionPoolGoBrrr.d.ts.map +1 -0
  485. package/dist/db/connectionPoolGoBrrr.js +548 -0
  486. package/dist/db/connectionPoolGoBrrr.js.map +1 -0
  487. package/dist/db/dashboardQueries.d.ts +182 -0
  488. package/dist/db/dashboardQueries.d.ts.map +1 -0
  489. package/dist/db/dashboardQueries.js +821 -0
  490. package/dist/db/dashboardQueries.js.map +1 -0
  491. package/dist/db/deploymentBootstrap.d.ts +43 -0
  492. package/dist/db/deploymentBootstrap.d.ts.map +1 -0
  493. package/dist/db/deploymentBootstrap.js +329 -0
  494. package/dist/db/deploymentBootstrap.js.map +1 -0
  495. package/dist/db/dimensionService.d.ts +140 -0
  496. package/dist/db/dimensionService.d.ts.map +1 -0
  497. package/dist/db/dimensionService.js +261 -0
  498. package/dist/db/dimensionService.js.map +1 -0
  499. package/dist/db/embeddingOverflow.d.ts +69 -0
  500. package/dist/db/embeddingOverflow.d.ts.map +1 -0
  501. package/dist/db/embeddingOverflow.js +332 -0
  502. package/dist/db/embeddingOverflow.js.map +1 -0
  503. package/dist/db/embeddingOverflow.sql +221 -0
  504. package/dist/db/findThatShit.d.ts +145 -0
  505. package/dist/db/findThatShit.d.ts.map +1 -0
  506. package/dist/db/findThatShit.js +782 -0
  507. package/dist/db/findThatShit.js.map +1 -0
  508. package/dist/db/hotPathManager.d.ts +187 -0
  509. package/dist/db/hotPathManager.d.ts.map +1 -0
  510. package/dist/db/hotPathManager.js +504 -0
  511. package/dist/db/hotPathManager.js.map +1 -0
  512. package/dist/db/index.d.ts +85 -0
  513. package/dist/db/index.d.ts.map +1 -0
  514. package/dist/db/index.js +219 -0
  515. package/dist/db/index.js.map +1 -0
  516. package/dist/db/memoryDrilldown.sql +99 -0
  517. package/dist/db/migrate.d.ts +3 -0
  518. package/dist/db/migrate.d.ts.map +1 -0
  519. package/dist/db/migrate.js +97 -0
  520. package/dist/db/migrate.js.map +1 -0
  521. package/dist/db/migrateJsonToPostgres.d.ts +43 -0
  522. package/dist/db/migrateJsonToPostgres.d.ts.map +1 -0
  523. package/dist/db/migrateJsonToPostgres.js +465 -0
  524. package/dist/db/migrateJsonToPostgres.js.map +1 -0
  525. package/dist/db/nukeFromOrbit.d.ts +63 -0
  526. package/dist/db/nukeFromOrbit.d.ts.map +1 -0
  527. package/dist/db/nukeFromOrbit.js +499 -0
  528. package/dist/db/nukeFromOrbit.js.map +1 -0
  529. package/dist/db/processedTraining.sql +60 -0
  530. package/dist/db/projectNamespacing.d.ts +258 -0
  531. package/dist/db/projectNamespacing.d.ts.map +1 -0
  532. package/dist/db/projectNamespacing.js +920 -0
  533. package/dist/db/projectNamespacing.js.map +1 -0
  534. package/dist/db/projectNamespacing.sql +374 -0
  535. package/dist/db/projectSchemaInit.sql +271 -0
  536. package/dist/db/spatialMemory.d.ts +296 -0
  537. package/dist/db/spatialMemory.d.ts.map +1 -0
  538. package/dist/db/spatialMemory.js +818 -0
  539. package/dist/db/spatialMemory.js.map +1 -0
  540. package/dist/db/streamingQuery.d.ts +143 -0
  541. package/dist/db/streamingQuery.d.ts.map +1 -0
  542. package/dist/db/streamingQuery.js +350 -0
  543. package/dist/db/streamingQuery.js.map +1 -0
  544. package/dist/db/teamComms.sql +224 -0
  545. package/dist/db/yeetStuffInDb.d.ts +72 -0
  546. package/dist/db/yeetStuffInDb.d.ts.map +1 -0
  547. package/dist/db/yeetStuffInDb.js +473 -0
  548. package/dist/db/yeetStuffInDb.js.map +1 -0
  549. package/dist/embedding-providers/index.d.ts +10 -0
  550. package/dist/embedding-providers/index.d.ts.map +1 -0
  551. package/dist/embedding-providers/index.js +12 -0
  552. package/dist/embedding-providers/index.js.map +1 -0
  553. package/dist/embeddings/projectionLayer.d.ts +114 -0
  554. package/dist/embeddings/projectionLayer.d.ts.map +1 -0
  555. package/dist/embeddings/projectionLayer.js +345 -0
  556. package/dist/embeddings/projectionLayer.js.map +1 -0
  557. package/dist/events/Publisher.d.ts +193 -0
  558. package/dist/events/Publisher.d.ts.map +1 -0
  559. package/dist/events/Publisher.js +439 -0
  560. package/dist/events/Publisher.js.map +1 -0
  561. package/dist/events/config.d.ts +139 -0
  562. package/dist/events/config.d.ts.map +1 -0
  563. package/dist/events/config.js +266 -0
  564. package/dist/events/config.js.map +1 -0
  565. package/dist/events/index.d.ts +19 -0
  566. package/dist/events/index.d.ts.map +1 -0
  567. package/dist/events/index.js +31 -0
  568. package/dist/events/index.js.map +1 -0
  569. package/dist/events/integration.d.ts +206 -0
  570. package/dist/events/integration.d.ts.map +1 -0
  571. package/dist/events/integration.js +348 -0
  572. package/dist/events/integration.js.map +1 -0
  573. package/dist/events/metrics.d.ts +147 -0
  574. package/dist/events/metrics.d.ts.map +1 -0
  575. package/dist/events/metrics.js +343 -0
  576. package/dist/events/metrics.js.map +1 -0
  577. package/dist/hooks/cli.d.ts +28 -0
  578. package/dist/hooks/cli.d.ts.map +1 -0
  579. package/dist/hooks/cli.js +118 -0
  580. package/dist/hooks/cli.js.map +1 -0
  581. package/dist/hooks/contextInjectionHook.d.ts +60 -0
  582. package/dist/hooks/contextInjectionHook.d.ts.map +1 -0
  583. package/dist/hooks/contextInjectionHook.js +294 -0
  584. package/dist/hooks/contextInjectionHook.js.map +1 -0
  585. package/dist/hooks/drilldownHook.d.ts +125 -0
  586. package/dist/hooks/drilldownHook.d.ts.map +1 -0
  587. package/dist/hooks/drilldownHook.js +181 -0
  588. package/dist/hooks/drilldownHook.js.map +1 -0
  589. package/dist/hooks/hookManager.d.ts +180 -0
  590. package/dist/hooks/hookManager.d.ts.map +1 -0
  591. package/dist/hooks/hookManager.js +782 -0
  592. package/dist/hooks/hookManager.js.map +1 -0
  593. package/dist/hooks/index.d.ts +62 -0
  594. package/dist/hooks/index.d.ts.map +1 -0
  595. package/dist/hooks/index.js +66 -0
  596. package/dist/hooks/index.js.map +1 -0
  597. package/dist/hooks/lowContextHook.d.ts +71 -0
  598. package/dist/hooks/lowContextHook.d.ts.map +1 -0
  599. package/dist/hooks/lowContextHook.js +258 -0
  600. package/dist/hooks/lowContextHook.js.map +1 -0
  601. package/dist/hooks/simpleContextHook.d.ts +65 -0
  602. package/dist/hooks/simpleContextHook.d.ts.map +1 -0
  603. package/dist/hooks/simpleContextHook.js +194 -0
  604. package/dist/hooks/simpleContextHook.js.map +1 -0
  605. package/dist/hooks/teamFramingCli.d.ts +56 -0
  606. package/dist/hooks/teamFramingCli.d.ts.map +1 -0
  607. package/dist/hooks/teamFramingCli.js +264 -0
  608. package/dist/hooks/teamFramingCli.js.map +1 -0
  609. package/dist/hooks/teamMemberPrepromptHook.d.ts +150 -0
  610. package/dist/hooks/teamMemberPrepromptHook.d.ts.map +1 -0
  611. package/dist/hooks/teamMemberPrepromptHook.js +308 -0
  612. package/dist/hooks/teamMemberPrepromptHook.js.map +1 -0
  613. package/dist/index.d.ts +42 -0
  614. package/dist/index.d.ts.map +1 -0
  615. package/dist/index.js +4433 -0
  616. package/dist/index.js.map +1 -0
  617. package/dist/init/claudeConfigInjector.d.ts +116 -0
  618. package/dist/init/claudeConfigInjector.d.ts.map +1 -0
  619. package/dist/init/claudeConfigInjector.js +1154 -0
  620. package/dist/init/claudeConfigInjector.js.map +1 -0
  621. package/dist/installer/autoInstall.d.ts +72 -0
  622. package/dist/installer/autoInstall.d.ts.map +1 -0
  623. package/dist/installer/autoInstall.js +617 -0
  624. package/dist/installer/autoInstall.js.map +1 -0
  625. package/dist/installer/dbSetup.d.ts +84 -0
  626. package/dist/installer/dbSetup.d.ts.map +1 -0
  627. package/dist/installer/dbSetup.js +350 -0
  628. package/dist/installer/dbSetup.js.map +1 -0
  629. package/dist/installer/firstRun.d.ts +49 -0
  630. package/dist/installer/firstRun.d.ts.map +1 -0
  631. package/dist/installer/firstRun.js +207 -0
  632. package/dist/installer/firstRun.js.map +1 -0
  633. package/dist/installer/index.d.ts +10 -0
  634. package/dist/installer/index.d.ts.map +1 -0
  635. package/dist/installer/index.js +10 -0
  636. package/dist/installer/index.js.map +1 -0
  637. package/dist/installer/silentAutoInstall.d.ts +99 -0
  638. package/dist/installer/silentAutoInstall.d.ts.map +1 -0
  639. package/dist/installer/silentAutoInstall.js +491 -0
  640. package/dist/installer/silentAutoInstall.js.map +1 -0
  641. package/dist/installer/systemDeps.d.ts +54 -0
  642. package/dist/installer/systemDeps.d.ts.map +1 -0
  643. package/dist/installer/systemDeps.js +322 -0
  644. package/dist/installer/systemDeps.js.map +1 -0
  645. package/dist/mcp/cliNotifications.d.ts +133 -0
  646. package/dist/mcp/cliNotifications.d.ts.map +1 -0
  647. package/dist/mcp/cliNotifications.js +289 -0
  648. package/dist/mcp/cliNotifications.js.map +1 -0
  649. package/dist/mcp/embeddingServerManager.d.ts +307 -0
  650. package/dist/mcp/embeddingServerManager.d.ts.map +1 -0
  651. package/dist/mcp/embeddingServerManager.js +2081 -0
  652. package/dist/mcp/embeddingServerManager.js.map +1 -0
  653. package/dist/mcp/healthMonitor.d.ts +196 -0
  654. package/dist/mcp/healthMonitor.d.ts.map +1 -0
  655. package/dist/mcp/healthMonitor.js +685 -0
  656. package/dist/mcp/healthMonitor.js.map +1 -0
  657. package/dist/mcp/hotReloadManager.d.ts +101 -0
  658. package/dist/mcp/hotReloadManager.d.ts.map +1 -0
  659. package/dist/mcp/hotReloadManager.js +251 -0
  660. package/dist/mcp/hotReloadManager.js.map +1 -0
  661. package/dist/mcp/index.d.ts +16 -0
  662. package/dist/mcp/index.d.ts.map +1 -0
  663. package/dist/mcp/index.js +22 -0
  664. package/dist/mcp/index.js.map +1 -0
  665. package/dist/mcp/mcpProtocolHandler.d.ts +64 -0
  666. package/dist/mcp/mcpProtocolHandler.d.ts.map +1 -0
  667. package/dist/mcp/mcpProtocolHandler.js +253 -0
  668. package/dist/mcp/mcpProtocolHandler.js.map +1 -0
  669. package/dist/mcp/miniCOTServerManager.d.ts +336 -0
  670. package/dist/mcp/miniCOTServerManager.d.ts.map +1 -0
  671. package/dist/mcp/miniCOTServerManager.js +1384 -0
  672. package/dist/mcp/miniCOTServerManager.js.map +1 -0
  673. package/dist/mcp/promptExecutor.d.ts +188 -0
  674. package/dist/mcp/promptExecutor.d.ts.map +1 -0
  675. package/dist/mcp/promptExecutor.js +469 -0
  676. package/dist/mcp/promptExecutor.js.map +1 -0
  677. package/dist/mcp/reloadBroadcast.d.ts +127 -0
  678. package/dist/mcp/reloadBroadcast.d.ts.map +1 -0
  679. package/dist/mcp/reloadBroadcast.js +275 -0
  680. package/dist/mcp/reloadBroadcast.js.map +1 -0
  681. package/dist/mcp/resilientTransport.d.ts +249 -0
  682. package/dist/mcp/resilientTransport.d.ts.map +1 -0
  683. package/dist/mcp/resilientTransport.js +931 -0
  684. package/dist/mcp/resilientTransport.js.map +1 -0
  685. package/dist/mcp/samplingHandler.d.ts +129 -0
  686. package/dist/mcp/samplingHandler.d.ts.map +1 -0
  687. package/dist/mcp/samplingHandler.js +276 -0
  688. package/dist/mcp/samplingHandler.js.map +1 -0
  689. package/dist/mcp/specMemServer.d.ts +305 -0
  690. package/dist/mcp/specMemServer.d.ts.map +1 -0
  691. package/dist/mcp/specMemServer.js +2048 -0
  692. package/dist/mcp/specMemServer.js.map +1 -0
  693. package/dist/mcp/toolRegistry.d.ts +122 -0
  694. package/dist/mcp/toolRegistry.d.ts.map +1 -0
  695. package/dist/mcp/toolRegistry.js +609 -0
  696. package/dist/mcp/toolRegistry.js.map +1 -0
  697. package/dist/mcp/tools/embeddingControl.d.ts +114 -0
  698. package/dist/mcp/tools/embeddingControl.d.ts.map +1 -0
  699. package/dist/mcp/tools/embeddingControl.js +222 -0
  700. package/dist/mcp/tools/embeddingControl.js.map +1 -0
  701. package/dist/mcp/tools/index.d.ts +10 -0
  702. package/dist/mcp/tools/index.d.ts.map +1 -0
  703. package/dist/mcp/tools/index.js +17 -0
  704. package/dist/mcp/tools/index.js.map +1 -0
  705. package/dist/mcp/tools/teamComms.d.ts +444 -0
  706. package/dist/mcp/tools/teamComms.d.ts.map +1 -0
  707. package/dist/mcp/tools/teamComms.js +1953 -0
  708. package/dist/mcp/tools/teamComms.js.map +1 -0
  709. package/dist/mcp/triggerSystem.d.ts +129 -0
  710. package/dist/mcp/triggerSystem.d.ts.map +1 -0
  711. package/dist/mcp/triggerSystem.js +363 -0
  712. package/dist/mcp/triggerSystem.js.map +1 -0
  713. package/dist/mcp/watcherIntegration.d.ts +77 -0
  714. package/dist/mcp/watcherIntegration.d.ts.map +1 -0
  715. package/dist/mcp/watcherIntegration.js +428 -0
  716. package/dist/mcp/watcherIntegration.js.map +1 -0
  717. package/dist/mcp/watcherToolWrappers.d.ts +89 -0
  718. package/dist/mcp/watcherToolWrappers.d.ts.map +1 -0
  719. package/dist/mcp/watcherToolWrappers.js +91 -0
  720. package/dist/mcp/watcherToolWrappers.js.map +1 -0
  721. package/dist/memorization/claudeCodeMigration.d.ts +34 -0
  722. package/dist/memorization/claudeCodeMigration.d.ts.map +1 -0
  723. package/dist/memorization/claudeCodeMigration.js +210 -0
  724. package/dist/memorization/claudeCodeMigration.js.map +1 -0
  725. package/dist/memorization/claudeCodeTracker.d.ts +147 -0
  726. package/dist/memorization/claudeCodeTracker.d.ts.map +1 -0
  727. package/dist/memorization/claudeCodeTracker.js +424 -0
  728. package/dist/memorization/claudeCodeTracker.js.map +1 -0
  729. package/dist/memorization/codeMemorizer.d.ts +158 -0
  730. package/dist/memorization/codeMemorizer.d.ts.map +1 -0
  731. package/dist/memorization/codeMemorizer.js +357 -0
  732. package/dist/memorization/codeMemorizer.js.map +1 -0
  733. package/dist/memorization/codeRecall.d.ts +156 -0
  734. package/dist/memorization/codeRecall.d.ts.map +1 -0
  735. package/dist/memorization/codeRecall.js +499 -0
  736. package/dist/memorization/codeRecall.js.map +1 -0
  737. package/dist/memorization/index.d.ts +55 -0
  738. package/dist/memorization/index.d.ts.map +1 -0
  739. package/dist/memorization/index.js +64 -0
  740. package/dist/memorization/index.js.map +1 -0
  741. package/dist/memorization/memorizationTools.d.ts +413 -0
  742. package/dist/memorization/memorizationTools.d.ts.map +1 -0
  743. package/dist/memorization/memorizationTools.js +513 -0
  744. package/dist/memorization/memorizationTools.js.map +1 -0
  745. package/dist/memorization/watcherIntegration.d.ts +100 -0
  746. package/dist/memorization/watcherIntegration.d.ts.map +1 -0
  747. package/dist/memorization/watcherIntegration.js +196 -0
  748. package/dist/memorization/watcherIntegration.js.map +1 -0
  749. package/dist/memory/humanLikeMemory.d.ts +206 -0
  750. package/dist/memory/humanLikeMemory.d.ts.map +1 -0
  751. package/dist/memory/humanLikeMemory.js +603 -0
  752. package/dist/memory/humanLikeMemory.js.map +1 -0
  753. package/dist/memory/index.d.ts +22 -0
  754. package/dist/memory/index.d.ts.map +1 -0
  755. package/dist/memory/index.js +24 -0
  756. package/dist/memory/index.js.map +1 -0
  757. package/dist/memory/memoryEvolutionMigration.d.ts +36 -0
  758. package/dist/memory/memoryEvolutionMigration.d.ts.map +1 -0
  759. package/dist/memory/memoryEvolutionMigration.js +371 -0
  760. package/dist/memory/memoryEvolutionMigration.js.map +1 -0
  761. package/dist/memory/quadrantSearch.d.ts +221 -0
  762. package/dist/memory/quadrantSearch.d.ts.map +1 -0
  763. package/dist/memory/quadrantSearch.js +897 -0
  764. package/dist/memory/quadrantSearch.js.map +1 -0
  765. package/dist/middleware/apiVersioning.d.ts +83 -0
  766. package/dist/middleware/apiVersioning.d.ts.map +1 -0
  767. package/dist/middleware/apiVersioning.js +152 -0
  768. package/dist/middleware/apiVersioning.js.map +1 -0
  769. package/dist/middleware/compression.d.ts +48 -0
  770. package/dist/middleware/compression.d.ts.map +1 -0
  771. package/dist/middleware/compression.js +240 -0
  772. package/dist/middleware/compression.js.map +1 -0
  773. package/dist/middleware/csrf.d.ts +118 -0
  774. package/dist/middleware/csrf.d.ts.map +1 -0
  775. package/dist/middleware/csrf.js +300 -0
  776. package/dist/middleware/csrf.js.map +1 -0
  777. package/dist/middleware/index.d.ts +13 -0
  778. package/dist/middleware/index.d.ts.map +1 -0
  779. package/dist/middleware/index.js +17 -0
  780. package/dist/middleware/index.js.map +1 -0
  781. package/dist/middleware/wsRateLimiter.d.ts +129 -0
  782. package/dist/middleware/wsRateLimiter.d.ts.map +1 -0
  783. package/dist/middleware/wsRateLimiter.js +279 -0
  784. package/dist/middleware/wsRateLimiter.js.map +1 -0
  785. package/dist/migrations/run.d.ts +2 -0
  786. package/dist/migrations/run.d.ts.map +1 -0
  787. package/dist/migrations/run.js +25 -0
  788. package/dist/migrations/run.js.map +1 -0
  789. package/dist/migrations/syncDimensions.d.ts +24 -0
  790. package/dist/migrations/syncDimensions.d.ts.map +1 -0
  791. package/dist/migrations/syncDimensions.js +140 -0
  792. package/dist/migrations/syncDimensions.js.map +1 -0
  793. package/dist/migrations/teamComms.d.ts +16 -0
  794. package/dist/migrations/teamComms.d.ts.map +1 -0
  795. package/dist/migrations/teamComms.js +210 -0
  796. package/dist/migrations/teamComms.js.map +1 -0
  797. package/dist/openapi/index.d.ts +10 -0
  798. package/dist/openapi/index.d.ts.map +1 -0
  799. package/dist/openapi/index.js +10 -0
  800. package/dist/openapi/index.js.map +1 -0
  801. package/dist/openapi/spec.d.ts +902 -0
  802. package/dist/openapi/spec.d.ts.map +1 -0
  803. package/dist/openapi/spec.js +733 -0
  804. package/dist/openapi/spec.js.map +1 -0
  805. package/dist/packages/dependencyHistory.d.ts +113 -0
  806. package/dist/packages/dependencyHistory.d.ts.map +1 -0
  807. package/dist/packages/dependencyHistory.js +360 -0
  808. package/dist/packages/dependencyHistory.js.map +1 -0
  809. package/dist/packages/index.d.ts +30 -0
  810. package/dist/packages/index.d.ts.map +1 -0
  811. package/dist/packages/index.js +65 -0
  812. package/dist/packages/index.js.map +1 -0
  813. package/dist/packages/packageTools.d.ts +255 -0
  814. package/dist/packages/packageTools.d.ts.map +1 -0
  815. package/dist/packages/packageTools.js +242 -0
  816. package/dist/packages/packageTools.js.map +1 -0
  817. package/dist/packages/packageTracker.d.ts +98 -0
  818. package/dist/packages/packageTracker.d.ts.map +1 -0
  819. package/dist/packages/packageTracker.js +268 -0
  820. package/dist/packages/packageTracker.js.map +1 -0
  821. package/dist/packages/packageWatcher.d.ts +62 -0
  822. package/dist/packages/packageWatcher.d.ts.map +1 -0
  823. package/dist/packages/packageWatcher.js +146 -0
  824. package/dist/packages/packageWatcher.js.map +1 -0
  825. package/dist/providers/MiniCOTProvider.d.ts +48 -0
  826. package/dist/providers/MiniCOTProvider.d.ts.map +1 -0
  827. package/dist/providers/MiniCOTProvider.js +98 -0
  828. package/dist/providers/MiniCOTProvider.js.map +1 -0
  829. package/dist/reminders/index.d.ts +5 -0
  830. package/dist/reminders/index.d.ts.map +1 -0
  831. package/dist/reminders/index.js +5 -0
  832. package/dist/reminders/index.js.map +1 -0
  833. package/dist/reminders/skillReminder.d.ts +131 -0
  834. package/dist/reminders/skillReminder.d.ts.map +1 -0
  835. package/dist/reminders/skillReminder.js +386 -0
  836. package/dist/reminders/skillReminder.js.map +1 -0
  837. package/dist/search.d.ts +35 -0
  838. package/dist/search.d.ts.map +1 -0
  839. package/dist/search.js +574 -0
  840. package/dist/search.js.map +1 -0
  841. package/dist/security/localhostOnly.d.ts +36 -0
  842. package/dist/security/localhostOnly.d.ts.map +1 -0
  843. package/dist/security/localhostOnly.js +101 -0
  844. package/dist/security/localhostOnly.js.map +1 -0
  845. package/dist/services/CameraZoomSearch.d.ts +206 -0
  846. package/dist/services/CameraZoomSearch.d.ts.map +1 -0
  847. package/dist/services/CameraZoomSearch.js +669 -0
  848. package/dist/services/CameraZoomSearch.js.map +1 -0
  849. package/dist/services/DataFlowPipeline.d.ts +111 -0
  850. package/dist/services/DataFlowPipeline.d.ts.map +1 -0
  851. package/dist/services/DataFlowPipeline.js +379 -0
  852. package/dist/services/DataFlowPipeline.js.map +1 -0
  853. package/dist/services/DimensionAdapter.d.ts +194 -0
  854. package/dist/services/DimensionAdapter.d.ts.map +1 -0
  855. package/dist/services/DimensionAdapter.js +566 -0
  856. package/dist/services/DimensionAdapter.js.map +1 -0
  857. package/dist/services/DimensionService.d.ts +252 -0
  858. package/dist/services/DimensionService.d.ts.map +1 -0
  859. package/dist/services/DimensionService.js +564 -0
  860. package/dist/services/DimensionService.js.map +1 -0
  861. package/dist/services/EmbeddingQueue.d.ts +71 -0
  862. package/dist/services/EmbeddingQueue.d.ts.map +1 -0
  863. package/dist/services/EmbeddingQueue.js +258 -0
  864. package/dist/services/EmbeddingQueue.js.map +1 -0
  865. package/dist/services/MemoryDrilldown.d.ts +226 -0
  866. package/dist/services/MemoryDrilldown.d.ts.map +1 -0
  867. package/dist/services/MemoryDrilldown.js +479 -0
  868. package/dist/services/MemoryDrilldown.js.map +1 -0
  869. package/dist/services/MiniCOTScorer.d.ts +140 -0
  870. package/dist/services/MiniCOTScorer.d.ts.map +1 -0
  871. package/dist/services/MiniCOTScorer.js +292 -0
  872. package/dist/services/MiniCOTScorer.js.map +1 -0
  873. package/dist/services/ProjectContext.d.ts +342 -0
  874. package/dist/services/ProjectContext.d.ts.map +1 -0
  875. package/dist/services/ProjectContext.js +667 -0
  876. package/dist/services/ProjectContext.js.map +1 -0
  877. package/dist/services/ResponseCompactor.d.ts +135 -0
  878. package/dist/services/ResponseCompactor.d.ts.map +1 -0
  879. package/dist/services/ResponseCompactor.js +501 -0
  880. package/dist/services/ResponseCompactor.js.map +1 -0
  881. package/dist/services/TeamCommsDbService.d.ts +202 -0
  882. package/dist/services/TeamCommsDbService.d.ts.map +1 -0
  883. package/dist/services/TeamCommsDbService.js +526 -0
  884. package/dist/services/TeamCommsDbService.js.map +1 -0
  885. package/dist/services/UnifiedPasswordService.d.ts +166 -0
  886. package/dist/services/UnifiedPasswordService.d.ts.map +1 -0
  887. package/dist/services/UnifiedPasswordService.js +587 -0
  888. package/dist/services/UnifiedPasswordService.js.map +1 -0
  889. package/dist/services/adaptiveSearchConfig.d.ts +64 -0
  890. package/dist/services/adaptiveSearchConfig.d.ts.map +1 -0
  891. package/dist/services/adaptiveSearchConfig.js +187 -0
  892. package/dist/services/adaptiveSearchConfig.js.map +1 -0
  893. package/dist/skills/index.d.ts +8 -0
  894. package/dist/skills/index.d.ts.map +1 -0
  895. package/dist/skills/index.js +8 -0
  896. package/dist/skills/index.js.map +1 -0
  897. package/dist/skills/skillScanner.d.ts +203 -0
  898. package/dist/skills/skillScanner.d.ts.map +1 -0
  899. package/dist/skills/skillScanner.js +559 -0
  900. package/dist/skills/skillScanner.js.map +1 -0
  901. package/dist/skills/skillsResource.d.ts +69 -0
  902. package/dist/skills/skillsResource.d.ts.map +1 -0
  903. package/dist/skills/skillsResource.js +257 -0
  904. package/dist/skills/skillsResource.js.map +1 -0
  905. package/dist/startup/index.d.ts +9 -0
  906. package/dist/startup/index.d.ts.map +1 -0
  907. package/dist/startup/index.js +12 -0
  908. package/dist/startup/index.js.map +1 -0
  909. package/dist/startup/startupIndexing.d.ts +80 -0
  910. package/dist/startup/startupIndexing.d.ts.map +1 -0
  911. package/dist/startup/startupIndexing.js +463 -0
  912. package/dist/startup/startupIndexing.js.map +1 -0
  913. package/dist/startup/validation.d.ts +89 -0
  914. package/dist/startup/validation.d.ts.map +1 -0
  915. package/dist/startup/validation.js +590 -0
  916. package/dist/startup/validation.js.map +1 -0
  917. package/dist/storage/index.d.ts +4 -0
  918. package/dist/storage/index.d.ts.map +1 -0
  919. package/dist/storage/index.js +4 -0
  920. package/dist/storage/index.js.map +1 -0
  921. package/dist/storage/overflowManager.d.ts +80 -0
  922. package/dist/storage/overflowManager.d.ts.map +1 -0
  923. package/dist/storage/overflowManager.js +317 -0
  924. package/dist/storage/overflowManager.js.map +1 -0
  925. package/dist/storage/overflowStorage.d.ts +69 -0
  926. package/dist/storage/overflowStorage.d.ts.map +1 -0
  927. package/dist/storage/overflowStorage.js +379 -0
  928. package/dist/storage/overflowStorage.js.map +1 -0
  929. package/dist/storage/toonFormat.d.ts +50 -0
  930. package/dist/storage/toonFormat.d.ts.map +1 -0
  931. package/dist/storage/toonFormat.js +224 -0
  932. package/dist/storage/toonFormat.js.map +1 -0
  933. package/dist/team-members/communication.d.ts +237 -0
  934. package/dist/team-members/communication.d.ts.map +1 -0
  935. package/dist/team-members/communication.js +650 -0
  936. package/dist/team-members/communication.js.map +1 -0
  937. package/dist/team-members/index.d.ts +14 -0
  938. package/dist/team-members/index.d.ts.map +1 -0
  939. package/dist/team-members/index.js +22 -0
  940. package/dist/team-members/index.js.map +1 -0
  941. package/dist/team-members/taskOrchestrator.d.ts +224 -0
  942. package/dist/team-members/taskOrchestrator.d.ts.map +1 -0
  943. package/dist/team-members/taskOrchestrator.js +574 -0
  944. package/dist/team-members/taskOrchestrator.js.map +1 -0
  945. package/dist/team-members/taskTeamMemberLogger.d.ts +157 -0
  946. package/dist/team-members/taskTeamMemberLogger.d.ts.map +1 -0
  947. package/dist/team-members/taskTeamMemberLogger.js +478 -0
  948. package/dist/team-members/taskTeamMemberLogger.js.map +1 -0
  949. package/dist/team-members/teamCommsService.d.ts +221 -0
  950. package/dist/team-members/teamCommsService.d.ts.map +1 -0
  951. package/dist/team-members/teamCommsService.js +628 -0
  952. package/dist/team-members/teamCommsService.js.map +1 -0
  953. package/dist/team-members/teamMemberChannels.d.ts +217 -0
  954. package/dist/team-members/teamMemberChannels.d.ts.map +1 -0
  955. package/dist/team-members/teamMemberChannels.js +687 -0
  956. package/dist/team-members/teamMemberChannels.js.map +1 -0
  957. package/dist/team-members/teamMemberDashboard.d.ts +222 -0
  958. package/dist/team-members/teamMemberDashboard.d.ts.map +1 -0
  959. package/dist/team-members/teamMemberDashboard.js +610 -0
  960. package/dist/team-members/teamMemberDashboard.js.map +1 -0
  961. package/dist/team-members/teamMemberDeployment.d.ts +60 -0
  962. package/dist/team-members/teamMemberDeployment.d.ts.map +1 -0
  963. package/dist/team-members/teamMemberDeployment.js +429 -0
  964. package/dist/team-members/teamMemberDeployment.js.map +1 -0
  965. package/dist/team-members/teamMemberDiscovery.d.ts +178 -0
  966. package/dist/team-members/teamMemberDiscovery.d.ts.map +1 -0
  967. package/dist/team-members/teamMemberDiscovery.js +446 -0
  968. package/dist/team-members/teamMemberDiscovery.js.map +1 -0
  969. package/dist/team-members/teamMemberHistory.d.ts +80 -0
  970. package/dist/team-members/teamMemberHistory.d.ts.map +1 -0
  971. package/dist/team-members/teamMemberHistory.js +426 -0
  972. package/dist/team-members/teamMemberHistory.js.map +1 -0
  973. package/dist/team-members/teamMemberLimits.d.ts +66 -0
  974. package/dist/team-members/teamMemberLimits.d.ts.map +1 -0
  975. package/dist/team-members/teamMemberLimits.js +259 -0
  976. package/dist/team-members/teamMemberLimits.js.map +1 -0
  977. package/dist/team-members/teamMemberRegistry.d.ts +199 -0
  978. package/dist/team-members/teamMemberRegistry.d.ts.map +1 -0
  979. package/dist/team-members/teamMemberRegistry.js +572 -0
  980. package/dist/team-members/teamMemberRegistry.js.map +1 -0
  981. package/dist/team-members/teamMemberTracker.d.ts +148 -0
  982. package/dist/team-members/teamMemberTracker.d.ts.map +1 -0
  983. package/dist/team-members/teamMemberTracker.js +828 -0
  984. package/dist/team-members/teamMemberTracker.js.map +1 -0
  985. package/dist/team-members/workers/aiWorker.d.ts +53 -0
  986. package/dist/team-members/workers/aiWorker.d.ts.map +1 -0
  987. package/dist/team-members/workers/aiWorker.js +322 -0
  988. package/dist/team-members/workers/aiWorker.js.map +1 -0
  989. package/dist/team-members/workers/baseWorker.d.ts +101 -0
  990. package/dist/team-members/workers/baseWorker.d.ts.map +1 -0
  991. package/dist/team-members/workers/baseWorker.js +179 -0
  992. package/dist/team-members/workers/baseWorker.js.map +1 -0
  993. package/dist/team-members/workers/codeReviewWorker.d.ts +3 -0
  994. package/dist/team-members/workers/codeReviewWorker.d.ts.map +1 -0
  995. package/dist/team-members/workers/codeReviewWorker.js +144 -0
  996. package/dist/team-members/workers/codeReviewWorker.js.map +1 -0
  997. package/dist/team-members/workers/index.d.ts +7 -0
  998. package/dist/team-members/workers/index.d.ts.map +1 -0
  999. package/dist/team-members/workers/index.js +7 -0
  1000. package/dist/team-members/workers/index.js.map +1 -0
  1001. package/dist/team-members/workers/repairWorker.d.ts +9 -0
  1002. package/dist/team-members/workers/repairWorker.d.ts.map +1 -0
  1003. package/dist/team-members/workers/repairWorker.js +102 -0
  1004. package/dist/team-members/workers/repairWorker.js.map +1 -0
  1005. package/dist/team-members/workers/sendToTeamMemberB.d.ts +9 -0
  1006. package/dist/team-members/workers/sendToTeamMemberB.d.ts.map +1 -0
  1007. package/dist/team-members/workers/sendToTeamMemberB.js +105 -0
  1008. package/dist/team-members/workers/sendToTeamMemberB.js.map +1 -0
  1009. package/dist/team-members/workers/specmemClient.d.ts +179 -0
  1010. package/dist/team-members/workers/specmemClient.d.ts.map +1 -0
  1011. package/dist/team-members/workers/specmemClient.js +421 -0
  1012. package/dist/team-members/workers/specmemClient.js.map +1 -0
  1013. package/dist/team-members/workers/testCommunication.d.ts +8 -0
  1014. package/dist/team-members/workers/testCommunication.d.ts.map +1 -0
  1015. package/dist/team-members/workers/testCommunication.js +136 -0
  1016. package/dist/team-members/workers/testCommunication.js.map +1 -0
  1017. package/dist/team-members/workers/testCommunicationSuite.d.ts +26 -0
  1018. package/dist/team-members/workers/testCommunicationSuite.d.ts.map +1 -0
  1019. package/dist/team-members/workers/testCommunicationSuite.js +415 -0
  1020. package/dist/team-members/workers/testCommunicationSuite.js.map +1 -0
  1021. package/dist/team-members/workers/testWorker.d.ts +9 -0
  1022. package/dist/team-members/workers/testWorker.d.ts.map +1 -0
  1023. package/dist/team-members/workers/testWorker.js +107 -0
  1024. package/dist/team-members/workers/testWorker.js.map +1 -0
  1025. package/dist/tools/agentDefinitions.d.ts +30 -0
  1026. package/dist/tools/agentDefinitions.d.ts.map +1 -0
  1027. package/dist/tools/agentDefinitions.js +166 -0
  1028. package/dist/tools/agentDefinitions.js.map +1 -0
  1029. package/dist/tools/goofy/checkSyncStatus.d.ts +68 -0
  1030. package/dist/tools/goofy/checkSyncStatus.d.ts.map +1 -0
  1031. package/dist/tools/goofy/checkSyncStatus.js +112 -0
  1032. package/dist/tools/goofy/checkSyncStatus.js.map +1 -0
  1033. package/dist/tools/goofy/codeMemoryLink.d.ts +82 -0
  1034. package/dist/tools/goofy/codeMemoryLink.d.ts.map +1 -0
  1035. package/dist/tools/goofy/codeMemoryLink.js +212 -0
  1036. package/dist/tools/goofy/codeMemoryLink.js.map +1 -0
  1037. package/dist/tools/goofy/compareInstanceMemory.d.ts +97 -0
  1038. package/dist/tools/goofy/compareInstanceMemory.d.ts.map +1 -0
  1039. package/dist/tools/goofy/compareInstanceMemory.js +218 -0
  1040. package/dist/tools/goofy/compareInstanceMemory.js.map +1 -0
  1041. package/dist/tools/goofy/createReasoningChain.d.ts +135 -0
  1042. package/dist/tools/goofy/createReasoningChain.d.ts.map +1 -0
  1043. package/dist/tools/goofy/createReasoningChain.js +257 -0
  1044. package/dist/tools/goofy/createReasoningChain.js.map +1 -0
  1045. package/dist/tools/goofy/deployTeamMember.d.ts +63 -0
  1046. package/dist/tools/goofy/deployTeamMember.d.ts.map +1 -0
  1047. package/dist/tools/goofy/deployTeamMember.js +103 -0
  1048. package/dist/tools/goofy/deployTeamMember.js.map +1 -0
  1049. package/dist/tools/goofy/drillDown.d.ts +143 -0
  1050. package/dist/tools/goofy/drillDown.d.ts.map +1 -0
  1051. package/dist/tools/goofy/drillDown.js +288 -0
  1052. package/dist/tools/goofy/drillDown.js.map +1 -0
  1053. package/dist/tools/goofy/extractClaudeSessions.d.ts +90 -0
  1054. package/dist/tools/goofy/extractClaudeSessions.d.ts.map +1 -0
  1055. package/dist/tools/goofy/extractClaudeSessions.js +277 -0
  1056. package/dist/tools/goofy/extractClaudeSessions.js.map +1 -0
  1057. package/dist/tools/goofy/extractContextRestorations.d.ts +70 -0
  1058. package/dist/tools/goofy/extractContextRestorations.d.ts.map +1 -0
  1059. package/dist/tools/goofy/extractContextRestorations.js +100 -0
  1060. package/dist/tools/goofy/extractContextRestorations.js.map +1 -0
  1061. package/dist/tools/goofy/findCodePointers.d.ts +364 -0
  1062. package/dist/tools/goofy/findCodePointers.d.ts.map +1 -0
  1063. package/dist/tools/goofy/findCodePointers.js +1764 -0
  1064. package/dist/tools/goofy/findCodePointers.js.map +1 -0
  1065. package/dist/tools/goofy/findMemoryGallery.d.ts +40 -0
  1066. package/dist/tools/goofy/findMemoryGallery.d.ts.map +1 -0
  1067. package/dist/tools/goofy/findMemoryGallery.js +66 -0
  1068. package/dist/tools/goofy/findMemoryGallery.js.map +1 -0
  1069. package/dist/tools/goofy/findWhatISaid.d.ts +300 -0
  1070. package/dist/tools/goofy/findWhatISaid.d.ts.map +1 -0
  1071. package/dist/tools/goofy/findWhatISaid.js +2547 -0
  1072. package/dist/tools/goofy/findWhatISaid.js.map +1 -0
  1073. package/dist/tools/goofy/forceResync.d.ts +57 -0
  1074. package/dist/tools/goofy/forceResync.d.ts.map +1 -0
  1075. package/dist/tools/goofy/forceResync.js +100 -0
  1076. package/dist/tools/goofy/forceResync.js.map +1 -0
  1077. package/dist/tools/goofy/getActiveTeamMembers.d.ts +48 -0
  1078. package/dist/tools/goofy/getActiveTeamMembers.d.ts.map +1 -0
  1079. package/dist/tools/goofy/getActiveTeamMembers.js +136 -0
  1080. package/dist/tools/goofy/getActiveTeamMembers.js.map +1 -0
  1081. package/dist/tools/goofy/getMemoryFull.d.ts +34 -0
  1082. package/dist/tools/goofy/getMemoryFull.d.ts.map +1 -0
  1083. package/dist/tools/goofy/getMemoryFull.js +58 -0
  1084. package/dist/tools/goofy/getMemoryFull.js.map +1 -0
  1085. package/dist/tools/goofy/getSessionWatcherStatus.d.ts +43 -0
  1086. package/dist/tools/goofy/getSessionWatcherStatus.d.ts.map +1 -0
  1087. package/dist/tools/goofy/getSessionWatcherStatus.js +92 -0
  1088. package/dist/tools/goofy/getSessionWatcherStatus.js.map +1 -0
  1089. package/dist/tools/goofy/getTeamMemberOutput.d.ts +35 -0
  1090. package/dist/tools/goofy/getTeamMemberOutput.d.ts.map +1 -0
  1091. package/dist/tools/goofy/getTeamMemberOutput.js +62 -0
  1092. package/dist/tools/goofy/getTeamMemberOutput.js.map +1 -0
  1093. package/dist/tools/goofy/getTeamMemberScreen.d.ts +28 -0
  1094. package/dist/tools/goofy/getTeamMemberScreen.d.ts.map +1 -0
  1095. package/dist/tools/goofy/getTeamMemberScreen.js +59 -0
  1096. package/dist/tools/goofy/getTeamMemberScreen.js.map +1 -0
  1097. package/dist/tools/goofy/getTeamMemberStatus.d.ts +33 -0
  1098. package/dist/tools/goofy/getTeamMemberStatus.d.ts.map +1 -0
  1099. package/dist/tools/goofy/getTeamMemberStatus.js +56 -0
  1100. package/dist/tools/goofy/getTeamMemberStatus.js.map +1 -0
  1101. package/dist/tools/goofy/index.d.ts +39 -0
  1102. package/dist/tools/goofy/index.d.ts.map +1 -0
  1103. package/dist/tools/goofy/index.js +51 -0
  1104. package/dist/tools/goofy/index.js.map +1 -0
  1105. package/dist/tools/goofy/interveneTeamMember.d.ts +33 -0
  1106. package/dist/tools/goofy/interveneTeamMember.d.ts.map +1 -0
  1107. package/dist/tools/goofy/interveneTeamMember.js +69 -0
  1108. package/dist/tools/goofy/interveneTeamMember.js.map +1 -0
  1109. package/dist/tools/goofy/killDeployedTeamMember.d.ts +29 -0
  1110. package/dist/tools/goofy/killDeployedTeamMember.d.ts.map +1 -0
  1111. package/dist/tools/goofy/killDeployedTeamMember.js +56 -0
  1112. package/dist/tools/goofy/killDeployedTeamMember.js.map +1 -0
  1113. package/dist/tools/goofy/linkTheVibes.d.ts +125 -0
  1114. package/dist/tools/goofy/linkTheVibes.d.ts.map +1 -0
  1115. package/dist/tools/goofy/linkTheVibes.js +354 -0
  1116. package/dist/tools/goofy/linkTheVibes.js.map +1 -0
  1117. package/dist/tools/goofy/listDeployedTeamMembers.d.ts +26 -0
  1118. package/dist/tools/goofy/listDeployedTeamMembers.d.ts.map +1 -0
  1119. package/dist/tools/goofy/listDeployedTeamMembers.js +52 -0
  1120. package/dist/tools/goofy/listDeployedTeamMembers.js.map +1 -0
  1121. package/dist/tools/goofy/listenForMessages.d.ts +56 -0
  1122. package/dist/tools/goofy/listenForMessages.d.ts.map +1 -0
  1123. package/dist/tools/goofy/listenForMessages.js +122 -0
  1124. package/dist/tools/goofy/listenForMessages.js.map +1 -0
  1125. package/dist/tools/goofy/memoryHealthCheck.d.ts +159 -0
  1126. package/dist/tools/goofy/memoryHealthCheck.d.ts.map +1 -0
  1127. package/dist/tools/goofy/memoryHealthCheck.js +443 -0
  1128. package/dist/tools/goofy/memoryHealthCheck.js.map +1 -0
  1129. package/dist/tools/goofy/rememberThisShit.d.ts +103 -0
  1130. package/dist/tools/goofy/rememberThisShit.d.ts.map +1 -0
  1131. package/dist/tools/goofy/rememberThisShit.js +291 -0
  1132. package/dist/tools/goofy/rememberThisShit.js.map +1 -0
  1133. package/dist/tools/goofy/sayToTeamMember.d.ts +55 -0
  1134. package/dist/tools/goofy/sayToTeamMember.d.ts.map +1 -0
  1135. package/dist/tools/goofy/sayToTeamMember.js +116 -0
  1136. package/dist/tools/goofy/sayToTeamMember.js.map +1 -0
  1137. package/dist/tools/goofy/selfMessage.d.ts +54 -0
  1138. package/dist/tools/goofy/selfMessage.d.ts.map +1 -0
  1139. package/dist/tools/goofy/selfMessage.js +111 -0
  1140. package/dist/tools/goofy/selfMessage.js.map +1 -0
  1141. package/dist/tools/goofy/sendHeartbeat.d.ts +53 -0
  1142. package/dist/tools/goofy/sendHeartbeat.d.ts.map +1 -0
  1143. package/dist/tools/goofy/sendHeartbeat.js +119 -0
  1144. package/dist/tools/goofy/sendHeartbeat.js.map +1 -0
  1145. package/dist/tools/goofy/showMeTheStats.d.ts +216 -0
  1146. package/dist/tools/goofy/showMeTheStats.d.ts.map +1 -0
  1147. package/dist/tools/goofy/showMeTheStats.js +535 -0
  1148. package/dist/tools/goofy/showMeTheStats.js.map +1 -0
  1149. package/dist/tools/goofy/smartRecall.d.ts +136 -0
  1150. package/dist/tools/goofy/smartRecall.d.ts.map +1 -0
  1151. package/dist/tools/goofy/smartRecall.js +286 -0
  1152. package/dist/tools/goofy/smartRecall.js.map +1 -0
  1153. package/dist/tools/goofy/smartSearch.d.ts +64 -0
  1154. package/dist/tools/goofy/smartSearch.d.ts.map +1 -0
  1155. package/dist/tools/goofy/smartSearch.js +89 -0
  1156. package/dist/tools/goofy/smartSearch.js.map +1 -0
  1157. package/dist/tools/goofy/smushMemoriesTogether.d.ts +128 -0
  1158. package/dist/tools/goofy/smushMemoriesTogether.d.ts.map +1 -0
  1159. package/dist/tools/goofy/smushMemoriesTogether.js +536 -0
  1160. package/dist/tools/goofy/smushMemoriesTogether.js.map +1 -0
  1161. package/dist/tools/goofy/spatialSearch.d.ts +198 -0
  1162. package/dist/tools/goofy/spatialSearch.d.ts.map +1 -0
  1163. package/dist/tools/goofy/spatialSearch.js +551 -0
  1164. package/dist/tools/goofy/spatialSearch.js.map +1 -0
  1165. package/dist/tools/goofy/spawnResearchTeamMember.d.ts +104 -0
  1166. package/dist/tools/goofy/spawnResearchTeamMember.d.ts.map +1 -0
  1167. package/dist/tools/goofy/spawnResearchTeamMember.js +290 -0
  1168. package/dist/tools/goofy/spawnResearchTeamMember.js.map +1 -0
  1169. package/dist/tools/goofy/spawnResearchTeamMemberTool.d.ts +121 -0
  1170. package/dist/tools/goofy/spawnResearchTeamMemberTool.d.ts.map +1 -0
  1171. package/dist/tools/goofy/spawnResearchTeamMemberTool.js +215 -0
  1172. package/dist/tools/goofy/spawnResearchTeamMemberTool.js.map +1 -0
  1173. package/dist/tools/goofy/startWatchingTheFiles.d.ts +81 -0
  1174. package/dist/tools/goofy/startWatchingTheFiles.d.ts.map +1 -0
  1175. package/dist/tools/goofy/startWatchingTheFiles.js +161 -0
  1176. package/dist/tools/goofy/startWatchingTheFiles.js.map +1 -0
  1177. package/dist/tools/goofy/stopWatchingTheFiles.d.ts +50 -0
  1178. package/dist/tools/goofy/stopWatchingTheFiles.d.ts.map +1 -0
  1179. package/dist/tools/goofy/stopWatchingTheFiles.js +81 -0
  1180. package/dist/tools/goofy/stopWatchingTheFiles.js.map +1 -0
  1181. package/dist/tools/goofy/whatDidIMean.d.ts +113 -0
  1182. package/dist/tools/goofy/whatDidIMean.d.ts.map +1 -0
  1183. package/dist/tools/goofy/whatDidIMean.js +401 -0
  1184. package/dist/tools/goofy/whatDidIMean.js.map +1 -0
  1185. package/dist/tools/goofy/yeahNahDeleteThat.d.ts +109 -0
  1186. package/dist/tools/goofy/yeahNahDeleteThat.d.ts.map +1 -0
  1187. package/dist/tools/goofy/yeahNahDeleteThat.js +319 -0
  1188. package/dist/tools/goofy/yeahNahDeleteThat.js.map +1 -0
  1189. package/dist/tools/index.d.ts +9 -0
  1190. package/dist/tools/index.d.ts.map +1 -0
  1191. package/dist/tools/index.js +9 -0
  1192. package/dist/tools/index.js.map +1 -0
  1193. package/dist/tools/teamMemberDeployer.d.ts +117 -0
  1194. package/dist/tools/teamMemberDeployer.d.ts.map +1 -0
  1195. package/dist/tools/teamMemberDeployer.js +613 -0
  1196. package/dist/tools/teamMemberDeployer.js.map +1 -0
  1197. package/dist/trace/index.d.ts +14 -0
  1198. package/dist/trace/index.d.ts.map +1 -0
  1199. package/dist/trace/index.js +16 -0
  1200. package/dist/trace/index.js.map +1 -0
  1201. package/dist/trace/tools/analyzeImpact.d.ts +90 -0
  1202. package/dist/trace/tools/analyzeImpact.d.ts.map +1 -0
  1203. package/dist/trace/tools/analyzeImpact.js +240 -0
  1204. package/dist/trace/tools/analyzeImpact.js.map +1 -0
  1205. package/dist/trace/tools/exploreDependencies.d.ts +81 -0
  1206. package/dist/trace/tools/exploreDependencies.d.ts.map +1 -0
  1207. package/dist/trace/tools/exploreDependencies.js +161 -0
  1208. package/dist/trace/tools/exploreDependencies.js.map +1 -0
  1209. package/dist/trace/tools/findSimilarBugs.d.ts +112 -0
  1210. package/dist/trace/tools/findSimilarBugs.d.ts.map +1 -0
  1211. package/dist/trace/tools/findSimilarBugs.js +216 -0
  1212. package/dist/trace/tools/findSimilarBugs.js.map +1 -0
  1213. package/dist/trace/tools/index.d.ts +22 -0
  1214. package/dist/trace/tools/index.d.ts.map +1 -0
  1215. package/dist/trace/tools/index.js +39 -0
  1216. package/dist/trace/tools/index.js.map +1 -0
  1217. package/dist/trace/tools/smartExplore.d.ts +126 -0
  1218. package/dist/trace/tools/smartExplore.d.ts.map +1 -0
  1219. package/dist/trace/tools/smartExplore.js +303 -0
  1220. package/dist/trace/tools/smartExplore.js.map +1 -0
  1221. package/dist/trace/tools/traceError.d.ts +101 -0
  1222. package/dist/trace/tools/traceError.d.ts.map +1 -0
  1223. package/dist/trace/tools/traceError.js +175 -0
  1224. package/dist/trace/tools/traceError.js.map +1 -0
  1225. package/dist/trace/traceExploreSystem.d.ts +271 -0
  1226. package/dist/trace/traceExploreSystem.d.ts.map +1 -0
  1227. package/dist/trace/traceExploreSystem.js +789 -0
  1228. package/dist/trace/traceExploreSystem.js.map +1 -0
  1229. package/dist/types/index.d.ts +421 -0
  1230. package/dist/types/index.d.ts.map +1 -0
  1231. package/dist/types/index.js +118 -0
  1232. package/dist/types/index.js.map +1 -0
  1233. package/dist/utils/circuitBreaker.d.ts +195 -0
  1234. package/dist/utils/circuitBreaker.d.ts.map +1 -0
  1235. package/dist/utils/circuitBreaker.js +374 -0
  1236. package/dist/utils/circuitBreaker.js.map +1 -0
  1237. package/dist/utils/cleanupHandler.d.ts +108 -0
  1238. package/dist/utils/cleanupHandler.d.ts.map +1 -0
  1239. package/dist/utils/cleanupHandler.js +203 -0
  1240. package/dist/utils/cleanupHandler.js.map +1 -0
  1241. package/dist/utils/compactXmlResponse.d.ts +60 -0
  1242. package/dist/utils/compactXmlResponse.d.ts.map +1 -0
  1243. package/dist/utils/compactXmlResponse.js +209 -0
  1244. package/dist/utils/compactXmlResponse.js.map +1 -0
  1245. package/dist/utils/cotBroadcast.d.ts +56 -0
  1246. package/dist/utils/cotBroadcast.d.ts.map +1 -0
  1247. package/dist/utils/cotBroadcast.js +157 -0
  1248. package/dist/utils/cotBroadcast.js.map +1 -0
  1249. package/dist/utils/debugLogger.d.ts +95 -0
  1250. package/dist/utils/debugLogger.d.ts.map +1 -0
  1251. package/dist/utils/debugLogger.js +610 -0
  1252. package/dist/utils/debugLogger.js.map +1 -0
  1253. package/dist/utils/fileProcessingQueue.d.ts +259 -0
  1254. package/dist/utils/fileProcessingQueue.d.ts.map +1 -0
  1255. package/dist/utils/fileProcessingQueue.js +714 -0
  1256. package/dist/utils/fileProcessingQueue.js.map +1 -0
  1257. package/dist/utils/humanReadableOutput.d.ts +124 -0
  1258. package/dist/utils/humanReadableOutput.d.ts.map +1 -0
  1259. package/dist/utils/humanReadableOutput.js +340 -0
  1260. package/dist/utils/humanReadableOutput.js.map +1 -0
  1261. package/dist/utils/index.d.ts +32 -0
  1262. package/dist/utils/index.d.ts.map +1 -0
  1263. package/dist/utils/index.js +71 -0
  1264. package/dist/utils/index.js.map +1 -0
  1265. package/dist/utils/instanceManager.d.ts +530 -0
  1266. package/dist/utils/instanceManager.d.ts.map +1 -0
  1267. package/dist/utils/instanceManager.js +1784 -0
  1268. package/dist/utils/instanceManager.js.map +1 -0
  1269. package/dist/utils/logger.d.ts +6 -0
  1270. package/dist/utils/logger.d.ts.map +1 -0
  1271. package/dist/utils/logger.js +49 -0
  1272. package/dist/utils/logger.js.map +1 -0
  1273. package/dist/utils/mapCleanup.d.ts +58 -0
  1274. package/dist/utils/mapCleanup.d.ts.map +1 -0
  1275. package/dist/utils/mapCleanup.js +150 -0
  1276. package/dist/utils/mapCleanup.js.map +1 -0
  1277. package/dist/utils/memoryManager.d.ts +349 -0
  1278. package/dist/utils/memoryManager.d.ts.map +1 -0
  1279. package/dist/utils/memoryManager.js +799 -0
  1280. package/dist/utils/memoryManager.js.map +1 -0
  1281. package/dist/utils/metrics.d.ts +160 -0
  1282. package/dist/utils/metrics.d.ts.map +1 -0
  1283. package/dist/utils/metrics.js +558 -0
  1284. package/dist/utils/metrics.js.map +1 -0
  1285. package/dist/utils/pathValidator.d.ts +96 -0
  1286. package/dist/utils/pathValidator.d.ts.map +1 -0
  1287. package/dist/utils/pathValidator.js +320 -0
  1288. package/dist/utils/pathValidator.js.map +1 -0
  1289. package/dist/utils/portAllocator.d.ts +296 -0
  1290. package/dist/utils/portAllocator.d.ts.map +1 -0
  1291. package/dist/utils/portAllocator.js +768 -0
  1292. package/dist/utils/portAllocator.js.map +1 -0
  1293. package/dist/utils/portUtils.d.ts +97 -0
  1294. package/dist/utils/portUtils.d.ts.map +1 -0
  1295. package/dist/utils/portUtils.js +285 -0
  1296. package/dist/utils/portUtils.js.map +1 -0
  1297. package/dist/utils/postgresAutoSetup.d.ts +55 -0
  1298. package/dist/utils/postgresAutoSetup.d.ts.map +1 -0
  1299. package/dist/utils/postgresAutoSetup.js +406 -0
  1300. package/dist/utils/postgresAutoSetup.js.map +1 -0
  1301. package/dist/utils/processHealthCheck.d.ts +61 -0
  1302. package/dist/utils/processHealthCheck.d.ts.map +1 -0
  1303. package/dist/utils/processHealthCheck.js +313 -0
  1304. package/dist/utils/processHealthCheck.js.map +1 -0
  1305. package/dist/utils/progressReporter.d.ts +151 -0
  1306. package/dist/utils/progressReporter.d.ts.map +1 -0
  1307. package/dist/utils/progressReporter.js +345 -0
  1308. package/dist/utils/progressReporter.js.map +1 -0
  1309. package/dist/utils/projectEnv.d.ts +73 -0
  1310. package/dist/utils/projectEnv.d.ts.map +1 -0
  1311. package/dist/utils/projectEnv.js +137 -0
  1312. package/dist/utils/projectEnv.js.map +1 -0
  1313. package/dist/utils/qoms.d.ts +122 -0
  1314. package/dist/utils/qoms.d.ts.map +1 -0
  1315. package/dist/utils/qoms.js +650 -0
  1316. package/dist/utils/qoms.js.map +1 -0
  1317. package/dist/utils/retryHelper.d.ts +122 -0
  1318. package/dist/utils/retryHelper.d.ts.map +1 -0
  1319. package/dist/utils/retryHelper.js +272 -0
  1320. package/dist/utils/retryHelper.js.map +1 -0
  1321. package/dist/utils/safeProcessTermination.d.ts +206 -0
  1322. package/dist/utils/safeProcessTermination.d.ts.map +1 -0
  1323. package/dist/utils/safeProcessTermination.js +552 -0
  1324. package/dist/utils/safeProcessTermination.js.map +1 -0
  1325. package/dist/utils/sessionInjector.d.ts +68 -0
  1326. package/dist/utils/sessionInjector.d.ts.map +1 -0
  1327. package/dist/utils/sessionInjector.js +189 -0
  1328. package/dist/utils/sessionInjector.js.map +1 -0
  1329. package/dist/utils/statsCache.d.ts +134 -0
  1330. package/dist/utils/statsCache.d.ts.map +1 -0
  1331. package/dist/utils/statsCache.js +285 -0
  1332. package/dist/utils/statsCache.js.map +1 -0
  1333. package/dist/utils/timeoutMiddleware.d.ts +81 -0
  1334. package/dist/utils/timeoutMiddleware.d.ts.map +1 -0
  1335. package/dist/utils/timeoutMiddleware.js +155 -0
  1336. package/dist/utils/timeoutMiddleware.js.map +1 -0
  1337. package/dist/utils/timerRegistry.d.ts +91 -0
  1338. package/dist/utils/timerRegistry.d.ts.map +1 -0
  1339. package/dist/utils/timerRegistry.js +187 -0
  1340. package/dist/utils/timerRegistry.js.map +1 -0
  1341. package/dist/utils/tokenCompressor.d.ts +332 -0
  1342. package/dist/utils/tokenCompressor.d.ts.map +1 -0
  1343. package/dist/utils/tokenCompressor.js +1306 -0
  1344. package/dist/utils/tokenCompressor.js.map +1 -0
  1345. package/dist/utils/tracing.d.ts +236 -0
  1346. package/dist/utils/tracing.d.ts.map +1 -0
  1347. package/dist/utils/tracing.js +378 -0
  1348. package/dist/utils/tracing.js.map +1 -0
  1349. package/dist/watcher/changeHandler.d.ts +123 -0
  1350. package/dist/watcher/changeHandler.d.ts.map +1 -0
  1351. package/dist/watcher/changeHandler.js +623 -0
  1352. package/dist/watcher/changeHandler.js.map +1 -0
  1353. package/dist/watcher/changeQueue.d.ts +133 -0
  1354. package/dist/watcher/changeQueue.d.ts.map +1 -0
  1355. package/dist/watcher/changeQueue.js +355 -0
  1356. package/dist/watcher/changeQueue.js.map +1 -0
  1357. package/dist/watcher/fileWatcher.d.ts +121 -0
  1358. package/dist/watcher/fileWatcher.d.ts.map +1 -0
  1359. package/dist/watcher/fileWatcher.js +531 -0
  1360. package/dist/watcher/fileWatcher.js.map +1 -0
  1361. package/dist/watcher/index.d.ts +94 -0
  1362. package/dist/watcher/index.d.ts.map +1 -0
  1363. package/dist/watcher/index.js +235 -0
  1364. package/dist/watcher/index.js.map +1 -0
  1365. package/dist/watcher/syncChecker.d.ts +93 -0
  1366. package/dist/watcher/syncChecker.d.ts.map +1 -0
  1367. package/dist/watcher/syncChecker.js +401 -0
  1368. package/dist/watcher/syncChecker.js.map +1 -0
  1369. package/dist/watcher/tsCompiler.d.ts +88 -0
  1370. package/dist/watcher/tsCompiler.d.ts.map +1 -0
  1371. package/dist/watcher/tsCompiler.js +212 -0
  1372. package/dist/watcher/tsCompiler.js.map +1 -0
  1373. package/embedding-sandbox/Dockerfile +77 -0
  1374. package/embedding-sandbox/Dockerfile.frankenstein +91 -0
  1375. package/embedding-sandbox/README.md +193 -0
  1376. package/embedding-sandbox/__pycache__/frankenstein-embeddings.cpython-312.pyc +0 -0
  1377. package/embedding-sandbox/__pycache__/frankenstein-embeddings.cpython-313.pyc +0 -0
  1378. package/embedding-sandbox/__pycache__/qqms_v2.cpython-312.pyc +0 -0
  1379. package/embedding-sandbox/__pycache__/qqms_v2.cpython-313.pyc +0 -0
  1380. package/embedding-sandbox/add_js_docs.py +684 -0
  1381. package/embedding-sandbox/build_docs_db.py +239 -0
  1382. package/embedding-sandbox/client.cjs +376 -0
  1383. package/embedding-sandbox/client.ts +913 -0
  1384. package/embedding-sandbox/deploy-frankenstein.sh +240 -0
  1385. package/embedding-sandbox/docker-compose.yml +60 -0
  1386. package/embedding-sandbox/docker-manager.py +325 -0
  1387. package/embedding-sandbox/docs/python_docs.db +0 -0
  1388. package/embedding-sandbox/download-model.mjs +79 -0
  1389. package/embedding-sandbox/download-model.py +28 -0
  1390. package/embedding-sandbox/embedding-supervisor.sh +164 -0
  1391. package/embedding-sandbox/frankenstein-embeddings.py +3940 -0
  1392. package/embedding-sandbox/manage-services.sh +354 -0
  1393. package/embedding-sandbox/overflow_queue.py +345 -0
  1394. package/embedding-sandbox/package.json +17 -0
  1395. package/embedding-sandbox/project_isolation.py +292 -0
  1396. package/embedding-sandbox/qqms_v2.py +967 -0
  1397. package/embedding-sandbox/ram-manager.sh +311 -0
  1398. package/embedding-sandbox/requirements-frankenstein.txt +7 -0
  1399. package/embedding-sandbox/run_js_docs.py +59 -0
  1400. package/embedding-sandbox/seed_docs.py +885 -0
  1401. package/embedding-sandbox/server-batch.mjs +228 -0
  1402. package/embedding-sandbox/server.mjs +389 -0
  1403. package/embedding-sandbox/specmem/sockets/claude-input-state.json +1 -0
  1404. package/embedding-sandbox/specmem/sockets/embedding-death-reason.txt +3 -0
  1405. package/embedding-sandbox/specmem/sockets/seen-sessions.json +1 -0
  1406. package/embedding-sandbox/specmem/sockets/session-start.lock +1 -0
  1407. package/embedding-sandbox/specmem/sockets/session-stops.log +7 -0
  1408. package/embedding-sandbox/start-frankenstein-throttled.sh +98 -0
  1409. package/embedding-sandbox/start-on-demand.sh +116 -0
  1410. package/embedding-sandbox/start-sandbox.sh +237 -0
  1411. package/embedding-sandbox/start-supervised.sh +11 -0
  1412. package/embedding-sandbox/stop-sandbox.sh +51 -0
  1413. package/embedding-sandbox/test-socket.mjs +61 -0
  1414. package/embedding-sandbox/warm-start.sh +353 -0
  1415. package/embedding-sandbox/warm_start_feeder.py +660 -0
  1416. package/legal/README.md +31 -0
  1417. package/legal/anthropic-privacy-center-screenshot-2026-01-30.png +0 -0
  1418. package/legal/anthropic-tos-screenshot-2026-01-30.png +0 -0
  1419. package/lib/codebase-bridge.cjs +308 -0
  1420. package/package.json +136 -0
  1421. package/plugins/specmem-agents/agents/bug-hunter.md +79 -0
  1422. package/plugins/specmem-agents/agents/memory-explorer.md +57 -0
  1423. package/plugins/specmem-agents/agents/team-coordinator.md +82 -0
  1424. package/scripts/auto-updater.cjs +399 -0
  1425. package/scripts/backfill-code-definition-embeddings.ts +440 -0
  1426. package/scripts/backfill-code-embeddings.ts +206 -0
  1427. package/scripts/capture-tos-screenshots.cjs +94 -0
  1428. package/scripts/check-global-install.cjs +67 -0
  1429. package/scripts/cleanup-embedding-servers.sh +25 -0
  1430. package/scripts/dashboard-standalone.sh +369 -0
  1431. package/scripts/deploy-hooks.cjs +1451 -0
  1432. package/scripts/deploy.sh +106 -0
  1433. package/scripts/docker-project-down.sh +83 -0
  1434. package/scripts/docker-project-list.sh +40 -0
  1435. package/scripts/docker-project-up.sh +79 -0
  1436. package/scripts/fast-backfill-embeddings.ts +173 -0
  1437. package/scripts/fast-batch-embedder.cjs +334 -0
  1438. package/scripts/first-run-model-setup.cjs +849 -0
  1439. package/scripts/global-postinstall.cjs +1957 -0
  1440. package/scripts/index-codebase.js +72 -0
  1441. package/scripts/migrate-fix-embeddings.py +110 -0
  1442. package/scripts/migrate-to-project-schemas.ts +525 -0
  1443. package/scripts/optimize-embedding-model.py +324 -0
  1444. package/scripts/optimize-instructions.cjs +530 -0
  1445. package/scripts/pack-docker-images.sh +68 -0
  1446. package/scripts/pack-for-testing.sh +130 -0
  1447. package/scripts/postinstall.cjs +54 -0
  1448. package/scripts/project-env.sh +51 -0
  1449. package/scripts/reset-db.sh +30 -0
  1450. package/scripts/run-indexer.ts +69 -0
  1451. package/scripts/run-migrations.js +47 -0
  1452. package/scripts/setup-db.sh +34 -0
  1453. package/scripts/setup-minimal-schema.sql +143 -0
  1454. package/scripts/skills/code-review.md +44 -0
  1455. package/scripts/skills/debugging.md +56 -0
  1456. package/scripts/skills/specmem-deployteam.md +239 -0
  1457. package/scripts/skills/teammemberskills/EFFICIENT_GREP.md +171 -0
  1458. package/scripts/skills/teammemberskills/task-planning.md +67 -0
  1459. package/scripts/specmem/sockets/session-start.lock +1 -0
  1460. package/scripts/specmem/sockets/session-stops.log +1 -0
  1461. package/scripts/specmem-health.sh +382 -0
  1462. package/scripts/specmem-init.cjs +6935 -0
  1463. package/scripts/strip-debug-logs.cjs +43 -0
  1464. package/scripts/test-mcp-standalone.sh +365 -0
  1465. package/scripts/test-optimized-models.py +166 -0
  1466. package/scripts/verify-embedding-fix.sh +148 -0
  1467. package/skills/code-review.md +44 -0
  1468. package/skills/debugging.md +56 -0
  1469. package/skills/specmem-deployteam.md +239 -0
  1470. package/skills/teammemberskills/EFFICIENT_GREP.md +171 -0
  1471. package/skills/teammemberskills/task-planning.md +67 -0
  1472. package/specmem-health.cjs +522 -0
  1473. package/specmem.env +216 -0
package/dist/index.js ADDED
@@ -0,0 +1,4433 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SpecMem - Speculative Memory MCP Server
4
+ *
5
+ * yo shoutout to doobidoo/mcp-memory-service for the inspo
6
+ * we took their SQLite version and made it POSTGRESQL BEAST MODE
7
+ * - hardwicksoftwareservices
8
+ *
9
+ * A high-performance memory management system with:
10
+ * - Semantic search using pgvector (cosine similarity)
11
+ * - Dream-inspired consolidation (DBSCAN clustering)
12
+ * - Auto-splitting for unlimited content length
13
+ * - Natural language time queries ("yesterday", "last week")
14
+ * - Embedding caching (90% hit rate target)
15
+ * - Image storage (base64 in BYTEA)
16
+ * - Memory relationships (graph traversal)
17
+ * - SKILLS SYSTEM - drag & drop .md files for instant capabilities
18
+ * - CODEBASE INDEXING - knows your entire project
19
+ *
20
+ * Scale Requirements:
21
+ * - Millions of lines of code
22
+ * - Thousands of prompts
23
+ * - Hundreds of images
24
+ * - <100ms semantic search
25
+ */
26
+ // ============================================================================
27
+ // STARTUP LOGGING - Debug MCP connection issues
28
+ // Write to same log file as bootstrap.cjs for unified timeline
29
+ // Uses project-isolated path: {PROJECT_DIR}/specmem/run/mcp-startup.log
30
+ // ============================================================================
31
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, statSync, openSync } from 'fs';
32
+ import { access, constants } from 'fs/promises';
33
+ import * as path from 'path';
34
+ import { fileURLToPath } from 'url';
35
+ // ESM __dirname equivalent - replaces hardcoded paths
36
+ const __filename = fileURLToPath(import.meta.url);
37
+ const __dirname = path.dirname(__filename);
38
+ // DEBUG LOGGING - only enabled when SPECMEM_DEBUG=1
39
+ const __debugLog = process.env['SPECMEM_DEBUG'] === '1'
40
+ ? (...args) => console.error('[DEBUG]', ...args) // stderr, not stdout!
41
+ : () => { };
42
+ // MULTI-PROJECT ISOLATION: SPECMEM_PROJECT_PATH is set at startup and NEVER changes
43
+ // REMOVED: Marker file - caused race condition with simultaneous projects!
44
+ function _getStartupProjectPath() {
45
+ return process.env['SPECMEM_PROJECT_PATH'] || process.cwd();
46
+ }
47
+ // Compute project identifiers early for log isolation
48
+ // NO MORE HASHES - use readable project directory name for EVERYTHING
49
+ const _projectPath = _getStartupProjectPath();
50
+ // Readable project directory name - used for containers, sockets, databases, etc.
51
+ const _projectDirName = process.env['SPECMEM_PROJECT_DIR_NAME'] ||
52
+ path.basename(_projectPath)
53
+ .toLowerCase()
54
+ .replace(/[^a-z0-9_.-]/g, '-')
55
+ .replace(/-+/g, '-')
56
+ .replace(/^-|-$/g, '') || 'default';
57
+ // DEPRECATED: _projectHash now equals _projectDirName for backwards compat
58
+ const _projectHash = _projectDirName;
59
+ // Use PROJECT DIRECTORY for all specmem data - NOT ~/.specmem, NOT /tmp
60
+ // User requirement: "EVERYTHING LOCALIZED WITHIN THE PROJECT"
61
+ // Pattern: {PROJECT_DIR}/specmem/
62
+ const _projectInstanceDir = path.join(_projectPath, 'specmem');
63
+ const _projectTmpDir = path.join(_projectInstanceDir, 'run');
64
+ // Ensure project instance directory exists
65
+ try {
66
+ if (!existsSync(_projectTmpDir)) {
67
+ mkdirSync(_projectTmpDir, { recursive: true, mode: 0o755 });
68
+ }
69
+ }
70
+ catch {
71
+ // Ignore - will be created on first write
72
+ }
73
+ const STARTUP_LOG_PATH = `${_projectTmpDir}/mcp-startup.log`;
74
+ function startupLog(msg, error) {
75
+ const timestamp = new Date().toISOString();
76
+ const pid = process.pid;
77
+ let logLine = `${timestamp} [PID:${pid}] [index.ts] ${msg}\n`;
78
+ if (error) {
79
+ const errMsg = error instanceof Error ? error.message : String(error);
80
+ const errStack = error instanceof Error ? error.stack : undefined;
81
+ logLine += `${timestamp} [PID:${pid}] [index.ts] ERROR: ${errMsg}\n`;
82
+ if (errStack) {
83
+ logLine += `${timestamp} [PID:${pid}] [index.ts] STACK: ${errStack}\n`;
84
+ }
85
+ }
86
+ try {
87
+ appendFileSync(STARTUP_LOG_PATH, logLine);
88
+ }
89
+ catch {
90
+ // Ignore write errors - logging should never break the app
91
+ }
92
+ }
93
+ startupLog('index.ts ENTRY POINT - ES module loading');
94
+ import { SpecMemServer } from './mcp/specMemServer.js';
95
+ import { CachingEmbeddingProvider } from './mcp/toolRegistry.js';
96
+ import { EmbeddingServerManager, getEmbeddingServerManager } from './mcp/embeddingServerManager.js';
97
+ import { config, loadSkillsConfig, loadCodebaseConfig, getEmbeddingSocketPath, getRunDir, getProjectInfo, getProjectPath } from './config.js';
98
+ import { logger } from './utils/logger.js';
99
+ import { ensureProjectEnv, getSpawnEnv, getPythonPath } from './utils/projectEnv.js';
100
+ import { reportRetry, reportError } from './utils/progressReporter.js';
101
+ import { ensureSocketDirAtomicSync } from './utils/fileProcessingQueue.js';
102
+ import { initializeWatcher, shutdownWatcher, registerCleanupHandlers } from './mcp/watcherIntegration.js';
103
+ import { initializeSessionWatcher, shutdownSessionWatcher } from './claude-sessions/sessionIntegration.js';
104
+ // Skills & Codebase system imports
105
+ import { getSkillScanner, resetSkillScanner } from './skills/skillScanner.js';
106
+ import { getSkillResourceProvider } from './skills/skillsResource.js';
107
+ import { getCodebaseIndexer, resetCodebaseIndexer } from './codebase/codebaseIndexer.js';
108
+ import { getSkillReminder, resetSkillReminder } from './reminders/skillReminder.js';
109
+ import { getDatabase } from './database.js';
110
+ // yooo we need the big brain db layer for watchers and team features
111
+ import { initializeTheBigBrainDb } from './db/index.js';
112
+ // fs imports consolidated above (line 32)
113
+ import { join } from 'path';
114
+ import { createConnection } from 'net';
115
+ import { execSync, spawn } from 'child_process';
116
+ import { setTargetDimension } from './embeddings/projectionLayer.js';
117
+ import { getDimensionAdapter, initializeDimensionAdapter } from './services/DimensionAdapter.js';
118
+ import { getEmbeddingQueue } from './services/EmbeddingQueue.js';
119
+ import { qoms } from './utils/qoms.js';
120
+ // Coordination server import - now uses lazy initialization
121
+ import { configureLazyCoordinationServer, disableLazyCoordinationServer, executeLazyShutdownHandlers, getLazyCoordinationServerStatus, resetLazyCoordinationServer, } from './coordination/index.js';
122
+ // Startup validation - pre-flight checks to catch issues early
123
+ import { quickValidation, fullValidation, formatValidationErrors, EXIT_CODES, } from './startup/index.js';
124
+ // Startup indexing - auto-index codebase and extract sessions on MCP startup
125
+ import { runStartupIndexing } from './startup/startupIndexing.js';
126
+ // Config Injector - auto-injects hooks and hot-patches running instances
127
+ import { injectConfig } from './init/claudeConfigInjector.js';
128
+ // Auto-config system - syncs config to ~/.specmem/config.json for hooks
129
+ import { syncConfigToUserFile } from './config/autoConfig.js';
130
+ // Auto-deploy hooks and commands to 's directories
131
+ import { deployTo } from './cli/deploy-to-claude.js';
132
+ // Centralized password management
133
+ import { getPassword, isUsingDefaultPassword } from './config/password.js';
134
+ // Unified embedding timeout configuration
135
+ import { getEmbeddingTimeout, getAllEmbeddingTimeouts, hasMasterTimeout } from './config/embeddingTimeouts.js';
136
+ // Port allocation for unique per-instance ports
137
+ import { allocatePorts, setAllocatedPorts } from './utils/portAllocator.js';
138
+ // Instance manager for per-project instance tracking
139
+ import { getInstanceManager, cleanupSameProjectInstances, migrateFromOldStructure, } from './utils/instanceManager.js';
140
+ // CLI Notifications - centralized notification system for Code CLI
141
+ import { getDashboardUrl } from './mcp/cliNotifications.js';
142
+ /**
143
+ * Display SpecMem LOADED banner in Code CLI
144
+ *
145
+ * Uses the centralized CLINotifier system which:
146
+ * 1. Writes a visual banner to stderr (appears in terminal)
147
+ * 2. Can optionally send MCP logging message (appears in 's logs)
148
+ *
149
+ * The banner includes:
150
+ * - "SpecMem Loaded" status message with emoji
151
+ * - Dashboard URL with emoji indicator
152
+ * - Hooks and commands deployment status
153
+ * - Quick reference for available commands
154
+ */
155
+ function displayLoadedBanner(deployResult, dashboardUrl) {
156
+ // Use centralized notification system
157
+ // Note: We can't use the full CLINotifier here because we don't have the MCP server instance
158
+ // The MCP-level notifications are handled by specMemServer.ts announceToOnStartup()
159
+ // This function focuses on the stderr banner display
160
+ const c = {
161
+ reset: '\x1b[0m',
162
+ bright: '\x1b[1m',
163
+ yellow: '\x1b[33m',
164
+ green: '\x1b[32m',
165
+ cyan: '\x1b[36m',
166
+ magenta: '\x1b[35m',
167
+ dim: '\x1b[2m',
168
+ };
169
+ const hooksCount = deployResult.hooksDeployed.length;
170
+ const commandsCount = deployResult.commandsDeployed.length;
171
+ // Format status with proper padding for alignment
172
+ const hooksStatus = hooksCount > 0
173
+ ? `${c.green}${hooksCount} registered${c.reset}`
174
+ : `${c.dim}already up-to-date${c.reset}`;
175
+ const commandsStatus = commandsCount > 0
176
+ ? `${c.green}${commandsCount} registered${c.reset}`
177
+ : `${c.dim}already up-to-date${c.reset}`;
178
+ // Dashboard URL with emoji - use appropriate host based on mode
179
+ const dashboardDisplay = dashboardUrl
180
+ ? `${c.magenta}${dashboardUrl}${c.reset}`
181
+ : `${c.dim}disabled${c.reset}`;
182
+ const banner = `
183
+ ${c.yellow}+==================================================================+${c.reset}
184
+ ${c.yellow}|${c.reset} ${c.bright}${c.green}SpecMem Loaded${c.reset} ${c.yellow}|${c.reset}
185
+ ${c.yellow}+==================================================================+${c.reset}
186
+ ${c.yellow}|${c.reset} ${c.cyan}Hooks:${c.reset} ${hooksStatus} ${c.yellow}|${c.reset}
187
+ ${c.yellow}|${c.reset} ${c.cyan}Commands:${c.reset} ${commandsStatus} ${c.yellow}|${c.reset}
188
+ ${c.yellow}|${c.reset} ${c.cyan}Dashboard:${c.reset} ${dashboardDisplay} ${c.yellow}|${c.reset}
189
+ ${c.yellow}+==================================================================+${c.reset}
190
+ ${c.yellow}|${c.reset} ${c.dim}Type /specmem for commands | /specmem-find to search memories${c.reset} ${c.yellow}|${c.reset}
191
+ ${c.yellow}+==================================================================+${c.reset}
192
+ `;
193
+ // Write to stderr so it appears in Code CLI terminal
194
+ // (stdout is reserved for MCP JSON-RPC protocol)
195
+ process.stderr.write(banner);
196
+ }
197
+ // Dashboard server import
198
+ import { getDashboardServer, resetDashboardServer } from './dashboard/index.js';
199
+ // Memory management import
200
+ import { getMemoryManager, resetMemoryManager } from './utils/memoryManager.js';
201
+ import { createEmbeddingOverflowHandler } from './db/embeddingOverflow.js';
202
+ // re-export for external use
203
+ export { SpecMemServer } from './mcp/specMemServer.js';
204
+ export { ToolRegistry, createToolRegistry, CachingEmbeddingProvider } from './mcp/toolRegistry.js';
205
+ export { MCPProtocolHandler, parseTimeExpression, splitContent } from './mcp/mcpProtocolHandler.js';
206
+ export { DatabaseManager, getDatabase, resetDatabase } from './database.js';
207
+ // export all the goofy tools
208
+ export { RememberThisShit, FindWhatISaid, WhatDidIMean, YeahNahDeleteThat, SmushMemoriesTogether, LinkTheVibes, ShowMeTheStats, FindCodePointers } from './tools/goofy/index.js';
209
+ // export the command system - doobidoo style slash commands
210
+ export { CommandHandler, createCommandHandler, MemoryCommands, CodebaseCommands, ContextCommands, PromptCommands, getCommandsResource, getCommandHelpResource } from './commands/index.js';
211
+ // export skills system
212
+ export { SkillScanner, getSkillScanner, resetSkillScanner } from './skills/skillScanner.js';
213
+ export { SkillResourceProvider, getSkillResourceProvider } from './skills/skillsResource.js';
214
+ // export codebase indexer
215
+ export { CodebaseIndexer, getCodebaseIndexer, resetCodebaseIndexer } from './codebase/codebaseIndexer.js';
216
+ // export skill reminder
217
+ export { SkillReminder, getSkillReminder, resetSkillReminder } from './reminders/skillReminder.js';
218
+ // export dashboard
219
+ export { DashboardWebServer, getDashboardServer, resetDashboardServer } from './dashboard/index.js';
220
+ // export memory manager with instance tracking
221
+ export { MemoryManager, LRUCache, getMemoryManager, resetMemoryManager, getInstanceRegistry } from './utils/memoryManager.js';
222
+ // export embedding overflow handler
223
+ export { EmbeddingOverflowHandler, createEmbeddingOverflowHandler } from './db/embeddingOverflow.js';
224
+ // export instance manager for per-project tracking
225
+ export { InstanceManager, getInstanceManager, resetInstanceManager, hasInstanceManager, listInstances, killInstance, killAllInstances, cleanupSameProjectInstances, hashProjectPath, migrateFromOldStructure, } from './utils/instanceManager.js';
226
+ // export startup validation for external use
227
+ export { runStartupValidation, quickValidation, fullValidation, formatValidationErrors, validateOrExit, EXIT_CODES, } from './startup/index.js';
228
+ /**
229
+ * Local Embedding Provider with Sandboxed ML Support
230
+ *
231
+ * FULLY DYNAMIC - all dimensions come from database!
232
+ *
233
+ * Priority:
234
+ * 1. Air-gapped sandbox (real ML embeddings) - if running
235
+ * 2. Hash-based fallback (deterministic pseudo-embeddings)
236
+ *
237
+ * The sandboxed embedding service:
238
+ * - Runs in Docker with --network none (air-gapped)
239
+ * - Uses all-MiniLM-L6-v2 model (or any model - dimension auto-detected!)
240
+ * - Communicates via Unix socket only
241
+ * - Cannot phone home or access the internet
242
+ *
243
+ * Fallback hash-based embeddings:
244
+ * - Deterministic (same text = same embedding)
245
+ * - Normalized to unit vectors (for cosine similarity)
246
+ * - Dimension fetched from database (pgvector table metadata)
247
+ *
248
+ * DATABASE IS THE SINGLE SOURCE OF TRUTH FOR DIMENSIONS!
249
+ */
250
+ class LocalEmbeddingProvider {
251
+ targetDimension = null; // Target dimension - fetched from DB dynamically!
252
+ detectedDimension = null; // Auto-detected from Frankenstein
253
+ sandboxSocketPath;
254
+ sandboxAvailable = false;
255
+ lastSandboxCheck = 0;
256
+ sandboxCheckInterval = 5000; // Re-check every 5 seconds
257
+ autoStartAttempted = false;
258
+ // FIX: Version counter to prevent thundering herd restart attempts
259
+ // Only first request to detect failure triggers restart
260
+ sandboxFailureVersion = 0;
261
+ restartInProgress = false;
262
+ dimensionFetched = false;
263
+ // CRITICAL: Container name must be PROJECT-ISOLATED to prevent multi-instance conflicts!
264
+ // Without this, two sessions on different projects fight over the same container
265
+ // Uses readable dir name for easier debugging (matches start-sandbox.sh)
266
+ static CONTAINER_NAME = `specmem-embedding-${_projectDirName}`;
267
+ static IMAGE_NAME = 'specmem-embedding:latest';
268
+ // Adaptive timeout tracking - timeout adjusts based on actual response times
269
+ // CONFIGURABLE via environment variables to prevent AbortError timeouts
270
+ responseTimes = []; // Rolling window of last N response times
271
+ static RESPONSE_TIME_WINDOW = 20; // Track last 20 responses
272
+ // WARM SOCKET - keeps ONE socket ready for fast embeddings
273
+ // Unlike broken persistent socket, this has simple health checks and immediate fallback
274
+ warmSocket = null;
275
+ warmSocketReady = false;
276
+ warmSocketPath = null;
277
+ warmSocketLastUsed = 0;
278
+ static WARM_SOCKET_IDLE_TIMEOUT_MS = 30000; // Close idle sockets after 30s
279
+ static WARM_SOCKET_HEALTH_CHECK_MS = 5000; // Health check every 5s
280
+ warmSocketHealthInterval = null;
281
+ // FIX: Mutex lock to prevent warm socket race condition
282
+ // Ensures only one request uses warm socket at a time
283
+ warmSocketLock = Promise.resolve();
284
+ // LEGACY - kept for backwards compat but not used by new warm socket approach
285
+ persistentSocket = null;
286
+ socketConnected = false;
287
+ socketReconnecting = false;
288
+ pendingRequests = new Map();
289
+ // Timeout values - now centralized in config/embeddingTimeouts.ts
290
+ // Set SPECMEM_EMBEDDING_TIMEOUT (in seconds) to control ALL timeouts at once
291
+ // See config/embeddingTimeouts.ts for full documentation
292
+ static MIN_TIMEOUT_MS = getEmbeddingTimeout('min');
293
+ static MAX_TIMEOUT_MS = getEmbeddingTimeout('max');
294
+ static INITIAL_TIMEOUT_MS = getEmbeddingTimeout('initial');
295
+ static TIMEOUT_MULTIPLIER = 3; // timeout = avg + 3x stddev
296
+ // Retry configuration for transient failures
297
+ static SOCKET_MAX_RETRIES = parseInt(process.env['SPECMEM_EMBEDDING_MAX_RETRIES'] || '3', 10);
298
+ static SOCKET_INITIAL_RETRY_DELAY_MS = 1000; // Start with 1 second delay
299
+ static SOCKET_MAX_RETRY_DELAY_MS = 10000; // Cap at 10 seconds
300
+ // PostgreSQL-backed embedding queue for overflow when socket is unavailable
301
+ embeddingQueue;
302
+ constructor(initialTargetDimension) {
303
+ // If dimension provided, use it; otherwise will query DB on first use
304
+ this.targetDimension = initialTargetDimension ?? null;
305
+ this.dimensionFetched = initialTargetDimension !== undefined;
306
+ // Initialize embedding queue for overflow handling when socket is unavailable
307
+ this.embeddingQueue = getEmbeddingQueue(getDatabase().getPool());
308
+ // Use centralized config for socket path
309
+ this.sandboxSocketPath = getEmbeddingSocketPath();
310
+ // Log timeout configuration for debugging using centralized config
311
+ const timeoutConfig = getAllEmbeddingTimeouts();
312
+ logger.info({
313
+ socketPath: this.sandboxSocketPath,
314
+ masterTimeout: hasMasterTimeout() ? `${timeoutConfig.master}ms` : 'not set',
315
+ minTimeoutMs: LocalEmbeddingProvider.MIN_TIMEOUT_MS,
316
+ maxTimeoutMs: LocalEmbeddingProvider.MAX_TIMEOUT_MS,
317
+ initialTimeoutMs: LocalEmbeddingProvider.INITIAL_TIMEOUT_MS,
318
+ maxRetries: LocalEmbeddingProvider.SOCKET_MAX_RETRIES,
319
+ targetDimension: this.targetDimension,
320
+ configSource: hasMasterTimeout() ? 'SPECMEM_EMBEDDING_TIMEOUT (master)' : 'individual env vars'
321
+ }, 'LocalEmbeddingProvider: initialized with timeout configuration');
322
+ // Sync check in constructor only - one time startup check is acceptable
323
+ this.checkSandboxAvailabilitySync();
324
+ // If sandbox not available, try to auto-start the container
325
+ if (!this.sandboxAvailable && !this.autoStartAttempted) {
326
+ this.tryAutoStartContainer();
327
+ }
328
+ // Initialize persistent socket connection
329
+ this.initPersistentSocket();
330
+ }
331
+ /**
332
+ * Initialize the persistent socket connection
333
+ * This keeps the socket OPEN and reuses it for all embedding requests
334
+ */
335
+ initPersistentSocket() {
336
+ const methodStart = Date.now();
337
+ // CRITICAL: Re-detect socket path on EVERY reconnect attempt
338
+ // The socket might not exist at MCP startup but appear later
339
+ const freshSocketPath = getEmbeddingSocketPath();
340
+ if (freshSocketPath !== this.sandboxSocketPath) {
341
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_PATH_CHANGED', {
342
+ oldPath: this.sandboxSocketPath,
343
+ newPath: freshSocketPath
344
+ });
345
+ // PATH CHANGED: Destroy old socket and reset state to force new connection
346
+ if (this.persistentSocket) {
347
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_DESTROYING_OLD', {
348
+ reason: 'path changed, need new socket'
349
+ });
350
+ try {
351
+ this.persistentSocket.destroy();
352
+ }
353
+ catch (e) {
354
+ // Ignore destroy errors
355
+ }
356
+ this.persistentSocket = null;
357
+ this.socketConnected = false;
358
+ this.socketReconnecting = false;
359
+ }
360
+ this.sandboxSocketPath = freshSocketPath;
361
+ }
362
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_START', {
363
+ alreadyHasPersistentSocket: !!this.persistentSocket,
364
+ socketReconnecting: this.socketReconnecting,
365
+ socketPath: this.sandboxSocketPath
366
+ });
367
+ // FORCE NEW CONNECTION: If socket exists but not connected, destroy and retry
368
+ if (this.persistentSocket && !this.socketConnected) {
369
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_FORCE_RECONNECT', {
370
+ reason: 'socket exists but not connected'
371
+ });
372
+ try {
373
+ this.persistentSocket.destroy();
374
+ }
375
+ catch (e) {
376
+ // Ignore
377
+ }
378
+ this.persistentSocket = null;
379
+ this.socketReconnecting = false;
380
+ }
381
+ if (this.persistentSocket || this.socketReconnecting) {
382
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_SKIPPED', {
383
+ reason: this.persistentSocket ? 'already has socket' : 'already reconnecting',
384
+ elapsedMs: Date.now() - methodStart
385
+ });
386
+ return;
387
+ }
388
+ this.socketReconnecting = true;
389
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_CREATING_CONNECTION', {
390
+ socketPath: this.sandboxSocketPath
391
+ });
392
+ logger.debug({ socketPath: this.sandboxSocketPath }, 'LocalEmbeddingProvider: initializing persistent socket');
393
+ try {
394
+ this.persistentSocket = createConnection(this.sandboxSocketPath);
395
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_CONNECTION_CREATED', {
396
+ elapsedMs: Date.now() - methodStart
397
+ });
398
+ this.persistentSocket.on('connect', () => {
399
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_ON_CONNECT', {
400
+ timeSinceInitMs: Date.now() - methodStart,
401
+ socketPath: this.sandboxSocketPath
402
+ });
403
+ this.socketConnected = true;
404
+ this.socketReconnecting = false;
405
+ logger.info('LocalEmbeddingProvider: persistent socket connected');
406
+ });
407
+ let buffer = '';
408
+ this.persistentSocket.on('data', (data) => {
409
+ const dataLength = data.length;
410
+ buffer += data.toString();
411
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_ON_DATA', {
412
+ dataLength,
413
+ bufferLength: buffer.length,
414
+ hasNewline: buffer.includes('\n')
415
+ });
416
+ // IDLE-BASED TIMEOUT: Reset ALL pending timeouts on any data received
417
+ // This means server is alive and working - keep waiting
418
+ const timeoutMs = this.getAdaptiveTimeout();
419
+ for (const [reqId, pending] of this.pendingRequests) {
420
+ clearTimeout(pending.timeout);
421
+ pending.timeout = setTimeout(() => {
422
+ if (this.pendingRequests.has(reqId)) {
423
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_IDLE_TIMEOUT', {
424
+ requestId: reqId,
425
+ timeoutMs
426
+ });
427
+ this.pendingRequests.delete(reqId);
428
+ pending.reject(new Error(`Embedding idle timeout after ${Math.round(timeoutMs / 1000)}s of no activity. ` +
429
+ `Socket: ${this.sandboxSocketPath}. ` +
430
+ `If model is slow, increase SPECMEM_EMBEDDING_TIMEOUT.`));
431
+ }
432
+ }, timeoutMs);
433
+ }
434
+ // Process all complete JSON messages in buffer
435
+ let newlineIndex;
436
+ while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
437
+ const message = buffer.slice(0, newlineIndex);
438
+ buffer = buffer.slice(newlineIndex + 1);
439
+ try {
440
+ const response = JSON.parse(message);
441
+ const requestId = response.requestId;
442
+ // Handle heartbeat/processing status - just reset timeout and continue
443
+ if (response.status === 'processing') {
444
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_HEARTBEAT', {
445
+ requestId,
446
+ textLength: response.text_length
447
+ });
448
+ continue; // Keep waiting for actual embedding
449
+ }
450
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_MESSAGE_PARSED', {
451
+ requestId,
452
+ hasEmbedding: !!response.embedding,
453
+ hasError: !!response.error,
454
+ pendingRequestsCount: this.pendingRequests.size
455
+ });
456
+ if (requestId && this.pendingRequests.has(requestId)) {
457
+ const pending = this.pendingRequests.get(requestId);
458
+ clearTimeout(pending.timeout);
459
+ this.pendingRequests.delete(requestId);
460
+ if (response.error) {
461
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_RESOLVING_ERROR', {
462
+ requestId,
463
+ error: response.error
464
+ });
465
+ pending.reject(new Error(response.error));
466
+ }
467
+ else if (response.embedding) {
468
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_RESOLVING_SUCCESS', {
469
+ requestId,
470
+ embeddingDim: response.embedding.length
471
+ });
472
+ pending.resolve(response.embedding);
473
+ }
474
+ else {
475
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_RESOLVING_INVALID', {
476
+ requestId,
477
+ responseKeys: Object.keys(response)
478
+ });
479
+ pending.reject(new Error('Invalid response from sandbox'));
480
+ }
481
+ }
482
+ else {
483
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_ORPHAN_RESPONSE', {
484
+ requestId,
485
+ pendingRequestIds: Array.from(this.pendingRequests.keys())
486
+ });
487
+ }
488
+ }
489
+ catch (err) {
490
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_PARSE_ERROR', {
491
+ error: err instanceof Error ? err.message : String(err),
492
+ messageLength: message.length,
493
+ messagePreview: message.substring(0, 100)
494
+ });
495
+ logger.debug({ err, message }, 'LocalEmbeddingProvider: failed to parse socket message');
496
+ }
497
+ }
498
+ });
499
+ this.persistentSocket.on('error', (err) => {
500
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_ON_ERROR', {
501
+ error: err.message,
502
+ code: err.code,
503
+ pendingRequestsCount: this.pendingRequests.size,
504
+ socketPath: this.sandboxSocketPath
505
+ });
506
+ logger.warn({ err }, 'LocalEmbeddingProvider: persistent socket error');
507
+ this.socketConnected = false;
508
+ this.persistentSocket = null;
509
+ // Reject all pending requests
510
+ for (const [requestId, pending] of this.pendingRequests) {
511
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_REJECTING_PENDING', {
512
+ requestId,
513
+ error: err.message
514
+ });
515
+ clearTimeout(pending.timeout);
516
+ pending.reject(new Error(`Socket error: ${err.message}`));
517
+ }
518
+ this.pendingRequests.clear();
519
+ // Try to reconnect after delay
520
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_SCHEDULING_RECONNECT', {
521
+ delayMs: 1000
522
+ });
523
+ this.socketReconnecting = false;
524
+ setTimeout(() => this.initPersistentSocket(), 1000);
525
+ });
526
+ this.persistentSocket.on('close', () => {
527
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_ON_CLOSE', {
528
+ pendingRequestsCount: this.pendingRequests.size,
529
+ socketPath: this.sandboxSocketPath
530
+ });
531
+ logger.debug('LocalEmbeddingProvider: persistent socket closed');
532
+ this.socketConnected = false;
533
+ this.persistentSocket = null;
534
+ this.socketReconnecting = false;
535
+ // Reject all pending requests
536
+ for (const [requestId, pending] of this.pendingRequests) {
537
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_CLOSE_REJECTING', {
538
+ requestId
539
+ });
540
+ clearTimeout(pending.timeout);
541
+ pending.reject(new Error('Socket closed'));
542
+ }
543
+ this.pendingRequests.clear();
544
+ });
545
+ }
546
+ catch (err) {
547
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_CATCH_ERROR', {
548
+ error: err instanceof Error ? err.message : String(err),
549
+ elapsedMs: Date.now() - methodStart,
550
+ socketPath: this.sandboxSocketPath
551
+ });
552
+ logger.debug({ err }, 'LocalEmbeddingProvider: failed to create persistent socket');
553
+ this.socketReconnecting = false;
554
+ }
555
+ }
556
+ /**
557
+ * PUBLIC: Force reset the persistent socket connection
558
+ * Call this after restarting the embedding server to pick up new socket
559
+ */
560
+ resetSocket() {
561
+ logger.info('[LocalEmbeddingProvider] Resetting socket connection...');
562
+ // CRITICAL: Close warm socket too - it caches connections that become stale after server restart
563
+ this.closeWarmSocket();
564
+ // Destroy existing socket if any
565
+ if (this.persistentSocket) {
566
+ try {
567
+ this.persistentSocket.destroy();
568
+ }
569
+ catch (e) {
570
+ // Ignore destroy errors
571
+ }
572
+ this.persistentSocket = null;
573
+ }
574
+ // Reset state
575
+ this.socketConnected = false;
576
+ this.socketReconnecting = false;
577
+ // Clear pending requests
578
+ for (const pending of this.pendingRequests.values()) {
579
+ clearTimeout(pending.timeout);
580
+ pending.reject(new Error('Socket reset by user'));
581
+ }
582
+ this.pendingRequests.clear();
583
+ // Re-detect socket path and reinitialize
584
+ this.sandboxSocketPath = getEmbeddingSocketPath();
585
+ this.initPersistentSocket();
586
+ logger.info({ socketPath: this.sandboxSocketPath }, '[LocalEmbeddingProvider] Socket reset complete');
587
+ }
588
+ /**
589
+ * Ensure persistent socket is connected, reconnect if needed
590
+ *
591
+ * CRITICAL: Previous 5-second timeout was TOO SHORT - caused fallback to
592
+ * slow per-request socket creation (120s timeout, 3 retries = 360s+ total).
593
+ * Now uses 30s default (configurable via SPECMEM_SOCKET_CONNECT_TIMEOUT_MS).
594
+ *
595
+ * FIX: Increased from 5000ms to 30000ms to give socket time to connect
596
+ * instead of immediately falling back to the slow path.
597
+ */
598
+ async ensurePersistentSocket() {
599
+ const methodStart = Date.now();
600
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'ENSURE_PERSISTENT_SOCKET_START', {
601
+ socketConnected: this.socketConnected,
602
+ hasPersistentSocket: !!this.persistentSocket,
603
+ socketReconnecting: this.socketReconnecting,
604
+ socketPath: this.sandboxSocketPath
605
+ });
606
+ if (this.socketConnected && this.persistentSocket) {
607
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'ENSURE_PERSISTENT_SOCKET_ALREADY_CONNECTED', {
608
+ elapsedMs: Date.now() - methodStart
609
+ });
610
+ return true;
611
+ }
612
+ // AUTO-FIX STALE STATE: If socketReconnecting is stuck true but no socket exists,
613
+ // reset the state and force a fresh connection attempt
614
+ if (this.socketReconnecting && !this.persistentSocket) {
615
+ const staleTime = 5000; // 5 seconds is too long to be "reconnecting" without a socket
616
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'ENSURE_PERSISTENT_SOCKET_STALE_STATE_DETECTED', {
617
+ reason: 'socketReconnecting=true but no socket exists - forcing reset'
618
+ });
619
+ this.socketReconnecting = false;
620
+ this.socketConnected = false;
621
+ }
622
+ if (!this.socketReconnecting) {
623
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'ENSURE_PERSISTENT_SOCKET_CALLING_INIT', {
624
+ reason: 'not reconnecting, initiating socket'
625
+ });
626
+ this.initPersistentSocket();
627
+ }
628
+ else {
629
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'ENSURE_PERSISTENT_SOCKET_ALREADY_RECONNECTING', {
630
+ reason: 'socketReconnecting=true, skipping init'
631
+ });
632
+ }
633
+ // Wait for connection with timeout - DYNAMIC via env var
634
+ // Balance: not too short (causes fallback) but not too long (makes MCP unresponsive)
635
+ // 10s is a good middle ground - enough for most connections, fast enough to fail gracefully
636
+ const maxWait = parseInt(process.env['SPECMEM_SOCKET_CONNECT_TIMEOUT_MS'] || '10000', 10);
637
+ const startTime = Date.now();
638
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'ENSURE_PERSISTENT_SOCKET_WAIT_LOOP_START', {
639
+ maxWaitMs: maxWait,
640
+ pollIntervalMs: 100
641
+ });
642
+ let pollCount = 0;
643
+ while (Date.now() - startTime < maxWait) {
644
+ pollCount++;
645
+ if (this.socketConnected && this.persistentSocket) {
646
+ const waitTimeMs = Date.now() - startTime;
647
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'ENSURE_PERSISTENT_SOCKET_CONNECTED_IN_LOOP', {
648
+ waitTimeMs,
649
+ pollCount,
650
+ totalMethodElapsedMs: Date.now() - methodStart
651
+ });
652
+ logger.debug({ waitTimeMs: Date.now() - startTime }, 'Persistent socket connected');
653
+ return true;
654
+ }
655
+ await new Promise(resolve => setTimeout(resolve, 100));
656
+ }
657
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'ENSURE_PERSISTENT_SOCKET_TIMEOUT', {
658
+ maxWait,
659
+ socketPath: this.sandboxSocketPath,
660
+ pollCount,
661
+ totalMethodElapsedMs: Date.now() - methodStart,
662
+ finalSocketConnected: this.socketConnected,
663
+ finalHasPersistentSocket: !!this.persistentSocket
664
+ });
665
+ logger.warn({ maxWait, socketPath: this.sandboxSocketPath }, 'Persistent socket connection timeout - falling back to per-request socket');
666
+ return false;
667
+ }
668
+ /**
669
+ * Fetch target dimension from database if not already known
670
+ * Database is the single source of truth for dimensions!
671
+ */
672
+ async ensureTargetDimension() {
673
+ if (this.targetDimension !== null) {
674
+ return this.targetDimension;
675
+ }
676
+ // Query database for dimension
677
+ try {
678
+ const db = getDatabase();
679
+ const result = await db.query(`
680
+ SELECT atttypmod FROM pg_attribute
681
+ WHERE attrelid = 'memories'::regclass AND attname = 'embedding'
682
+ `);
683
+ if (result.rows.length > 0) {
684
+ this.targetDimension = result.rows[0].atttypmod;
685
+ this.dimensionFetched = true;
686
+ logger.info({ targetDimension: this.targetDimension }, 'LocalEmbeddingProvider: fetched target dimension from database');
687
+ return this.targetDimension;
688
+ }
689
+ }
690
+ catch (err) {
691
+ logger.debug({ error: err }, 'LocalEmbeddingProvider: could not fetch dimension from DB (may not be initialized)');
692
+ }
693
+ // If DB query fails, detect from first embedding and use that
694
+ // This handles the cold-start case where DB schema isn't ready yet
695
+ logger.warn('LocalEmbeddingProvider: could not get dimension from DB, will use embedding dimension');
696
+ return 0; // Signal to use embedding dimension as-is
697
+ }
698
+ /**
699
+ * Auto-detect embedding dimension from Frankenstein
700
+ * Called once on first use, cached thereafter
701
+ */
702
+ async getEmbeddingDimension() {
703
+ if (this.detectedDimension) {
704
+ return this.detectedDimension;
705
+ }
706
+ // Try to get a test embedding from Frankenstein
707
+ if (await this.isSandboxAvailableAsync()) {
708
+ try {
709
+ const testEmbedding = await this.generateSandboxedEmbedding('test dimension detection');
710
+ this.detectedDimension = testEmbedding.length;
711
+ logger.info({ dimension: this.detectedDimension }, 'Auto-detected embedding dimension from Frankenstein');
712
+ return this.detectedDimension;
713
+ }
714
+ catch (err) {
715
+ logger.warn({ error: err }, 'Failed to detect dimension from Frankenstein, using target dimension');
716
+ }
717
+ }
718
+ // Fallback to target dimension if detection fails
719
+ this.detectedDimension = this.targetDimension;
720
+ return this.detectedDimension;
721
+ }
722
+ /**
723
+ * Try to auto-start the embedding Docker container
724
+ * This runs asynchronously and updates sandboxAvailable when ready
725
+ */
726
+ tryAutoStartContainer() {
727
+ this.autoStartAttempted = true;
728
+ // Run auto-start in background to not block constructor
729
+ this.autoStartContainer().catch(err => {
730
+ logger.warn({ error: err }, 'failed to auto-start embedding container');
731
+ });
732
+ }
733
+ /**
734
+ * Fix permissions on all parent directories of a path
735
+ * Ensures each parent has at least 755 (world-readable/executable)
736
+ * This is critical for Docker to access the socket directory
737
+ */
738
+ fixParentDirectoryPermissions(targetPath) {
739
+ const { chmodSync } = require('fs');
740
+ const pathModule = require('path');
741
+ // Get all parent directories from target up to root
742
+ const parents = [];
743
+ let current = pathModule.dirname(targetPath);
744
+ // Collect all parents (stop at /tmp or / to avoid modifying system dirs)
745
+ while (current && current !== '/' && current !== '/tmp' && !parents.includes(current)) {
746
+ parents.push(current);
747
+ current = pathModule.dirname(current);
748
+ }
749
+ // Fix permissions from root towards target (top-down)
750
+ parents.reverse();
751
+ for (const dir of parents) {
752
+ try {
753
+ if (existsSync(dir)) {
754
+ const stats = statSync(dir);
755
+ const currentMode = stats.mode & 0o777;
756
+ // Check if directory has at least 755 (rwxr-xr-x)
757
+ // This means: owner has rwx, group and others have rx
758
+ const minMode = 0o755;
759
+ if ((currentMode & minMode) !== minMode) {
760
+ // Add the missing permissions (don't remove existing ones)
761
+ const newMode = currentMode | minMode;
762
+ chmodSync(dir, newMode);
763
+ logger.info({
764
+ dir,
765
+ oldMode: currentMode.toString(8),
766
+ newMode: newMode.toString(8)
767
+ }, 'fixed parent directory permissions');
768
+ }
769
+ }
770
+ }
771
+ catch (err) {
772
+ // Log but don't fail - we may not have permission to modify some dirs
773
+ logger.debug({ error: err, dir }, 'could not fix permissions on parent directory');
774
+ }
775
+ }
776
+ }
777
+ /**
778
+ * Auto-start the embedding Docker container if not running
779
+ *
780
+ * CRITICAL: The SOCKET_PATH env var MUST be within the mounted volume!
781
+ * Container mount: -v ${socketDir}:${socketDir}
782
+ * Socket path: ${socketDir}/embeddings.sock
783
+ *
784
+ * If these don't match, the container will fail with EACCES trying to
785
+ * create a socket in a non-existent directory.
786
+ */
787
+ async autoStartContainer() {
788
+ // Step 1: Ensure socket directory exists (use centralized config)
789
+ const socketDir = getRunDir();
790
+ // CRITICAL: Fix permissions on ALL parent directories BEFORE creating socket dir
791
+ // This ensures Docker can traverse the path to access the socket
792
+ this.fixParentDirectoryPermissions(socketDir);
793
+ // Task #17 FIX: Use atomic mkdir to prevent race condition when multiple
794
+ // MCP servers try to create the socket directory simultaneously
795
+ try {
796
+ const created = ensureSocketDirAtomicSync(socketDir);
797
+ if (created) {
798
+ logger.info({ dir: socketDir }, 'created socket directory atomically');
799
+ }
800
+ }
801
+ catch (err) {
802
+ logger.error({ error: err, dir: socketDir }, 'failed to create socket directory');
803
+ return;
804
+ }
805
+ // Ensure socket directory has 777 permissions for Docker access
806
+ try {
807
+ const { chmodSync } = require('fs');
808
+ chmodSync(socketDir, 0o777);
809
+ logger.info({ dir: socketDir, mode: '777' }, 'set socket directory permissions');
810
+ }
811
+ catch (err) {
812
+ logger.warn({ error: err, dir: socketDir }, 'could not set socket directory to 777');
813
+ }
814
+ // CRITICAL FIX: Container socket path MUST be inside the mounted volume
815
+ // NOT this.sandboxSocketPath which might point elsewhere
816
+ const containerSocketPath = `${socketDir}/embeddings.sock`;
817
+ // Step 1.5: Cleanup any stale socket files before starting container
818
+ // A stale socket can prevent the container from binding properly
819
+ await this.cleanupStaleSocket(containerSocketPath);
820
+ // Step 2: ALWAYS prefer native Python over Docker
821
+ // Docker containers crash-loop when native Python owns the socket
822
+ // Native Python is faster, more reliable, and doesn't have permission issues
823
+ const pythonAvailable = await this.isPythonEmbeddingAvailable();
824
+ if (pythonAvailable) {
825
+ logger.info('Native Python embedding available - using Python (preferred over Docker)');
826
+ await this.autoStartPythonEmbedding();
827
+ return;
828
+ }
829
+ // Step 2b: Check if Docker is available - only use Docker if Python isn't available
830
+ if (!this.isDockerAvailable()) {
831
+ logger.info('Docker not available - falling back to Python embedding server');
832
+ await this.autoStartPythonEmbedding();
833
+ return;
834
+ }
835
+ // Step 3: Check if container is already running
836
+ if (this.isContainerRunning()) {
837
+ logger.debug({ container: LocalEmbeddingProvider.CONTAINER_NAME }, 'embedding container already running');
838
+ // Update socket path to match what the running container uses
839
+ this.sandboxSocketPath = containerSocketPath;
840
+ // Wait a bit for socket to appear if container just started
841
+ await this.waitForSocket(10000);
842
+ return;
843
+ }
844
+ // Step 4: Check if image exists
845
+ if (!this.isImageAvailable()) {
846
+ logger.warn({ image: LocalEmbeddingProvider.IMAGE_NAME }, 'embedding image not found - cannot auto-start');
847
+ return;
848
+ }
849
+ // Step 5: Remove any stopped container with the same name
850
+ this.removeStoppedContainer();
851
+ // Step 6: Start the container with security flags
852
+ logger.info({
853
+ container: LocalEmbeddingProvider.CONTAINER_NAME,
854
+ socketDir,
855
+ containerSocketPath
856
+ }, 'auto-starting embedding container...');
857
+ try {
858
+ execSync(`docker run -d ` +
859
+ `--name ${LocalEmbeddingProvider.CONTAINER_NAME} ` +
860
+ `--restart=on-failure:5 ` + // Auto-restart up to 5 times on crash/OOM
861
+ `--network none ` +
862
+ `--read-only ` +
863
+ `--cap-drop ALL ` +
864
+ `--security-opt no-new-privileges:true ` +
865
+ `--memory=2g ` +
866
+ `--cpus=2 ` +
867
+ `-v ${socketDir}:${socketDir} ` +
868
+ `-v specmem-model-cache:/app/models ` +
869
+ `-e SOCKET_PATH=${containerSocketPath} ` + // MUST match the mounted volume!
870
+ `-e TARGET_DIMENSION=384 ` + // Fixed dimension for air-gapped container
871
+ `-e SKIP_DB_DIMENSION_QUERY=true ` + // Container is air-gapped, can't query DB
872
+ `-l specmem.project=${_projectDirName} ` + // Label for cleanup
873
+ `${LocalEmbeddingProvider.IMAGE_NAME}`, { stdio: 'pipe', timeout: parseInt(process.env['SPECMEM_DOCKER_EXEC_TIMEOUT_MS'] || '30000', 10) });
874
+ // Update our socket path to match what we told the container
875
+ this.sandboxSocketPath = containerSocketPath;
876
+ logger.info({
877
+ container: LocalEmbeddingProvider.CONTAINER_NAME,
878
+ socketPath: containerSocketPath
879
+ }, 'embedding container started');
880
+ // Wait for socket to become available - configurable via SPECMEM_SOCKET_WAIT_TIMEOUT_MS
881
+ const socketWaitTimeout = parseInt(process.env['SPECMEM_SOCKET_WAIT_TIMEOUT_MS'] || '30000', 10);
882
+ await this.waitForSocket(socketWaitTimeout);
883
+ }
884
+ catch (err) {
885
+ // Task #16 FIX: Proper error handling for Docker spawn failures
886
+ // Extract detailed error info for debugging
887
+ const errorMessage = err instanceof Error ? err.message : String(err);
888
+ const errorCode = err?.code || 'UNKNOWN';
889
+ const errorSignal = err?.signal;
890
+ const errorStatus = err?.status;
891
+ // Log detailed error with all available context
892
+ logger.error({
893
+ error: errorMessage,
894
+ code: errorCode,
895
+ signal: errorSignal,
896
+ status: errorStatus,
897
+ container: LocalEmbeddingProvider.CONTAINER_NAME,
898
+ socketDir,
899
+ containerSocketPath,
900
+ image: LocalEmbeddingProvider.IMAGE_NAME
901
+ }, 'Docker container spawn failed - falling back to Python embedding server');
902
+ // Common failure modes with helpful messages:
903
+ // - ETIMEDOUT: Docker command took too long (increase SPECMEM_DOCKER_EXEC_TIMEOUT_MS)
904
+ // - exit code 125: Docker daemon error (permissions, daemon not running)
905
+ // - exit code 126: Container command cannot be invoked
906
+ // - exit code 127: Container command not found
907
+ // - exit code 137: OOM killed (increase --memory limit)
908
+ if (errorStatus === 125) {
909
+ logger.warn('Docker daemon error - check if Docker is running and user has permissions');
910
+ }
911
+ else if (errorCode === 'ETIMEDOUT') {
912
+ logger.warn({ timeout: process.env['SPECMEM_DOCKER_EXEC_TIMEOUT_MS'] || '30000' }, 'Docker command timed out - increase SPECMEM_DOCKER_EXEC_TIMEOUT_MS if needed');
913
+ }
914
+ else if (errorStatus === 137) {
915
+ logger.warn('Container was OOM killed - may need more memory');
916
+ }
917
+ // CRITICAL: Fall back to Python embedding server instead of silent failure
918
+ logger.info('Attempting Python embedding server fallback after Docker failure...');
919
+ try {
920
+ await this.autoStartPythonEmbedding();
921
+ }
922
+ catch (fallbackErr) {
923
+ logger.error({
924
+ error: fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)
925
+ }, 'Python embedding fallback also failed - embedding service unavailable');
926
+ }
927
+ }
928
+ }
929
+ /**
930
+ * Check if native Python embedding server can be started
931
+ * Always prefer Python over Docker - it's faster and doesn't have permission issues
932
+ */
933
+ async isPythonEmbeddingAvailable() {
934
+ try {
935
+ // Check if frankenstein-embeddings.py exists in the expected location
936
+ const projectPath = getProjectPath();
937
+ const embeddingScript = join(projectPath, 'embedding-sandbox', 'frankenstein-embeddings.py');
938
+ if (existsSync(embeddingScript)) {
939
+ logger.debug({ embeddingScript }, 'Python embedding script found');
940
+ return true;
941
+ }
942
+ // Also check relative to this file (for npm installed packages)
943
+ const altScript = join(path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url)))), 'embedding-sandbox', 'frankenstein-embeddings.py');
944
+ if (existsSync(altScript)) {
945
+ logger.debug({ altScript }, 'Python embedding script found (alt location)');
946
+ return true;
947
+ }
948
+ return false;
949
+ }
950
+ catch {
951
+ return false;
952
+ }
953
+ }
954
+ /**
955
+ * Check if Docker daemon is available
956
+ */
957
+ isDockerAvailable() {
958
+ try {
959
+ execSync('docker info', { stdio: 'pipe', timeout: 5000 });
960
+ return true;
961
+ }
962
+ catch {
963
+ return false;
964
+ }
965
+ }
966
+ /**
967
+ * Check if the embedding container is currently running
968
+ */
969
+ isContainerRunning() {
970
+ try {
971
+ const result = execSync(`docker ps --filter "name=^${LocalEmbeddingProvider.CONTAINER_NAME}$" --format "{{.Names}}"`, { stdio: 'pipe', timeout: 5000 });
972
+ return result.toString().trim() === LocalEmbeddingProvider.CONTAINER_NAME;
973
+ }
974
+ catch {
975
+ return false;
976
+ }
977
+ }
978
+ /**
979
+ * Check if the embedding image is available locally
980
+ */
981
+ isImageAvailable() {
982
+ try {
983
+ execSync(`docker image inspect ${LocalEmbeddingProvider.IMAGE_NAME}`, { stdio: 'pipe', timeout: 5000 });
984
+ return true;
985
+ }
986
+ catch {
987
+ return false;
988
+ }
989
+ }
990
+ /**
991
+ * Remove any stopped container with our name
992
+ */
993
+ removeStoppedContainer() {
994
+ try {
995
+ execSync(`docker rm ${LocalEmbeddingProvider.CONTAINER_NAME}`, { stdio: 'pipe', timeout: 5000 });
996
+ logger.debug({ container: LocalEmbeddingProvider.CONTAINER_NAME }, 'removed stopped container');
997
+ }
998
+ catch {
999
+ // Container doesn't exist, which is fine
1000
+ }
1001
+ }
1002
+ /**
1003
+ * Cleanup stale socket file on startup
1004
+ *
1005
+ * A stale socket can occur when:
1006
+ * - Container crashed without cleanup
1007
+ * - Host rebooted while container was running
1008
+ * - Manual docker kill without socket cleanup
1009
+ *
1010
+ * This tries to connect to the socket. If connection fails, the socket is stale
1011
+ * and should be removed before starting a new container.
1012
+ */
1013
+ async cleanupStaleSocket(socketPath) {
1014
+ if (!existsSync(socketPath)) {
1015
+ logger.debug({ socketPath }, 'LocalEmbeddingProvider: no socket file to cleanup');
1016
+ return;
1017
+ }
1018
+ logger.info({ socketPath }, 'LocalEmbeddingProvider: checking if socket is stale...');
1019
+ // Try to connect to the socket to see if it's alive
1020
+ return new Promise((resolve) => {
1021
+ const testSocket = createConnection(socketPath);
1022
+ const timeout = setTimeout(() => {
1023
+ testSocket.destroy();
1024
+ // Connection timed out - socket is stale
1025
+ this.removeStaleSocketFile(socketPath);
1026
+ resolve();
1027
+ }, 2000); // 2 second timeout for connection test
1028
+ testSocket.on('connect', () => {
1029
+ // Socket is alive - don't remove it
1030
+ clearTimeout(timeout);
1031
+ testSocket.destroy();
1032
+ logger.info({ socketPath }, 'LocalEmbeddingProvider: socket is alive, not removing');
1033
+ resolve();
1034
+ });
1035
+ testSocket.on('error', (err) => {
1036
+ // Connection failed - socket is stale
1037
+ clearTimeout(timeout);
1038
+ testSocket.destroy();
1039
+ logger.info({ socketPath, error: err.message }, 'LocalEmbeddingProvider: socket connection failed, removing stale socket');
1040
+ this.removeStaleSocketFile(socketPath);
1041
+ resolve();
1042
+ });
1043
+ });
1044
+ }
1045
+ /**
1046
+ * Remove a stale socket file from the filesystem
1047
+ */
1048
+ removeStaleSocketFile(socketPath) {
1049
+ try {
1050
+ unlinkSync(socketPath);
1051
+ logger.info({ socketPath }, 'LocalEmbeddingProvider: removed stale socket file');
1052
+ }
1053
+ catch (err) {
1054
+ logger.warn({ socketPath, error: err }, 'LocalEmbeddingProvider: failed to remove stale socket file');
1055
+ }
1056
+ }
1057
+ // Track Python embedding server process with overload protection
1058
+ pythonEmbeddingPid = null;
1059
+ pythonAutoStartAttempted = false;
1060
+ lastPythonStartAttempt = 0;
1061
+ pythonRestartCount = 0;
1062
+ pythonRestartWindowStart = 0;
1063
+ pythonConsecutiveFailures = 0;
1064
+ static PYTHON_RESTART_COOLDOWN_MS = 5000; // 5 seconds base cooldown
1065
+ static PYTHON_MAX_RESTARTS_PER_MINUTE = 4; // Max 4 restarts per minute
1066
+ static PYTHON_MAX_CONSECUTIVE_FAILURES = 3; // Give up after 3 consecutive failures
1067
+ static PYTHON_FAILURE_BACKOFF_MS = 60000; // 1 minute backoff after max failures
1068
+ /**
1069
+ * Auto-start the embedding server as a Python script (not Docker)
1070
+ *
1071
+ * CRITICAL: This enables transparent restart when embedding server dies!
1072
+ * User runs command → socket dead → auto-restart → command succeeds
1073
+ *
1074
+ * OVERLOAD PROTECTION:
1075
+ * - Max 4 restarts per minute
1076
+ * - After 3 consecutive failures, 1 minute backoff
1077
+ * - Exponential backoff on repeated failures
1078
+ *
1079
+ * Triggered when:
1080
+ * - Session starts and Docker not available
1081
+ * - Socket connection fails mid-session
1082
+ * - Server process gets OOM killed
1083
+ * - Server idle-shutdown and user needs embedding
1084
+ *
1085
+ * @returns true if started or already running, false if failed
1086
+ */
1087
+ async autoStartPythonEmbedding() {
1088
+ const now = Date.now();
1089
+ // OVERLOAD PROTECTION: Check consecutive failures
1090
+ if (this.pythonConsecutiveFailures >= LocalEmbeddingProvider.PYTHON_MAX_CONSECUTIVE_FAILURES) {
1091
+ const timeSinceLastAttempt = now - this.lastPythonStartAttempt;
1092
+ const backoffTime = LocalEmbeddingProvider.PYTHON_FAILURE_BACKOFF_MS *
1093
+ Math.pow(2, Math.min(this.pythonConsecutiveFailures - LocalEmbeddingProvider.PYTHON_MAX_CONSECUTIVE_FAILURES, 3));
1094
+ if (timeSinceLastAttempt < backoffTime) {
1095
+ const waitTime = Math.round((backoffTime - timeSinceLastAttempt) / 1000);
1096
+ logger.warn({
1097
+ consecutiveFailures: this.pythonConsecutiveFailures,
1098
+ backoffSeconds: Math.round(backoffTime / 1000),
1099
+ waitSeconds: waitTime
1100
+ }, `Embedding server restart in backoff mode (${this.pythonConsecutiveFailures} failures). ` +
1101
+ `Wait ${waitTime}s or check logs at {PROJECT}/specmem/sockets/embedding-autostart.log`);
1102
+ return false;
1103
+ }
1104
+ // Backoff expired, reset failure count and try again
1105
+ logger.info('Backoff expired, retrying embedding server...');
1106
+ this.pythonConsecutiveFailures = 0;
1107
+ }
1108
+ // RATE LIMIT: Check restarts per minute
1109
+ if (now - this.pythonRestartWindowStart > 60000) {
1110
+ // Reset window
1111
+ this.pythonRestartWindowStart = now;
1112
+ this.pythonRestartCount = 0;
1113
+ }
1114
+ if (this.pythonRestartCount >= LocalEmbeddingProvider.PYTHON_MAX_RESTARTS_PER_MINUTE) {
1115
+ logger.warn({
1116
+ restartCount: this.pythonRestartCount,
1117
+ maxPerMinute: LocalEmbeddingProvider.PYTHON_MAX_RESTARTS_PER_MINUTE
1118
+ }, 'Embedding server restart rate limit hit. Will retry in ~1 minute.');
1119
+ return false;
1120
+ }
1121
+ // COOLDOWN: Short delay between attempts
1122
+ if (now - this.lastPythonStartAttempt < LocalEmbeddingProvider.PYTHON_RESTART_COOLDOWN_MS) {
1123
+ const waitMs = LocalEmbeddingProvider.PYTHON_RESTART_COOLDOWN_MS - (now - this.lastPythonStartAttempt);
1124
+ await new Promise(r => setTimeout(r, waitMs));
1125
+ }
1126
+ this.lastPythonStartAttempt = Date.now();
1127
+ this.pythonRestartCount++;
1128
+ // Get project path
1129
+ const projectPath = getProjectPath();
1130
+ const socketDir = join(projectPath, 'specmem', 'sockets');
1131
+ const socketPath = join(socketDir, 'embeddings.sock');
1132
+ const lockPath = join(socketDir, 'embedding.lock');
1133
+ // Ensure socket directory exists
1134
+ // Task #17 FIX: Use atomic mkdir to prevent race condition when multiple
1135
+ // MCP servers try to create the socket directory simultaneously
1136
+ try {
1137
+ ensureSocketDirAtomicSync(socketDir);
1138
+ }
1139
+ catch (err) {
1140
+ logger.warn({ error: err, socketDir }, 'Failed to create socket directory for Python embedding');
1141
+ }
1142
+ // Check if socket exists and is responsive
1143
+ if (existsSync(socketPath)) {
1144
+ try {
1145
+ const isAlive = await this.testSocketConnection(socketPath);
1146
+ if (isAlive) {
1147
+ logger.debug('Python embedding server already running');
1148
+ this.sandboxAvailable = true;
1149
+ this.sandboxSocketPath = socketPath;
1150
+ return true;
1151
+ }
1152
+ // Socket exists but not responsive - clean it up
1153
+ await this.cleanupStaleSocket(socketPath);
1154
+ }
1155
+ catch (e) {
1156
+ logger.debug({ error: e }, 'Socket test failed, will restart');
1157
+ }
1158
+ }
1159
+ // ═══════════════════════════════════════════════════════════════════════════
1160
+ // LOCK FILE: Prevent race condition when multiple MCP instances start
1161
+ // ═══════════════════════════════════════════════════════════════════════════
1162
+ const acquireLock = () => {
1163
+ try {
1164
+ // Check if lock exists
1165
+ if (existsSync(lockPath)) {
1166
+ const lockContent = readFileSync(lockPath, 'utf8').trim();
1167
+ const [lockPid, lockTime] = lockContent.split(':').map(Number);
1168
+ // Check if lock is stale (PID not running or lock too old - 5 min max)
1169
+ const lockAge = Date.now() - lockTime;
1170
+ const isStale = lockAge > 300000; // 5 minutes
1171
+ let pidRunning = false;
1172
+ try {
1173
+ process.kill(lockPid, 0); // Signal 0 = check if process exists
1174
+ pidRunning = true;
1175
+ }
1176
+ catch {
1177
+ pidRunning = false;
1178
+ }
1179
+ if (!pidRunning || isStale) {
1180
+ // Lock is stale - remove it
1181
+ logger.info({ lockPid, lockAge, pidRunning, isStale }, 'Removing stale embedding lock');
1182
+ unlinkSync(lockPath);
1183
+ }
1184
+ else {
1185
+ // Lock is valid - another process is spawning
1186
+ logger.debug({ lockPid, lockAge }, 'Embedding lock held by another process');
1187
+ return { acquired: false, existingPid: lockPid };
1188
+ }
1189
+ }
1190
+ // Try to acquire lock atomically
1191
+ writeFileSync(lockPath, `${process.pid}:${Date.now()}`, { flag: 'wx' });
1192
+ logger.debug({ pid: process.pid }, 'Acquired embedding lock');
1193
+ return { acquired: true };
1194
+ }
1195
+ catch (e) {
1196
+ // Lock file creation failed (likely another process beat us)
1197
+ if (e && typeof e === 'object' && 'code' in e && e.code === 'EEXIST') {
1198
+ logger.debug('Embedding lock already exists (race)');
1199
+ return { acquired: false };
1200
+ }
1201
+ logger.warn({ error: e }, 'Failed to acquire embedding lock');
1202
+ return { acquired: false };
1203
+ }
1204
+ };
1205
+ const releaseLock = () => {
1206
+ try {
1207
+ if (existsSync(lockPath)) {
1208
+ const lockContent = readFileSync(lockPath, 'utf8').trim();
1209
+ const [lockPid] = lockContent.split(':').map(Number);
1210
+ // Only release if we own it
1211
+ if (lockPid === process.pid) {
1212
+ unlinkSync(lockPath);
1213
+ logger.debug({ pid: process.pid }, 'Released embedding lock');
1214
+ }
1215
+ }
1216
+ }
1217
+ catch { /* ignore release errors */ }
1218
+ };
1219
+ // Try to acquire lock
1220
+ const lockResult = acquireLock();
1221
+ if (!lockResult.acquired) {
1222
+ // Another process is spawning - wait for socket to appear
1223
+ logger.info({ existingPid: lockResult.existingPid }, 'Another process spawning embedding, waiting...');
1224
+ this.sandboxSocketPath = socketPath;
1225
+ await this.waitForSocket(20000); // Wait up to 20s for other process
1226
+ if (this.sandboxAvailable) {
1227
+ return true;
1228
+ }
1229
+ // Still not available - try again next time
1230
+ return false;
1231
+ }
1232
+ // We have the lock - verify socket one more time (other process may have finished)
1233
+ if (existsSync(socketPath)) {
1234
+ try {
1235
+ const isAlive = await this.testSocketConnection(socketPath);
1236
+ if (isAlive) {
1237
+ logger.debug('Socket became available while acquiring lock');
1238
+ this.sandboxAvailable = true;
1239
+ this.sandboxSocketPath = socketPath;
1240
+ releaseLock();
1241
+ return true;
1242
+ }
1243
+ }
1244
+ catch { /* proceed to spawn */ }
1245
+ }
1246
+ // Find the embedding server script
1247
+ // Try multiple locations: SPECMEM_PKG env, relative to this file, project specmem dir
1248
+ // Docker location via env var (defaults to /opt/specmem if SPECMEM_DOCKER_PATH set)
1249
+ const dockerPath = process.env.SPECMEM_DOCKER_PATH || (process.env.SPECMEM_IN_DOCKER ? '/opt/specmem' : null);
1250
+ const candidatePaths = [
1251
+ process.env.SPECMEM_PKG ? join(process.env.SPECMEM_PKG, 'embedding-sandbox', 'frankenstein-embeddings.py') : null,
1252
+ join(__dirname, '..', 'embedding-sandbox', 'frankenstein-embeddings.py'),
1253
+ join(projectPath, 'specmem', 'embedding-sandbox', 'frankenstein-embeddings.py'),
1254
+ dockerPath ? join(dockerPath, 'embedding-sandbox', 'frankenstein-embeddings.py') : null,
1255
+ // Global npm install fallback (platform-agnostic)
1256
+ join(path.dirname(path.dirname(process.execPath)), 'lib', 'node_modules', 'specmem-hardwicksoftware', 'embedding-sandbox', 'frankenstein-embeddings.py'),
1257
+ ].filter(Boolean);
1258
+ let embeddingScript = null;
1259
+ for (const candidate of candidatePaths) {
1260
+ if (existsSync(candidate)) {
1261
+ embeddingScript = candidate;
1262
+ break;
1263
+ }
1264
+ }
1265
+ if (!embeddingScript) {
1266
+ logger.warn({ candidatePaths }, 'Embedding server script not found - cannot auto-start');
1267
+ return false;
1268
+ }
1269
+ // Start the embedding server in background
1270
+ try {
1271
+ const logFile = join(socketDir, 'embedding-autostart.log');
1272
+ logger.info({
1273
+ script: embeddingScript,
1274
+ projectPath,
1275
+ socketPath,
1276
+ logFile
1277
+ }, 'Auto-starting Python embedding server...');
1278
+ // bruh ALWAYS use getSpawnEnv for project isolation
1279
+ // Task #22 fix: Use getPythonPath() instead of hardcoded 'python3'
1280
+ const pythonPath = getPythonPath();
1281
+ logger.debug({ pythonPath }, 'Using Python executable for embedding server');
1282
+ const child = spawn(pythonPath, [embeddingScript], {
1283
+ cwd: path.dirname(embeddingScript),
1284
+ env: getSpawnEnv(), // includes SPECMEM_PROJECT_PATH and all other project env vars
1285
+ detached: true,
1286
+ stdio: ['ignore', openSync(logFile, 'a'), openSync(logFile, 'a')]
1287
+ });
1288
+ child.unref();
1289
+ this.pythonEmbeddingPid = child.pid || null;
1290
+ // CRITICAL: Persist PID to file for orphan cleanup on next startup
1291
+ // Without this, orphaned embedding processes accumulate forever!
1292
+ if (this.pythonEmbeddingPid) {
1293
+ const pidFile = join(socketDir, 'embedding.pid');
1294
+ try {
1295
+ writeFileSync(pidFile, `${this.pythonEmbeddingPid}:${Date.now()}`);
1296
+ logger.debug({ pidFile, pid: this.pythonEmbeddingPid }, 'Embedding PID file written');
1297
+ }
1298
+ catch (pidErr) {
1299
+ logger.warn({ pidErr, pidFile }, 'Failed to write embedding PID file (orphan cleanup may fail)');
1300
+ }
1301
+ }
1302
+ logger.info({
1303
+ pid: this.pythonEmbeddingPid,
1304
+ socketPath
1305
+ }, 'Python embedding server spawned');
1306
+ // Wait for socket to appear
1307
+ this.sandboxSocketPath = socketPath;
1308
+ await this.waitForSocket(15000); // 15 second timeout for Python startup
1309
+ if (this.sandboxAvailable) {
1310
+ logger.info({ socketPath, restartCount: this.pythonRestartCount }, 'Python embedding server ready');
1311
+ this.pythonConsecutiveFailures = 0; // SUCCESS: reset failure counter
1312
+ releaseLock(); // Release lock on success
1313
+ return true;
1314
+ }
1315
+ else {
1316
+ logger.warn({ socketPath }, 'Python embedding server started but socket not ready');
1317
+ this.pythonConsecutiveFailures++; // FAILURE: increment counter
1318
+ releaseLock(); // Release lock on failure
1319
+ return false;
1320
+ }
1321
+ }
1322
+ catch (err) {
1323
+ logger.error({ error: err, script: embeddingScript }, 'Failed to spawn Python embedding server');
1324
+ this.pythonConsecutiveFailures++; // FAILURE: increment counter
1325
+ releaseLock(); // Release lock on error
1326
+ return false;
1327
+ }
1328
+ }
1329
+ /**
1330
+ * Test if a socket is responsive
1331
+ * @returns true if socket responds to a test request
1332
+ */
1333
+ testSocketConnection(socketPath) {
1334
+ return new Promise((resolve) => {
1335
+ const testSocket = createConnection(socketPath);
1336
+ const timeout = setTimeout(() => {
1337
+ testSocket.destroy();
1338
+ resolve(false);
1339
+ }, 2000);
1340
+ testSocket.on('connect', () => {
1341
+ clearTimeout(timeout);
1342
+ // Try sending a minimal request
1343
+ testSocket.write('{"text":"test"}\n');
1344
+ });
1345
+ testSocket.on('data', (data) => {
1346
+ clearTimeout(timeout);
1347
+ testSocket.destroy();
1348
+ // Check if we got a valid embedding response
1349
+ const response = data.toString();
1350
+ resolve(response.includes('embedding') || response.includes('['));
1351
+ });
1352
+ testSocket.on('error', () => {
1353
+ clearTimeout(timeout);
1354
+ testSocket.destroy();
1355
+ resolve(false);
1356
+ });
1357
+ testSocket.on('timeout', () => {
1358
+ clearTimeout(timeout);
1359
+ testSocket.destroy();
1360
+ resolve(false);
1361
+ });
1362
+ });
1363
+ }
1364
+ /**
1365
+ * Wait for the socket to become available
1366
+ */
1367
+ async waitForSocket(timeoutMs) {
1368
+ const startTime = Date.now();
1369
+ const checkInterval = 500;
1370
+ while (Date.now() - startTime < timeoutMs) {
1371
+ if (existsSync(this.sandboxSocketPath)) {
1372
+ this.sandboxAvailable = true;
1373
+ this.lastSandboxCheck = Date.now();
1374
+ logger.info({ socketPath: this.sandboxSocketPath }, 'embedding socket now available');
1375
+ return;
1376
+ }
1377
+ await new Promise(resolve => setTimeout(resolve, checkInterval));
1378
+ }
1379
+ logger.warn({ socketPath: this.sandboxSocketPath, timeoutMs }, 'timeout waiting for embedding socket');
1380
+ }
1381
+ // Track restart attempts to avoid infinite restart loops
1382
+ restartAttempts = 0;
1383
+ lastRestartAttempt = 0;
1384
+ static MAX_RESTART_ATTEMPTS = 3;
1385
+ static RESTART_COOLDOWN_MS = 60000; // 1 minute between restart attempts
1386
+ /**
1387
+ * Try to restart the embedding container when it becomes unhealthy
1388
+ * Runs in background to not block embedding requests
1389
+ */
1390
+ tryRestartContainer() {
1391
+ const now = Date.now();
1392
+ // Check cooldown
1393
+ if (now - this.lastRestartAttempt < LocalEmbeddingProvider.RESTART_COOLDOWN_MS) {
1394
+ logger.debug('restart cooldown active, skipping');
1395
+ return;
1396
+ }
1397
+ // Check max attempts
1398
+ if (this.restartAttempts >= LocalEmbeddingProvider.MAX_RESTART_ATTEMPTS) {
1399
+ logger.warn({ attempts: this.restartAttempts }, 'max restart attempts reached - giving up');
1400
+ return;
1401
+ }
1402
+ this.lastRestartAttempt = now;
1403
+ this.restartAttempts++;
1404
+ // Run restart in background
1405
+ this.restartContainer().catch(err => {
1406
+ logger.error({ error: err }, 'failed to restart embedding container');
1407
+ });
1408
+ }
1409
+ /**
1410
+ * Restart the embedding Docker container
1411
+ * Task #16 FIX: Added proper error handling with detailed diagnostics and Python fallback
1412
+ */
1413
+ async restartContainer() {
1414
+ logger.info({ attempt: this.restartAttempts }, 'attempting to restart embedding container');
1415
+ // Check if Docker is available - fall back to Python if not
1416
+ if (!this.isDockerAvailable()) {
1417
+ logger.warn('Docker not available - falling back to Python embedding server');
1418
+ try {
1419
+ await this.autoStartPythonEmbedding();
1420
+ }
1421
+ catch (fallbackErr) {
1422
+ logger.error({
1423
+ error: fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)
1424
+ }, 'Python embedding fallback failed after Docker unavailable');
1425
+ }
1426
+ return;
1427
+ }
1428
+ try {
1429
+ // First try to just restart the existing container
1430
+ // Docker command timeout - configurable via SPECMEM_DOCKER_EXEC_TIMEOUT_MS
1431
+ const dockerTimeout = parseInt(process.env['SPECMEM_DOCKER_EXEC_TIMEOUT_MS'] || '30000', 10);
1432
+ if (this.isContainerRunning()) {
1433
+ logger.info('restarting running container');
1434
+ execSync(`docker restart ${LocalEmbeddingProvider.CONTAINER_NAME}`, {
1435
+ stdio: 'pipe',
1436
+ timeout: dockerTimeout
1437
+ });
1438
+ }
1439
+ else {
1440
+ // Container stopped - start it
1441
+ logger.info('starting stopped container');
1442
+ execSync(`docker start ${LocalEmbeddingProvider.CONTAINER_NAME}`, {
1443
+ stdio: 'pipe',
1444
+ timeout: dockerTimeout
1445
+ });
1446
+ }
1447
+ // Wait for socket to become available - configurable via SPECMEM_SOCKET_WAIT_TIMEOUT_MS
1448
+ const socketWaitTimeout = parseInt(process.env['SPECMEM_SOCKET_WAIT_TIMEOUT_MS'] || '15000', 10);
1449
+ await this.waitForSocket(socketWaitTimeout);
1450
+ if (this.sandboxAvailable) {
1451
+ logger.info('embedding container successfully restarted');
1452
+ // Reset restart attempts on success
1453
+ this.restartAttempts = 0;
1454
+ }
1455
+ }
1456
+ catch (err) {
1457
+ // Task #16 FIX: Extract detailed error info for debugging
1458
+ const errorMessage = err instanceof Error ? err.message : String(err);
1459
+ const errorCode = err?.code || 'UNKNOWN';
1460
+ const errorSignal = err?.signal;
1461
+ const errorStatus = err?.status;
1462
+ logger.error({
1463
+ error: errorMessage,
1464
+ code: errorCode,
1465
+ signal: errorSignal,
1466
+ status: errorStatus,
1467
+ container: LocalEmbeddingProvider.CONTAINER_NAME,
1468
+ attempt: this.restartAttempts
1469
+ }, 'Docker restart command failed');
1470
+ // If restart failed, try full recreation
1471
+ try {
1472
+ logger.info('attempting full container recreation');
1473
+ this.removeStoppedContainer();
1474
+ await this.autoStartContainer();
1475
+ }
1476
+ catch (recreateErr) {
1477
+ const recreateMsg = recreateErr instanceof Error ? recreateErr.message : String(recreateErr);
1478
+ logger.error({ error: recreateMsg }, 'full container recreation also failed - trying Python fallback');
1479
+ // CRITICAL: Fall back to Python as last resort
1480
+ try {
1481
+ await this.autoStartPythonEmbedding();
1482
+ }
1483
+ catch (pythonErr) {
1484
+ logger.error({
1485
+ error: pythonErr instanceof Error ? pythonErr.message : String(pythonErr)
1486
+ }, 'All embedding service options exhausted - service unavailable');
1487
+ }
1488
+ }
1489
+ }
1490
+ }
1491
+ // Sync version - ONLY for constructor initial check (one-time)
1492
+ checkSandboxAvailabilitySync() {
1493
+ this.sandboxAvailable = existsSync(this.sandboxSocketPath);
1494
+ if (this.sandboxAvailable) {
1495
+ logger.info({ socketPath: this.sandboxSocketPath }, 'sandboxed embedding service available - using real ML embeddings');
1496
+ }
1497
+ else {
1498
+ logger.debug({ socketPath: this.sandboxSocketPath }, 'sandbox not available - using hash fallback');
1499
+ }
1500
+ this.lastSandboxCheck = Date.now();
1501
+ }
1502
+ // Async version - use this for all runtime checks (non-blocking)
1503
+ // SELF-HEALING: If socket missing, attempt warm start immediately
1504
+ async checkSandboxAvailabilityAsync() {
1505
+ try {
1506
+ await access(this.sandboxSocketPath, constants.F_OK);
1507
+ // Socket file exists - assume it's available (model may be lazy-loading)
1508
+ this.sandboxAvailable = true;
1509
+ }
1510
+ catch {
1511
+ // Socket missing - MAKE IT!
1512
+ logger.info({ socketPath: this.sandboxSocketPath }, '[SELF-HEAL] Socket missing - starting embedding server');
1513
+ this.sandboxAvailable = false;
1514
+ // Try to start embedding server
1515
+ const started = await this.autoStartPythonEmbedding();
1516
+ if (started) {
1517
+ this.sandboxAvailable = true;
1518
+ logger.info({ socketPath: this.sandboxSocketPath }, '[SELF-HEAL] Embedding server started successfully');
1519
+ }
1520
+ }
1521
+ if (this.sandboxAvailable) {
1522
+ logger.debug({ socketPath: this.sandboxSocketPath }, 'sandboxed embedding service available - using real ML embeddings');
1523
+ }
1524
+ else {
1525
+ logger.warn({ socketPath: this.sandboxSocketPath }, 'sandbox not available after self-heal attempt');
1526
+ }
1527
+ this.lastSandboxCheck = Date.now();
1528
+ }
1529
+ // Async availability check - use this at runtime (non-blocking)
1530
+ async isSandboxAvailableAsync() {
1531
+ if (Date.now() - this.lastSandboxCheck > this.sandboxCheckInterval) {
1532
+ await this.checkSandboxAvailabilityAsync();
1533
+ }
1534
+ return this.sandboxAvailable;
1535
+ }
1536
+ async generateEmbedding(text) {
1537
+ const methodStart = Date.now();
1538
+ const textPreview = text.length > 50 ? text.substring(0, 50) + '...' : text;
1539
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_START', {
1540
+ textLength: text.length,
1541
+ textPreview,
1542
+ sandboxAvailable: this.sandboxAvailable,
1543
+ socketConnected: this.socketConnected
1544
+ });
1545
+ // Use QOMS to ensure we never exceed 20% CPU/RAM
1546
+ return qoms.medium(async () => {
1547
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_QOMS_ACQUIRED', {
1548
+ elapsedMs: Date.now() - methodStart
1549
+ });
1550
+ // Ensure we have a target dimension from database
1551
+ const dimStart = Date.now();
1552
+ const targetDim = await this.ensureTargetDimension();
1553
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_TARGET_DIM_FETCHED', {
1554
+ targetDim,
1555
+ dimFetchMs: Date.now() - dimStart,
1556
+ totalElapsedMs: Date.now() - methodStart
1557
+ });
1558
+ // CRITICAL: ML model MUST be available - no fallback to hash embeddings!
1559
+ // Hash embeddings are in a completely different vector space and break semantic search.
1560
+ // SELF-HEALING: checkSandboxAvailabilityAsync now auto-starts server if socket missing/dead
1561
+ const MAX_RETRIES = 5;
1562
+ const RETRY_DELAY_MS = 1000; // Faster retries since self-healing is aggressive
1563
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
1564
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_ATTEMPT_START', {
1565
+ attempt,
1566
+ maxRetries: MAX_RETRIES,
1567
+ totalElapsedMs: Date.now() - methodStart
1568
+ });
1569
+ // SELF-HEAL: This now auto-starts server if socket missing or dead!
1570
+ if (!(await this.isSandboxAvailableAsync())) {
1571
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_SANDBOX_UNAVAILABLE', {
1572
+ attempt,
1573
+ socketPath: this.sandboxSocketPath,
1574
+ totalElapsedMs: Date.now() - methodStart
1575
+ });
1576
+ logger.warn({ attempt, maxRetries: MAX_RETRIES, socketPath: this.sandboxSocketPath }, '[SELF-HEAL] Embedding service unavailable - auto-healing in progress');
1577
+ // Report retry progress
1578
+ reportRetry('embedding', attempt, MAX_RETRIES);
1579
+ // SELF-HEAL: Try multiple restart strategies
1580
+ if (attempt === 1) {
1581
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_RESTART_CONTAINER', {
1582
+ attempt
1583
+ });
1584
+ this.tryRestartContainer();
1585
+ } else if (attempt === 2) {
1586
+ // Second attempt: try Python directly (in case Docker is the problem)
1587
+ logger.info('[SELF-HEAL] Attempt 2: Trying direct Python startup');
1588
+ await this.autoStartPythonEmbedding();
1589
+ }
1590
+ // Wait before retry - faster since self-healing is aggressive
1591
+ const waitMs = RETRY_DELAY_MS * attempt;
1592
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_WAITING_FOR_SANDBOX', {
1593
+ waitMs,
1594
+ attempt
1595
+ });
1596
+ await new Promise(resolve => setTimeout(resolve, waitMs));
1597
+ await this.checkSandboxAvailabilityAsync();
1598
+ continue;
1599
+ }
1600
+ try {
1601
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_CALLING_SANDBOXED', {
1602
+ attempt,
1603
+ totalElapsedMs: Date.now() - methodStart
1604
+ });
1605
+ const sandboxStart = Date.now();
1606
+ const embedding = await this.generateSandboxedEmbedding(text);
1607
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_SANDBOXED_COMPLETE', {
1608
+ attempt,
1609
+ embeddingDim: embedding.length,
1610
+ sandboxedMs: Date.now() - sandboxStart,
1611
+ totalElapsedMs: Date.now() - methodStart
1612
+ });
1613
+ // Auto-detect model dimension on first call
1614
+ if (!this.detectedDimension) {
1615
+ this.detectedDimension = embedding.length;
1616
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_DIMENSION_DETECTED', {
1617
+ detectedDimension: this.detectedDimension,
1618
+ targetDim
1619
+ });
1620
+ logger.info({ modelDim: this.detectedDimension, dbDim: targetDim }, 'Auto-detected embedding dimension from AI model');
1621
+ }
1622
+ // DYNAMIC SCALING - match DB dimension, whatever it is!
1623
+ // If targetDim is 0 (DB not ready), return embedding as-is
1624
+ if (targetDim > 0 && embedding.length !== targetDim) {
1625
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_SCALING', {
1626
+ from: embedding.length,
1627
+ to: targetDim
1628
+ });
1629
+ const scaled = this.scaleEmbedding(embedding, targetDim);
1630
+ logger.debug({ from: embedding.length, to: targetDim }, 'Scaled embedding to match DB');
1631
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_SUCCESS', {
1632
+ finalDim: scaled.length,
1633
+ scaled: true,
1634
+ totalElapsedMs: Date.now() - methodStart
1635
+ });
1636
+ return scaled;
1637
+ }
1638
+ // Return as-is if dimensions already match or DB not ready
1639
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_SUCCESS', {
1640
+ finalDim: embedding.length,
1641
+ scaled: false,
1642
+ totalElapsedMs: Date.now() - methodStart
1643
+ });
1644
+ return embedding;
1645
+ }
1646
+ catch (error) {
1647
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_SANDBOXED_ERROR', {
1648
+ attempt,
1649
+ error: error instanceof Error ? error.message : String(error),
1650
+ totalElapsedMs: Date.now() - methodStart
1651
+ });
1652
+ logger.warn({ error, attempt }, 'sandbox embedding failed - will retry');
1653
+ // FIX: Use version tracking to prevent thundering herd restarts
1654
+ // Only first request to detect failure triggers restart
1655
+ const failureVersion = ++this.sandboxFailureVersion;
1656
+ this.sandboxAvailable = false;
1657
+ // Report retry progress
1658
+ reportRetry('embedding', attempt, MAX_RETRIES);
1659
+ if (attempt < MAX_RETRIES) {
1660
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_RETRY_WAIT', {
1661
+ attempt,
1662
+ nextAttempt: attempt + 1,
1663
+ waitMs: RETRY_DELAY_MS * attempt,
1664
+ failureVersion
1665
+ });
1666
+ // Only restart if we're the first to detect failure (no other restart in progress)
1667
+ if (!this.restartInProgress && failureVersion === this.sandboxFailureVersion) {
1668
+ this.restartInProgress = true;
1669
+ this.tryRestartContainer();
1670
+ this.restartInProgress = false;
1671
+ }
1672
+ await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS * attempt));
1673
+ }
1674
+ }
1675
+ }
1676
+ // All retries exhausted - FAIL LOUDLY instead of silent hash fallback
1677
+ // Hash embeddings are in different vector space and poison the search index!
1678
+ const errorMsg = `ML embedding service unavailable after ${MAX_RETRIES} attempts. ` +
1679
+ `Socket: ${this.sandboxSocketPath}. ` +
1680
+ `REFUSING to use hash fallback - would corrupt semantic search. ` +
1681
+ `Start the Frankenstein embedding service!`;
1682
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDING_ALL_RETRIES_EXHAUSTED', {
1683
+ maxRetries: MAX_RETRIES,
1684
+ socketPath: this.sandboxSocketPath,
1685
+ totalElapsedMs: Date.now() - methodStart
1686
+ });
1687
+ logger.error({ socketPath: this.sandboxSocketPath }, errorMsg);
1688
+ reportError('embedding', 'ML embedding service unavailable');
1689
+ throw new Error(errorMsg);
1690
+ });
1691
+ }
1692
+ /**
1693
+ * BATCH EMBEDDING - generates multiple embeddings in a single socket request!
1694
+ * Uses the Python server's batch protocol: {"texts": [...]} -> {"embeddings": [[...], ...]}
1695
+ * This is 5-10x faster than individual calls for large batches.
1696
+ */
1697
+ async generateEmbeddingsBatch(texts) {
1698
+ if (texts.length === 0)
1699
+ return [];
1700
+ if (texts.length === 1) {
1701
+ // Single text - use regular method
1702
+ const embedding = await this.generateEmbedding(texts[0]);
1703
+ return [embedding];
1704
+ }
1705
+ const methodStart = Date.now();
1706
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDINGS_BATCH_START', {
1707
+ batchSize: texts.length,
1708
+ totalChars: texts.reduce((sum, t) => sum + t.length, 0)
1709
+ });
1710
+ // Use QOMS to ensure we never exceed resource limits
1711
+ return qoms.medium(async () => {
1712
+ // Ensure we have target dimension from database
1713
+ const targetDim = await this.ensureTargetDimension();
1714
+ // Wait for sandbox to be available
1715
+ const MAX_RETRIES = 5;
1716
+ const RETRY_DELAY_MS = 2000;
1717
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
1718
+ if (!(await this.isSandboxAvailableAsync())) {
1719
+ logger.warn({ attempt, maxRetries: MAX_RETRIES, socketPath: this.sandboxSocketPath }, 'ML embedding service unavailable for batch - waiting');
1720
+ if (attempt === 1)
1721
+ this.tryRestartContainer();
1722
+ await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS * attempt));
1723
+ await this.checkSandboxAvailabilityAsync();
1724
+ continue;
1725
+ }
1726
+ try {
1727
+ const embeddings = await this.generateBatchWithDirectSocket(texts);
1728
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'GENERATE_EMBEDDINGS_BATCH_SUCCESS', {
1729
+ batchSize: texts.length,
1730
+ embeddingsDim: embeddings[0]?.length,
1731
+ totalMs: Date.now() - methodStart
1732
+ });
1733
+ // Scale all embeddings to target dimension if needed
1734
+ if (targetDim > 0 && embeddings.length > 0 && embeddings[0].length !== targetDim) {
1735
+ return embeddings.map(emb => this.scaleEmbedding(emb, targetDim));
1736
+ }
1737
+ return embeddings;
1738
+ }
1739
+ catch (error) {
1740
+ logger.warn({ error, attempt }, 'Batch embedding failed - will retry');
1741
+ // FIX: Use version tracking to prevent thundering herd restarts
1742
+ const failureVersion = ++this.sandboxFailureVersion;
1743
+ this.sandboxAvailable = false;
1744
+ if (attempt < MAX_RETRIES) {
1745
+ // Only restart if we're first to detect failure
1746
+ if (!this.restartInProgress && failureVersion === this.sandboxFailureVersion) {
1747
+ this.restartInProgress = true;
1748
+ this.tryRestartContainer();
1749
+ this.restartInProgress = false;
1750
+ }
1751
+ await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS * attempt));
1752
+ }
1753
+ }
1754
+ }
1755
+ // Fallback: sequential calls if batch fails
1756
+ logger.warn({ batchSize: texts.length }, 'Batch embedding failed, falling back to sequential');
1757
+ const results = [];
1758
+ for (const text of texts) {
1759
+ results.push(await this.generateEmbedding(text));
1760
+ }
1761
+ return results;
1762
+ });
1763
+ }
1764
+ /**
1765
+ * Generate batch embeddings using direct socket connection
1766
+ * Uses {"texts": [...]} protocol for single round-trip
1767
+ */
1768
+ generateBatchWithDirectSocket(texts) {
1769
+ return new Promise((resolve, reject) => {
1770
+ const socketPath = getEmbeddingSocketPath();
1771
+ const socket = createConnection(socketPath);
1772
+ let buffer = '';
1773
+ let resolved = false;
1774
+ const startTime = Date.now();
1775
+ // Longer timeout for batches - scale with batch size
1776
+ const baseTimeout = this.getAdaptiveTimeout();
1777
+ const timeoutMs = Math.min(baseTimeout * Math.ceil(texts.length / 10), 300000); // Max 5 min
1778
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'BATCH_SOCKET_CONNECTING', {
1779
+ socketPath,
1780
+ batchSize: texts.length,
1781
+ timeoutMs
1782
+ });
1783
+ let timeout = setTimeout(() => {
1784
+ if (!resolved) {
1785
+ resolved = true;
1786
+ socket.destroy();
1787
+ reject(new Error(`Batch embedding timeout after ${Math.round(timeoutMs / 1000)}s for ${texts.length} texts`));
1788
+ }
1789
+ }, timeoutMs);
1790
+ socket.on('connect', () => {
1791
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'BATCH_SOCKET_CONNECTED', {
1792
+ batchSize: texts.length,
1793
+ connectTimeMs: Date.now() - startTime
1794
+ });
1795
+ // Use batch protocol: {"texts": [...]}
1796
+ const request = JSON.stringify({ texts }) + '\n';
1797
+ socket.write(request);
1798
+ });
1799
+ socket.on('data', (data) => {
1800
+ buffer += data.toString();
1801
+ // Reset timeout on data received
1802
+ clearTimeout(timeout);
1803
+ timeout = setTimeout(() => {
1804
+ if (!resolved) {
1805
+ resolved = true;
1806
+ socket.destroy();
1807
+ reject(new Error(`Batch embedding idle timeout for ${texts.length} texts`));
1808
+ }
1809
+ }, timeoutMs);
1810
+ // Process all complete JSON messages (newline-delimited)
1811
+ // Server sends heartbeat first: {"status":"processing",...}
1812
+ // Then actual response: {"embeddings":[...],...}
1813
+ let newlineIndex;
1814
+ while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
1815
+ if (resolved)
1816
+ return;
1817
+ const responseJson = buffer.slice(0, newlineIndex);
1818
+ buffer = buffer.slice(newlineIndex + 1);
1819
+ try {
1820
+ const response = JSON.parse(responseJson);
1821
+ // Skip heartbeat/processing status - keep waiting
1822
+ if (response.status === 'processing') {
1823
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'BATCH_SOCKET_HEARTBEAT', {
1824
+ batchSize: texts.length,
1825
+ count: response.count,
1826
+ elapsedMs: Date.now() - startTime
1827
+ });
1828
+ continue; // Keep waiting for actual embeddings
1829
+ }
1830
+ // Got actual response - resolve or reject
1831
+ clearTimeout(timeout);
1832
+ resolved = true;
1833
+ socket.destroy();
1834
+ const responseTime = Date.now() - startTime;
1835
+ this.recordResponseTime(responseTime / texts.length); // Per-text average
1836
+ if (response.error) {
1837
+ reject(new Error(`Batch embedding error: ${response.error}`));
1838
+ }
1839
+ else if (response.embeddings && Array.isArray(response.embeddings)) {
1840
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'BATCH_SOCKET_SUCCESS', {
1841
+ batchSize: texts.length,
1842
+ embeddingsCount: response.embeddings.length,
1843
+ totalMs: responseTime,
1844
+ msPerText: Math.round(responseTime / texts.length)
1845
+ });
1846
+ resolve(response.embeddings);
1847
+ }
1848
+ else {
1849
+ reject(new Error('Invalid batch response from embedding service'));
1850
+ }
1851
+ }
1852
+ catch (err) {
1853
+ clearTimeout(timeout);
1854
+ resolved = true;
1855
+ socket.destroy();
1856
+ reject(new Error(`Failed to parse batch embedding response: ${err}`));
1857
+ }
1858
+ }
1859
+ });
1860
+ socket.on('error', (err) => {
1861
+ if (!resolved) {
1862
+ resolved = true;
1863
+ clearTimeout(timeout);
1864
+ reject(err);
1865
+ }
1866
+ });
1867
+ socket.on('close', () => {
1868
+ if (!resolved) {
1869
+ resolved = true;
1870
+ clearTimeout(timeout);
1871
+ reject(new Error('Batch socket closed unexpectedly'));
1872
+ }
1873
+ });
1874
+ });
1875
+ }
1876
+ /**
1877
+ * Scale embedding to target dimension (up or down)
1878
+ * Uses interpolation for downscaling, pattern repetition for upscaling
1879
+ */
1880
+ scaleEmbedding(embedding, targetDim) {
1881
+ const srcDim = embedding.length;
1882
+ if (srcDim === targetDim)
1883
+ return embedding;
1884
+ const result = new Array(targetDim);
1885
+ if (targetDim < srcDim) {
1886
+ // DOWNSCALE: Average neighboring values
1887
+ const ratio = srcDim / targetDim;
1888
+ for (let i = 0; i < targetDim; i++) {
1889
+ const start = Math.floor(i * ratio);
1890
+ const end = Math.floor((i + 1) * ratio);
1891
+ let sum = 0;
1892
+ for (let j = start; j < end; j++) {
1893
+ sum += embedding[j];
1894
+ }
1895
+ result[i] = sum / (end - start);
1896
+ }
1897
+ }
1898
+ else {
1899
+ // UPSCALE: Linear interpolation
1900
+ const ratio = (srcDim - 1) / (targetDim - 1);
1901
+ for (let i = 0; i < targetDim; i++) {
1902
+ const srcIdx = i * ratio;
1903
+ const low = Math.floor(srcIdx);
1904
+ const high = Math.min(low + 1, srcDim - 1);
1905
+ const frac = srcIdx - low;
1906
+ result[i] = embedding[low] * (1 - frac) + embedding[high] * frac;
1907
+ }
1908
+ }
1909
+ // Normalize to unit length (important for cosine similarity!)
1910
+ const norm = Math.sqrt(result.reduce((sum, v) => sum + v * v, 0));
1911
+ if (norm > 0) {
1912
+ for (let i = 0; i < targetDim; i++) {
1913
+ result[i] /= norm;
1914
+ }
1915
+ }
1916
+ return result;
1917
+ }
1918
+ async generateSandboxedEmbedding(text) {
1919
+ const methodStart = Date.now();
1920
+ // Get fresh socket path
1921
+ const freshSocketPath = getEmbeddingSocketPath();
1922
+ this.sandboxSocketPath = freshSocketPath;
1923
+ // WARM SOCKET APPROACH: Try warm socket first, fall back to direct if fails
1924
+ // This gives us fast starts (~50ms) when warm, with reliable fallback (~500ms) when cold
1925
+ // FIX: Use lock to prevent race condition where two requests use warm socket,
1926
+ // one fails and destroys it while the other is mid-request
1927
+ // Try warm socket if available and path matches
1928
+ if (this.warmSocketReady && this.warmSocket && this.warmSocketPath === freshSocketPath) {
1929
+ // Acquire lock - wait for previous warm socket operation to complete
1930
+ let releaseLock = () => { };
1931
+ const lockPromise = new Promise(resolve => { releaseLock = resolve; });
1932
+ const previousLock = this.warmSocketLock;
1933
+ this.warmSocketLock = lockPromise;
1934
+ try {
1935
+ await previousLock; // Wait for previous operation
1936
+ // Re-check conditions after acquiring lock (may have changed)
1937
+ if (!this.warmSocketReady || !this.warmSocket || this.warmSocketPath !== freshSocketPath) {
1938
+ releaseLock();
1939
+ // Fall through to direct socket
1940
+ }
1941
+ else {
1942
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'WARM_SOCKET_ATTEMPT', {
1943
+ textLength: text.length,
1944
+ socketPath: freshSocketPath,
1945
+ idleMs: Date.now() - this.warmSocketLastUsed
1946
+ });
1947
+ const result = await this.generateWithWarmSocket(text);
1948
+ this.warmSocketLastUsed = Date.now();
1949
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'WARM_SOCKET_SUCCESS', {
1950
+ totalMs: Date.now() - methodStart,
1951
+ resultDim: result.length
1952
+ });
1953
+ releaseLock();
1954
+ return result;
1955
+ }
1956
+ }
1957
+ catch (err) {
1958
+ // Warm socket failed - close it and fall back to direct
1959
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'WARM_SOCKET_FAILED', {
1960
+ error: err instanceof Error ? err.message : String(err),
1961
+ fallingBackToDirect: true
1962
+ });
1963
+ this.closeWarmSocket();
1964
+ releaseLock();
1965
+ }
1966
+ }
1967
+ // Direct connection (cold start or fallback)
1968
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DIRECT_SOCKET_START', {
1969
+ textLength: text.length,
1970
+ socketPath: freshSocketPath,
1971
+ reason: !this.warmSocketReady ? 'no warm socket' : 'path mismatch or failed'
1972
+ });
1973
+ try {
1974
+ const result = await this.generateWithDirectSocket(text, freshSocketPath);
1975
+ // Warm up socket in background for next call
1976
+ this.warmUpSocketInBackground(freshSocketPath);
1977
+ return result;
1978
+ }
1979
+ catch (directSocketError) {
1980
+ // Step 3: Direct socket failed - TRY ON-DEMAND DOCKER UNPAUSE
1981
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DIRECT_SOCKET_FAILED', {
1982
+ error: directSocketError instanceof Error ? directSocketError.message : String(directSocketError),
1983
+ textLength: text.length,
1984
+ tryingOnDemandUnpause: true
1985
+ });
1986
+ // ON-DEMAND UNPAUSE: Docker stays paused until we need it
1987
+ const wasUnpaused = await this.unpauseDockerIfNeeded();
1988
+ if (wasUnpaused) {
1989
+ // Docker was paused and is now unpaused - retry the direct socket
1990
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'RETRY_AFTER_UNPAUSE', {
1991
+ textLength: text.length
1992
+ });
1993
+ try {
1994
+ const result = await this.generateWithDirectSocket(text, freshSocketPath);
1995
+ // Warm up socket in background for next call
1996
+ this.warmUpSocketInBackground(freshSocketPath);
1997
+ return result;
1998
+ }
1999
+ catch (retryError) {
2000
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'RETRY_AFTER_UNPAUSE_FAILED', {
2001
+ error: retryError instanceof Error ? retryError.message : String(retryError)
2002
+ });
2003
+ // Fall through to Python restart
2004
+ }
2005
+ }
2006
+ // Step 4: Try Python auto-start (for non-Docker environments or dead sockets)
2007
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'TRYING_PYTHON_AUTOSTART', {
2008
+ textLength: text.length,
2009
+ wasUnpaused
2010
+ });
2011
+ const pythonStarted = await this.autoStartPythonEmbedding();
2012
+ if (pythonStarted) {
2013
+ // Python server started - retry with new socket
2014
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'RETRY_AFTER_PYTHON_START', {
2015
+ textLength: text.length,
2016
+ newSocketPath: this.sandboxSocketPath
2017
+ });
2018
+ try {
2019
+ const result = await this.generateWithDirectSocket(text, this.sandboxSocketPath);
2020
+ this.warmUpSocketInBackground(this.sandboxSocketPath);
2021
+ return result;
2022
+ }
2023
+ catch (retryError) {
2024
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'RETRY_AFTER_PYTHON_START_FAILED', {
2025
+ error: retryError instanceof Error ? retryError.message : String(retryError)
2026
+ });
2027
+ // Fall through to queue
2028
+ }
2029
+ }
2030
+ // Step 5: Still failed - queue the request for later processing
2031
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'QUEUEING_REQUEST', {
2032
+ textLength: text.length,
2033
+ wasUnpaused
2034
+ });
2035
+ // Queue the request and return the promise that will resolve when socket warms up
2036
+ if (this.embeddingQueue) {
2037
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'QUEUING_TO_POSTGRES', {
2038
+ textLength: text.length
2039
+ });
2040
+ // Also trigger warm-up in background (which will drain queue when ready)
2041
+ this.warmUpSocketInBackground(freshSocketPath);
2042
+ return this.embeddingQueue.queueForEmbedding(text);
2043
+ }
2044
+ else {
2045
+ // No queue available - re-throw the original error
2046
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'NO_QUEUE_AVAILABLE', {
2047
+ error: 'EmbeddingQueue not initialized, cannot queue request'
2048
+ });
2049
+ throw directSocketError;
2050
+ }
2051
+ }
2052
+ }
2053
+ /**
2054
+ * Generate embedding using the WARM socket (fast path ~50ms)
2055
+ * Simple timeout-based approach - no complex state machine
2056
+ */
2057
+ generateWithWarmSocket(text) {
2058
+ return new Promise((resolve, reject) => {
2059
+ if (!this.warmSocket || !this.warmSocketReady) {
2060
+ reject(new Error('Warm socket not ready'));
2061
+ return;
2062
+ }
2063
+ const startTime = Date.now();
2064
+ const timeoutMs = this.getAdaptiveTimeout();
2065
+ let buffer = '';
2066
+ let resolved = false;
2067
+ const timeout = setTimeout(() => {
2068
+ if (!resolved) {
2069
+ resolved = true;
2070
+ reject(new Error(`Warm socket timeout after ${timeoutMs}ms`));
2071
+ }
2072
+ }, timeoutMs);
2073
+ const dataHandler = (data) => {
2074
+ buffer += data.toString();
2075
+ // Process all complete JSON lines in buffer
2076
+ let newlineIndex;
2077
+ while ((newlineIndex = buffer.indexOf('\n')) !== -1 && !resolved) {
2078
+ const line = buffer.slice(0, newlineIndex);
2079
+ buffer = buffer.slice(newlineIndex + 1); // Keep remainder for next line
2080
+ try {
2081
+ const response = JSON.parse(line);
2082
+ // HEARTBEAT: "processing" status means server is working - reset timeout and keep waiting
2083
+ if (response.status === 'processing') {
2084
+ clearTimeout(timeout);
2085
+ timeout = setTimeout(() => {
2086
+ if (!resolved) {
2087
+ resolved = true;
2088
+ reject(new Error(`Warm socket timeout after ${timeoutMs}ms`));
2089
+ }
2090
+ }, timeoutMs);
2091
+ continue; // Keep reading for actual embedding
2092
+ }
2093
+ // Got actual response - resolve
2094
+ clearTimeout(timeout);
2095
+ resolved = true;
2096
+ this.warmSocket?.removeListener('data', dataHandler);
2097
+ const responseTime = Date.now() - startTime;
2098
+ this.recordResponseTime(responseTime);
2099
+ if (response.error) {
2100
+ reject(new Error(`Embedding service error: ${response.error}`));
2101
+ }
2102
+ else if (response.embedding) {
2103
+ resolve(response.embedding);
2104
+ }
2105
+ else {
2106
+ reject(new Error('Invalid response from embedding service'));
2107
+ }
2108
+ return;
2109
+ }
2110
+ catch (err) {
2111
+ clearTimeout(timeout);
2112
+ resolved = true;
2113
+ this.warmSocket?.removeListener('data', dataHandler);
2114
+ reject(new Error(`Failed to parse embedding response: ${err}`));
2115
+ return;
2116
+ }
2117
+ }
2118
+ };
2119
+ this.warmSocket.on('data', dataHandler);
2120
+ // Send request
2121
+ const request = JSON.stringify({ type: 'embed', text }) + '\n';
2122
+ this.warmSocket.write(request);
2123
+ });
2124
+ }
2125
+ /**
2126
+ * Close the warm socket cleanly
2127
+ */
2128
+ closeWarmSocket() {
2129
+ if (this.warmSocket) {
2130
+ try {
2131
+ this.warmSocket.removeAllListeners();
2132
+ this.warmSocket.destroy();
2133
+ }
2134
+ catch (e) {
2135
+ // Ignore errors during cleanup
2136
+ }
2137
+ this.warmSocket = null;
2138
+ }
2139
+ this.warmSocketReady = false;
2140
+ this.warmSocketPath = null;
2141
+ if (this.warmSocketHealthInterval) {
2142
+ clearInterval(this.warmSocketHealthInterval);
2143
+ this.warmSocketHealthInterval = null;
2144
+ }
2145
+ }
2146
+ /**
2147
+ * Warm up a socket in the background for faster subsequent calls
2148
+ * Non-blocking - doesn't affect current request
2149
+ */
2150
+ warmUpSocketInBackground(socketPath) {
2151
+ // Don't warm up if already warming or path matches
2152
+ if (this.warmSocketReady && this.warmSocketPath === socketPath) {
2153
+ return;
2154
+ }
2155
+ // Close existing warm socket if path changed
2156
+ if (this.warmSocketPath && this.warmSocketPath !== socketPath) {
2157
+ this.closeWarmSocket();
2158
+ }
2159
+ // Create new warm socket in background
2160
+ setImmediate(() => {
2161
+ try {
2162
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'WARMING_SOCKET', { socketPath });
2163
+ this.warmSocket = createConnection(socketPath);
2164
+ this.warmSocketPath = socketPath;
2165
+ this.warmSocket.on('connect', () => {
2166
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'WARM_SOCKET_CONNECTED', { socketPath });
2167
+ // QUEUE DRAIN: Process pending embeddings BEFORE marking socket ready
2168
+ // This ensures queued requests get resolved before new requests come in
2169
+ this.drainEmbeddingQueue(socketPath).then((drained) => {
2170
+ if (drained > 0) {
2171
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'QUEUE_DRAINED', {
2172
+ socketPath,
2173
+ itemsProcessed: drained
2174
+ });
2175
+ }
2176
+ // NOW mark socket ready for new requests
2177
+ this.warmSocketReady = true;
2178
+ this.warmSocketLastUsed = Date.now();
2179
+ // Start health check interval
2180
+ this.startWarmSocketHealthCheck();
2181
+ }).catch((err) => {
2182
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'QUEUE_DRAIN_ERROR', {
2183
+ socketPath,
2184
+ error: err instanceof Error ? err.message : String(err)
2185
+ });
2186
+ // Still mark socket ready even if drain fails - new requests should work
2187
+ this.warmSocketReady = true;
2188
+ this.warmSocketLastUsed = Date.now();
2189
+ this.startWarmSocketHealthCheck();
2190
+ });
2191
+ });
2192
+ this.warmSocket.on('error', (err) => {
2193
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'WARM_SOCKET_ERROR', {
2194
+ socketPath,
2195
+ error: err.message
2196
+ });
2197
+ this.closeWarmSocket();
2198
+ });
2199
+ this.warmSocket.on('close', () => {
2200
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'WARM_SOCKET_CLOSED', { socketPath });
2201
+ this.warmSocketReady = false;
2202
+ });
2203
+ }
2204
+ catch (err) {
2205
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'WARM_SOCKET_CREATE_ERROR', {
2206
+ socketPath,
2207
+ error: err instanceof Error ? err.message : String(err)
2208
+ });
2209
+ }
2210
+ });
2211
+ }
2212
+ // On-demand Docker unpause tracking
2213
+ lastDockerUnpauseAttempt = 0;
2214
+ static DOCKER_UNPAUSE_COOLDOWN_MS = 5000; // Don't spam unpause attempts
2215
+ /**
2216
+ * Check if the Docker container is paused
2217
+ * Uses docker inspect to check container state
2218
+ */
2219
+ async isDockerPaused() {
2220
+ try {
2221
+ const containerName = LocalEmbeddingProvider.CONTAINER_NAME;
2222
+ const { execSync } = await import('child_process');
2223
+ const result = execSync(`docker inspect -f "{{.State.Paused}}" ${containerName} 2>/dev/null`, {
2224
+ encoding: 'utf-8',
2225
+ timeout: 5000
2226
+ }).trim();
2227
+ return result === 'true';
2228
+ }
2229
+ catch (err) {
2230
+ // Container doesn't exist or docker not available - not paused (different issue)
2231
+ return false;
2232
+ }
2233
+ }
2234
+ /**
2235
+ * Unpause the Docker container if it's paused
2236
+ * ON-DEMAND activation - Docker stays paused until we need embeddings
2237
+ */
2238
+ async unpauseDockerIfNeeded() {
2239
+ // Cooldown to prevent spamming unpause
2240
+ const now = Date.now();
2241
+ if (now - this.lastDockerUnpauseAttempt < LocalEmbeddingProvider.DOCKER_UNPAUSE_COOLDOWN_MS) {
2242
+ return false;
2243
+ }
2244
+ this.lastDockerUnpauseAttempt = now;
2245
+ try {
2246
+ const isPaused = await this.isDockerPaused();
2247
+ if (!isPaused) {
2248
+ return false; // Already running
2249
+ }
2250
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DOCKER_ON_DEMAND_UNPAUSE', {
2251
+ containerName: LocalEmbeddingProvider.CONTAINER_NAME,
2252
+ reason: 'embedding request received'
2253
+ });
2254
+ const containerName = LocalEmbeddingProvider.CONTAINER_NAME;
2255
+ const { execSync } = await import('child_process');
2256
+ execSync(`docker unpause ${containerName}`, { timeout: 10000 });
2257
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DOCKER_UNPAUSED_SUCCESS', {
2258
+ containerName
2259
+ });
2260
+ // Wait briefly for socket to be ready
2261
+ await new Promise(resolve => setTimeout(resolve, 500));
2262
+ return true;
2263
+ }
2264
+ catch (err) {
2265
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DOCKER_UNPAUSE_FAILED', {
2266
+ error: err instanceof Error ? err.message : String(err)
2267
+ });
2268
+ return false;
2269
+ }
2270
+ }
2271
+ /**
2272
+ * Drain the embedding queue when socket becomes available
2273
+ * Uses generateWithDirectSocket for the actual embedding generation
2274
+ *
2275
+ * @param socketPath - The socket path to use for embedding generation
2276
+ * @returns Number of items drained from queue
2277
+ */
2278
+ async drainEmbeddingQueue(socketPath) {
2279
+ if (!this.embeddingQueue) {
2280
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DRAIN_SKIP_NO_QUEUE', {
2281
+ reason: 'embeddingQueue not initialized'
2282
+ });
2283
+ return 0;
2284
+ }
2285
+ try {
2286
+ // Check if there are pending items first
2287
+ const pendingCount = await this.embeddingQueue.getPendingCount();
2288
+ if (pendingCount === 0) {
2289
+ return 0;
2290
+ }
2291
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DRAINING_QUEUE', {
2292
+ socketPath,
2293
+ pendingCount
2294
+ });
2295
+ // Drain the queue using generateWithDirectSocket for actual embedding
2296
+ const drained = await this.embeddingQueue.drainQueue((text) => this.generateWithDirectSocket(text, socketPath));
2297
+ return drained;
2298
+ }
2299
+ catch (err) {
2300
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DRAIN_ERROR', {
2301
+ socketPath,
2302
+ error: err instanceof Error ? err.message : String(err)
2303
+ });
2304
+ throw err;
2305
+ }
2306
+ }
2307
+ /**
2308
+ * Start periodic health check for warm socket
2309
+ * Closes socket if idle too long or unhealthy
2310
+ */
2311
+ startWarmSocketHealthCheck() {
2312
+ if (this.warmSocketHealthInterval) {
2313
+ clearInterval(this.warmSocketHealthInterval);
2314
+ }
2315
+ this.warmSocketHealthInterval = setInterval(() => {
2316
+ // Close if idle too long
2317
+ const idleTime = Date.now() - this.warmSocketLastUsed;
2318
+ if (idleTime > LocalEmbeddingProvider.WARM_SOCKET_IDLE_TIMEOUT_MS) {
2319
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'WARM_SOCKET_IDLE_CLOSE', {
2320
+ idleMs: idleTime,
2321
+ maxIdleMs: LocalEmbeddingProvider.WARM_SOCKET_IDLE_TIMEOUT_MS
2322
+ });
2323
+ this.closeWarmSocket();
2324
+ return;
2325
+ }
2326
+ // Check if socket is still alive
2327
+ if (this.warmSocket && !this.warmSocket.destroyed) {
2328
+ // Socket looks healthy
2329
+ }
2330
+ else {
2331
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'WARM_SOCKET_UNHEALTHY', {
2332
+ destroyed: this.warmSocket?.destroyed
2333
+ });
2334
+ this.closeWarmSocket();
2335
+ }
2336
+ }, LocalEmbeddingProvider.WARM_SOCKET_HEALTH_CHECK_MS);
2337
+ }
2338
+ /**
2339
+ * NUCLEAR FIX: Generate embedding using a DIRECT socket connection
2340
+ * Bypasses the broken persistent socket state machine entirely.
2341
+ * Creates a fresh connection for each call - no state, no caching, no bugs.
2342
+ * Takes socket path as parameter to ensure we ALWAYS use the fresh path.
2343
+ */
2344
+ async generateWithDirectSocket(text, socketPath) {
2345
+ let lastError = null;
2346
+ for (let attempt = 1; attempt <= LocalEmbeddingProvider.SOCKET_MAX_RETRIES; attempt++) {
2347
+ try {
2348
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DIRECT_SOCKET_ATTEMPT', {
2349
+ attempt,
2350
+ socketPath,
2351
+ maxRetries: LocalEmbeddingProvider.SOCKET_MAX_RETRIES
2352
+ });
2353
+ return await this.generateWithDirectSocketAttempt(text, socketPath, attempt);
2354
+ }
2355
+ catch (err) {
2356
+ lastError = err instanceof Error ? err : new Error(String(err));
2357
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DIRECT_SOCKET_ATTEMPT_ERROR', {
2358
+ attempt,
2359
+ error: lastError.message,
2360
+ socketPath
2361
+ });
2362
+ // Check if error is retryable (timeout or transient socket error)
2363
+ const isRetryable = lastError.message.includes('timeout') ||
2364
+ lastError.message.includes('ECONNRESET') ||
2365
+ lastError.message.includes('ECONNREFUSED') ||
2366
+ lastError.message.includes('EPIPE') ||
2367
+ lastError.message.includes('ENOENT');
2368
+ if (!isRetryable || attempt >= LocalEmbeddingProvider.SOCKET_MAX_RETRIES) {
2369
+ break;
2370
+ }
2371
+ // Exponential backoff with jitter
2372
+ const baseDelay = LocalEmbeddingProvider.SOCKET_INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt - 1);
2373
+ const jitter = Math.random() * 0.3 * baseDelay; // 0-30% jitter
2374
+ const delay = Math.min(baseDelay + jitter, LocalEmbeddingProvider.SOCKET_MAX_RETRY_DELAY_MS);
2375
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DIRECT_SOCKET_BACKOFF', {
2376
+ attempt,
2377
+ delayMs: Math.round(delay),
2378
+ nextAttempt: attempt + 1
2379
+ });
2380
+ logger.warn({
2381
+ attempt,
2382
+ maxRetries: LocalEmbeddingProvider.SOCKET_MAX_RETRIES,
2383
+ error: lastError.message,
2384
+ retryDelayMs: Math.round(delay),
2385
+ socketPath
2386
+ }, 'Direct socket embedding failed, retrying with exponential backoff');
2387
+ await new Promise(resolve => setTimeout(resolve, delay));
2388
+ }
2389
+ }
2390
+ // All retries exhausted
2391
+ throw new Error(`Embedding generation failed after ${LocalEmbeddingProvider.SOCKET_MAX_RETRIES} attempts. ` +
2392
+ `Socket: ${socketPath}. ` +
2393
+ `Last error: ${lastError?.message || 'unknown'}. ` +
2394
+ `Check if Frankenstein embedding service is running.`);
2395
+ }
2396
+ /**
2397
+ * Single attempt to generate embedding via DIRECT socket connection
2398
+ * Uses the passed socketPath, not cached state
2399
+ */
2400
+ generateWithDirectSocketAttempt(text, socketPath, attempt) {
2401
+ return new Promise((resolve, reject) => {
2402
+ const socket = createConnection(socketPath);
2403
+ let buffer = '';
2404
+ let resolved = false;
2405
+ const startTime = Date.now();
2406
+ const timeoutMs = this.getAdaptiveTimeout();
2407
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DIRECT_SOCKET_CONNECTING', {
2408
+ socketPath,
2409
+ attempt,
2410
+ timeoutMs
2411
+ });
2412
+ // IDLE-BASED TIMEOUT: Timer resets on any data received from socket
2413
+ // This allows long-running embeddings as long as server sends heartbeats
2414
+ let timeout = setTimeout(() => {
2415
+ if (!resolved) {
2416
+ resolved = true;
2417
+ socket.destroy();
2418
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DIRECT_SOCKET_INITIAL_TIMEOUT', {
2419
+ socketPath,
2420
+ attempt,
2421
+ timeoutMs
2422
+ });
2423
+ reject(new Error(`Embedding idle timeout after ${Math.round(timeoutMs / 1000)}s of no activity ` +
2424
+ `(attempt ${attempt}/${LocalEmbeddingProvider.SOCKET_MAX_RETRIES}). ` +
2425
+ `Socket: ${socketPath}. ` +
2426
+ `If model is slow, increase SPECMEM_EMBEDDING_TIMEOUT.`));
2427
+ }
2428
+ }, timeoutMs);
2429
+ socket.on('connect', () => {
2430
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DIRECT_SOCKET_CONNECTED', {
2431
+ socketPath,
2432
+ attempt,
2433
+ connectTimeMs: Date.now() - startTime
2434
+ });
2435
+ const request = JSON.stringify({ type: 'embed', text }) + '\n';
2436
+ socket.write(request);
2437
+ });
2438
+ socket.on('data', (data) => {
2439
+ buffer += data.toString();
2440
+ // IDLE-BASED TIMEOUT: Reset timer on ANY data received
2441
+ // This means if the server is actively sending, we keep waiting
2442
+ clearTimeout(timeout);
2443
+ timeout = setTimeout(() => {
2444
+ if (!resolved) {
2445
+ resolved = true;
2446
+ socket.destroy();
2447
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DIRECT_SOCKET_IDLE_TIMEOUT', {
2448
+ socketPath,
2449
+ attempt,
2450
+ timeoutMs
2451
+ });
2452
+ reject(new Error(`Embedding idle timeout after ${Math.round(timeoutMs / 1000)}s of no activity ` +
2453
+ `(attempt ${attempt}/${LocalEmbeddingProvider.SOCKET_MAX_RETRIES}). ` +
2454
+ `Socket: ${socketPath}. ` +
2455
+ `If model is slow, increase SPECMEM_EMBEDDING_TIMEOUT.`));
2456
+ }
2457
+ }, timeoutMs);
2458
+ // Process complete JSON messages (newline-delimited)
2459
+ let newlineIndex;
2460
+ while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
2461
+ if (resolved)
2462
+ return;
2463
+ const responseJson = buffer.slice(0, newlineIndex);
2464
+ buffer = buffer.slice(newlineIndex + 1);
2465
+ try {
2466
+ const response = JSON.parse(responseJson);
2467
+ // Handle heartbeat/processing status - just keep waiting
2468
+ if (response.status === 'processing') {
2469
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DIRECT_SOCKET_HEARTBEAT', {
2470
+ socketPath,
2471
+ attempt,
2472
+ textLength: response.text_length,
2473
+ elapsedMs: Date.now() - startTime
2474
+ });
2475
+ continue; // Keep waiting for actual embedding
2476
+ }
2477
+ // Got actual response - resolve or reject
2478
+ clearTimeout(timeout);
2479
+ resolved = true;
2480
+ const responseTime = Date.now() - startTime;
2481
+ this.recordResponseTime(responseTime);
2482
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DIRECT_SOCKET_RESPONSE', {
2483
+ socketPath,
2484
+ attempt,
2485
+ responseTimeMs: responseTime,
2486
+ bufferLength: buffer.length
2487
+ });
2488
+ if (response.error) {
2489
+ reject(new Error(`Embedding service error: ${response.error} (socket: ${socketPath})`));
2490
+ }
2491
+ else if (response.embedding) {
2492
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DIRECT_SOCKET_SUCCESS', {
2493
+ socketPath,
2494
+ attempt,
2495
+ embeddingDim: response.embedding.length,
2496
+ totalTimeMs: Date.now() - startTime
2497
+ });
2498
+ resolve(response.embedding);
2499
+ }
2500
+ else {
2501
+ reject(new Error(`Invalid response from embedding service (socket: ${socketPath})`));
2502
+ }
2503
+ socket.end();
2504
+ return;
2505
+ }
2506
+ catch (err) {
2507
+ clearTimeout(timeout);
2508
+ resolved = true;
2509
+ reject(new Error(`Failed to parse embedding response: ${err instanceof Error ? err.message : err} (socket: ${socketPath})`));
2510
+ socket.end();
2511
+ return;
2512
+ }
2513
+ }
2514
+ });
2515
+ socket.on('error', (err) => {
2516
+ clearTimeout(timeout);
2517
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'DIRECT_SOCKET_ERROR', {
2518
+ socketPath,
2519
+ attempt,
2520
+ error: err.message
2521
+ });
2522
+ if (!resolved) {
2523
+ resolved = true;
2524
+ reject(new Error(`Socket error: ${err.message} (socket: ${socketPath})`));
2525
+ }
2526
+ });
2527
+ });
2528
+ }
2529
+ /**
2530
+ * Generate embedding using the PERSISTENT socket (fast path)
2531
+ * No connection overhead - socket stays open!
2532
+ * Includes retry logic with exponential backoff for transient failures.
2533
+ */
2534
+ async generateWithPersistentSocket(text) {
2535
+ const methodStart = Date.now();
2536
+ const textPreview = text.length > 30 ? text.substring(0, 30) + '...' : text;
2537
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_START', {
2538
+ textLength: text.length,
2539
+ textPreview,
2540
+ maxRetries: LocalEmbeddingProvider.SOCKET_MAX_RETRIES,
2541
+ socketConnected: this.socketConnected,
2542
+ hasPersistentSocket: !!this.persistentSocket,
2543
+ socketPath: this.sandboxSocketPath
2544
+ });
2545
+ let lastError = null;
2546
+ for (let attempt = 1; attempt <= LocalEmbeddingProvider.SOCKET_MAX_RETRIES; attempt++) {
2547
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_ATTEMPT_START', {
2548
+ attempt,
2549
+ maxRetries: LocalEmbeddingProvider.SOCKET_MAX_RETRIES,
2550
+ totalElapsedMs: Date.now() - methodStart,
2551
+ socketConnected: this.socketConnected
2552
+ });
2553
+ try {
2554
+ const attemptStart = Date.now();
2555
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_CALLING_ATTEMPT', {
2556
+ attempt
2557
+ });
2558
+ const result = await this.generateWithPersistentSocketAttempt(text, attempt);
2559
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_ATTEMPT_SUCCESS', {
2560
+ attempt,
2561
+ attemptMs: Date.now() - attemptStart,
2562
+ totalElapsedMs: Date.now() - methodStart,
2563
+ resultDim: result.length
2564
+ });
2565
+ return result;
2566
+ }
2567
+ catch (err) {
2568
+ lastError = err instanceof Error ? err : new Error(String(err));
2569
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_ATTEMPT_ERROR', {
2570
+ attempt,
2571
+ error: lastError.message,
2572
+ totalElapsedMs: Date.now() - methodStart
2573
+ });
2574
+ // Check if error is retryable (timeout or transient socket error)
2575
+ const isRetryable = lastError.message.includes('timeout') ||
2576
+ lastError.message.includes('ECONNRESET') ||
2577
+ lastError.message.includes('EPIPE') ||
2578
+ lastError.message.includes('socket');
2579
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_RETRY_CHECK', {
2580
+ attempt,
2581
+ isRetryable,
2582
+ isLastAttempt: attempt >= LocalEmbeddingProvider.SOCKET_MAX_RETRIES,
2583
+ errorMessage: lastError.message
2584
+ });
2585
+ if (!isRetryable || attempt >= LocalEmbeddingProvider.SOCKET_MAX_RETRIES) {
2586
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_BREAKING_LOOP', {
2587
+ attempt,
2588
+ reason: !isRetryable ? 'not retryable' : 'max retries reached'
2589
+ });
2590
+ break;
2591
+ }
2592
+ // Exponential backoff with jitter
2593
+ const baseDelay = LocalEmbeddingProvider.SOCKET_INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt - 1);
2594
+ const jitter = Math.random() * 0.3 * baseDelay; // 0-30% jitter
2595
+ const delay = Math.min(baseDelay + jitter, LocalEmbeddingProvider.SOCKET_MAX_RETRY_DELAY_MS);
2596
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_BACKOFF_WAIT', {
2597
+ attempt,
2598
+ baseDelay,
2599
+ jitter: Math.round(jitter),
2600
+ delay: Math.round(delay)
2601
+ });
2602
+ logger.warn({
2603
+ attempt,
2604
+ maxRetries: LocalEmbeddingProvider.SOCKET_MAX_RETRIES,
2605
+ error: lastError.message,
2606
+ retryDelayMs: Math.round(delay),
2607
+ socketPath: this.sandboxSocketPath
2608
+ }, 'Persistent socket embedding failed, retrying with exponential backoff');
2609
+ await new Promise(resolve => setTimeout(resolve, delay));
2610
+ // Try to reconnect socket before retry
2611
+ if (!this.socketConnected) {
2612
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_RECONNECTING', {
2613
+ attempt,
2614
+ nextAttempt: attempt + 1
2615
+ });
2616
+ this.initPersistentSocket();
2617
+ await new Promise(resolve => setTimeout(resolve, 500)); // Wait for reconnection
2618
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_RECONNECT_WAIT_DONE', {
2619
+ socketConnected: this.socketConnected,
2620
+ hasPersistentSocket: !!this.persistentSocket
2621
+ });
2622
+ }
2623
+ }
2624
+ }
2625
+ // All retries exhausted
2626
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_ALL_RETRIES_EXHAUSTED', {
2627
+ maxRetries: LocalEmbeddingProvider.SOCKET_MAX_RETRIES,
2628
+ lastError: lastError?.message,
2629
+ totalElapsedMs: Date.now() - methodStart,
2630
+ socketPath: this.sandboxSocketPath
2631
+ });
2632
+ throw new Error(`Embedding generation failed after ${LocalEmbeddingProvider.SOCKET_MAX_RETRIES} attempts. ` +
2633
+ `Socket: ${this.sandboxSocketPath}. ` +
2634
+ `Last error: ${lastError?.message || 'unknown'}. ` +
2635
+ `Check if Frankenstein embedding service is running.`);
2636
+ }
2637
+ /**
2638
+ * Single attempt to generate embedding via persistent socket
2639
+ */
2640
+ generateWithPersistentSocketAttempt(text, attempt) {
2641
+ const methodStart = Date.now();
2642
+ const textPreview = text.length > 30 ? text.substring(0, 30) + '...' : text;
2643
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_ATTEMPT_INIT', {
2644
+ attempt,
2645
+ textLength: text.length,
2646
+ textPreview,
2647
+ hasPersistentSocket: !!this.persistentSocket,
2648
+ socketConnected: this.socketConnected,
2649
+ socketPath: this.sandboxSocketPath
2650
+ });
2651
+ return new Promise((resolve, reject) => {
2652
+ if (!this.persistentSocket || !this.socketConnected) {
2653
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_ATTEMPT_NOT_CONNECTED', {
2654
+ attempt,
2655
+ hasPersistentSocket: !!this.persistentSocket,
2656
+ socketConnected: this.socketConnected,
2657
+ elapsedMs: Date.now() - methodStart
2658
+ });
2659
+ return reject(new Error(`Persistent socket not connected (socket: ${this.sandboxSocketPath})`));
2660
+ }
2661
+ const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
2662
+ const startTime = Date.now();
2663
+ const timeoutMs = this.getAdaptiveTimeout();
2664
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_ATTEMPT_SETUP', {
2665
+ attempt,
2666
+ requestId,
2667
+ timeoutMs,
2668
+ pendingRequestsCount: this.pendingRequests.size
2669
+ });
2670
+ const timeout = setTimeout(() => {
2671
+ if (this.pendingRequests.has(requestId)) {
2672
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_ATTEMPT_TIMEOUT_FIRED', {
2673
+ attempt,
2674
+ requestId,
2675
+ timeoutMs,
2676
+ totalElapsedMs: Date.now() - methodStart,
2677
+ pendingRequestsCount: this.pendingRequests.size
2678
+ });
2679
+ this.pendingRequests.delete(requestId);
2680
+ reject(new Error(`Embedding generation timeout after ${Math.round(timeoutMs / 1000)}s ` +
2681
+ `(attempt ${attempt}/${LocalEmbeddingProvider.SOCKET_MAX_RETRIES}). ` +
2682
+ `Socket: ${this.sandboxSocketPath}. ` +
2683
+ `Cold starts may need longer timeout - set SPECMEM_EMBEDDING_TIMEOUT=60 for 60 seconds.`));
2684
+ }
2685
+ }, timeoutMs);
2686
+ // Store pending request
2687
+ this.pendingRequests.set(requestId, {
2688
+ resolve: (embedding) => {
2689
+ const responseTime = Date.now() - startTime;
2690
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_ATTEMPT_RESPONSE_RECEIVED', {
2691
+ attempt,
2692
+ requestId,
2693
+ responseTimeMs: responseTime,
2694
+ embeddingDim: embedding.length,
2695
+ totalElapsedMs: Date.now() - methodStart
2696
+ });
2697
+ this.recordResponseTime(responseTime);
2698
+ resolve(embedding);
2699
+ },
2700
+ reject,
2701
+ timeout
2702
+ });
2703
+ // Send request with ID so we can match responses
2704
+ const request = JSON.stringify({ type: 'embed', text, requestId }) + '\n';
2705
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_ATTEMPT_WRITING_REQUEST', {
2706
+ attempt,
2707
+ requestId,
2708
+ requestLength: request.length,
2709
+ elapsedMs: Date.now() - methodStart
2710
+ });
2711
+ this.persistentSocket.write(request);
2712
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_ATTEMPT_REQUEST_WRITTEN', {
2713
+ attempt,
2714
+ requestId,
2715
+ waitingForResponseWithTimeoutMs: timeoutMs
2716
+ });
2717
+ });
2718
+ }
2719
+ /**
2720
+ * Generate embedding with a NEW socket (slow fallback)
2721
+ * Used when persistent socket is unavailable.
2722
+ * Includes retry logic with exponential backoff for transient failures.
2723
+ */
2724
+ async generateWithNewSocket(text) {
2725
+ let lastError = null;
2726
+ for (let attempt = 1; attempt <= LocalEmbeddingProvider.SOCKET_MAX_RETRIES; attempt++) {
2727
+ try {
2728
+ return await this.generateWithNewSocketAttempt(text, attempt);
2729
+ }
2730
+ catch (err) {
2731
+ lastError = err instanceof Error ? err : new Error(String(err));
2732
+ // Check if error is retryable (timeout or transient socket error)
2733
+ const isRetryable = lastError.message.includes('timeout') ||
2734
+ lastError.message.includes('ECONNRESET') ||
2735
+ lastError.message.includes('ECONNREFUSED') ||
2736
+ lastError.message.includes('EPIPE') ||
2737
+ lastError.message.includes('ENOENT');
2738
+ if (!isRetryable || attempt >= LocalEmbeddingProvider.SOCKET_MAX_RETRIES) {
2739
+ break;
2740
+ }
2741
+ // Exponential backoff with jitter
2742
+ const baseDelay = LocalEmbeddingProvider.SOCKET_INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt - 1);
2743
+ const jitter = Math.random() * 0.3 * baseDelay; // 0-30% jitter
2744
+ const delay = Math.min(baseDelay + jitter, LocalEmbeddingProvider.SOCKET_MAX_RETRY_DELAY_MS);
2745
+ logger.warn({
2746
+ attempt,
2747
+ maxRetries: LocalEmbeddingProvider.SOCKET_MAX_RETRIES,
2748
+ error: lastError.message,
2749
+ retryDelayMs: Math.round(delay),
2750
+ socketPath: this.sandboxSocketPath
2751
+ }, 'New socket embedding failed, retrying with exponential backoff');
2752
+ await new Promise(resolve => setTimeout(resolve, delay));
2753
+ }
2754
+ }
2755
+ // All retries exhausted
2756
+ throw new Error(`Embedding generation failed after ${LocalEmbeddingProvider.SOCKET_MAX_RETRIES} attempts. ` +
2757
+ `Socket: ${this.sandboxSocketPath}. ` +
2758
+ `Last error: ${lastError?.message || 'unknown'}. ` +
2759
+ `Check if Frankenstein embedding service is running.`);
2760
+ }
2761
+ /**
2762
+ * Single attempt to generate embedding via new socket
2763
+ * Uses IDLE-BASED timeout - resets on any data received
2764
+ */
2765
+ generateWithNewSocketAttempt(text, attempt) {
2766
+ return new Promise((resolve, reject) => {
2767
+ const socket = createConnection(this.sandboxSocketPath);
2768
+ let buffer = '';
2769
+ let resolved = false;
2770
+ const startTime = Date.now();
2771
+ const timeoutMs = this.getAdaptiveTimeout();
2772
+ // IDLE-BASED TIMEOUT: Resets on any data received
2773
+ let timeout = setTimeout(() => {
2774
+ if (!resolved) {
2775
+ resolved = true;
2776
+ socket.destroy();
2777
+ reject(new Error(`Embedding idle timeout after ${Math.round(timeoutMs / 1000)}s of no activity ` +
2778
+ `(attempt ${attempt}/${LocalEmbeddingProvider.SOCKET_MAX_RETRIES}). ` +
2779
+ `Socket: ${this.sandboxSocketPath}. ` +
2780
+ `If model is slow, increase SPECMEM_EMBEDDING_TIMEOUT.`));
2781
+ }
2782
+ }, timeoutMs);
2783
+ socket.on('connect', () => {
2784
+ const request = JSON.stringify({ type: 'embed', text }) + '\n';
2785
+ socket.write(request);
2786
+ });
2787
+ socket.on('data', (data) => {
2788
+ buffer += data.toString();
2789
+ // IDLE-BASED TIMEOUT: Reset timer on ANY data received
2790
+ clearTimeout(timeout);
2791
+ timeout = setTimeout(() => {
2792
+ if (!resolved) {
2793
+ resolved = true;
2794
+ socket.destroy();
2795
+ reject(new Error(`Embedding idle timeout after ${Math.round(timeoutMs / 1000)}s of no activity ` +
2796
+ `(attempt ${attempt}/${LocalEmbeddingProvider.SOCKET_MAX_RETRIES}). ` +
2797
+ `Socket: ${this.sandboxSocketPath}. ` +
2798
+ `If model is slow, increase SPECMEM_EMBEDDING_TIMEOUT.`));
2799
+ }
2800
+ }, timeoutMs);
2801
+ // Process complete JSON messages (newline-delimited)
2802
+ let newlineIndex;
2803
+ while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
2804
+ if (resolved)
2805
+ return;
2806
+ const responseJson = buffer.slice(0, newlineIndex);
2807
+ buffer = buffer.slice(newlineIndex + 1);
2808
+ try {
2809
+ const response = JSON.parse(responseJson);
2810
+ // Handle heartbeat/processing status - just keep waiting
2811
+ if (response.status === 'processing') {
2812
+ continue;
2813
+ }
2814
+ // Got actual response - resolve or reject
2815
+ clearTimeout(timeout);
2816
+ resolved = true;
2817
+ const responseTime = Date.now() - startTime;
2818
+ this.recordResponseTime(responseTime);
2819
+ if (response.error) {
2820
+ reject(new Error(`Embedding service error: ${response.error} (socket: ${this.sandboxSocketPath})`));
2821
+ }
2822
+ else if (response.embedding) {
2823
+ resolve(response.embedding);
2824
+ }
2825
+ else {
2826
+ reject(new Error(`Invalid response from embedding service (socket: ${this.sandboxSocketPath})`));
2827
+ }
2828
+ socket.end();
2829
+ return;
2830
+ }
2831
+ catch (err) {
2832
+ clearTimeout(timeout);
2833
+ resolved = true;
2834
+ reject(new Error(`Failed to parse embedding response: ${err instanceof Error ? err.message : err} (socket: ${this.sandboxSocketPath})`));
2835
+ socket.end();
2836
+ return;
2837
+ }
2838
+ }
2839
+ });
2840
+ socket.on('error', (err) => {
2841
+ clearTimeout(timeout);
2842
+ if (!resolved) {
2843
+ resolved = true;
2844
+ reject(new Error(`Socket error: ${err.message} (socket: ${this.sandboxSocketPath})`));
2845
+ }
2846
+ });
2847
+ });
2848
+ }
2849
+ /**
2850
+ * Calculate adaptive timeout based on recent response times
2851
+ * Uses rolling average + 3x standard deviation for safety margin
2852
+ */
2853
+ getAdaptiveTimeout() {
2854
+ if (this.responseTimes.length < 3) {
2855
+ // Not enough data yet, use initial timeout
2856
+ return LocalEmbeddingProvider.INITIAL_TIMEOUT_MS;
2857
+ }
2858
+ // Calculate mean
2859
+ const sum = this.responseTimes.reduce((a, b) => a + b, 0);
2860
+ const mean = sum / this.responseTimes.length;
2861
+ // Calculate standard deviation
2862
+ const squaredDiffs = this.responseTimes.map(t => Math.pow(t - mean, 2));
2863
+ const avgSquaredDiff = squaredDiffs.reduce((a, b) => a + b, 0) / squaredDiffs.length;
2864
+ const stdDev = Math.sqrt(avgSquaredDiff);
2865
+ // Timeout = mean + MULTIPLIER * stdDev (covers 99.7% of cases with 3x)
2866
+ const adaptiveTimeout = mean + (LocalEmbeddingProvider.TIMEOUT_MULTIPLIER * stdDev);
2867
+ // Clamp to min/max bounds
2868
+ const clampedTimeout = Math.max(LocalEmbeddingProvider.MIN_TIMEOUT_MS, Math.min(LocalEmbeddingProvider.MAX_TIMEOUT_MS, adaptiveTimeout));
2869
+ logger.debug({
2870
+ mean: Math.round(mean),
2871
+ stdDev: Math.round(stdDev),
2872
+ adaptiveTimeout: Math.round(adaptiveTimeout),
2873
+ clampedTimeout: Math.round(clampedTimeout),
2874
+ sampleCount: this.responseTimes.length
2875
+ }, 'Calculated adaptive embedding timeout');
2876
+ return clampedTimeout;
2877
+ }
2878
+ /**
2879
+ * Record a response time for adaptive timeout calculation
2880
+ */
2881
+ recordResponseTime(responseTimeMs) {
2882
+ this.responseTimes.push(responseTimeMs);
2883
+ // Keep only the last N response times (rolling window)
2884
+ if (this.responseTimes.length > LocalEmbeddingProvider.RESPONSE_TIME_WINDOW) {
2885
+ this.responseTimes.shift();
2886
+ }
2887
+ }
2888
+ padEmbedding(embedding, targetDims) {
2889
+ // Pad smaller embedding to target dimensions by repeating pattern
2890
+ const padded = new Array(targetDims);
2891
+ for (let i = 0; i < targetDims; i++) {
2892
+ padded[i] = embedding[i % embedding.length];
2893
+ }
2894
+ // Re-normalize
2895
+ const magnitude = Math.sqrt(padded.reduce((sum, val) => sum + val * val, 0));
2896
+ if (magnitude > 0) {
2897
+ for (let i = 0; i < padded.length; i++) {
2898
+ padded[i] = padded[i] / magnitude;
2899
+ }
2900
+ }
2901
+ return padded;
2902
+ }
2903
+ generateHashEmbedding(text, overrideDimension) {
2904
+ // Normalize text for more consistent embeddings
2905
+ const normalizedText = text.toLowerCase().trim();
2906
+ // Use override dimension, or fall back to instance target dimension
2907
+ // If no dimension is known (DB not ready), we CANNOT generate hash embedding
2908
+ // because the dimension must come from the database. Throw to force retry.
2909
+ const dimension = overrideDimension ?? this.targetDimension;
2910
+ if (dimension === null || dimension === 0) {
2911
+ throw new Error('Cannot generate hash embedding: database dimension not yet known. Ensure database is initialized first.');
2912
+ }
2913
+ // Generate base hash
2914
+ const hash = this.hashString(normalizedText);
2915
+ const embedding = new Array(dimension);
2916
+ // Create embedding using multiple hash seeds for distribution
2917
+ for (let i = 0; i < dimension; i++) {
2918
+ // Use combination of position and text hash for variety
2919
+ const seed1 = hash + i * 31;
2920
+ const seed2 = this.hashString(normalizedText.slice(0, Math.min(i + 10, normalizedText.length)));
2921
+ const combined = seed1 ^ seed2;
2922
+ // Generate value between -1 and 1
2923
+ embedding[i] = Math.sin(combined) * Math.cos(combined * 0.7);
2924
+ }
2925
+ // Add n-gram influence for better semantic grouping
2926
+ this.addNgramInfluence(normalizedText, embedding, dimension);
2927
+ // Normalize to unit vector for cosine similarity
2928
+ const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
2929
+ if (magnitude > 0) {
2930
+ for (let i = 0; i < embedding.length; i++) {
2931
+ embedding[i] = embedding[i] / magnitude;
2932
+ }
2933
+ }
2934
+ return embedding;
2935
+ }
2936
+ hashString(str) {
2937
+ let hash = 5381;
2938
+ for (let i = 0; i < str.length; i++) {
2939
+ const char = str.charCodeAt(i);
2940
+ hash = ((hash << 5) + hash) ^ char;
2941
+ }
2942
+ return Math.abs(hash);
2943
+ }
2944
+ addNgramInfluence(text, embedding, dimension) {
2945
+ // Add 3-gram influence for better semantic grouping
2946
+ const ngramSize = 3;
2947
+ for (let i = 0; i <= text.length - ngramSize; i++) {
2948
+ const ngram = text.slice(i, i + ngramSize);
2949
+ const ngramHash = this.hashString(ngram);
2950
+ const position = ngramHash % dimension;
2951
+ embedding[position] = (embedding[position] ?? 0) + 0.1;
2952
+ }
2953
+ }
2954
+ /**
2955
+ * Shutdown the embedding provider - kills Python embedding process if running
2956
+ * Called during graceful shutdown to prevent orphaned processes
2957
+ *
2958
+ * Handles two cases:
2959
+ * 1. MCP server started the embedding (pythonEmbeddingPid is set)
2960
+ * 2. Session hook started the embedding (read from PID file)
2961
+ */
2962
+ async shutdown() {
2963
+ let pidToKill = this.pythonEmbeddingPid;
2964
+ // If we didn't start the embedding server, check the PID file
2965
+ // (session start hook may have started it)
2966
+ if (!pidToKill) {
2967
+ const projectPath = getProjectPath();
2968
+ const pidFile = join(projectPath, 'specmem', 'sockets', 'embedding.pid');
2969
+ try {
2970
+ if (existsSync(pidFile)) {
2971
+ const pidData = readFileSync(pidFile, 'utf8').trim();
2972
+ const [pidStr] = pidData.split(':');
2973
+ const pid = parseInt(pidStr, 10);
2974
+ if (!isNaN(pid) && pid > 0) {
2975
+ pidToKill = pid;
2976
+ logger.info({ pid, pidFile }, 'Found embedding PID from file (started by session hook)');
2977
+ }
2978
+ }
2979
+ }
2980
+ catch (err) {
2981
+ logger.debug({ pidFile, error: err }, 'Could not read embedding PID file');
2982
+ }
2983
+ }
2984
+ if (pidToKill) {
2985
+ logger.info({ pid: pidToKill }, 'Killing Python embedding server process');
2986
+ try {
2987
+ process.kill(pidToKill, 'SIGTERM');
2988
+ // Give it a moment to exit gracefully
2989
+ await new Promise(r => setTimeout(r, 500));
2990
+ // If still running, force kill
2991
+ try {
2992
+ process.kill(pidToKill, 0); // Check if process exists
2993
+ process.kill(pidToKill, 'SIGKILL');
2994
+ logger.warn({ pid: pidToKill }, 'Had to SIGKILL embedding process');
2995
+ }
2996
+ catch (e) {
2997
+ // Process already exited - good
2998
+ }
2999
+ }
3000
+ catch (err) {
3001
+ // Process may already be dead
3002
+ logger.debug({ pid: pidToKill, error: err }, 'Embedding process kill failed (may already be dead)');
3003
+ }
3004
+ this.pythonEmbeddingPid = null;
3005
+ // Clean up PID file
3006
+ const projectPath = getProjectPath();
3007
+ const pidFile = join(projectPath, 'specmem', 'sockets', 'embedding.pid');
3008
+ try {
3009
+ if (existsSync(pidFile)) {
3010
+ unlinkSync(pidFile);
3011
+ }
3012
+ }
3013
+ catch (err) {
3014
+ // Ignore - file may not exist
3015
+ }
3016
+ }
3017
+ }
3018
+ }
3019
+ /**
3020
+ * Create the embedding provider
3021
+ * Always uses local embeddings - no external API needed
3022
+ * DYNAMICALLY detects dimension from database (no hardcoding!)
3023
+ *
3024
+ * NOTE: Embedding dimensions are AUTO-DETECTED from the database pgvector column.
3025
+ * The SPECMEM_EMBEDDING_DIMENSIONS config setting is DEPRECATED and ignored.
3026
+ * The database pg_attribute table is the single source of truth for dimensions.
3027
+ *
3028
+ * Uses the centralized DimensionAdapter for comprehensive dimension detection.
3029
+ */
3030
+ async function createEmbeddingProvider() {
3031
+ // FIX: Wait for embedding server to be ready before creating provider
3032
+ // This prevents 21+ embedding errors during startup when the server is still loading
3033
+ const embeddingSocketPath = getEmbeddingSocketPath();
3034
+ const maxWaitMs = parseInt(process.env['SPECMEM_EMBEDDING_WAIT_MS'] || '30000', 10);
3035
+ const checkIntervalMs = 1000;
3036
+ const startWait = Date.now();
3037
+ logger.info({ socketPath: embeddingSocketPath, maxWaitMs }, '[createEmbeddingProvider] Waiting for embedding server to be ready...');
3038
+ while (Date.now() - startWait < maxWaitMs) {
3039
+ if (existsSync(embeddingSocketPath)) {
3040
+ // Socket file exists - try a quick health check
3041
+ try {
3042
+ const testResult = await new Promise((resolve) => {
3043
+ const testSocket = createConnection(embeddingSocketPath);
3044
+ const timeout = setTimeout(() => {
3045
+ testSocket.destroy();
3046
+ resolve(false);
3047
+ }, 5000);
3048
+ testSocket.on('connect', () => {
3049
+ testSocket.write('{"type":"health"}\n');
3050
+ });
3051
+ testSocket.on('data', () => {
3052
+ clearTimeout(timeout);
3053
+ testSocket.destroy();
3054
+ resolve(true);
3055
+ });
3056
+ testSocket.on('error', () => {
3057
+ clearTimeout(timeout);
3058
+ testSocket.destroy();
3059
+ resolve(false);
3060
+ });
3061
+ });
3062
+ if (testResult) {
3063
+ logger.info({ elapsed: Date.now() - startWait }, '[createEmbeddingProvider] Embedding server is ready');
3064
+ break;
3065
+ }
3066
+ }
3067
+ catch {
3068
+ // Socket test failed - keep waiting
3069
+ }
3070
+ }
3071
+ // Still waiting - log progress every 5 seconds
3072
+ if ((Date.now() - startWait) % 5000 < checkIntervalMs) {
3073
+ logger.debug({ elapsed: Date.now() - startWait, maxWaitMs }, '[createEmbeddingProvider] Still waiting for embedding server...');
3074
+ }
3075
+ await new Promise(resolve => setTimeout(resolve, checkIntervalMs));
3076
+ }
3077
+ // Log if we timed out (non-fatal - provider will handle server startup)
3078
+ if (!existsSync(embeddingSocketPath)) {
3079
+ logger.warn({ elapsed: Date.now() - startWait, maxWaitMs }, '[createEmbeddingProvider] Embedding server not ready after wait - will use fallback mechanisms');
3080
+ }
3081
+ // Initialize the DimensionAdapter for centralized dimension management
3082
+ // This detects dimensions from ALL tables with vector columns
3083
+ let dimensionResult = null;
3084
+ let dbDimension = null;
3085
+ try {
3086
+ const db = getDatabase();
3087
+ // Initialize DimensionAdapter - detects ALL vector columns in database
3088
+ dimensionResult = await initializeDimensionAdapter(db);
3089
+ if (dimensionResult.success && dimensionResult.canonicalDimension !== null) {
3090
+ dbDimension = dimensionResult.canonicalDimension;
3091
+ // Log comprehensive dimension detection results
3092
+ logger.info('='.repeat(60));
3093
+ logger.info('DIMENSION DETECTION COMPLETE - Database is Source of Truth');
3094
+ logger.info('='.repeat(60));
3095
+ logger.info({ canonicalDimension: dbDimension }, 'Canonical dimension (from memories table)');
3096
+ logger.info({ tablesWithVectors: dimensionResult.tables.length }, 'Tables with vector columns detected');
3097
+ // Log each table's dimension
3098
+ for (const table of dimensionResult.tables) {
3099
+ const indexInfo = table.hasIndex ? ` (${table.indexType || 'indexed'})` : ' (no index)';
3100
+ logger.info({
3101
+ table: table.tableName,
3102
+ column: table.columnName,
3103
+ dimension: table.dimension ?? 'unbounded',
3104
+ indexed: table.hasIndex
3105
+ }, ` ${table.tableName}.${table.columnName}: ${table.dimension ?? 'unbounded'}${indexInfo}`);
3106
+ }
3107
+ // Warn about inconsistencies
3108
+ if (dimensionResult.inconsistencies.length > 0) {
3109
+ logger.warn({ count: dimensionResult.inconsistencies.length }, 'DIMENSION INCONSISTENCIES DETECTED');
3110
+ for (const inc of dimensionResult.inconsistencies) {
3111
+ logger.warn({
3112
+ table: inc.table,
3113
+ column: inc.column,
3114
+ dimension: inc.dimension,
3115
+ expected: inc.expected
3116
+ }, ` ${inc.table}.${inc.column}: has ${inc.dimension}, expected ${inc.expected}`);
3117
+ }
3118
+ }
3119
+ else {
3120
+ logger.info('All vector columns have consistent dimensions');
3121
+ }
3122
+ logger.info('='.repeat(60));
3123
+ // Warm the projection layer cache with the database dimension
3124
+ setTargetDimension(dbDimension);
3125
+ }
3126
+ else {
3127
+ logger.warn('DimensionAdapter: Could not detect canonical dimension - will use dimension from first embedding');
3128
+ // Fallback to direct query for memories table
3129
+ try {
3130
+ const result = await db.query(`
3131
+ SELECT atttypmod FROM pg_attribute
3132
+ WHERE attrelid = 'memories'::regclass AND attname = 'embedding'
3133
+ `);
3134
+ if (result.rows.length > 0 && result.rows[0].atttypmod > 0) {
3135
+ dbDimension = result.rows[0].atttypmod;
3136
+ logger.info({ dbDimension }, 'Fallback: detected dimension from memories table');
3137
+ setTargetDimension(dbDimension);
3138
+ }
3139
+ }
3140
+ catch (fallbackErr) {
3141
+ logger.debug({ error: fallbackErr }, 'Fallback dimension query failed (table may not exist yet)');
3142
+ }
3143
+ }
3144
+ }
3145
+ catch (err) {
3146
+ logger.warn({ error: err }, 'Failed to initialize DimensionAdapter - will use dimension from first embedding');
3147
+ }
3148
+ // If we got a dimension from DB, use it; otherwise LocalEmbeddingProvider will auto-detect from first embedding
3149
+ if (dbDimension) {
3150
+ logger.info({ targetDimension: dbDimension }, 'Using local standalone embeddings with DB-detected dimension');
3151
+ return new LocalEmbeddingProvider(dbDimension);
3152
+ }
3153
+ else {
3154
+ // No DB dimension yet - let LocalEmbeddingProvider query DB on first embedding
3155
+ logger.info('Using local standalone embeddings - dimension will be auto-detected from database on first use');
3156
+ return new LocalEmbeddingProvider(); // Constructor will auto-detect from DB
3157
+ }
3158
+ }
3159
+ // Global state for skill/codebase systems
3160
+ let skillScanner = null;
3161
+ let skillResourceProvider = null;
3162
+ let codebaseIndexer = null;
3163
+ let skillReminder = null;
3164
+ // Global state for memory management
3165
+ let memoryManager = null;
3166
+ let embeddingOverflowHandler = null;
3167
+ /**
3168
+ * Initialize Skills System
3169
+ */
3170
+ async function initializeSkillsSystem(embeddingProvider) {
3171
+ const skillsConfig = loadSkillsConfig();
3172
+ if (!skillsConfig.enabled) {
3173
+ logger.info('skills system disabled');
3174
+ return;
3175
+ }
3176
+ logger.info({ skillsPath: skillsConfig.skillsPath }, 'initializing skills system...');
3177
+ try {
3178
+ // create and initialize skill scanner
3179
+ skillScanner = getSkillScanner({
3180
+ skillsPath: skillsConfig.skillsPath,
3181
+ autoReload: skillsConfig.autoReload,
3182
+ debounceMs: 500
3183
+ });
3184
+ const scanResult = await skillScanner.initialize();
3185
+ logger.info({
3186
+ totalSkills: scanResult.totalCount,
3187
+ categories: Array.from(scanResult.categories.keys()),
3188
+ autoReload: skillsConfig.autoReload
3189
+ }, 'skills scanner initialized');
3190
+ // create resource provider
3191
+ skillResourceProvider = getSkillResourceProvider(skillScanner);
3192
+ // log skill reminder
3193
+ logger.info(`\n${'-'.repeat(50)}`);
3194
+ logger.info('SKILLS LOADED:');
3195
+ for (const category of skillScanner.getCategories()) {
3196
+ const skills = skillScanner.getSkillsByCategory(category);
3197
+ logger.info(` ${category}: ${skills.map(s => s.name).join(', ')}`);
3198
+ }
3199
+ logger.info(`${'-'.repeat(50)}\n`);
3200
+ }
3201
+ catch (error) {
3202
+ logger.error({ error }, 'failed to initialize skills system');
3203
+ }
3204
+ }
3205
+ /**
3206
+ * Initialize Codebase Indexer
3207
+ */
3208
+ async function initializeCodebaseSystem(embeddingProvider) {
3209
+ const codebaseConfig = loadCodebaseConfig();
3210
+ if (!codebaseConfig.enabled) {
3211
+ logger.info('codebase indexer disabled');
3212
+ return;
3213
+ }
3214
+ logger.info({ codebasePath: codebaseConfig.codebasePath }, 'initializing codebase indexer...');
3215
+ try {
3216
+ // Get database for persisting files
3217
+ const db = getDatabase();
3218
+ // Wrap embedding provider with caching layer (SHA-256 hash keys for correct relevancy)
3219
+ const cachingProvider = new CachingEmbeddingProvider(embeddingProvider);
3220
+ codebaseIndexer = getCodebaseIndexer({
3221
+ codebasePath: codebaseConfig.codebasePath,
3222
+ excludePatterns: codebaseConfig.excludePatterns,
3223
+ watchForChanges: codebaseConfig.watchForChanges,
3224
+ generateEmbeddings: true
3225
+ }, cachingProvider, db);
3226
+ const stats = await codebaseIndexer.initialize();
3227
+ logger.info({
3228
+ totalFiles: stats.totalFiles,
3229
+ totalLines: stats.totalLines,
3230
+ languages: Object.keys(stats.languageBreakdown).length,
3231
+ watching: stats.isWatching
3232
+ }, 'codebase indexer initialized');
3233
+ }
3234
+ catch (error) {
3235
+ logger.error({ error }, 'failed to initialize codebase indexer');
3236
+ }
3237
+ }
3238
+ /**
3239
+ * Initialize Skill Reminder System
3240
+ */
3241
+ async function initializeReminderSystem() {
3242
+ if (!skillScanner) {
3243
+ logger.debug('skill scanner not initialized - skipping reminder system');
3244
+ return;
3245
+ }
3246
+ logger.info('initializing skill reminder system...');
3247
+ try {
3248
+ skillReminder = getSkillReminder({
3249
+ enabled: true,
3250
+ includeFullSkillContent: true,
3251
+ includeCodebaseOverview: codebaseIndexer !== null,
3252
+ refreshIntervalMinutes: 30
3253
+ }, skillScanner, codebaseIndexer || undefined);
3254
+ await skillReminder.initialize();
3255
+ // log startup reminder
3256
+ const reminder = skillReminder.getStartupReminder();
3257
+ logger.info(reminder);
3258
+ }
3259
+ catch (error) {
3260
+ logger.error({ error }, 'failed to initialize reminder system');
3261
+ }
3262
+ }
3263
+ /**
3264
+ * Initialize Memory Manager with PostgreSQL Overflow
3265
+ *
3266
+ * Implements 100MB RAM limit with:
3267
+ * - 70% warning threshold
3268
+ * - 80% critical threshold (triggers PostgreSQL overflow)
3269
+ * - 90% emergency threshold (aggressive eviction)
3270
+ */
3271
+ async function initializeMemoryManager() {
3272
+ // Support both SPECMEM_MEMORY_LIMIT (dashboard config) and SPECMEM_MAX_HEAP_MB (legacy)
3273
+ const maxHeapMB = parseInt(process.env['SPECMEM_MEMORY_LIMIT'] || process.env['SPECMEM_MAX_HEAP_MB'] || '100', 10);
3274
+ const warningThreshold = parseFloat(process.env['SPECMEM_MEMORY_WARNING'] || '0.7');
3275
+ const criticalThreshold = parseFloat(process.env['SPECMEM_MEMORY_CRITICAL'] || '0.8');
3276
+ const emergencyThreshold = parseFloat(process.env['SPECMEM_MEMORY_EMERGENCY'] || '0.9');
3277
+ logger.info({
3278
+ maxHeapMB,
3279
+ warningThreshold: `${warningThreshold * 100}%`,
3280
+ criticalThreshold: `${criticalThreshold * 100}%`,
3281
+ emergencyThreshold: `${emergencyThreshold * 100}%`
3282
+ }, 'initializing memory manager with RAM limits...');
3283
+ try {
3284
+ // Create memory manager with configured limits
3285
+ memoryManager = getMemoryManager({
3286
+ maxHeapBytes: maxHeapMB * 1024 * 1024,
3287
+ warningThreshold,
3288
+ criticalThreshold,
3289
+ emergencyThreshold,
3290
+ checkIntervalMs: 5000,
3291
+ maxCacheEntries: 1000
3292
+ });
3293
+ // Connect PostgreSQL overflow handler
3294
+ try {
3295
+ const db = getDatabase();
3296
+ embeddingOverflowHandler = createEmbeddingOverflowHandler(db);
3297
+ await embeddingOverflowHandler.initialize();
3298
+ memoryManager.setOverflowHandler(embeddingOverflowHandler);
3299
+ logger.info('memory manager connected to PostgreSQL overflow');
3300
+ }
3301
+ catch (error) {
3302
+ logger.warn({ error }, 'PostgreSQL overflow not available - running without persistence');
3303
+ }
3304
+ // Initialize and start monitoring
3305
+ memoryManager.initialize();
3306
+ // Log initial stats
3307
+ const stats = memoryManager.getStats();
3308
+ logger.info({
3309
+ heapUsedMB: Math.round(stats.heapUsed / 1024 / 1024),
3310
+ maxHeapMB: Math.round(stats.maxHeap / 1024 / 1024),
3311
+ usagePercent: `${(stats.usagePercent * 100).toFixed(1)}%`,
3312
+ pressureLevel: stats.pressureLevel
3313
+ }, 'memory manager initialized');
3314
+ }
3315
+ catch (error) {
3316
+ logger.error({ error }, 'failed to initialize memory manager');
3317
+ }
3318
+ }
3319
+ /**
3320
+ * Shutdown Memory Manager
3321
+ */
3322
+ async function shutdownMemoryManager() {
3323
+ if (memoryManager) {
3324
+ logger.info('shutting down memory manager...');
3325
+ await memoryManager.shutdown();
3326
+ await resetMemoryManager();
3327
+ memoryManager = null;
3328
+ embeddingOverflowHandler = null;
3329
+ logger.info('memory manager shut down');
3330
+ }
3331
+ }
3332
+ /**
3333
+ * Shutdown Skills & Codebase Systems
3334
+ */
3335
+ async function shutdownBrainSystems() {
3336
+ logger.info('shutting down brain systems...');
3337
+ if (skillReminder) {
3338
+ await skillReminder.shutdown();
3339
+ resetSkillReminder();
3340
+ }
3341
+ if (codebaseIndexer) {
3342
+ await codebaseIndexer.shutdown();
3343
+ resetCodebaseIndexer();
3344
+ }
3345
+ if (skillScanner) {
3346
+ await skillScanner.shutdown();
3347
+ resetSkillScanner();
3348
+ }
3349
+ logger.info('brain systems shut down');
3350
+ }
3351
+ /**
3352
+ * Main entry point
3353
+ */
3354
+ // Global instance manager reference
3355
+ let instanceManager = null;
3356
+ /**
3357
+ * Initialize the instance manager for per-project tracking
3358
+ * This replaces the old cleanupStaleLocks() approach with proper per-project isolation
3359
+ */
3360
+ async function initializeInstanceManager() {
3361
+ const projectPath = process.env.SPECMEM_PROJECT_PATH || process.cwd();
3362
+ // Migrate from old structure if needed
3363
+ try {
3364
+ await migrateFromOldStructure(projectPath);
3365
+ }
3366
+ catch (err) {
3367
+ logger.debug({ err }, 'Migration check failed (non-fatal)');
3368
+ }
3369
+ // Clean up any zombie instances for this project BEFORE we initialize
3370
+ try {
3371
+ const cleanup = cleanupSameProjectInstances(projectPath);
3372
+ if (cleanup.killed.length > 0) {
3373
+ logger.info({ killedPIDs: cleanup.killed, projectPath }, 'Cleaned up previous zombie instances for this project');
3374
+ }
3375
+ if (cleanup.skipped.length > 0) {
3376
+ logger.debug({ skippedPIDs: cleanup.skipped }, 'Skipped PIDs during cleanup');
3377
+ }
3378
+ if (cleanup.errors.length > 0) {
3379
+ logger.warn({ errors: cleanup.errors }, 'Some zombie instances could not be cleaned up');
3380
+ }
3381
+ }
3382
+ catch (err) {
3383
+ logger.warn({ err }, 'Failed to cleanup zombie instances (non-fatal)');
3384
+ }
3385
+ // Initialize instance manager
3386
+ instanceManager = getInstanceManager({
3387
+ projectPath,
3388
+ autoCleanup: true,
3389
+ lockStrategy: 'both',
3390
+ healthCheckIntervalMs: 30000,
3391
+ });
3392
+ const result = await instanceManager.initialize();
3393
+ if (!result.success) {
3394
+ if (result.alreadyRunning) {
3395
+ logger.warn('Another SpecMem instance is already running for this project');
3396
+ // Don't throw - allow this instance to continue but log the conflict
3397
+ }
3398
+ else if (result.error) {
3399
+ logger.warn({ error: result.error }, 'Instance manager initialization issue (non-fatal)');
3400
+ }
3401
+ }
3402
+ else {
3403
+ logger.info({
3404
+ projectPath,
3405
+ instanceDir: instanceManager.getInstanceDir(),
3406
+ pid: process.pid,
3407
+ }, 'Instance manager initialized - per-project tracking enabled');
3408
+ }
3409
+ }
3410
+ /**
3411
+ * Clean up stale lock files from previous runs
3412
+ * This now uses the InstanceManager for per-project instance tracking
3413
+ * IMPORTANT: Checks file age to avoid race conditions with new processes
3414
+ *
3415
+ * @deprecated Use InstanceManager.cleanupStaleLocks() instead
3416
+ */
3417
+ function cleanupStaleLocks() {
3418
+ // Use the new instance manager if available
3419
+ if (instanceManager) {
3420
+ instanceManager.cleanupStaleLocks();
3421
+ return;
3422
+ }
3423
+ // Fallback to legacy behavior for backward compatibility
3424
+ const specmemDir = join(process.cwd(), '.specmem');
3425
+ if (!existsSync(specmemDir)) {
3426
+ return; // No lock dir, nothing to clean
3427
+ }
3428
+ const pidFile = join(specmemDir, 'specmem.pid');
3429
+ const sockFile = join(specmemDir, 'specmem.sock');
3430
+ const instanceFile = join(specmemDir, 'instance.json');
3431
+ // Check PID file age - don't clean if too recent (might be a new process starting)
3432
+ const MIN_AGE_MS = 10000; // 10 seconds
3433
+ if (existsSync(pidFile)) {
3434
+ try {
3435
+ const stats = statSync(pidFile);
3436
+ const pidFileAge = Date.now() - stats.mtimeMs;
3437
+ // If the PID file is very recent, skip cleanup to avoid race condition
3438
+ if (pidFileAge < MIN_AGE_MS) {
3439
+ logger.debug({ ageMs: pidFileAge }, 'PID file is too recent, skipping stale check');
3440
+ return;
3441
+ }
3442
+ }
3443
+ catch (e) {
3444
+ // Can't stat, continue with cleanup check
3445
+ }
3446
+ }
3447
+ // Check if there's a stale PID file
3448
+ if (existsSync(pidFile)) {
3449
+ try {
3450
+ const pid = parseInt(readFileSync(pidFile, 'utf8').trim(), 10);
3451
+ // Never clean our own PID!
3452
+ if (pid === process.pid) {
3453
+ return;
3454
+ }
3455
+ // Check if the process is still running
3456
+ try {
3457
+ process.kill(pid, 0); // Signal 0 = check if process exists
3458
+ // Process exists - check if it's actually a specmem/node process
3459
+ const cmdline = execSync(`ps -p ${pid} -o comm= 2>/dev/null || echo ""`, { encoding: 'utf8' }).trim();
3460
+ if (cmdline.includes('node') || cmdline.includes('bootstrap')) {
3461
+ // It's a node process, but is it US starting up? Check if it's very old
3462
+ if (existsSync(instanceFile)) {
3463
+ const instance = JSON.parse(readFileSync(instanceFile, 'utf8'));
3464
+ const startTime = new Date(instance.startTime).getTime();
3465
+ const ageMs = Date.now() - startTime;
3466
+ // If it's been running for more than 1 hour and we're starting fresh, kill it
3467
+ if (ageMs > 3600000) {
3468
+ logger.warn({ pid, ageMs }, 'Killing very old specmem process to allow fresh start');
3469
+ try {
3470
+ process.kill(pid, 'SIGTERM');
3471
+ }
3472
+ catch (e) { /* ignore */ }
3473
+ }
3474
+ }
3475
+ }
3476
+ }
3477
+ catch (e) {
3478
+ // Process doesn't exist - stale lock!
3479
+ logger.info({ pid }, 'Cleaning up stale lock files from dead process');
3480
+ try {
3481
+ unlinkSync(pidFile);
3482
+ }
3483
+ catch (e) { /* ignore */ }
3484
+ try {
3485
+ unlinkSync(sockFile);
3486
+ }
3487
+ catch (e) { /* ignore */ }
3488
+ try {
3489
+ unlinkSync(instanceFile);
3490
+ }
3491
+ catch (e) { /* ignore */ }
3492
+ }
3493
+ }
3494
+ catch (e) {
3495
+ // Can't read PID file - just clean up
3496
+ logger.debug('Cleaning up unreadable lock files');
3497
+ try {
3498
+ unlinkSync(pidFile);
3499
+ }
3500
+ catch (e) { /* ignore */ }
3501
+ try {
3502
+ unlinkSync(sockFile);
3503
+ }
3504
+ catch (e) { /* ignore */ }
3505
+ }
3506
+ }
3507
+ // Also clean stale socket if it exists without a PID file
3508
+ if (existsSync(sockFile) && !existsSync(pidFile)) {
3509
+ logger.info('Cleaning up orphaned socket file');
3510
+ try {
3511
+ unlinkSync(sockFile);
3512
+ }
3513
+ catch (e) { /* ignore */ }
3514
+ }
3515
+ }
3516
+ async function main() {
3517
+ startupLog('main() CALLED - entering main async function');
3518
+ // =========================================================================
3519
+ // ENSURE PROJECT ENVIRONMENT IS SET
3520
+ // This must happen early before any project-scoped operations
3521
+ // =========================================================================
3522
+ ensureProjectEnv();
3523
+ const projectInfo = getProjectInfo();
3524
+ startupLog(`Project path: ${projectInfo.path}`);
3525
+ startupLog(`Project hash: ${projectInfo.hashFull}`);
3526
+ startupLog(`Instance dir: ${projectInfo.instanceDir}`);
3527
+ // NOTE: cleanupStaleLocks() moved to after MCP transport connection
3528
+ // to ensure fastest possible startup time
3529
+ logger.info('Starting SpecMem MCP Server - THE BRAIN OF CLAUDE...');
3530
+ logger.info({ ...projectInfo }, 'Project environment initialized');
3531
+ startupLog('Starting SpecMem MCP Server...');
3532
+ // =========================================================================
3533
+ // PRE-FLIGHT VALIDATION (FAST - must complete in < 100ms)
3534
+ // Validates socket directories and environment variables BEFORE MCP connects.
3535
+ // Database validation is deferred to Phase 2 to not block MCP connection.
3536
+ // =========================================================================
3537
+ startupLog('Running pre-flight validation (fast checks only)...');
3538
+ let preflightResult;
3539
+ try {
3540
+ preflightResult = await quickValidation();
3541
+ if (!preflightResult.valid) {
3542
+ // Critical errors found - log and exit before MCP connects
3543
+ const errorOutput = formatValidationErrors(preflightResult);
3544
+ process.stderr.write(errorOutput);
3545
+ startupLog(`Pre-flight validation FAILED with ${preflightResult.errors.length} errors`);
3546
+ // Log each error for debugging
3547
+ for (const error of preflightResult.errors) {
3548
+ startupLog(` ERROR [${error.code}]: ${error.message}`);
3549
+ logger.error({
3550
+ code: error.code,
3551
+ message: error.message,
3552
+ details: error.details,
3553
+ suggestion: error.suggestion,
3554
+ }, 'Startup validation error');
3555
+ }
3556
+ // Exit with the first error's code
3557
+ const exitCode = preflightResult.errors[0]?.code || EXIT_CODES.GENERAL_ERROR;
3558
+ process.exit(exitCode);
3559
+ }
3560
+ // Log warnings but continue
3561
+ if (preflightResult.warnings.length > 0) {
3562
+ startupLog(`Pre-flight validation passed with ${preflightResult.warnings.length} warnings`);
3563
+ for (const warning of preflightResult.warnings) {
3564
+ logger.warn({ warning }, 'Startup validation warning');
3565
+ }
3566
+ }
3567
+ else {
3568
+ startupLog(`Pre-flight validation passed (${preflightResult.duration}ms)`);
3569
+ }
3570
+ }
3571
+ catch (validationError) {
3572
+ // Validation itself failed - log but continue (don't block MCP connection)
3573
+ startupLog('Pre-flight validation threw an exception - continuing anyway', validationError);
3574
+ logger.error({ error: validationError }, 'Pre-flight validation failed unexpectedly');
3575
+ preflightResult = { valid: true, errors: [], warnings: [], duration: 0 };
3576
+ }
3577
+ // ==========================================================================
3578
+ // CRITICAL TIMING FIX: MCP CONNECTION MUST BE ESTABLISHED FIRST!
3579
+ // ==========================================================================
3580
+ //
3581
+ // Code has a short timeout (~5-10s) for MCP server connections.
3582
+ // If we don't establish the stdio transport quickly, shows:
3583
+ // "Failed to connect to MCP server"
3584
+ //
3585
+ // PREVIOUS BUG: We did heavy initialization BEFORE starting the server:
3586
+ // - Config sync (file I/O)
3587
+ // - Deploy to (file I/O)
3588
+ // - Database init (can be 1-3s if cold)
3589
+ // - Config injection (file I/O)
3590
+ // - Embedding provider creation (DB queries)
3591
+ // All of this could take 5-10+ seconds, causing to timeout!
3592
+ //
3593
+ // NEW APPROACH: Start MCP server IMMEDIATELY, defer everything else.
3594
+ // The server connects transport first, then initializes DB in background.
3595
+ // Tools will wait for DB if they need it.
3596
+ // ==========================================================================
3597
+ // === PHASE 1: FAST MCP CONNECTION (< 500ms target) ===
3598
+ startupLog('PHASE 1: Creating deferred embedding provider...');
3599
+ // Create minimal embedding provider stub - will be upgraded after DB init
3600
+ // Using null-safe pattern that defers to real provider once ready
3601
+ let embeddingProvider = null;
3602
+ let embeddingProviderReady = false;
3603
+ // HIGH-23 FIX: Queue for pending embedding requests instead of returning garbage
3604
+ // Requests wait for provider to be ready, with timeout to prevent indefinite hangs
3605
+ // REDUCED from 30s to 5s to fail fast and not make MCP appear unresponsive
3606
+ const EMBEDDING_PROVIDER_TIMEOUT_MS = 5000; // 5 second timeout - fail fast!
3607
+ const pendingEmbeddingQueue = [];
3608
+ // Process queued requests once provider is ready
3609
+ const processEmbeddingQueue = async () => {
3610
+ if (!embeddingProviderReady || !embeddingProvider)
3611
+ return;
3612
+ while (pendingEmbeddingQueue.length > 0) {
3613
+ const request = pendingEmbeddingQueue.shift();
3614
+ if (!request)
3615
+ continue;
3616
+ // Check if request has timed out
3617
+ const elapsed = Date.now() - request.timestamp;
3618
+ if (elapsed > EMBEDDING_PROVIDER_TIMEOUT_MS) {
3619
+ request.reject(new Error(`Embedding request timed out after ${EMBEDDING_PROVIDER_TIMEOUT_MS}ms`));
3620
+ continue;
3621
+ }
3622
+ try {
3623
+ const embedding = await embeddingProvider.generateEmbedding(request.text);
3624
+ request.resolve(embedding);
3625
+ }
3626
+ catch (err) {
3627
+ request.reject(err instanceof Error ? err : new Error(String(err)));
3628
+ }
3629
+ }
3630
+ };
3631
+ // Stub provider that queues requests until real provider is ready
3632
+ const deferredEmbeddingProvider = {
3633
+ async generateEmbedding(text) {
3634
+ // If provider is ready, use it directly
3635
+ if (embeddingProviderReady && embeddingProvider) {
3636
+ return embeddingProvider.generateEmbedding(text);
3637
+ }
3638
+ // HIGH-23 FIX: Queue the request and wait for provider to be ready
3639
+ // Instead of returning garbage placeholder data, we properly queue
3640
+ logger.debug('Embedding provider not ready yet, queueing request');
3641
+ return new Promise((resolve, reject) => {
3642
+ const timestamp = Date.now();
3643
+ pendingEmbeddingQueue.push({ text, resolve, reject, timestamp });
3644
+ // Set timeout to reject if provider doesn't become ready
3645
+ setTimeout(() => {
3646
+ const idx = pendingEmbeddingQueue.findIndex(r => r.resolve === resolve && r.timestamp === timestamp);
3647
+ if (idx !== -1) {
3648
+ pendingEmbeddingQueue.splice(idx, 1);
3649
+ reject(new Error(`Embedding service starting up - retry in a few seconds (waited ${EMBEDDING_PROVIDER_TIMEOUT_MS}ms)`));
3650
+ }
3651
+ }, EMBEDDING_PROVIDER_TIMEOUT_MS);
3652
+ });
3653
+ }
3654
+ };
3655
+ // Create server with deferred embedding provider
3656
+ startupLog('Creating SpecMemServer instance...');
3657
+ const server = new SpecMemServer(deferredEmbeddingProvider);
3658
+ startupLog('SpecMemServer instance created');
3659
+ // START MCP SERVER IMMEDIATELY - establishes transport connection
3660
+ // This is the CRITICAL path - must complete in < 1 second
3661
+ startupLog('CRITICAL: About to call server.start() - this establishes MCP transport connection');
3662
+ const startTime = Date.now();
3663
+ await server.start();
3664
+ const startDuration = Date.now() - startTime;
3665
+ startupLog(`MCP SERVER STARTED in ${startDuration}ms - transport connection established!`);
3666
+ logger.info('MCP server started - connection established!');
3667
+ // ==========================================================================
3668
+ // EARLY EMBEDDING CHECK: If socket exists and responds, mark ready NOW!
3669
+ // This allows find_memory to work IMMEDIATELY if server is already running
3670
+ // ==========================================================================
3671
+ const earlySocketPath = getEmbeddingSocketPath();
3672
+ if (existsSync(earlySocketPath)) {
3673
+ startupLog('Embedding socket exists - testing if server is already running...');
3674
+ try {
3675
+ const earlyTestResult = await new Promise((resolve) => {
3676
+ const testSocket = createConnection(earlySocketPath);
3677
+ const timeout = setTimeout(() => { testSocket.destroy(); resolve(false); }, 2000);
3678
+ testSocket.on('connect', () => { testSocket.write('{"type":"health"}\n'); });
3679
+ testSocket.on('data', () => { clearTimeout(timeout); testSocket.end(); resolve(true); });
3680
+ testSocket.on('error', () => { clearTimeout(timeout); resolve(false); });
3681
+ });
3682
+ if (earlyTestResult) {
3683
+ startupLog('FAST PATH: Embedding server already running! Creating early provider...');
3684
+ // Create minimal provider that talks directly to socket
3685
+ const socketPath = earlySocketPath;
3686
+ embeddingProvider = {
3687
+ generateEmbedding: async (text) => {
3688
+ return new Promise((resolve, reject) => {
3689
+ const socket = createConnection(socketPath);
3690
+ let buffer = '';
3691
+ let resolved = false;
3692
+ const timeout = setTimeout(() => {
3693
+ if (!resolved) { resolved = true; socket.destroy(); reject(new Error('Embedding timeout')); }
3694
+ }, 60000);
3695
+ socket.on('connect', () => { socket.write(JSON.stringify({ text }) + '\n'); });
3696
+ socket.on('data', (data) => {
3697
+ buffer += data.toString();
3698
+ let idx;
3699
+ while ((idx = buffer.indexOf('\n')) !== -1) {
3700
+ if (resolved) return;
3701
+ const line = buffer.slice(0, idx);
3702
+ buffer = buffer.slice(idx + 1);
3703
+ try {
3704
+ const resp = JSON.parse(line);
3705
+ if (resp.error) { clearTimeout(timeout); resolved = true; socket.end(); reject(new Error(resp.error)); return; }
3706
+ if (resp.status === 'processing') continue;
3707
+ if (resp.embedding && Array.isArray(resp.embedding)) {
3708
+ clearTimeout(timeout); resolved = true; socket.end(); resolve(resp.embedding); return;
3709
+ }
3710
+ } catch (e) { /* ignore parse errors */ }
3711
+ }
3712
+ });
3713
+ socket.on('error', (e) => { clearTimeout(timeout); if (!resolved) { resolved = true; reject(e); } });
3714
+ });
3715
+ },
3716
+ generateEmbeddingsBatch: async (texts) => {
3717
+ return Promise.all(texts.map(t => embeddingProvider.generateEmbedding(t)));
3718
+ }
3719
+ };
3720
+ embeddingProviderReady = true;
3721
+ logger.info('EARLY EMBEDDING PROVIDER READY - find_memory will work immediately!');
3722
+ // CRITICAL: Start KYS heartbeat to keep embedding server alive!
3723
+ // Without this, the server will suicide after 90s of no heartbeat
3724
+ const { EmbeddingServerManager } = await import('./mcp/embeddingServerManager.js');
3725
+ const earlyManager = EmbeddingServerManager.getInstance();
3726
+ earlyManager.startKysHeartbeat();
3727
+ logger.info('KYS heartbeat started for early provider');
3728
+ // Process any already-queued requests
3729
+ if (pendingEmbeddingQueue.length > 0) {
3730
+ startupLog(`Processing ${pendingEmbeddingQueue.length} early-queued requests`);
3731
+ processEmbeddingQueue().catch(e => logger.warn({ e }, 'Early queue processing failed'));
3732
+ }
3733
+ } else {
3734
+ startupLog('Embedding socket exists but server not responding - will initialize normally');
3735
+ }
3736
+ } catch (e) {
3737
+ startupLog('Early embedding check failed (non-fatal): ' + e.message);
3738
+ }
3739
+ } else {
3740
+ startupLog('No existing embedding socket - will initialize normally');
3741
+ }
3742
+ // ==========================================================================
3743
+ // PHASE 2: DEFERRED INITIALIZATION (runs after MCP connection established)
3744
+ // ==========================================================================
3745
+ // These can take time but is already connected and won't timeout
3746
+ startupLog('PHASE 2: Beginning deferred initialization (MCP already connected)');
3747
+ // Initialize instance manager for per-project tracking
3748
+ // This replaces the old cleanupStaleLocks() with proper project isolation
3749
+ try {
3750
+ await initializeInstanceManager();
3751
+ startupLog('Instance manager initialized - per-project tracking enabled');
3752
+ }
3753
+ catch (err) {
3754
+ logger.warn({ err }, 'Instance manager initialization failed (non-fatal), falling back to legacy cleanup');
3755
+ cleanupStaleLocks();
3756
+ }
3757
+ startupLog('Stale locks cleaned (deferred)');
3758
+ // ==========================================================================
3759
+ // EMBEDDING SERVER MANAGER - ENSURE FRESH START
3760
+ // ==========================================================================
3761
+ // CRITICAL: Initialize embedding server manager EARLY to ensure fresh start
3762
+ // This kills any old embedding servers and removes stale sockets BEFORE
3763
+ // LocalEmbeddingProvider tries to connect
3764
+ startupLog('Initializing EmbeddingServerManager for fresh start...');
3765
+ let embeddingManager = null;
3766
+ try {
3767
+ // CRITICAL FIX: Use the singleton getter so MCP tools share the same instance!
3768
+ // Previously this used `new EmbeddingServerManager()` directly, causing two separate
3769
+ // manager instances - startup had heartbeat running, tools didn't share it
3770
+ embeddingManager = getEmbeddingServerManager({
3771
+ healthCheckIntervalMs: 30000,
3772
+ // FIX: Increased from 5s to 15s - health checks during startup can take longer
3773
+ // while the model is still loading into memory. 5s was causing false negatives.
3774
+ healthCheckTimeoutMs: 15000,
3775
+ maxFailuresBeforeRestart: 3,
3776
+ restartCooldownMs: 10000,
3777
+ // FIX: Reduced from 60s to 45s to match DEFAULT_CONFIG and avoid unnecessary waiting
3778
+ // The server should be ready well within 45s; 60s just delays error detection
3779
+ startupTimeoutMs: 45000,
3780
+ maxRestartAttempts: 5,
3781
+ autoStart: true, // Auto-start the embedding server
3782
+ killStaleOnStart: true, // CRITICAL: Kill any stale processes
3783
+ maxProcessAgeHours: 1
3784
+ });
3785
+ await embeddingManager.initialize();
3786
+ logger.info('EmbeddingServerManager: Fresh embedding server started');
3787
+ startupLog('EmbeddingServerManager initialized - fresh server ready');
3788
+ }
3789
+ catch (err) {
3790
+ logger.warn({ error: err }, 'EmbeddingServerManager initialization failed (non-fatal)');
3791
+ startupLog('EmbeddingServerManager failed (non-fatal)', err);
3792
+ // LOW-32 FIX: Set explicit fallback state to indicate manager failed initialization
3793
+ // This prevents ambiguity between "not initialized yet" and "failed to initialize"
3794
+ embeddingManager = null;
3795
+ // Set an env var so downstream code knows embedding manager failed
3796
+ process.env['SPECMEM_EMBEDDING_MANAGER_FAILED'] = 'true';
3797
+ }
3798
+ // Cleanup orphaned embedding processes for THIS project
3799
+ // CRITICAL FIX: SKIP if EmbeddingServerManager is active - it already handles this!
3800
+ // The legacy cleanup was killing servers that the manager JUST started (race condition)
3801
+ if (embeddingManager && embeddingManager.isRunning) {
3802
+ startupLog('Skipping legacy orphan cleanup - EmbeddingServerManager is active');
3803
+ }
3804
+ const cleanupOrphanedEmbeddings = async () => {
3805
+ // CRITICAL: Skip cleanup if embedding manager started successfully
3806
+ if (embeddingManager && embeddingManager.isRunning) {
3807
+ return; // Manager owns the embedding server, don't kill it!
3808
+ }
3809
+ const projectPath = getProjectPath();
3810
+ const socketDir = path.join(projectPath, 'specmem', 'sockets');
3811
+ const socketPath = path.join(socketDir, 'embeddings.sock');
3812
+ const lockPath = path.join(socketDir, 'embedding.lock');
3813
+ const pidFile = path.join(socketDir, 'embedding.pid');
3814
+ try {
3815
+ // STEP 1: Kill orphaned embedding process using PID file
3816
+ if (existsSync(pidFile)) {
3817
+ try {
3818
+ const pidContent = readFileSync(pidFile, 'utf8').trim();
3819
+ const [oldPid, spawnTime] = pidContent.split(':').map(Number);
3820
+ if (oldPid && !isNaN(oldPid)) {
3821
+ // Check if process is still running
3822
+ let isRunning = false;
3823
+ try {
3824
+ process.kill(oldPid, 0);
3825
+ isRunning = true;
3826
+ }
3827
+ catch {
3828
+ isRunning = false;
3829
+ }
3830
+ if (isRunning) {
3831
+ startupLog(`Killing orphaned embedding server: PID ${oldPid} (spawned ${Date.now() - spawnTime}ms ago)`);
3832
+ try {
3833
+ process.kill(oldPid, 'SIGTERM');
3834
+ // Wait a bit for graceful shutdown
3835
+ await new Promise(resolve => setTimeout(resolve, 500));
3836
+ // Force kill if still running
3837
+ try {
3838
+ process.kill(oldPid, 0);
3839
+ process.kill(oldPid, 'SIGKILL');
3840
+ }
3841
+ catch { /* dead */ }
3842
+ }
3843
+ catch (killErr) {
3844
+ logger.warn({ killErr, pid: oldPid }, 'Failed to kill orphaned embedding (may be already dead)');
3845
+ }
3846
+ }
3847
+ // Clean up PID file regardless
3848
+ try {
3849
+ unlinkSync(pidFile);
3850
+ }
3851
+ catch { /* ignore */ }
3852
+ }
3853
+ }
3854
+ catch (pidErr) {
3855
+ logger.warn({ pidErr, pidFile }, 'Failed to read/process embedding PID file');
3856
+ }
3857
+ }
3858
+ // STEP 2: Clean up stale socket if not responsive
3859
+ if (existsSync(socketPath)) {
3860
+ const testAlive = () => new Promise((resolve) => {
3861
+ const testSocket = createConnection(socketPath);
3862
+ const timeout = setTimeout(() => { testSocket.destroy(); resolve(false); }, 2000);
3863
+ testSocket.on('connect', () => {
3864
+ clearTimeout(timeout);
3865
+ testSocket.write('{"text":"test"}\n');
3866
+ });
3867
+ testSocket.on('data', () => { clearTimeout(timeout); testSocket.destroy(); resolve(true); });
3868
+ testSocket.on('error', () => { clearTimeout(timeout); testSocket.destroy(); resolve(false); });
3869
+ });
3870
+ const isAlive = await testAlive();
3871
+ if (!isAlive) {
3872
+ startupLog(`Cleaning stale embedding socket: ${socketPath}`);
3873
+ try {
3874
+ unlinkSync(socketPath);
3875
+ }
3876
+ catch { /* ignore */ }
3877
+ }
3878
+ }
3879
+ // STEP 3: Clean up stale lock files
3880
+ if (existsSync(lockPath)) {
3881
+ try {
3882
+ const lockContent = readFileSync(lockPath, 'utf8').trim();
3883
+ const [lockPid, lockTime] = lockContent.split(':').map(Number);
3884
+ const lockAge = Date.now() - lockTime;
3885
+ let pidRunning = false;
3886
+ try {
3887
+ process.kill(lockPid, 0);
3888
+ pidRunning = true;
3889
+ }
3890
+ catch {
3891
+ pidRunning = false;
3892
+ }
3893
+ if (!pidRunning || lockAge > 300000) {
3894
+ startupLog(`Cleaning stale embedding lock: pid=${lockPid}, age=${lockAge}ms`);
3895
+ unlinkSync(lockPath);
3896
+ }
3897
+ }
3898
+ catch { /* ignore */ }
3899
+ }
3900
+ logger.debug({ projectPath }, 'Embedding orphan cleanup completed for project');
3901
+ }
3902
+ catch (err) {
3903
+ logger.warn({ err, projectPath }, 'Embedding cleanup failed (non-fatal)');
3904
+ }
3905
+ };
3906
+ try {
3907
+ await cleanupOrphanedEmbeddings();
3908
+ startupLog('Orphaned embeddings cleaned');
3909
+ }
3910
+ catch (err) {
3911
+ startupLog('Embedding cleanup failed (non-fatal)', err);
3912
+ }
3913
+ // Track deployment result for banner display later
3914
+ let deployResult = {
3915
+ hooksDeployed: [],
3916
+ hooksSkipped: [],
3917
+ commandsDeployed: [],
3918
+ commandsSkipped: [],
3919
+ settingsUpdated: false,
3920
+ errors: [],
3921
+ success: true,
3922
+ version: '0.0.0'
3923
+ };
3924
+ // --- Config Sync (deferred, non-blocking) ---
3925
+ startupLog('Config sync starting...');
3926
+ try {
3927
+ await syncConfigToUserFile(config);
3928
+ logger.info('[ConfigSync] Config synced to ~/.specmem/config.json');
3929
+ startupLog('Config sync complete');
3930
+ }
3931
+ catch (error) {
3932
+ logger.warn('[ConfigSync] Failed to sync config (non-fatal):', error);
3933
+ startupLog('Config sync failed (non-fatal)', error);
3934
+ }
3935
+ // --- Deploy Hooks and Commands (deferred, non-blocking) ---
3936
+ startupLog('Deploy to starting...');
3937
+ try {
3938
+ deployResult = await deployTo();
3939
+ const totalDeployed = deployResult.hooksDeployed.length + deployResult.commandsDeployed.length;
3940
+ const totalSkipped = deployResult.hooksSkipped.length + deployResult.commandsSkipped.length;
3941
+ if (totalDeployed > 0) {
3942
+ logger.info('[DeployTo] Deployed to :', {
3943
+ version: deployResult.version,
3944
+ hooksDeployed: deployResult.hooksDeployed.length,
3945
+ hooksSkipped: deployResult.hooksSkipped.length,
3946
+ commandsDeployed: deployResult.commandsDeployed.length,
3947
+ commandsSkipped: deployResult.commandsSkipped.length,
3948
+ settingsUpdated: deployResult.settingsUpdated
3949
+ });
3950
+ }
3951
+ else if (totalSkipped > 0) {
3952
+ logger.info('[DeployTo] All files up-to-date', {
3953
+ version: deployResult.version,
3954
+ filesChecked: totalSkipped
3955
+ });
3956
+ }
3957
+ else {
3958
+ logger.info('[DeployTo] No files to deploy');
3959
+ }
3960
+ if (deployResult.errors.length > 0) {
3961
+ logger.warn('[DeployTo] Deployment warnings:', deployResult.errors);
3962
+ }
3963
+ startupLog('Deploy to complete');
3964
+ }
3965
+ catch (error) {
3966
+ logger.warn('[DeployTo] Failed to deploy (non-fatal):', error);
3967
+ startupLog('Deploy to failed (non-fatal)', error);
3968
+ }
3969
+ // --- Database Initialization (critical but deferred) ---
3970
+ // Note: Server already initialized DB via its own deferred init
3971
+ // This is a safety check / upgrade path
3972
+ startupLog('Database initialization starting...');
3973
+ logger.info('[Database] Ensuring database connection is ready...');
3974
+ try {
3975
+ const db = getDatabase(config.database);
3976
+ // Check if already initialized by server, if not initialize
3977
+ if (!db.isConnected()) {
3978
+ startupLog('Database not connected, initializing...');
3979
+ await db.initialize();
3980
+ }
3981
+ logger.info('[Database] Database ready');
3982
+ startupLog('Database ready');
3983
+ // yooo THIS IS THE ROOT CAUSE FIX!
3984
+ // The watcher needs getDbContext() which requires initializeTheBigBrainDb()
3985
+ // Without this, watcher fails silently and we lose file watching capability
3986
+ startupLog('Initializing BigBrain database layer...');
3987
+ try {
3988
+ await initializeTheBigBrainDb(config.database, false); // migrations already run by db.initialize()
3989
+ logger.info('[Database] BigBrain database layer initialized - watchers can now function');
3990
+ startupLog('BigBrain database layer ready');
3991
+ }
3992
+ catch (bigBrainErr) {
3993
+ // nah fr this is critical - watcher won't work without it
3994
+ logger.error({ error: bigBrainErr }, '[Database] Failed to initialize BigBrain layer (watcher will be disabled)');
3995
+ startupLog('BigBrain database layer FAILED', bigBrainErr);
3996
+ // Don't throw - let the server continue, watcher will just be disabled
3997
+ }
3998
+ // --- Full Validation (deferred - includes database checks) ---
3999
+ // Now that DB is connected, run full validation to catch any remaining issues
4000
+ startupLog('Running full validation (including database)...');
4001
+ try {
4002
+ const fullResult = await fullValidation();
4003
+ if (!fullResult.valid) {
4004
+ // Database validation failed - log errors but don't exit
4005
+ // (MCP is already connected, tools may still work partially)
4006
+ const errorOutput = formatValidationErrors(fullResult);
4007
+ process.stderr.write(errorOutput);
4008
+ startupLog(`Full validation FAILED with ${fullResult.errors.length} errors (non-fatal)`);
4009
+ for (const error of fullResult.errors) {
4010
+ logger.error({
4011
+ code: error.code,
4012
+ message: error.message,
4013
+ details: error.details,
4014
+ suggestion: error.suggestion,
4015
+ }, 'Database validation error (non-fatal)');
4016
+ }
4017
+ }
4018
+ else {
4019
+ startupLog(`Full validation passed (${fullResult.duration}ms)`);
4020
+ logger.info({ duration: fullResult.duration }, 'Full startup validation passed');
4021
+ }
4022
+ // Log any additional warnings from full validation
4023
+ if (fullResult.warnings.length > 0) {
4024
+ for (const warning of fullResult.warnings) {
4025
+ if (!preflightResult.warnings.includes(warning)) {
4026
+ logger.warn({ warning }, 'Database validation warning');
4027
+ }
4028
+ }
4029
+ }
4030
+ }
4031
+ catch (fullValidationError) {
4032
+ startupLog('Full validation threw an exception (non-fatal)', fullValidationError);
4033
+ logger.warn({ error: fullValidationError }, 'Full validation failed unexpectedly (non-fatal)');
4034
+ }
4035
+ }
4036
+ catch (error) {
4037
+ logger.error('[Database] Failed to initialize database:', error);
4038
+ startupLog('DATABASE INITIALIZATION FAILED (fatal)', error);
4039
+ throw error; // Fatal - cannot continue without database
4040
+ }
4041
+ // --- Config Injection (deferred, non-blocking) ---
4042
+ try {
4043
+ const injectionResult = await injectConfig();
4044
+ logger.info('[ConfigInjector] Result:', {
4045
+ settingsUpdated: injectionResult.settingsUpdated,
4046
+ hooksCopied: injectionResult.hooksCopied,
4047
+ commandsCopied: injectionResult.commandsCopied,
4048
+ permissionsAdded: injectionResult.permissionsAdded,
4049
+ alreadyConfigured: injectionResult.alreadyConfigured
4050
+ });
4051
+ }
4052
+ catch (error) {
4053
+ logger.warn('[ConfigInjector] Config injection failed (non-fatal):', error);
4054
+ }
4055
+ // --- Real Embedding Provider (now DB is ready) ---
4056
+ embeddingProvider = await createEmbeddingProvider();
4057
+ embeddingProviderReady = true;
4058
+ logger.info('Real embedding provider ready - upgrading from stub');
4059
+ // HIGH-23 FIX: Process any queued embedding requests now that provider is ready
4060
+ if (pendingEmbeddingQueue.length > 0) {
4061
+ logger.info(`Processing ${pendingEmbeddingQueue.length} queued embedding requests`);
4062
+ await processEmbeddingQueue();
4063
+ }
4064
+ // Wire embedding provider to DimensionAdapter
4065
+ try {
4066
+ const adapter = getDimensionAdapter();
4067
+ adapter.setEmbeddingProvider(embeddingProvider);
4068
+ logger.info('DimensionAdapter: Connected to embedding provider');
4069
+ }
4070
+ catch (error) {
4071
+ logger.debug({ error }, 'Could not connect DimensionAdapter (non-fatal)');
4072
+ }
4073
+ // Server is already started, log that initialization is continuing
4074
+ logger.info('MCP server running - continuing background initialization...');
4075
+ // === INITIALIZE MEMORY MANAGER ===
4076
+ // Must be initialized early to monitor memory from the start
4077
+ await initializeMemoryManager();
4078
+ // === INITIALIZE BRAIN SYSTEMS ===
4079
+ // 1. Initialize Skills System (drag & drop .md files)
4080
+ await initializeSkillsSystem(embeddingProvider);
4081
+ // 2. Initialize Codebase Indexer (knows your whole project)
4082
+ await initializeCodebaseSystem(embeddingProvider);
4083
+ // 3. Initialize Reminder System (never forget skills)
4084
+ await initializeReminderSystem();
4085
+ // === INITIALIZE WATCHERS ===
4086
+ // initialize file watcher if enabled (PROJECT-SCOPED - only watches current project)
4087
+ let watcherInitialized = false;
4088
+ try {
4089
+ // Register cleanup handlers for graceful shutdown
4090
+ registerCleanupHandlers();
4091
+ const watcher = await initializeWatcher(embeddingProvider);
4092
+ watcherInitialized = watcher !== null;
4093
+ if (watcherInitialized) {
4094
+ logger.info('PROJECT-SCOPED file watcher enabled and running');
4095
+ }
4096
+ }
4097
+ catch (error) {
4098
+ logger.error({ error }, 'failed to initialize file watcher - continuing without it');
4099
+ }
4100
+ // initialize session watcher if enabled
4101
+ let sessionWatcherInitialized = false;
4102
+ try {
4103
+ const sessionWatcher = await initializeSessionWatcher(embeddingProvider);
4104
+ sessionWatcherInitialized = sessionWatcher !== null;
4105
+ if (sessionWatcherInitialized) {
4106
+ logger.info(' session watcher enabled and running');
4107
+ }
4108
+ }
4109
+ catch (error) {
4110
+ logger.error({ error }, 'failed to initialize session watcher - continuing without it');
4111
+ }
4112
+ // === STARTUP INDEXING - ENSURE EVERYTHING IS READY ===
4113
+ // Check if codebase is indexed and sessions are extracted
4114
+ // Triggers background indexing/extraction if needed
4115
+ // This ensures starts with a fully indexed codebase and extracted sessions
4116
+ try {
4117
+ startupLog('Running startup indexing checks...');
4118
+ const indexingResult = await runStartupIndexing(embeddingProvider, {
4119
+ skipCodebase: false, // Always check codebase
4120
+ skipSessions: false, // Always check sessions
4121
+ force: false // Only reindex if stale or missing
4122
+ });
4123
+ if (indexingResult.codebaseStatus.triggeredIndexing) {
4124
+ startupLog('Background codebase indexing triggered');
4125
+ }
4126
+ if (indexingResult.sessionStatus.triggeredExtraction) {
4127
+ startupLog('Background session extraction triggered');
4128
+ }
4129
+ logger.info({
4130
+ codebase: indexingResult.codebaseStatus,
4131
+ sessions: indexingResult.sessionStatus
4132
+ }, 'Startup indexing checks complete');
4133
+ }
4134
+ catch (error) {
4135
+ logger.warn({ error }, 'Startup indexing checks failed (non-fatal) - continuing');
4136
+ startupLog('Startup indexing checks failed (non-fatal)', error);
4137
+ }
4138
+ // === ALLOCATE UNIQUE PORTS FOR THIS INSTANCE ===
4139
+ // Uses project path hash for deterministic allocation with conflict detection
4140
+ // CRITICAL: Must use SPECMEM_PROJECT_PATH (set by bootstrap.cjs) for per-instance isolation
4141
+ // This ensures each session gets unique ports based on project directory
4142
+ const projectPath = process.env['SPECMEM_PROJECT_PATH'] || process.cwd();
4143
+ logger.info({ projectPath, projectHash: process.env['SPECMEM_PROJECT_HASH'] }, 'Using project path for port allocation');
4144
+ let allocatedPorts = null;
4145
+ try {
4146
+ allocatedPorts = await allocatePorts({
4147
+ projectPath: projectPath,
4148
+ verifyAvailability: true,
4149
+ persistAllocation: true
4150
+ });
4151
+ // Update global port allocation
4152
+ setAllocatedPorts(allocatedPorts);
4153
+ logger.info({
4154
+ dashboard: allocatedPorts.dashboard,
4155
+ coordination: allocatedPorts.coordination,
4156
+ postgres: allocatedPorts.postgres,
4157
+ projectPath: allocatedPorts.projectPath,
4158
+ verified: allocatedPorts.verified
4159
+ }, 'Port allocation complete');
4160
+ // Register ports with instance manager for global tracking
4161
+ if (instanceManager && instanceManager.isInitialized()) {
4162
+ instanceManager.registerInstance({
4163
+ dashboard: allocatedPorts.dashboard,
4164
+ coordination: allocatedPorts.coordination,
4165
+ postgres: allocatedPorts.postgres,
4166
+ });
4167
+ logger.debug('Ports registered with instance manager');
4168
+ }
4169
+ }
4170
+ catch (error) {
4171
+ logger.warn({ error }, 'Port allocation failed, using defaults from environment');
4172
+ }
4173
+ // === CONFIGURE LAZY COORDINATION SERVER ===
4174
+ // The coordination server is now lazy - it only starts when team features are first used
4175
+ // This saves resources when team member coordination is not needed
4176
+ // Use allocated port or fall back to project-hash-derived port from portAllocator
4177
+ const { getDashboardPort: getDashPort, getCoordinationPort: getCoordPort } = await import('./utils/portAllocator.js');
4178
+ const coordinationPort = (allocatedPorts?.coordination ??
4179
+ parseInt(process.env['SPECMEM_COORDINATION_PORT'] || '', 10)) || getCoordPort();
4180
+ const coordinationHost = process.env['SPECMEM_COORDINATION_HOST'] || '127.0.0.1';
4181
+ const coordinationEnabled = process.env['SPECMEM_COORDINATION_ENABLED'] !== 'false';
4182
+ const coordinationMaxRetries = parseInt(process.env['SPECMEM_COORDINATION_MAX_RETRIES'] || '3', 10);
4183
+ // Configure the lazy coordination server (but don't start it yet)
4184
+ if (coordinationEnabled) {
4185
+ configureLazyCoordinationServer({
4186
+ port: coordinationPort,
4187
+ host: coordinationHost,
4188
+ maxPortAttempts: 10,
4189
+ maxStartupRetries: coordinationMaxRetries,
4190
+ retryDelayMs: 1000
4191
+ });
4192
+ logger.info({
4193
+ coordinationPort,
4194
+ coordinationHost,
4195
+ maxRetries: coordinationMaxRetries
4196
+ }, 'Coordination server configured for lazy initialization (will start on first team feature use)');
4197
+ }
4198
+ else {
4199
+ disableLazyCoordinationServer();
4200
+ logger.info('Coordination server disabled via SPECMEM_COORDINATION_ENABLED=false');
4201
+ }
4202
+ // Track coordination availability for status reporting
4203
+ // Note: coordinationAvailable now means "can be started" rather than "is running"
4204
+ const coordinationAvailable = coordinationEnabled;
4205
+ // === INITIALIZE DASHBOARD SERVER ===
4206
+ let dashboardServer = null;
4207
+ let dashboardAvailable = false;
4208
+ let actualDashboardPort = null;
4209
+ // Use allocated port or fall back to project-hash-derived port from portAllocator
4210
+ const dashboardPort = (allocatedPorts?.dashboard ??
4211
+ parseInt(process.env['SPECMEM_DASHBOARD_PORT'] || '', 10)) || getDashPort();
4212
+ const dashboardHost = process.env['SPECMEM_DASHBOARD_HOST'] || '127.0.0.1';
4213
+ const dashboardEnabled = process.env['SPECMEM_DASHBOARD_ENABLED'] !== 'false';
4214
+ const dashboardMaxRetries = parseInt(process.env['SPECMEM_DASHBOARD_MAX_RETRIES'] || '3', 10);
4215
+ // Use centralized password module - supports SPECMEM_PASSWORD (unified) and legacy vars
4216
+ const dashboardPassword = getPassword();
4217
+ // Warn if using default password (security concern in production)
4218
+ if (isUsingDefaultPassword() && dashboardEnabled) {
4219
+ logger.warn('Using default password - consider setting SPECMEM_PASSWORD or SPECMEM_DASHBOARD_PASSWORD for production');
4220
+ }
4221
+ if (dashboardEnabled) {
4222
+ // Retry loop with exponential backoff
4223
+ for (let attempt = 1; attempt <= dashboardMaxRetries; attempt++) {
4224
+ try {
4225
+ dashboardServer = getDashboardServer({
4226
+ port: dashboardPort,
4227
+ host: dashboardHost,
4228
+ password: dashboardPassword,
4229
+ coordinationPort: coordinationPort, // Coordination server starts lazily, use configured port
4230
+ maxPortAttempts: 10,
4231
+ maxStartupRetries: 2,
4232
+ retryDelayMs: 1000
4233
+ });
4234
+ // Connect dashboard to brain systems
4235
+ try {
4236
+ dashboardServer.setDatabase(getDatabase());
4237
+ }
4238
+ catch (e) {
4239
+ logger.debug({ error: e }, 'database not ready for dashboard connection');
4240
+ }
4241
+ // Set embedding provider so HTTP API can use REAL MCP tool semantic search!
4242
+ dashboardServer.setEmbeddingProvider(embeddingProvider);
4243
+ if (skillScanner) {
4244
+ dashboardServer.setSkillScanner(skillScanner);
4245
+ }
4246
+ if (codebaseIndexer) {
4247
+ dashboardServer.setCodebaseIndexer(codebaseIndexer);
4248
+ }
4249
+ await dashboardServer.start();
4250
+ actualDashboardPort = dashboardServer.getActualPort();
4251
+ dashboardAvailable = true;
4252
+ logger.info({
4253
+ port: actualDashboardPort,
4254
+ configuredPort: dashboardPort,
4255
+ host: dashboardHost,
4256
+ url: `http://${dashboardHost}:${actualDashboardPort}`,
4257
+ attempt
4258
+ }, 'CSGO-themed dashboard server started - TACTICAL OPS READY');
4259
+ break;
4260
+ }
4261
+ catch (error) {
4262
+ logger.warn({
4263
+ error: error instanceof Error ? error.message : String(error),
4264
+ attempt,
4265
+ maxRetries: dashboardMaxRetries
4266
+ }, 'dashboard server startup attempt failed');
4267
+ // Reset for next attempt
4268
+ try {
4269
+ await resetDashboardServer();
4270
+ }
4271
+ catch (resetError) {
4272
+ logger.debug({ resetError }, 'error resetting dashboard server');
4273
+ }
4274
+ dashboardServer = null;
4275
+ // Wait before retry with exponential backoff (1s, 2s, 4s...)
4276
+ if (attempt < dashboardMaxRetries) {
4277
+ const delay = 1000 * Math.pow(2, attempt - 1);
4278
+ logger.info({ delayMs: delay }, 'waiting before dashboard retry');
4279
+ await new Promise(resolve => setTimeout(resolve, delay));
4280
+ }
4281
+ }
4282
+ }
4283
+ if (!dashboardAvailable) {
4284
+ logger.error({
4285
+ dashboardPort,
4286
+ dashboardHost,
4287
+ maxRetries: dashboardMaxRetries
4288
+ }, 'DASHBOARD SERVER UNAVAILABLE - web UI will not be accessible');
4289
+ }
4290
+ }
4291
+ else {
4292
+ logger.info('dashboard server disabled via SPECMEM_DASHBOARD_ENABLED=false');
4293
+ }
4294
+ // handle shutdown signals gracefully
4295
+ const gracefulShutdown = async () => {
4296
+ logger.info('shutting down gracefully...');
4297
+ // Import timer registry for cleanup
4298
+ const { clearAllTimers } = await import('./utils/timerRegistry.js');
4299
+ // Clear all timers FIRST to prevent new work from being scheduled
4300
+ const clearedTimers = clearAllTimers();
4301
+ logger.info({ clearedTimers }, 'cleared all registered timers');
4302
+ // shutdown brain systems
4303
+ await shutdownBrainSystems();
4304
+ // shutdown memory manager
4305
+ await shutdownMemoryManager();
4306
+ // shutdown watchers
4307
+ if (watcherInitialized) {
4308
+ await shutdownWatcher();
4309
+ }
4310
+ if (sessionWatcherInitialized) {
4311
+ await shutdownSessionWatcher();
4312
+ }
4313
+ // shutdown coordination server (lazy - may or may not have been started)
4314
+ await executeLazyShutdownHandlers();
4315
+ await resetLazyCoordinationServer();
4316
+ // shutdown dashboard server
4317
+ if (dashboardServer) {
4318
+ await resetDashboardServer();
4319
+ }
4320
+ // shutdown instance manager (releases locks, unregisters from global registry)
4321
+ if (instanceManager) {
4322
+ instanceManager.shutdown();
4323
+ logger.info('Instance manager shutdown complete');
4324
+ }
4325
+ // shutdown embedding server manager (kills embedding server gracefully)
4326
+ // CRITICAL: This prevents orphaned frankenstein-embeddings.py processes!
4327
+ if (embeddingManager) {
4328
+ await embeddingManager.shutdown();
4329
+ logger.info('Embedding server manager shutdown complete');
4330
+ }
4331
+ // shutdown embedding provider (kills Python embedding process if running)
4332
+ // Note: embeddingManager now handles this, but kept for backward compat
4333
+ if (embeddingProvider && typeof embeddingProvider.shutdown === 'function') {
4334
+ await embeddingProvider.shutdown();
4335
+ logger.info('Embedding provider shutdown complete');
4336
+ }
4337
+ // then shutdown server
4338
+ await server.shutdown();
4339
+ process.exit(0);
4340
+ };
4341
+ process.on('SIGINT', gracefulShutdown);
4342
+ process.on('SIGTERM', gracefulShutdown);
4343
+ // SIGUSR1 handler for hot reload - triggers graceful restart
4344
+ // When code is updated, send SIGUSR1 to trigger graceful shutdown
4345
+ // will respawn the process with the new code
4346
+ process.on('SIGUSR1', async () => {
4347
+ logger.info('SIGUSR1 received - initiating graceful restart for hot reload');
4348
+ startupLog('SIGUSR1 received - hot reload triggered');
4349
+ await gracefulShutdown();
4350
+ // gracefulShutdown() calls process.exit(0), so will respawn with new code
4351
+ });
4352
+ // handle uncaught errors - SELF-HEALING MODE
4353
+ // Track recent errors to detect crash loops
4354
+ let recentErrors = [];
4355
+ const MAX_ERRORS_BEFORE_EXIT = 10;
4356
+ const ERROR_WINDOW_MS = 60000; // 1 minute
4357
+
4358
+ process.on('uncaughtException', (error) => {
4359
+ // Log but don't crash for known non-fatal errors
4360
+ const errMsg = error?.message || String(error);
4361
+ const isFatal = errMsg.includes('EADDRINUSE') ||
4362
+ errMsg.includes('ENOMEM') ||
4363
+ errMsg.includes('heap') ||
4364
+ errMsg.includes('stack');
4365
+
4366
+ logger.error({ error, isFatal }, 'Uncaught exception - attempting recovery');
4367
+
4368
+ // Track error frequency
4369
+ recentErrors.push(Date.now());
4370
+ recentErrors = recentErrors.filter(t => Date.now() - t < ERROR_WINDOW_MS);
4371
+
4372
+ if (isFatal || recentErrors.length >= MAX_ERRORS_BEFORE_EXIT) {
4373
+ logger.fatal({ error, recentErrorCount: recentErrors.length }, 'Fatal error or error loop detected - exiting');
4374
+ process.exit(1);
4375
+ }
4376
+ // Non-fatal: log and continue (MCP stays alive)
4377
+ startupLog(`Non-fatal uncaught exception (${recentErrors.length}/${MAX_ERRORS_BEFORE_EXIT}): ${errMsg}`);
4378
+ });
4379
+
4380
+ process.on('unhandledRejection', (reason) => {
4381
+ // SELF-HEALING: Log but DON'T exit for promise rejections
4382
+ // These are usually timeout/network errors that can be safely ignored
4383
+ const reasonStr = reason instanceof Error ? reason.message : String(reason);
4384
+ logger.warn({ reason: reasonStr }, 'Unhandled promise rejection - continuing (MCP stays alive)');
4385
+ startupLog(`Unhandled rejection (non-fatal): ${reasonStr.slice(0, 200)}`);
4386
+ // Don't exit - let MCP continue serving
4387
+ });
4388
+ // Server already started at the beginning for fast MCP connection
4389
+ // Now everything is initialized and ready!
4390
+ // Final status log with memory stats
4391
+ const memStats = memoryManager?.getStats();
4392
+ // Use getDashboardUrl helper for proper host handling (0.0.0.0 -> localhost for display)
4393
+ const dashboardUrl = dashboardAvailable && actualDashboardPort
4394
+ ? getDashboardUrl(dashboardHost, actualDashboardPort)
4395
+ : null;
4396
+ // Get lazy coordination server status for logging
4397
+ const coordStatus = getLazyCoordinationServerStatus();
4398
+ logger.info({
4399
+ skillsEnabled: skillScanner !== null,
4400
+ skillCount: skillScanner?.getAllSkills().length ?? 0,
4401
+ codebaseEnabled: codebaseIndexer !== null,
4402
+ codebaseFiles: codebaseIndexer?.getStats().totalFiles ?? 0,
4403
+ watcherEnabled: watcherInitialized,
4404
+ rootPath: config.watcher.rootPath,
4405
+ sessionWatcherEnabled: sessionWatcherInitialized,
4406
+ claudeDir: config.sessionWatcher.claudeDir ?? '~/.claude',
4407
+ coordinationServerEnabled: coordinationAvailable,
4408
+ coordinationServerLazy: true, // Now uses lazy initialization
4409
+ coordinationServerRunning: coordStatus.running,
4410
+ coordinationPort: coordStatus.port ?? coordinationPort,
4411
+ dashboardEnabled: dashboardAvailable,
4412
+ dashboardPort: actualDashboardPort,
4413
+ dashboardConfiguredPort: dashboardPort,
4414
+ dashboardUrl,
4415
+ memoryManagerEnabled: memoryManager !== null,
4416
+ memoryHeapUsedMB: memStats ? Math.round(memStats.heapUsed / 1024 / 1024) : null,
4417
+ memoryMaxHeapMB: memStats ? Math.round(memStats.maxHeap / 1024 / 1024) : null,
4418
+ memoryPressureLevel: memStats?.pressureLevel ?? 'unknown'
4419
+ }, 'SpecMem server fully initialized - THE BRAIN IS ALIVE');
4420
+ startupLog('SpecMem server FULLY INITIALIZED - all components ready');
4421
+ // === DISPLAY SPECMEM LOADED BANNER ===
4422
+ // Show a nice banner in Code CLI
4423
+ displayLoadedBanner(deployResult, dashboardUrl);
4424
+ startupLog('main() COMPLETE - server running and waiting for requests');
4425
+ }
4426
+ // run if this is the main module
4427
+ startupLog('All imports complete - calling main()');
4428
+ main().catch((error) => {
4429
+ startupLog('main() REJECTED with error', error);
4430
+ logger.fatal({ error }, 'Failed to start SpecMem server');
4431
+ process.exit(1);
4432
+ });
4433
+ //# sourceMappingURL=index.js.map