synabun 1.3.0 → 2.0.0-beta

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 (1153) hide show
  1. package/CHANGELOG.md +197 -662
  2. package/LICENSE-COMMERCIAL.md +33 -0
  3. package/NOTICE +17 -0
  4. package/README.md +175 -853
  5. package/THIRD-PARTY-LICENSES.md +131 -0
  6. package/benchmarks/browser-live-results.json +79 -0
  7. package/benchmarks/browser-v2-results.json +258 -0
  8. package/benchmarks/fixtures/browser/README.md +57 -0
  9. package/benchmarks/fixtures/browser/page-state.json +132 -0
  10. package/benchmarks/fixtures/browser/social.json +92 -0
  11. package/benchmarks/fixtures/browser/target.json +319 -0
  12. package/benchmarks/fixtures/desktop/README.md +58 -0
  13. package/benchmarks/fixtures/desktop/abstention.json +59 -0
  14. package/benchmarks/fixtures/desktop/adversarial.json +22 -0
  15. package/benchmarks/fixtures/desktop/forbidden.json +93 -0
  16. package/benchmarks/fixtures/desktop/generate.mjs +1023 -0
  17. package/benchmarks/fixtures/desktop/target.json +136 -0
  18. package/bin/synabun.js +30 -0
  19. package/docs/browser-v2.md +109 -0
  20. package/docs/hook-integrity.md +89 -0
  21. package/docs/judgments.md +182 -0
  22. package/docs/style-guide.md +246 -0
  23. package/docs/whatsapp.md +325 -0
  24. package/hooks/claude-code/post-plan.mjs +613 -391
  25. package/hooks/claude-code/post-remember.mjs +246 -122
  26. package/hooks/claude-code/pre-compact.mjs +171 -110
  27. package/hooks/claude-code/pre-task.mjs +133 -0
  28. package/hooks/claude-code/pre-websearch.mjs +15 -4
  29. package/hooks/claude-code/prompt-origin.mjs +94 -0
  30. package/hooks/claude-code/prompt-submit.mjs +1244 -861
  31. package/hooks/claude-code/session-start.mjs +114 -156
  32. package/hooks/claude-code/shared.mjs +1228 -368
  33. package/hooks/claude-code/state.mjs +167 -0
  34. package/hooks/claude-code/stop.mjs +515 -140
  35. package/hooks/claude-code/subagent-start.mjs +154 -0
  36. package/hooks/claude-code/subagent-stop.mjs +102 -0
  37. package/hooks/claude-code/task-gate.mjs +150 -0
  38. package/lib/claude-hooks.js +84 -0
  39. package/lib/claude-usage-audit.js +141 -0
  40. package/lib/client-config-repair.js +144 -0
  41. package/lib/data-home-migration.js +498 -0
  42. package/lib/first-launch-protection.js +240 -0
  43. package/lib/paths.d.ts +21 -0
  44. package/lib/paths.js +103 -0
  45. package/mcp-server/dist/config.d.ts +22 -0
  46. package/mcp-server/dist/config.d.ts.map +1 -0
  47. package/mcp-server/dist/config.js +109 -0
  48. package/mcp-server/dist/config.js.map +1 -0
  49. package/mcp-server/dist/http.d.ts +30 -0
  50. package/mcp-server/dist/http.d.ts.map +1 -0
  51. package/mcp-server/dist/http.js +276 -0
  52. package/mcp-server/dist/http.js.map +1 -0
  53. package/mcp-server/dist/index.d.ts +48 -0
  54. package/mcp-server/dist/index.d.ts.map +1 -0
  55. package/mcp-server/dist/index.js +538 -0
  56. package/mcp-server/dist/index.js.map +1 -0
  57. package/mcp-server/dist/preload-http.d.ts +3 -0
  58. package/mcp-server/dist/preload-http.d.ts.map +1 -0
  59. package/mcp-server/dist/preload-http.js +28 -0
  60. package/mcp-server/dist/preload-http.js.map +1 -0
  61. package/mcp-server/dist/preload.d.ts +3 -0
  62. package/mcp-server/dist/preload.d.ts.map +1 -0
  63. package/mcp-server/dist/preload.js +32 -0
  64. package/mcp-server/dist/preload.js.map +1 -0
  65. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.d.ts +19 -0
  66. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.d.ts.map +1 -0
  67. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.js +403 -0
  68. package/mcp-server/dist/scripts/migrate-qdrant-to-sqlite.js.map +1 -0
  69. package/mcp-server/dist/scripts/setup-embeddings.d.ts +3 -0
  70. package/mcp-server/dist/scripts/setup-embeddings.d.ts.map +1 -0
  71. package/mcp-server/dist/scripts/setup-embeddings.js +15 -0
  72. package/mcp-server/dist/scripts/setup-embeddings.js.map +1 -0
  73. package/mcp-server/dist/services/assistant-judgments.d.ts +79 -0
  74. package/mcp-server/dist/services/assistant-judgments.d.ts.map +1 -0
  75. package/mcp-server/dist/services/assistant-judgments.js +128 -0
  76. package/mcp-server/dist/services/assistant-judgments.js.map +1 -0
  77. package/mcp-server/dist/services/browser-assist-gate.d.ts +314 -0
  78. package/mcp-server/dist/services/browser-assist-gate.d.ts.map +1 -0
  79. package/mcp-server/dist/services/browser-assist-gate.js +344 -0
  80. package/mcp-server/dist/services/browser-assist-gate.js.map +1 -0
  81. package/mcp-server/dist/services/browser-assist.d.ts +104 -0
  82. package/mcp-server/dist/services/browser-assist.d.ts.map +1 -0
  83. package/mcp-server/dist/services/browser-assist.js +282 -0
  84. package/mcp-server/dist/services/browser-assist.js.map +1 -0
  85. package/mcp-server/dist/services/browser-bench.d.ts +772 -0
  86. package/mcp-server/dist/services/browser-bench.d.ts.map +1 -0
  87. package/mcp-server/dist/services/browser-bench.js +424 -0
  88. package/mcp-server/dist/services/browser-bench.js.map +1 -0
  89. package/mcp-server/dist/services/browser-judgments.d.ts +196 -0
  90. package/mcp-server/dist/services/browser-judgments.d.ts.map +1 -0
  91. package/mcp-server/dist/services/browser-judgments.js +363 -0
  92. package/mcp-server/dist/services/browser-judgments.js.map +1 -0
  93. package/mcp-server/dist/services/browser-risk.d.ts +166 -0
  94. package/mcp-server/dist/services/browser-risk.d.ts.map +1 -0
  95. package/mcp-server/dist/services/browser-risk.js +458 -0
  96. package/mcp-server/dist/services/browser-risk.js.map +1 -0
  97. package/mcp-server/dist/services/categories.d.ts +61 -0
  98. package/mcp-server/dist/services/categories.d.ts.map +1 -0
  99. package/mcp-server/dist/services/categories.js +377 -0
  100. package/mcp-server/dist/services/categories.js.map +1 -0
  101. package/mcp-server/dist/services/codex-config-heal.d.ts +49 -0
  102. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -0
  103. package/mcp-server/dist/services/codex-config-heal.js +346 -0
  104. package/mcp-server/dist/services/codex-config-heal.js.map +1 -0
  105. package/mcp-server/dist/services/desktop-assist-gate.d.ts +198 -0
  106. package/mcp-server/dist/services/desktop-assist-gate.d.ts.map +1 -0
  107. package/mcp-server/dist/services/desktop-assist-gate.js +202 -0
  108. package/mcp-server/dist/services/desktop-assist-gate.js.map +1 -0
  109. package/mcp-server/dist/services/desktop-assist.d.ts +104 -0
  110. package/mcp-server/dist/services/desktop-assist.d.ts.map +1 -0
  111. package/mcp-server/dist/services/desktop-assist.js +266 -0
  112. package/mcp-server/dist/services/desktop-assist.js.map +1 -0
  113. package/mcp-server/dist/services/desktop-bench.d.ts +553 -0
  114. package/mcp-server/dist/services/desktop-bench.d.ts.map +1 -0
  115. package/mcp-server/dist/services/desktop-bench.js +311 -0
  116. package/mcp-server/dist/services/desktop-bench.js.map +1 -0
  117. package/mcp-server/dist/services/desktop-client.d.ts +23 -0
  118. package/mcp-server/dist/services/desktop-client.d.ts.map +1 -0
  119. package/mcp-server/dist/services/desktop-client.js +79 -0
  120. package/mcp-server/dist/services/desktop-client.js.map +1 -0
  121. package/mcp-server/dist/services/desktop-judgments.d.ts +125 -0
  122. package/mcp-server/dist/services/desktop-judgments.d.ts.map +1 -0
  123. package/mcp-server/dist/services/desktop-judgments.js +177 -0
  124. package/mcp-server/dist/services/desktop-judgments.js.map +1 -0
  125. package/mcp-server/dist/services/desktop-risk.d.ts +283 -0
  126. package/mcp-server/dist/services/desktop-risk.d.ts.map +1 -0
  127. package/mcp-server/dist/services/desktop-risk.js +605 -0
  128. package/mcp-server/dist/services/desktop-risk.js.map +1 -0
  129. package/mcp-server/dist/services/discord.d.ts +112 -0
  130. package/mcp-server/dist/services/discord.d.ts.map +1 -0
  131. package/mcp-server/dist/services/discord.js +477 -0
  132. package/mcp-server/dist/services/discord.js.map +1 -0
  133. package/mcp-server/dist/services/edit-stale.d.ts +81 -0
  134. package/mcp-server/dist/services/edit-stale.d.ts.map +1 -0
  135. package/mcp-server/dist/services/edit-stale.js +358 -0
  136. package/mcp-server/dist/services/edit-stale.js.map +1 -0
  137. package/mcp-server/dist/services/embedding-service.d.ts +16 -0
  138. package/mcp-server/dist/services/embedding-service.d.ts.map +1 -0
  139. package/mcp-server/dist/services/embedding-service.js +126 -0
  140. package/mcp-server/dist/services/embedding-service.js.map +1 -0
  141. package/mcp-server/dist/services/embedding-worker.d.ts +2 -0
  142. package/mcp-server/dist/services/embedding-worker.d.ts.map +1 -0
  143. package/mcp-server/dist/services/embedding-worker.js +52 -0
  144. package/mcp-server/dist/services/embedding-worker.js.map +1 -0
  145. package/mcp-server/dist/services/fb-regions.d.ts +71 -0
  146. package/mcp-server/dist/services/fb-regions.d.ts.map +1 -0
  147. package/mcp-server/dist/services/fb-regions.js +211 -0
  148. package/mcp-server/dist/services/fb-regions.js.map +1 -0
  149. package/mcp-server/dist/services/file-checksums.d.ts +22 -0
  150. package/mcp-server/dist/services/file-checksums.d.ts.map +1 -0
  151. package/mcp-server/dist/services/file-checksums.js +55 -0
  152. package/mcp-server/dist/services/file-checksums.js.map +1 -0
  153. package/mcp-server/dist/services/file-excerpt.d.ts +24 -0
  154. package/mcp-server/dist/services/file-excerpt.d.ts.map +1 -0
  155. package/mcp-server/dist/services/file-excerpt.js +65 -0
  156. package/mcp-server/dist/services/file-excerpt.js.map +1 -0
  157. package/mcp-server/dist/services/identity.d.ts +124 -0
  158. package/mcp-server/dist/services/identity.d.ts.map +1 -0
  159. package/mcp-server/dist/services/identity.js +241 -0
  160. package/mcp-server/dist/services/identity.js.map +1 -0
  161. package/mcp-server/dist/services/local-embeddings.d.ts +2 -0
  162. package/mcp-server/dist/services/local-embeddings.d.ts.map +1 -0
  163. package/mcp-server/dist/services/local-embeddings.js +3 -0
  164. package/mcp-server/dist/services/local-embeddings.js.map +1 -0
  165. package/mcp-server/dist/services/memory-client.d.ts +4 -0
  166. package/mcp-server/dist/services/memory-client.d.ts.map +1 -0
  167. package/mcp-server/dist/services/memory-client.js +27 -0
  168. package/mcp-server/dist/services/memory-client.js.map +1 -0
  169. package/mcp-server/dist/services/memory-judge-backfill.d.ts +135 -0
  170. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -0
  171. package/mcp-server/dist/services/memory-judge-backfill.js +510 -0
  172. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -0
  173. package/mcp-server/dist/services/memory-judgments.d.ts +362 -0
  174. package/mcp-server/dist/services/memory-judgments.d.ts.map +1 -0
  175. package/mcp-server/dist/services/memory-judgments.js +674 -0
  176. package/mcp-server/dist/services/memory-judgments.js.map +1 -0
  177. package/mcp-server/dist/services/memory-maintenance.d.ts +108 -0
  178. package/mcp-server/dist/services/memory-maintenance.d.ts.map +1 -0
  179. package/mcp-server/dist/services/memory-maintenance.js +403 -0
  180. package/mcp-server/dist/services/memory-maintenance.js.map +1 -0
  181. package/mcp-server/dist/services/memory-retrieval.d.ts +66 -0
  182. package/mcp-server/dist/services/memory-retrieval.d.ts.map +1 -0
  183. package/mcp-server/dist/services/memory-retrieval.js +412 -0
  184. package/mcp-server/dist/services/memory-retrieval.js.map +1 -0
  185. package/mcp-server/dist/services/memory-schema.d.ts +16 -0
  186. package/mcp-server/dist/services/memory-schema.d.ts.map +1 -0
  187. package/mcp-server/dist/services/memory-schema.js +282 -0
  188. package/mcp-server/dist/services/memory-schema.js.map +1 -0
  189. package/mcp-server/dist/services/memory-sessions.d.ts +24 -0
  190. package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -0
  191. package/mcp-server/dist/services/memory-sessions.js +110 -0
  192. package/mcp-server/dist/services/memory-sessions.js.map +1 -0
  193. package/mcp-server/dist/services/memory-triage.d.ts +60 -0
  194. package/mcp-server/dist/services/memory-triage.d.ts.map +1 -0
  195. package/mcp-server/dist/services/memory-triage.js +189 -0
  196. package/mcp-server/dist/services/memory-triage.js.map +1 -0
  197. package/mcp-server/dist/services/memory-verification.d.ts +24 -0
  198. package/mcp-server/dist/services/memory-verification.d.ts.map +1 -0
  199. package/mcp-server/dist/services/memory-verification.js +92 -0
  200. package/mcp-server/dist/services/memory-verification.js.map +1 -0
  201. package/mcp-server/dist/services/memory-writer.d.ts +26 -0
  202. package/mcp-server/dist/services/memory-writer.d.ts.map +1 -0
  203. package/mcp-server/dist/services/memory-writer.js +126 -0
  204. package/mcp-server/dist/services/memory-writer.js.map +1 -0
  205. package/mcp-server/dist/services/neural-interface.d.ts +439 -0
  206. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -0
  207. package/mcp-server/dist/services/neural-interface.js +1135 -0
  208. package/mcp-server/dist/services/neural-interface.js.map +1 -0
  209. package/mcp-server/dist/services/profiles.d.ts +120 -0
  210. package/mcp-server/dist/services/profiles.d.ts.map +1 -0
  211. package/mcp-server/dist/services/profiles.js +387 -0
  212. package/mcp-server/dist/services/profiles.js.map +1 -0
  213. package/mcp-server/dist/services/secret-gate.d.ts +29 -0
  214. package/mcp-server/dist/services/secret-gate.d.ts.map +1 -0
  215. package/mcp-server/dist/services/secret-gate.js +43 -0
  216. package/mcp-server/dist/services/secret-gate.js.map +1 -0
  217. package/mcp-server/dist/services/server-instructions.d.ts +10 -0
  218. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -0
  219. package/mcp-server/dist/services/server-instructions.js +101 -0
  220. package/mcp-server/dist/services/server-instructions.js.map +1 -0
  221. package/mcp-server/dist/services/sqlite.d.ts +235 -0
  222. package/mcp-server/dist/services/sqlite.d.ts.map +1 -0
  223. package/mcp-server/dist/services/sqlite.js +1185 -0
  224. package/mcp-server/dist/services/sqlite.js.map +1 -0
  225. package/mcp-server/dist/services/typesafe-bench.d.ts +99 -0
  226. package/mcp-server/dist/services/typesafe-bench.d.ts.map +1 -0
  227. package/mcp-server/dist/services/typesafe-bench.js +218 -0
  228. package/mcp-server/dist/services/typesafe-bench.js.map +1 -0
  229. package/mcp-server/dist/services/typesafe-config.d.ts +342 -0
  230. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -0
  231. package/mcp-server/dist/services/typesafe-config.js +746 -0
  232. package/mcp-server/dist/services/typesafe-config.js.map +1 -0
  233. package/mcp-server/dist/services/typesafe.d.ts +213 -0
  234. package/mcp-server/dist/services/typesafe.d.ts.map +1 -0
  235. package/mcp-server/dist/services/typesafe.js +330 -0
  236. package/mcp-server/dist/services/typesafe.js.map +1 -0
  237. package/mcp-server/dist/services/vector-cache.d.ts +68 -0
  238. package/mcp-server/dist/services/vector-cache.d.ts.map +1 -0
  239. package/mcp-server/dist/services/vector-cache.js +271 -0
  240. package/mcp-server/dist/services/vector-cache.js.map +1 -0
  241. package/mcp-server/dist/services/whiteboard-geometry.d.ts +305 -0
  242. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -0
  243. package/mcp-server/dist/services/whiteboard-geometry.js +666 -0
  244. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -0
  245. package/mcp-server/dist/tools/agents.d.ts +212 -0
  246. package/mcp-server/dist/tools/agents.d.ts.map +1 -0
  247. package/mcp-server/dist/tools/agents.js +407 -0
  248. package/mcp-server/dist/tools/agents.js.map +1 -0
  249. package/mcp-server/dist/tools/bluesky.d.ts +39 -0
  250. package/mcp-server/dist/tools/bluesky.d.ts.map +1 -0
  251. package/mcp-server/dist/tools/bluesky.js +1144 -0
  252. package/mcp-server/dist/tools/bluesky.js.map +1 -0
  253. package/mcp-server/dist/tools/browser-advanced.d.ts +58 -0
  254. package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -0
  255. package/mcp-server/dist/tools/browser-advanced.js +106 -0
  256. package/mcp-server/dist/tools/browser-advanced.js.map +1 -0
  257. package/mcp-server/dist/tools/browser-batch.d.ts +99 -0
  258. package/mcp-server/dist/tools/browser-batch.d.ts.map +1 -0
  259. package/mcp-server/dist/tools/browser-batch.js +187 -0
  260. package/mcp-server/dist/tools/browser-batch.js.map +1 -0
  261. package/mcp-server/dist/tools/browser-cheatsheet.d.ts +21 -0
  262. package/mcp-server/dist/tools/browser-cheatsheet.d.ts.map +1 -0
  263. package/mcp-server/dist/tools/browser-cheatsheet.js +144 -0
  264. package/mcp-server/dist/tools/browser-cheatsheet.js.map +1 -0
  265. package/mcp-server/dist/tools/browser-interact.d.ts +239 -0
  266. package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -0
  267. package/mcp-server/dist/tools/browser-interact.js +300 -0
  268. package/mcp-server/dist/tools/browser-interact.js.map +1 -0
  269. package/mcp-server/dist/tools/browser-navigate.d.ts +90 -0
  270. package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -0
  271. package/mcp-server/dist/tools/browser-navigate.js +142 -0
  272. package/mcp-server/dist/tools/browser-navigate.js.map +1 -0
  273. package/mcp-server/dist/tools/browser-observe.d.ts +561 -0
  274. package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -0
  275. package/mcp-server/dist/tools/browser-observe.js +2009 -0
  276. package/mcp-server/dist/tools/browser-observe.js.map +1 -0
  277. package/mcp-server/dist/tools/browser.d.ts +34 -0
  278. package/mcp-server/dist/tools/browser.d.ts.map +1 -0
  279. package/mcp-server/dist/tools/browser.js +112 -0
  280. package/mcp-server/dist/tools/browser.js.map +1 -0
  281. package/mcp-server/dist/tools/card-tools.d.ts +82 -0
  282. package/mcp-server/dist/tools/card-tools.d.ts.map +1 -0
  283. package/mcp-server/dist/tools/card-tools.js +137 -0
  284. package/mcp-server/dist/tools/card-tools.js.map +1 -0
  285. package/mcp-server/dist/tools/card.d.ts +8 -0
  286. package/mcp-server/dist/tools/card.d.ts.map +1 -0
  287. package/mcp-server/dist/tools/card.js +16 -0
  288. package/mcp-server/dist/tools/card.js.map +1 -0
  289. package/mcp-server/dist/tools/category.d.ts +32 -0
  290. package/mcp-server/dist/tools/category.d.ts.map +1 -0
  291. package/mcp-server/dist/tools/category.js +290 -0
  292. package/mcp-server/dist/tools/category.js.map +1 -0
  293. package/mcp-server/dist/tools/choice.d.ts +160 -0
  294. package/mcp-server/dist/tools/choice.d.ts.map +1 -0
  295. package/mcp-server/dist/tools/choice.js +80 -0
  296. package/mcp-server/dist/tools/choice.js.map +1 -0
  297. package/mcp-server/dist/tools/computer.d.ts +71 -0
  298. package/mcp-server/dist/tools/computer.d.ts.map +1 -0
  299. package/mcp-server/dist/tools/computer.js +174 -0
  300. package/mcp-server/dist/tools/computer.js.map +1 -0
  301. package/mcp-server/dist/tools/discord-channel.d.ts +44 -0
  302. package/mcp-server/dist/tools/discord-channel.d.ts.map +1 -0
  303. package/mcp-server/dist/tools/discord-channel.js +236 -0
  304. package/mcp-server/dist/tools/discord-channel.js.map +1 -0
  305. package/mcp-server/dist/tools/discord-guild.d.ts +20 -0
  306. package/mcp-server/dist/tools/discord-guild.d.ts.map +1 -0
  307. package/mcp-server/dist/tools/discord-guild.js +138 -0
  308. package/mcp-server/dist/tools/discord-guild.js.map +1 -0
  309. package/mcp-server/dist/tools/discord-member.d.ts +26 -0
  310. package/mcp-server/dist/tools/discord-member.d.ts.map +1 -0
  311. package/mcp-server/dist/tools/discord-member.js +122 -0
  312. package/mcp-server/dist/tools/discord-member.js.map +1 -0
  313. package/mcp-server/dist/tools/discord-message.d.ts +36 -0
  314. package/mcp-server/dist/tools/discord-message.d.ts.map +1 -0
  315. package/mcp-server/dist/tools/discord-message.js +184 -0
  316. package/mcp-server/dist/tools/discord-message.js.map +1 -0
  317. package/mcp-server/dist/tools/discord-onboarding.d.ts +32 -0
  318. package/mcp-server/dist/tools/discord-onboarding.d.ts.map +1 -0
  319. package/mcp-server/dist/tools/discord-onboarding.js +179 -0
  320. package/mcp-server/dist/tools/discord-onboarding.js.map +1 -0
  321. package/mcp-server/dist/tools/discord-role.d.ts +34 -0
  322. package/mcp-server/dist/tools/discord-role.d.ts.map +1 -0
  323. package/mcp-server/dist/tools/discord-role.js +178 -0
  324. package/mcp-server/dist/tools/discord-role.js.map +1 -0
  325. package/mcp-server/dist/tools/discord-thread.d.ts +28 -0
  326. package/mcp-server/dist/tools/discord-thread.d.ts.map +1 -0
  327. package/mcp-server/dist/tools/discord-thread.js +88 -0
  328. package/mcp-server/dist/tools/discord-thread.js.map +1 -0
  329. package/mcp-server/dist/tools/discord-webhook.d.ts +32 -0
  330. package/mcp-server/dist/tools/discord-webhook.d.ts.map +1 -0
  331. package/mcp-server/dist/tools/discord-webhook.js +154 -0
  332. package/mcp-server/dist/tools/discord-webhook.js.map +1 -0
  333. package/mcp-server/dist/tools/discord.d.ts +7 -0
  334. package/mcp-server/dist/tools/discord.d.ts.map +1 -0
  335. package/mcp-server/dist/tools/discord.js +25 -0
  336. package/mcp-server/dist/tools/discord.js.map +1 -0
  337. package/mcp-server/dist/tools/fb-groups.d.ts +98 -0
  338. package/mcp-server/dist/tools/fb-groups.d.ts.map +1 -0
  339. package/mcp-server/dist/tools/fb-groups.js +369 -0
  340. package/mcp-server/dist/tools/fb-groups.js.map +1 -0
  341. package/mcp-server/dist/tools/forget.d.ts +14 -0
  342. package/mcp-server/dist/tools/forget.d.ts.map +1 -0
  343. package/mcp-server/dist/tools/forget.js +26 -0
  344. package/mcp-server/dist/tools/forget.js.map +1 -0
  345. package/mcp-server/dist/tools/git-tools.d.ts +24 -0
  346. package/mcp-server/dist/tools/git-tools.d.ts.map +1 -0
  347. package/mcp-server/dist/tools/git-tools.js +107 -0
  348. package/mcp-server/dist/tools/git-tools.js.map +1 -0
  349. package/mcp-server/dist/tools/git.d.ts +7 -0
  350. package/mcp-server/dist/tools/git.d.ts.map +1 -0
  351. package/mcp-server/dist/tools/git.js +11 -0
  352. package/mcp-server/dist/tools/git.js.map +1 -0
  353. package/mcp-server/dist/tools/gsc/experience.d.ts +135 -0
  354. package/mcp-server/dist/tools/gsc/experience.d.ts.map +1 -0
  355. package/mcp-server/dist/tools/gsc/experience.js +351 -0
  356. package/mcp-server/dist/tools/gsc/experience.js.map +1 -0
  357. package/mcp-server/dist/tools/gsc/helpers.d.ts +78 -0
  358. package/mcp-server/dist/tools/gsc/helpers.d.ts.map +1 -0
  359. package/mcp-server/dist/tools/gsc/helpers.js +258 -0
  360. package/mcp-server/dist/tools/gsc/helpers.js.map +1 -0
  361. package/mcp-server/dist/tools/gsc/indexing.d.ts +121 -0
  362. package/mcp-server/dist/tools/gsc/indexing.d.ts.map +1 -0
  363. package/mcp-server/dist/tools/gsc/indexing.js +357 -0
  364. package/mcp-server/dist/tools/gsc/indexing.js.map +1 -0
  365. package/mcp-server/dist/tools/gsc/inspect.d.ts +78 -0
  366. package/mcp-server/dist/tools/gsc/inspect.d.ts.map +1 -0
  367. package/mcp-server/dist/tools/gsc/inspect.js +321 -0
  368. package/mcp-server/dist/tools/gsc/inspect.js.map +1 -0
  369. package/mcp-server/dist/tools/gsc/navigate.d.ts +44 -0
  370. package/mcp-server/dist/tools/gsc/navigate.d.ts.map +1 -0
  371. package/mcp-server/dist/tools/gsc/navigate.js +197 -0
  372. package/mcp-server/dist/tools/gsc/navigate.js.map +1 -0
  373. package/mcp-server/dist/tools/gsc/performance.d.ts +84 -0
  374. package/mcp-server/dist/tools/gsc/performance.d.ts.map +1 -0
  375. package/mcp-server/dist/tools/gsc/performance.js +274 -0
  376. package/mcp-server/dist/tools/gsc/performance.js.map +1 -0
  377. package/mcp-server/dist/tools/gsc/settings.d.ts +153 -0
  378. package/mcp-server/dist/tools/gsc/settings.d.ts.map +1 -0
  379. package/mcp-server/dist/tools/gsc/settings.js +425 -0
  380. package/mcp-server/dist/tools/gsc/settings.js.map +1 -0
  381. package/mcp-server/dist/tools/gsc.d.ts +7 -0
  382. package/mcp-server/dist/tools/gsc.d.ts.map +1 -0
  383. package/mcp-server/dist/tools/gsc.js +51 -0
  384. package/mcp-server/dist/tools/gsc.js.map +1 -0
  385. package/mcp-server/dist/tools/image-tools.d.ts +24 -0
  386. package/mcp-server/dist/tools/image-tools.d.ts.map +1 -0
  387. package/mcp-server/dist/tools/image-tools.js +80 -0
  388. package/mcp-server/dist/tools/image-tools.js.map +1 -0
  389. package/mcp-server/dist/tools/image.d.ts +10 -0
  390. package/mcp-server/dist/tools/image.d.ts.map +1 -0
  391. package/mcp-server/dist/tools/image.js +14 -0
  392. package/mcp-server/dist/tools/image.js.map +1 -0
  393. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +97 -0
  394. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -0
  395. package/mcp-server/dist/tools/leonardo-browser-tools.js +228 -0
  396. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -0
  397. package/mcp-server/dist/tools/leonardo.d.ts +13 -0
  398. package/mcp-server/dist/tools/leonardo.d.ts.map +1 -0
  399. package/mcp-server/dist/tools/leonardo.js +21 -0
  400. package/mcp-server/dist/tools/leonardo.js.map +1 -0
  401. package/mcp-server/dist/tools/loop.d.ts +30 -0
  402. package/mcp-server/dist/tools/loop.d.ts.map +1 -0
  403. package/mcp-server/dist/tools/loop.js +401 -0
  404. package/mcp-server/dist/tools/loop.js.map +1 -0
  405. package/mcp-server/dist/tools/memories.d.ts +56 -0
  406. package/mcp-server/dist/tools/memories.d.ts.map +1 -0
  407. package/mcp-server/dist/tools/memories.js +171 -0
  408. package/mcp-server/dist/tools/memories.js.map +1 -0
  409. package/mcp-server/dist/tools/morelogin.d.ts +24 -0
  410. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -0
  411. package/mcp-server/dist/tools/morelogin.js +79 -0
  412. package/mcp-server/dist/tools/morelogin.js.map +1 -0
  413. package/mcp-server/dist/tools/profile.d.ts +17 -0
  414. package/mcp-server/dist/tools/profile.d.ts.map +1 -0
  415. package/mcp-server/dist/tools/profile.js +114 -0
  416. package/mcp-server/dist/tools/profile.js.map +1 -0
  417. package/mcp-server/dist/tools/recall.d.ts +71 -0
  418. package/mcp-server/dist/tools/recall.d.ts.map +1 -0
  419. package/mcp-server/dist/tools/recall.js +147 -0
  420. package/mcp-server/dist/tools/recall.js.map +1 -0
  421. package/mcp-server/dist/tools/reflect.d.ts +56 -0
  422. package/mcp-server/dist/tools/reflect.d.ts.map +1 -0
  423. package/mcp-server/dist/tools/reflect.js +157 -0
  424. package/mcp-server/dist/tools/reflect.js.map +1 -0
  425. package/mcp-server/dist/tools/remember.d.ts +62 -0
  426. package/mcp-server/dist/tools/remember.d.ts.map +1 -0
  427. package/mcp-server/dist/tools/remember.js +201 -0
  428. package/mcp-server/dist/tools/remember.js.map +1 -0
  429. package/mcp-server/dist/tools/response.d.ts +23 -0
  430. package/mcp-server/dist/tools/response.d.ts.map +1 -0
  431. package/mcp-server/dist/tools/response.js +24 -0
  432. package/mcp-server/dist/tools/response.js.map +1 -0
  433. package/mcp-server/dist/tools/restore.d.ts +14 -0
  434. package/mcp-server/dist/tools/restore.d.ts.map +1 -0
  435. package/mcp-server/dist/tools/restore.js +23 -0
  436. package/mcp-server/dist/tools/restore.js.map +1 -0
  437. package/mcp-server/dist/tools/style-guide-tools.d.ts +51 -0
  438. package/mcp-server/dist/tools/style-guide-tools.d.ts.map +1 -0
  439. package/mcp-server/dist/tools/style-guide-tools.js +231 -0
  440. package/mcp-server/dist/tools/style-guide-tools.js.map +1 -0
  441. package/mcp-server/dist/tools/style-guide.d.ts +8 -0
  442. package/mcp-server/dist/tools/style-guide.d.ts.map +1 -0
  443. package/mcp-server/dist/tools/style-guide.js +12 -0
  444. package/mcp-server/dist/tools/style-guide.js.map +1 -0
  445. package/mcp-server/dist/tools/sync.d.ts +20 -0
  446. package/mcp-server/dist/tools/sync.d.ts.map +1 -0
  447. package/mcp-server/dist/tools/sync.js +156 -0
  448. package/mcp-server/dist/tools/sync.js.map +1 -0
  449. package/mcp-server/dist/tools/tictactoe-tools.d.ts +18 -0
  450. package/mcp-server/dist/tools/tictactoe-tools.d.ts.map +1 -0
  451. package/mcp-server/dist/tools/tictactoe-tools.js +78 -0
  452. package/mcp-server/dist/tools/tictactoe-tools.js.map +1 -0
  453. package/mcp-server/dist/tools/tictactoe.d.ts +7 -0
  454. package/mcp-server/dist/tools/tictactoe.d.ts.map +1 -0
  455. package/mcp-server/dist/tools/tictactoe.js +11 -0
  456. package/mcp-server/dist/tools/tictactoe.js.map +1 -0
  457. package/mcp-server/dist/tools/utils.d.ts +19 -0
  458. package/mcp-server/dist/tools/utils.d.ts.map +1 -0
  459. package/mcp-server/dist/tools/utils.js +54 -0
  460. package/mcp-server/dist/tools/utils.js.map +1 -0
  461. package/mcp-server/dist/tools/whiteboard-format.d.ts +88 -0
  462. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -0
  463. package/mcp-server/dist/tools/whiteboard-format.js +337 -0
  464. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -0
  465. package/mcp-server/dist/tools/whiteboard-tools.d.ts +326 -0
  466. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -0
  467. package/mcp-server/dist/tools/whiteboard-tools.js +222 -0
  468. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -0
  469. package/mcp-server/dist/tools/whiteboard.d.ts +8 -0
  470. package/mcp-server/dist/tools/whiteboard.d.ts.map +1 -0
  471. package/mcp-server/dist/tools/whiteboard.js +16 -0
  472. package/mcp-server/dist/tools/whiteboard.js.map +1 -0
  473. package/mcp-server/dist/tools/youtube/analytics.d.ts +46 -0
  474. package/mcp-server/dist/tools/youtube/analytics.d.ts.map +1 -0
  475. package/mcp-server/dist/tools/youtube/analytics.js +83 -0
  476. package/mcp-server/dist/tools/youtube/analytics.js.map +1 -0
  477. package/mcp-server/dist/tools/youtube/dedup.d.ts +35 -0
  478. package/mcp-server/dist/tools/youtube/dedup.d.ts.map +1 -0
  479. package/mcp-server/dist/tools/youtube/dedup.js +56 -0
  480. package/mcp-server/dist/tools/youtube/dedup.js.map +1 -0
  481. package/mcp-server/dist/tools/youtube/download.d.ts +31 -0
  482. package/mcp-server/dist/tools/youtube/download.d.ts.map +1 -0
  483. package/mcp-server/dist/tools/youtube/download.js +30 -0
  484. package/mcp-server/dist/tools/youtube/download.js.map +1 -0
  485. package/mcp-server/dist/tools/youtube/helpers.d.ts +66 -0
  486. package/mcp-server/dist/tools/youtube/helpers.d.ts.map +1 -0
  487. package/mcp-server/dist/tools/youtube/helpers.js +143 -0
  488. package/mcp-server/dist/tools/youtube/helpers.js.map +1 -0
  489. package/mcp-server/dist/tools/youtube/media.d.ts +43 -0
  490. package/mcp-server/dist/tools/youtube/media.d.ts.map +1 -0
  491. package/mcp-server/dist/tools/youtube/media.js +97 -0
  492. package/mcp-server/dist/tools/youtube/media.js.map +1 -0
  493. package/mcp-server/dist/tools/youtube/metadata.d.ts +33 -0
  494. package/mcp-server/dist/tools/youtube/metadata.d.ts.map +1 -0
  495. package/mcp-server/dist/tools/youtube/metadata.js +106 -0
  496. package/mcp-server/dist/tools/youtube/metadata.js.map +1 -0
  497. package/mcp-server/dist/tools/youtube/navigate.d.ts +30 -0
  498. package/mcp-server/dist/tools/youtube/navigate.d.ts.map +1 -0
  499. package/mcp-server/dist/tools/youtube/navigate.js +71 -0
  500. package/mcp-server/dist/tools/youtube/navigate.js.map +1 -0
  501. package/mcp-server/dist/tools/youtube/playlist.d.ts +29 -0
  502. package/mcp-server/dist/tools/youtube/playlist.d.ts.map +1 -0
  503. package/mcp-server/dist/tools/youtube/playlist.js +128 -0
  504. package/mcp-server/dist/tools/youtube/playlist.js.map +1 -0
  505. package/mcp-server/dist/tools/youtube/schedule.d.ts +31 -0
  506. package/mcp-server/dist/tools/youtube/schedule.d.ts.map +1 -0
  507. package/mcp-server/dist/tools/youtube/schedule.js +147 -0
  508. package/mcp-server/dist/tools/youtube/schedule.js.map +1 -0
  509. package/mcp-server/dist/tools/youtube/source.d.ts +36 -0
  510. package/mcp-server/dist/tools/youtube/source.d.ts.map +1 -0
  511. package/mcp-server/dist/tools/youtube/source.js +85 -0
  512. package/mcp-server/dist/tools/youtube/source.js.map +1 -0
  513. package/mcp-server/dist/tools/youtube/upload.d.ts +53 -0
  514. package/mcp-server/dist/tools/youtube/upload.d.ts.map +1 -0
  515. package/mcp-server/dist/tools/youtube/upload.js +255 -0
  516. package/mcp-server/dist/tools/youtube/upload.js.map +1 -0
  517. package/mcp-server/dist/tools/youtube.d.ts +12 -0
  518. package/mcp-server/dist/tools/youtube.d.ts.map +1 -0
  519. package/mcp-server/dist/tools/youtube.js +38 -0
  520. package/mcp-server/dist/tools/youtube.js.map +1 -0
  521. package/mcp-server/dist/tui.d.ts +3 -0
  522. package/mcp-server/dist/tui.d.ts.map +1 -0
  523. package/mcp-server/dist/tui.js +806 -0
  524. package/mcp-server/dist/tui.js.map +1 -0
  525. package/mcp-server/dist/types.d.ts +77 -0
  526. package/mcp-server/dist/types.d.ts.map +1 -0
  527. package/mcp-server/dist/types.js +2 -0
  528. package/mcp-server/dist/types.js.map +1 -0
  529. package/mcp-server/package-lock.json +4798 -0
  530. package/mcp-server/package.json +55 -38
  531. package/mcp-server/run.mjs +12 -2
  532. package/mcp-server/src/config.ts +48 -3
  533. package/mcp-server/src/http.ts +296 -93
  534. package/mcp-server/src/index.ts +546 -203
  535. package/mcp-server/src/preload-http.ts +19 -18
  536. package/mcp-server/src/preload.ts +19 -18
  537. package/mcp-server/src/scripts/setup-embeddings.ts +9 -0
  538. package/mcp-server/src/services/assistant-judgments.ts +157 -0
  539. package/mcp-server/src/services/browser-assist-gate.ts +383 -0
  540. package/mcp-server/src/services/browser-assist.ts +267 -0
  541. package/mcp-server/src/services/browser-bench.ts +477 -0
  542. package/mcp-server/src/services/browser-judgments.ts +414 -0
  543. package/mcp-server/src/services/browser-risk.ts +484 -0
  544. package/mcp-server/src/services/categories.ts +36 -19
  545. package/mcp-server/src/services/codex-config-heal.ts +403 -0
  546. package/mcp-server/src/services/desktop-assist-gate.ts +251 -0
  547. package/mcp-server/src/services/desktop-assist.ts +308 -0
  548. package/mcp-server/src/services/desktop-bench.ts +372 -0
  549. package/mcp-server/src/services/desktop-client.ts +76 -0
  550. package/mcp-server/src/services/desktop-judgments.ts +218 -0
  551. package/mcp-server/src/services/desktop-risk.ts +743 -0
  552. package/mcp-server/src/services/edit-stale.ts +299 -0
  553. package/mcp-server/src/services/embedding-service.ts +108 -0
  554. package/mcp-server/src/services/embedding-worker.ts +46 -0
  555. package/mcp-server/src/services/fb-regions.ts +221 -0
  556. package/mcp-server/src/services/file-checksums.ts +26 -9
  557. package/mcp-server/src/services/file-excerpt.ts +59 -0
  558. package/mcp-server/src/services/identity.ts +291 -0
  559. package/mcp-server/src/services/local-embeddings.ts +2 -77
  560. package/mcp-server/src/services/memory-client.ts +23 -0
  561. package/mcp-server/src/services/memory-judge-backfill.ts +462 -0
  562. package/mcp-server/src/services/memory-judgments.ts +901 -0
  563. package/mcp-server/src/services/memory-maintenance.ts +360 -0
  564. package/mcp-server/src/services/memory-retrieval.ts +349 -0
  565. package/mcp-server/src/services/memory-schema.ts +280 -0
  566. package/mcp-server/src/services/memory-sessions.ts +87 -0
  567. package/mcp-server/src/services/memory-triage.ts +182 -0
  568. package/mcp-server/src/services/memory-verification.ts +68 -0
  569. package/mcp-server/src/services/memory-writer.ts +125 -0
  570. package/mcp-server/src/services/neural-interface.ts +1217 -73
  571. package/mcp-server/src/services/profiles.ts +428 -0
  572. package/mcp-server/src/services/secret-gate.ts +52 -0
  573. package/mcp-server/src/services/server-instructions.ts +122 -0
  574. package/mcp-server/src/services/sqlite.ts +1456 -823
  575. package/mcp-server/src/services/typesafe-bench.ts +247 -0
  576. package/mcp-server/src/services/typesafe-config.ts +790 -0
  577. package/mcp-server/src/services/typesafe.ts +390 -0
  578. package/mcp-server/src/services/vector-cache.ts +255 -0
  579. package/mcp-server/src/services/whiteboard-geometry.ts +711 -0
  580. package/mcp-server/src/tools/agents.ts +539 -0
  581. package/mcp-server/src/tools/bluesky.ts +1186 -0
  582. package/mcp-server/src/tools/browser-advanced.ts +15 -6
  583. package/mcp-server/src/tools/browser-batch.ts +178 -0
  584. package/mcp-server/src/tools/browser-cheatsheet.ts +155 -0
  585. package/mcp-server/src/tools/browser-interact.ts +362 -255
  586. package/mcp-server/src/tools/browser-navigate.ts +164 -77
  587. package/mcp-server/src/tools/browser-observe.ts +2222 -1330
  588. package/mcp-server/src/tools/browser.ts +181 -107
  589. package/mcp-server/src/tools/card.ts +7 -5
  590. package/mcp-server/src/tools/choice.ts +103 -0
  591. package/mcp-server/src/tools/computer.ts +190 -0
  592. package/mcp-server/src/tools/discord.ts +10 -23
  593. package/mcp-server/src/tools/fb-groups.ts +387 -0
  594. package/mcp-server/src/tools/forget.ts +1 -1
  595. package/mcp-server/src/tools/git.ts +3 -1
  596. package/mcp-server/src/tools/gsc/experience.ts +440 -0
  597. package/mcp-server/src/tools/gsc/helpers.ts +288 -0
  598. package/mcp-server/src/tools/gsc/indexing.ts +495 -0
  599. package/mcp-server/src/tools/gsc/inspect.ts +391 -0
  600. package/mcp-server/src/tools/gsc/navigate.ts +236 -0
  601. package/mcp-server/src/tools/gsc/performance.ts +352 -0
  602. package/mcp-server/src/tools/gsc/settings.ts +564 -0
  603. package/mcp-server/src/tools/gsc.ts +95 -0
  604. package/mcp-server/src/tools/image.ts +3 -1
  605. package/mcp-server/src/tools/leonardo-browser-tools.ts +47 -37
  606. package/mcp-server/src/tools/leonardo.ts +7 -5
  607. package/mcp-server/src/tools/loop.ts +118 -25
  608. package/mcp-server/src/tools/memories.ts +65 -5
  609. package/mcp-server/src/tools/morelogin.ts +81 -0
  610. package/mcp-server/src/tools/profile.ts +122 -0
  611. package/mcp-server/src/tools/recall.ts +105 -261
  612. package/mcp-server/src/tools/reflect.ts +171 -145
  613. package/mcp-server/src/tools/remember.ts +223 -115
  614. package/mcp-server/src/tools/style-guide-tools.ts +250 -0
  615. package/mcp-server/src/tools/style-guide.ts +16 -0
  616. package/mcp-server/src/tools/sync.ts +66 -9
  617. package/mcp-server/src/tools/tictactoe.ts +3 -1
  618. package/mcp-server/src/tools/utils.ts +22 -0
  619. package/mcp-server/src/tools/whiteboard-format.ts +394 -0
  620. package/mcp-server/src/tools/whiteboard-tools.ts +191 -183
  621. package/mcp-server/src/tools/whiteboard.ts +7 -5
  622. package/mcp-server/src/tools/youtube/analytics.ts +106 -0
  623. package/mcp-server/src/tools/youtube/dedup.ts +57 -0
  624. package/mcp-server/src/tools/youtube/download.ts +41 -0
  625. package/mcp-server/src/tools/youtube/helpers.ts +183 -0
  626. package/mcp-server/src/tools/youtube/media.ts +109 -0
  627. package/mcp-server/src/tools/youtube/metadata.ts +112 -0
  628. package/mcp-server/src/tools/youtube/navigate.ts +81 -0
  629. package/mcp-server/src/tools/youtube/playlist.ts +134 -0
  630. package/mcp-server/src/tools/youtube/schedule.ts +158 -0
  631. package/mcp-server/src/tools/youtube/source.ts +101 -0
  632. package/mcp-server/src/tools/youtube/upload.ts +296 -0
  633. package/mcp-server/src/tools/youtube.ts +47 -0
  634. package/mcp-server/src/tui.ts +8 -9
  635. package/neural-interface/games/TicTacToe/1.svg +1 -0
  636. package/neural-interface/games/TicTacToe/2.svg +1 -0
  637. package/neural-interface/games/TicTacToe/3.svg +1 -0
  638. package/neural-interface/games/TicTacToe/4.svg +1 -0
  639. package/neural-interface/games/TicTacToe/5.svg +1 -0
  640. package/neural-interface/games/TicTacToe/6.svg +1 -0
  641. package/neural-interface/games/TicTacToe/7.svg +1 -0
  642. package/neural-interface/games/TicTacToe/8.svg +1 -0
  643. package/neural-interface/games/TicTacToe/9.svg +1 -0
  644. package/neural-interface/games/TicTacToe/Board.svg +1 -0
  645. package/neural-interface/games/TicTacToe/Circle.svg +1 -0
  646. package/neural-interface/games/TicTacToe/Cross.svg +1 -0
  647. package/neural-interface/i18n/en.json +6983 -0
  648. package/neural-interface/i18n/pt-BR.json +6987 -0
  649. package/neural-interface/lib/assistant-api.js +1005 -0
  650. package/neural-interface/lib/assistant-attachments.js +121 -0
  651. package/neural-interface/lib/assistant-brains/claude.js +207 -0
  652. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +43 -0
  653. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +56 -0
  654. package/neural-interface/lib/assistant-brains/codex.js +213 -0
  655. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +47 -0
  656. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +62 -0
  657. package/neural-interface/lib/assistant-brains/opencode.js +320 -0
  658. package/neural-interface/lib/assistant-budget.js +318 -0
  659. package/neural-interface/lib/assistant-catalog.js +731 -0
  660. package/neural-interface/lib/assistant-clarify.js +664 -0
  661. package/neural-interface/lib/assistant-config.js +232 -0
  662. package/neural-interface/lib/assistant-dispatch.js +2744 -0
  663. package/neural-interface/lib/assistant-envelope.js +579 -0
  664. package/neural-interface/lib/assistant-evidence.js +236 -0
  665. package/neural-interface/lib/assistant-hidden-models.js +272 -0
  666. package/neural-interface/lib/assistant-jev.js +117 -0
  667. package/neural-interface/lib/assistant-judgments.js +111 -0
  668. package/neural-interface/lib/assistant-media.js +335 -0
  669. package/neural-interface/lib/assistant-memory.js +398 -0
  670. package/neural-interface/lib/assistant-persona.js +444 -0
  671. package/neural-interface/lib/assistant-plan-permissions.js +448 -0
  672. package/neural-interface/lib/assistant-playbooks/design.md +87 -0
  673. package/neural-interface/lib/assistant-playbooks.js +72 -0
  674. package/neural-interface/lib/assistant-pricing.js +159 -0
  675. package/neural-interface/lib/assistant-route-gate-codex.js +163 -0
  676. package/neural-interface/lib/assistant-route-gate.js +212 -0
  677. package/neural-interface/lib/assistant-router.js +1324 -0
  678. package/neural-interface/lib/assistant-runtime.js +3337 -0
  679. package/neural-interface/lib/assistant-task-prompt.js +472 -0
  680. package/neural-interface/lib/assistant-usage.js +1688 -0
  681. package/neural-interface/lib/augmented-path.js +50 -0
  682. package/neural-interface/lib/automation-storage-migration.js +54 -0
  683. package/neural-interface/lib/backup-service.js +851 -0
  684. package/neural-interface/lib/backup-zip-reader.js +262 -0
  685. package/neural-interface/lib/browser-assist-api.js +41 -0
  686. package/neural-interface/lib/browser-console.js +176 -0
  687. package/neural-interface/lib/browser-execution.js +241 -0
  688. package/neural-interface/lib/browser-extraction.js +191 -0
  689. package/neural-interface/lib/browser-screenshot.js +399 -0
  690. package/neural-interface/lib/browser-semantic-context.js +639 -0
  691. package/neural-interface/lib/browser-snapshots.js +192 -0
  692. package/neural-interface/lib/browser-tab-recovery.js +119 -0
  693. package/neural-interface/lib/browser-targets.js +70 -0
  694. package/neural-interface/lib/browser-tool-policy.js +926 -0
  695. package/neural-interface/lib/claude-accounts.js +230 -0
  696. package/neural-interface/lib/claude-agent-bridge.js +3296 -0
  697. package/neural-interface/lib/claude-bypass-policy.js +99 -0
  698. package/neural-interface/lib/claude-history.js +429 -0
  699. package/neural-interface/lib/claude-model-catalog.js +483 -0
  700. package/neural-interface/lib/claude-panel-requests.js +165 -0
  701. package/neural-interface/lib/claude-panel-session.js +272 -0
  702. package/neural-interface/lib/claude-permission-rules.js +153 -0
  703. package/neural-interface/lib/claude-session-cleanup.js +71 -0
  704. package/neural-interface/lib/claude-session-list.js +82 -0
  705. package/neural-interface/lib/claude-session-ops.js +140 -0
  706. package/neural-interface/lib/claude-settings-view.js +51 -0
  707. package/neural-interface/lib/claude-skin-connect.js +79 -0
  708. package/neural-interface/lib/claude-stall-policy.js +7 -0
  709. package/neural-interface/lib/claude-temporary.js +435 -0
  710. package/neural-interface/lib/cli-update-planner.js +312 -0
  711. package/neural-interface/lib/codex-capabilities.d.ts +41 -0
  712. package/neural-interface/lib/codex-capabilities.js +148 -0
  713. package/neural-interface/lib/codex-collaboration-mode.js +17 -0
  714. package/neural-interface/lib/codex-compat-rpc.js +241 -0
  715. package/neural-interface/lib/codex-mcp-approvals.js +56 -0
  716. package/neural-interface/lib/codex-mcp-apps.js +47 -0
  717. package/neural-interface/lib/codex-model-context.js +202 -0
  718. package/neural-interface/lib/codex-plan-store.js +71 -0
  719. package/neural-interface/lib/codex-request-journal.js +242 -0
  720. package/neural-interface/lib/codex-runtime-path.js +213 -0
  721. package/neural-interface/lib/codex-session-catalog.js +231 -0
  722. package/neural-interface/lib/codex-session-settings.js +46 -0
  723. package/neural-interface/lib/codex-sidepanel-permissions.js +110 -0
  724. package/neural-interface/lib/codex-thread-history.js +165 -0
  725. package/neural-interface/lib/codex-user-verification.js +34 -0
  726. package/neural-interface/lib/codex-writer-lifecycle.js +152 -0
  727. package/neural-interface/lib/confined-fs.js +256 -0
  728. package/neural-interface/lib/db.js +922 -0
  729. package/neural-interface/lib/design-md.js +9 -0
  730. package/neural-interface/lib/desktop/api.js +92 -0
  731. package/neural-interface/lib/desktop/audit.js +62 -0
  732. package/neural-interface/lib/desktop/build.js +423 -0
  733. package/neural-interface/lib/desktop/config.js +606 -0
  734. package/neural-interface/lib/desktop/coords.js +76 -0
  735. package/neural-interface/lib/desktop/fake-helper.js +1192 -0
  736. package/neural-interface/lib/desktop/frames.js +77 -0
  737. package/neural-interface/lib/desktop/grants.js +84 -0
  738. package/neural-interface/lib/desktop/guards.js +170 -0
  739. package/neural-interface/lib/desktop/helper/SynabunDesktop.swift +3056 -0
  740. package/neural-interface/lib/desktop/lease.js +51 -0
  741. package/neural-interface/lib/desktop/manager.js +545 -0
  742. package/neural-interface/lib/desktop/press-contexts.js +103 -0
  743. package/neural-interface/lib/desktop/protocol.js +495 -0
  744. package/neural-interface/lib/desktop/service.js +1202 -0
  745. package/neural-interface/lib/effort-levels.js +148 -0
  746. package/neural-interface/lib/event-loop-monitor.js +67 -0
  747. package/neural-interface/lib/exec-loop-command.js +209 -0
  748. package/neural-interface/lib/folder-picker.ps1 +77 -0
  749. package/neural-interface/lib/http-guards.js +287 -0
  750. package/neural-interface/lib/jsonl-lines.js +94 -0
  751. package/neural-interface/lib/loop-logger.js +127 -0
  752. package/neural-interface/lib/mcp-client-config.js +133 -0
  753. package/neural-interface/lib/mcp-installer.js +487 -0
  754. package/neural-interface/lib/mcp-profile-refresh-coordinator.js +78 -0
  755. package/neural-interface/lib/mcp-profile-servers.js +120 -0
  756. package/neural-interface/lib/memo-cache.js +84 -0
  757. package/neural-interface/lib/memory-api.js +758 -0
  758. package/neural-interface/lib/memory-map-api.js +1150 -0
  759. package/neural-interface/lib/memory-map-layout.js +872 -0
  760. package/neural-interface/lib/memory-map-worker.js +165 -0
  761. package/neural-interface/lib/memory-session-capture.js +81 -0
  762. package/neural-interface/lib/memory-timers.js +62 -0
  763. package/neural-interface/lib/morelogin.js +301 -0
  764. package/neural-interface/lib/native-binary-runtime.js +650 -0
  765. package/neural-interface/lib/native-loop-goal.js +33 -0
  766. package/neural-interface/lib/native-loop-providers.js +1016 -0
  767. package/neural-interface/lib/native-loop-runtime.js +747 -0
  768. package/neural-interface/lib/opencode-hidden-models.js +113 -0
  769. package/neural-interface/lib/opencode-history.js +253 -0
  770. package/neural-interface/lib/opencode-runtime-config.js +58 -0
  771. package/neural-interface/lib/opencode-v2-client.js +486 -0
  772. package/neural-interface/lib/opencode-v2-ws-requests.js +955 -0
  773. package/neural-interface/lib/path-confine.js +213 -0
  774. package/neural-interface/lib/plugin-installer.js +264 -0
  775. package/neural-interface/lib/project-storage.js +766 -0
  776. package/neural-interface/lib/pty-host/core.js +603 -0
  777. package/neural-interface/lib/pty-host/flow.js +43 -0
  778. package/neural-interface/lib/pty-host/host.js +47 -0
  779. package/neural-interface/lib/pty-host/manager.js +360 -0
  780. package/neural-interface/lib/pty-host/protocol.js +81 -0
  781. package/neural-interface/lib/pty-host/snapshot.js +133 -0
  782. package/neural-interface/lib/remote-policy.js +582 -0
  783. package/neural-interface/lib/rulesets/api.js +175 -0
  784. package/neural-interface/lib/rulesets/fs-safe.js +245 -0
  785. package/neural-interface/lib/rulesets/installer.js +1453 -0
  786. package/neural-interface/lib/rulesets/json-edit.js +186 -0
  787. package/neural-interface/lib/rulesets/lock.js +170 -0
  788. package/neural-interface/lib/rulesets/managed-text.js +314 -0
  789. package/neural-interface/lib/rulesets/normalise.js +24 -0
  790. package/neural-interface/lib/rulesets/render.js +84 -0
  791. package/neural-interface/lib/schedule-launch-intent.js +40 -0
  792. package/neural-interface/lib/session-cache-builder.js +524 -0
  793. package/neural-interface/lib/session-chunker.js +344 -0
  794. package/neural-interface/lib/session-indexer.js +327 -0
  795. package/neural-interface/lib/session-title-generator.js +227 -0
  796. package/neural-interface/lib/skill-prompt-bridge.js +11 -0
  797. package/neural-interface/lib/style-guide/api.js +375 -0
  798. package/neural-interface/lib/style-guide/color.js +352 -0
  799. package/neural-interface/lib/style-guide/export-css.js +99 -0
  800. package/neural-interface/lib/style-guide/export-dtcg.js +209 -0
  801. package/neural-interface/lib/style-guide/export-tailwind.js +102 -0
  802. package/neural-interface/lib/style-guide/fonts.json +227 -0
  803. package/neural-interface/lib/style-guide/import.js +1268 -0
  804. package/neural-interface/lib/style-guide/pointers.js +103 -0
  805. package/neural-interface/lib/style-guide/presets.js +232 -0
  806. package/neural-interface/lib/style-guide/render-design-md.js +319 -0
  807. package/neural-interface/lib/style-guide/schema.js +848 -0
  808. package/neural-interface/lib/style-guide/security.js +43 -0
  809. package/neural-interface/lib/style-guide/store.js +491 -0
  810. package/neural-interface/lib/style-guide/summary.js +106 -0
  811. package/neural-interface/lib/style-guide/tokens.js +170 -0
  812. package/neural-interface/lib/synabun-update-plan.js +305 -0
  813. package/neural-interface/lib/synabun-version.js +179 -0
  814. package/neural-interface/lib/system-restore.js +99 -0
  815. package/neural-interface/lib/terminal-env.js +86 -0
  816. package/neural-interface/lib/ui-blob-store.js +106 -0
  817. package/neural-interface/lib/vector-cache.js +2 -0
  818. package/neural-interface/lib/virtual-socket.js +60 -0
  819. package/neural-interface/lib/whatsapp/api.js +146 -0
  820. package/neural-interface/lib/whatsapp/auth-store.js +389 -0
  821. package/neural-interface/lib/whatsapp/baileys-adapter.js +457 -0
  822. package/neural-interface/lib/whatsapp/brain.js +128 -0
  823. package/neural-interface/lib/whatsapp/bridge.js +1499 -0
  824. package/neural-interface/lib/whatsapp/cards.js +519 -0
  825. package/neural-interface/lib/whatsapp/commands.js +92 -0
  826. package/neural-interface/lib/whatsapp/config.js +291 -0
  827. package/neural-interface/lib/whatsapp/connector/entry.mjs +25 -0
  828. package/neural-interface/lib/whatsapp/connector/manifest.json +6 -0
  829. package/neural-interface/lib/whatsapp/connector/npm-shrinkwrap.json +717 -0
  830. package/neural-interface/lib/whatsapp/connector/package.json +8 -0
  831. package/neural-interface/lib/whatsapp/fake-baileys.js +444 -0
  832. package/neural-interface/lib/whatsapp/format.js +552 -0
  833. package/neural-interface/lib/whatsapp/host-core.js +1225 -0
  834. package/neural-interface/lib/whatsapp/host.js +119 -0
  835. package/neural-interface/lib/whatsapp/identity.js +288 -0
  836. package/neural-interface/lib/whatsapp/inbound.js +149 -0
  837. package/neural-interface/lib/whatsapp/installer.js +562 -0
  838. package/neural-interface/lib/whatsapp/manager.js +638 -0
  839. package/neural-interface/lib/whatsapp/paths.js +81 -0
  840. package/neural-interface/lib/whatsapp/protocol.js +327 -0
  841. package/neural-interface/lib/whatsapp/qr-svg.js +57 -0
  842. package/neural-interface/lib/whatsapp/redact.js +130 -0
  843. package/neural-interface/lib/whatsapp/service.js +1305 -0
  844. package/neural-interface/lib/whatsapp/vendor/qrcodegen.js +848 -0
  845. package/neural-interface/package-lock.json +4682 -0
  846. package/neural-interface/package.json +58 -27
  847. package/neural-interface/public/apple-touch-icon.png +0 -0
  848. package/neural-interface/public/category-logos/synabun.png +0 -0
  849. package/neural-interface/public/claude.svg +1 -0
  850. package/neural-interface/public/favicon-16x16.png +0 -0
  851. package/neural-interface/public/favicon-32x32.png +0 -0
  852. package/neural-interface/public/icon-192.png +0 -0
  853. package/neural-interface/public/icon-512.png +0 -0
  854. package/neural-interface/public/index.html +32 -182
  855. package/neural-interface/public/index2d.html +2 -2
  856. package/neural-interface/public/logo-synabun.png +0 -0
  857. package/neural-interface/public/logoHD.png +0 -0
  858. package/neural-interface/public/mcp.svg +4 -0
  859. package/neural-interface/public/offline.html +335 -299
  860. package/neural-interface/public/onboarding.html +692 -155
  861. package/neural-interface/public/opencode-logo-dark-square.svg +1 -0
  862. package/neural-interface/public/shared/agent-runtime-options.js +596 -0
  863. package/neural-interface/public/shared/api.js +339 -28
  864. package/neural-interface/public/shared/assistant/asst-accounts.js +318 -0
  865. package/neural-interface/public/shared/assistant/asst-agents-dock.js +296 -0
  866. package/neural-interface/public/shared/assistant/asst-api.js +270 -0
  867. package/neural-interface/public/shared/assistant/asst-brain-picker.js +744 -0
  868. package/neural-interface/public/shared/assistant/asst-budget.js +333 -0
  869. package/neural-interface/public/shared/assistant/asst-composer.js +656 -0
  870. package/neural-interface/public/shared/assistant/asst-computer.js +803 -0
  871. package/neural-interface/public/shared/assistant/asst-control.js +1018 -0
  872. package/neural-interface/public/shared/assistant/asst-hosts.js +262 -0
  873. package/neural-interface/public/shared/assistant/asst-mascot.js +311 -0
  874. package/neural-interface/public/shared/assistant/asst-menu.js +375 -0
  875. package/neural-interface/public/shared/assistant/asst-panel.js +1845 -0
  876. package/neural-interface/public/shared/assistant/asst-rack-styles.js +589 -0
  877. package/neural-interface/public/shared/assistant/asst-render.js +3296 -0
  878. package/neural-interface/public/shared/assistant/asst-route.js +1653 -0
  879. package/neural-interface/public/shared/assistant/asst-roving.js +75 -0
  880. package/neural-interface/public/shared/assistant/asst-sanitize.js +117 -0
  881. package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +488 -0
  882. package/neural-interface/public/shared/assistant/asst-sidepanel.js +675 -0
  883. package/neural-interface/public/shared/assistant/asst-slash.js +258 -0
  884. package/neural-interface/public/shared/assistant/asst-state.js +727 -0
  885. package/neural-interface/public/shared/assistant/asst-styles.js +1684 -0
  886. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +1017 -0
  887. package/neural-interface/public/shared/assistant/asst-usage.js +444 -0
  888. package/neural-interface/public/shared/assistant/asst-ws.js +175 -0
  889. package/neural-interface/public/shared/automation-context.js +75 -0
  890. package/neural-interface/public/shared/blob-store.js +99 -0
  891. package/neural-interface/public/shared/cdx/cdx-capabilities.js +118 -0
  892. package/neural-interface/public/shared/cdx/cdx-cards.js +37 -0
  893. package/neural-interface/public/shared/cdx/cdx-commands.js +171 -0
  894. package/neural-interface/public/shared/cdx/cdx-context-menu.js +402 -0
  895. package/neural-interface/public/shared/cdx/cdx-context-model.js +220 -0
  896. package/neural-interface/public/shared/cdx/cdx-features.js +408 -0
  897. package/neural-interface/public/shared/cdx/cdx-icons.js +89 -0
  898. package/neural-interface/public/shared/cdx/cdx-mcp-app-host.js +414 -0
  899. package/neural-interface/public/shared/cdx/cdx-mcp-app-proxy.html +6 -0
  900. package/neural-interface/public/shared/cdx/cdx-mcp-app-proxy.js +56 -0
  901. package/neural-interface/public/shared/cdx/cdx-mcp-app.js +178 -0
  902. package/neural-interface/public/shared/cdx/cdx-model-catalog.js +120 -0
  903. package/neural-interface/public/shared/cdx/cdx-output-model.js +261 -0
  904. package/neural-interface/public/shared/cdx/cdx-output.js +382 -0
  905. package/neural-interface/public/shared/cdx/cdx-panel.js +1372 -0
  906. package/neural-interface/public/shared/cdx/cdx-plan.js +266 -0
  907. package/neural-interface/public/shared/cdx/cdx-protocol.js +682 -0
  908. package/neural-interface/public/shared/cdx/cdx-render.js +3381 -0
  909. package/neural-interface/public/shared/cdx/cdx-requests.js +1136 -0
  910. package/neural-interface/public/shared/cdx/cdx-styles.js +3251 -0
  911. package/neural-interface/public/shared/cdx/cdx-tabs.js +7992 -0
  912. package/neural-interface/public/shared/cdx/cdx-ws.js +363 -0
  913. package/neural-interface/public/shared/cli-status.js +169 -0
  914. package/neural-interface/public/shared/constants.js +22 -5
  915. package/neural-interface/public/shared/cp/cp-agents.js +186 -0
  916. package/neural-interface/public/shared/cp/cp-bash.js +252 -0
  917. package/neural-interface/public/shared/cp/cp-compose.js +159 -0
  918. package/neural-interface/public/shared/cp/cp-context-menu.js +309 -0
  919. package/neural-interface/public/shared/cp/cp-context-model.js +157 -0
  920. package/neural-interface/public/shared/cp/cp-context-window.js +80 -0
  921. package/neural-interface/public/shared/cp/cp-ctx.js +11 -0
  922. package/neural-interface/public/shared/cp/cp-diff.js +255 -0
  923. package/neural-interface/public/shared/cp/cp-event-rows.js +637 -0
  924. package/neural-interface/public/shared/cp/cp-events.js +846 -0
  925. package/neural-interface/public/shared/cp/cp-markdown.js +104 -0
  926. package/neural-interface/public/shared/cp/cp-permission-model.js +905 -0
  927. package/neural-interface/public/shared/cp/cp-permissions.js +626 -0
  928. package/neural-interface/public/shared/cp/cp-rehydrate.js +113 -0
  929. package/neural-interface/public/shared/cp/cp-restore.js +224 -0
  930. package/neural-interface/public/shared/cp/cp-session-model.js +338 -0
  931. package/neural-interface/public/shared/cp/cp-session-settings.js +254 -0
  932. package/neural-interface/public/shared/cp/cp-sessions.js +227 -0
  933. package/neural-interface/public/shared/cp/cp-statusline.js +153 -0
  934. package/neural-interface/public/shared/cp/cp-styles.js +349 -0
  935. package/neural-interface/public/shared/cp/cp-tasks-model.js +282 -0
  936. package/neural-interface/public/shared/cp/cp-tasks.js +107 -0
  937. package/neural-interface/public/shared/cp/cp-temporary-view.js +56 -0
  938. package/neural-interface/public/shared/cp/cp-temporary.js +231 -0
  939. package/neural-interface/public/shared/cp/cp-tool-cards.js +326 -0
  940. package/neural-interface/public/shared/cp/cp-tool-results.js +618 -0
  941. package/neural-interface/public/shared/cp/cp-usage-model.js +124 -0
  942. package/neural-interface/public/shared/graph-activity.js +68 -0
  943. package/neural-interface/public/shared/html-shell.js +107 -12
  944. package/neural-interface/public/shared/i18n.js +132 -34
  945. package/neural-interface/public/shared/node-positions-store.js +44 -0
  946. package/neural-interface/public/shared/ocp-hidden-models.js +116 -0
  947. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +355 -0
  948. package/neural-interface/public/shared/ocp-v2/ocp-v2-autoaccept-button.js +35 -0
  949. package/neural-interface/public/shared/ocp-v2/ocp-v2-automation-ownership.js +72 -0
  950. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +280 -0
  951. package/neural-interface/public/shared/ocp-v2/ocp-v2-caps.js +67 -0
  952. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +179 -0
  953. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes.js +164 -0
  954. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +725 -0
  955. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +75 -0
  956. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +1204 -0
  957. package/neural-interface/public/shared/ocp-v2/ocp-v2-confirm-logic.js +117 -0
  958. package/neural-interface/public/shared/ocp-v2/ocp-v2-confirm.js +59 -0
  959. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-gauge.js +252 -0
  960. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +478 -0
  961. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +179 -0
  962. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +233 -0
  963. package/neural-interface/public/shared/ocp-v2/ocp-v2-icons.js +30 -0
  964. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +1065 -0
  965. package/neural-interface/public/shared/ocp-v2/ocp-v2-mention-hints.js +182 -0
  966. package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +655 -0
  967. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +2636 -0
  968. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +601 -0
  969. package/neural-interface/public/shared/ocp-v2/ocp-v2-profile-inheritance.js +78 -0
  970. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +649 -0
  971. package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +35 -0
  972. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +357 -0
  973. package/neural-interface/public/shared/ocp-v2/ocp-v2-render-logic.js +189 -0
  974. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +1869 -0
  975. package/neural-interface/public/shared/ocp-v2/ocp-v2-select.js +42 -0
  976. package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +117 -0
  977. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +1714 -0
  978. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +468 -0
  979. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-widgets.js +63 -0
  980. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +366 -0
  981. package/neural-interface/public/shared/ocp-v2/ocp-v2-settings-link.js +19 -0
  982. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +343 -0
  983. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +746 -0
  984. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +347 -0
  985. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +338 -0
  986. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +3543 -0
  987. package/neural-interface/public/shared/ocp-v2/ocp-v2-subagents-logic.js +482 -0
  988. package/neural-interface/public/shared/ocp-v2/ocp-v2-tools-logic.js +351 -0
  989. package/neural-interface/public/shared/ocp-v2/ocp-v2-tools.js +168 -0
  990. package/neural-interface/public/shared/ocp-v2/ocp-v2-variantpicker.js +315 -0
  991. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +545 -0
  992. package/neural-interface/public/shared/provider-icons.js +30 -0
  993. package/neural-interface/public/shared/session-title.js +100 -0
  994. package/neural-interface/public/shared/settings/settings-ia.js +389 -0
  995. package/neural-interface/public/shared/settings/settings-kit.js +234 -0
  996. package/neural-interface/public/shared/settings/settings-search.js +463 -0
  997. package/neural-interface/public/shared/settings/settings-shell.js +570 -0
  998. package/neural-interface/public/shared/state.js +5 -0
  999. package/neural-interface/public/shared/storage.js +50 -7
  1000. package/neural-interface/public/shared/styleguide/sg-agents.js +42 -0
  1001. package/neural-interface/public/shared/styleguide/sg-brand.js +4 -0
  1002. package/neural-interface/public/shared/styleguide/sg-color-client.js +388 -0
  1003. package/neural-interface/public/shared/styleguide/sg-colors.js +148 -0
  1004. package/neural-interface/public/shared/styleguide/sg-components.js +35 -0
  1005. package/neural-interface/public/shared/styleguide/sg-history.js +41 -0
  1006. package/neural-interface/public/shared/styleguide/sg-icons-logo.js +38 -0
  1007. package/neural-interface/public/shared/styleguide/sg-imagery.js +4 -0
  1008. package/neural-interface/public/shared/styleguide/sg-import-export.js +47 -0
  1009. package/neural-interface/public/shared/styleguide/sg-kit.js +281 -0
  1010. package/neural-interface/public/shared/styleguide/sg-labels.js +318 -0
  1011. package/neural-interface/public/shared/styleguide/sg-layout.js +4 -0
  1012. package/neural-interface/public/shared/styleguide/sg-motion.js +5 -0
  1013. package/neural-interface/public/shared/styleguide/sg-panel.js +229 -0
  1014. package/neural-interface/public/shared/styleguide/sg-preview.js +138 -0
  1015. package/neural-interface/public/shared/styleguide/sg-rules.js +9 -0
  1016. package/neural-interface/public/shared/styleguide/sg-shape.js +24 -0
  1017. package/neural-interface/public/shared/styleguide/sg-state.js +132 -0
  1018. package/neural-interface/public/shared/styleguide/sg-typography.js +124 -0
  1019. package/neural-interface/public/shared/styles.css +7883 -1155
  1020. package/neural-interface/public/shared/synabun-mascot.js +1625 -0
  1021. package/neural-interface/public/shared/synabun-ticker.js +376 -0
  1022. package/neural-interface/public/shared/term-renderer.js +343 -0
  1023. package/neural-interface/public/shared/ui-assistant-panel.js +10 -0
  1024. package/neural-interface/public/shared/ui-assistant.js +77 -0
  1025. package/neural-interface/public/shared/ui-automation-studio.js +5105 -3573
  1026. package/neural-interface/public/shared/ui-claude-panel.js +11685 -6333
  1027. package/neural-interface/public/shared/ui-codex-panel.js +12 -0
  1028. package/neural-interface/public/shared/ui-cost-widget.js +55 -34
  1029. package/neural-interface/public/shared/ui-detail.js +77 -14
  1030. package/neural-interface/public/shared/ui-explorer.js +360 -181
  1031. package/neural-interface/public/shared/ui-file-explorer.js +485 -107
  1032. package/neural-interface/public/shared/ui-help.js +2 -2
  1033. package/neural-interface/public/shared/ui-image-gallery.js +45 -21
  1034. package/neural-interface/public/shared/ui-judgments.js +1346 -0
  1035. package/neural-interface/public/shared/ui-keybinds.js +3 -2
  1036. package/neural-interface/public/shared/ui-link.js +105 -51
  1037. package/neural-interface/public/shared/ui-loading.js +4 -4
  1038. package/neural-interface/public/shared/ui-memory-maintenance.js +111 -0
  1039. package/neural-interface/public/shared/ui-menubar.js +89 -4
  1040. package/neural-interface/public/shared/ui-native-loop-router-state.js +94 -0
  1041. package/neural-interface/public/shared/ui-native-loop-router.js +493 -0
  1042. package/neural-interface/public/shared/ui-native-window-id.js +7 -0
  1043. package/neural-interface/public/shared/ui-navbar.js +357 -222
  1044. package/neural-interface/public/shared/ui-notifications.js +58 -12
  1045. package/neural-interface/public/shared/ui-opencode-panel-v2.js +17 -0
  1046. package/neural-interface/public/shared/ui-panels.js +15 -7
  1047. package/neural-interface/public/shared/ui-resume.js +823 -653
  1048. package/neural-interface/public/shared/ui-schedule-queue.js +172 -0
  1049. package/neural-interface/public/shared/ui-schedules-studio.js +1882 -0
  1050. package/neural-interface/public/shared/ui-scroll-follow.js +194 -0
  1051. package/neural-interface/public/shared/ui-sessions.js +789 -34
  1052. package/neural-interface/public/shared/ui-settings.js +5793 -2190
  1053. package/neural-interface/public/shared/ui-sidepanel-layout.js +109 -0
  1054. package/neural-interface/public/shared/ui-sidepanel-runtime.js +19 -0
  1055. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +40 -0
  1056. package/neural-interface/public/shared/ui-sidepanel-sessions.js +221 -0
  1057. package/neural-interface/public/shared/ui-sidepanel-state.js +131 -0
  1058. package/neural-interface/public/shared/ui-sidepanel-tray.js +547 -0
  1059. package/neural-interface/public/shared/ui-sidepanel-windows.js +387 -0
  1060. package/neural-interface/public/shared/ui-skills.js +18 -32
  1061. package/neural-interface/public/shared/ui-styleguide.js +35 -0
  1062. package/neural-interface/public/shared/ui-sync.js +87 -0
  1063. package/neural-interface/public/shared/ui-terminal.js +7877 -6385
  1064. package/neural-interface/public/shared/ui-tool-updates.js +292 -0
  1065. package/neural-interface/public/shared/ui-tooltip.js +156 -15
  1066. package/neural-interface/public/shared/ui-tutorial-draw.js +119 -683
  1067. package/neural-interface/public/shared/ui-tutorial-layout.js +135 -0
  1068. package/neural-interface/public/shared/ui-tutorial-steps.js +59 -2209
  1069. package/neural-interface/public/shared/ui-tutorial.css +102 -0
  1070. package/neural-interface/public/shared/ui-tutorial.js +557 -1076
  1071. package/neural-interface/public/shared/ui-update.js +420 -68
  1072. package/neural-interface/public/shared/ui-whatsapp.js +941 -0
  1073. package/neural-interface/public/shared/ui-whiteboard.js +1076 -233
  1074. package/neural-interface/public/shared/ui-workspaces.js +2 -1
  1075. package/neural-interface/public/shared/utils.js +58 -0
  1076. package/neural-interface/public/shared/vterm-buffer.js +15 -5
  1077. package/neural-interface/public/shared/whatsapp/wa-phone.js +65 -0
  1078. package/neural-interface/public/shared/whatsapp/wa-view.js +411 -0
  1079. package/neural-interface/public/sidepanel-session.html +18 -0
  1080. package/neural-interface/public/sw.js +8 -2
  1081. package/neural-interface/public/synabun.png +0 -0
  1082. package/neural-interface/public/variant/2d/graph.js +1707 -1635
  1083. package/neural-interface/public/variant/2d/lasso.js +2 -1
  1084. package/neural-interface/public/variant/2d/layout.js +595 -594
  1085. package/neural-interface/public/variant/2d/main.js +556 -521
  1086. package/neural-interface/public/variant/2d/settings-gfx.js +18 -24
  1087. package/neural-interface/public/variant/3d/main.js +516 -500
  1088. package/neural-interface/public/variant/3d/map-controls.js +144 -0
  1089. package/neural-interface/public/variant/3d/map-data.js +251 -0
  1090. package/neural-interface/public/variant/3d/map-labels.js +241 -0
  1091. package/neural-interface/public/variant/3d/map-pick.js +90 -0
  1092. package/neural-interface/public/variant/3d/map-renderer.js +521 -0
  1093. package/neural-interface/public/variant/3d/map-shaders.js +158 -0
  1094. package/neural-interface/public/variant/3d/map-view.js +618 -0
  1095. package/neural-interface/public/vendor/three/manifest.json +3 -0
  1096. package/neural-interface/public/vendor/three/three-0.169.0.min.js +3844 -0
  1097. package/neural-interface/public/vendor/xterm/manifest.json +3 -0
  1098. package/neural-interface/public/vendor/xterm/xterm-6.0.0.css +285 -0
  1099. package/neural-interface/public/vendor/xterm/xterm-6.0.0.min.js +192 -0
  1100. package/neural-interface/scripts/build-vendor.mjs +64 -0
  1101. package/neural-interface/scripts/codex-protocol-fixture.mjs +83 -0
  1102. package/neural-interface/scripts/rebuild-pty.js +37 -23
  1103. package/neural-interface/scripts/vendor-three-entry.js +24 -0
  1104. package/neural-interface/scripts/vendor-xterm-entry.js +8 -0
  1105. package/neural-interface/server.js +33948 -15000
  1106. package/neural-interface/skins/default/skin.css +90 -0
  1107. package/neural-interface/skins/default/skin.json +8 -0
  1108. package/neural-interface/templates/rulesets/README.md +9 -0
  1109. package/neural-interface/templates/rulesets/claude.md +3 -0
  1110. package/neural-interface/templates/rulesets/codex.md +2 -0
  1111. package/neural-interface/templates/rulesets/coexistence.md +6 -0
  1112. package/neural-interface/templates/rulesets/core.md +26 -0
  1113. package/neural-interface/templates/rulesets/cursor.md +2 -0
  1114. package/neural-interface/templates/rulesets/gemini.md +2 -0
  1115. package/neural-interface/templates/rulesets/legacy-hashes.json +227 -0
  1116. package/neural-interface/templates/rulesets/manifest.json +12 -0
  1117. package/neural-interface/templates/rulesets/opencode.md +2 -0
  1118. package/package.json +119 -53
  1119. package/postinstall.js +125 -26
  1120. package/preuninstall.js +187 -0
  1121. package/scripts/audit-claude-hook-usage.mjs +12 -0
  1122. package/scripts/audit-memory-duplicates.mjs +44 -0
  1123. package/scripts/measure-claude-hook-overhead.mjs +77 -0
  1124. package/scripts/recover-user-config.mjs +254 -0
  1125. package/scripts/restore-data-home-from-backup.mjs +239 -0
  1126. package/setup.js +804 -260
  1127. package/skills/leonardo/SKILL.md +19 -10
  1128. package/skills/leonardo/modules/image-prompter.md +18 -12
  1129. package/skills/leonardo/modules/video-prompter.md +19 -13
  1130. package/skills/synabun/SKILL.md +209 -185
  1131. package/skills/synabun/icon.png +0 -0
  1132. package/skills/synabun/modules/audit.md +380 -374
  1133. package/skills/synabun/modules/changelog.md +15 -6
  1134. package/skills/synabun/modules/idea.md +148 -144
  1135. package/skills/synabun/modules/memorize.md +13 -4
  1136. package/skills/synabun/modules/schedule.md +198 -0
  1137. package/skills/synabun/synabunicon.png +0 -0
  1138. package/updater.cmd +31 -0
  1139. package/updater.mjs +382 -0
  1140. package/updater.sh +29 -0
  1141. package/hooks/claude-code/test-user-learning.mjs +0 -531
  1142. package/neural-interface/public/claude-chat.css +0 -878
  1143. package/neural-interface/public/claude-chat.html +0 -92
  1144. package/neural-interface/public/claude-chat.js +0 -1168
  1145. package/neural-interface/public/index-legacy.html +0 -14677
  1146. package/neural-interface/public/index2d-legacy.html +0 -8193
  1147. package/neural-interface/public/shared/html-term-renderer.js +0 -1022
  1148. package/neural-interface/public/variant/3d/background.js +0 -884
  1149. package/neural-interface/public/variant/3d/camera.js +0 -661
  1150. package/neural-interface/public/variant/3d/gfx.js +0 -116
  1151. package/neural-interface/public/variant/3d/graph.js +0 -2399
  1152. package/neural-interface/public/variant/3d/settings-gfx.js +0 -228
  1153. package/neural-interface/templates/CLAUDE-template.md +0 -211
@@ -1,368 +1,1228 @@
1
- /**
2
- * Shared utilities for SynaBun Claude Code hooks.
3
- *
4
- * Extracted to avoid duplication across session-start, prompt-submit,
5
- * post-remember, pre-compact, and stop hooks.
6
- */
7
-
8
- import { readFileSync, existsSync, writeFileSync, readdirSync, unlinkSync } from 'node:fs';
9
- import { dirname, join, basename } from 'node:path';
10
- import { fileURLToPath } from 'node:url';
11
-
12
- const __dirname = dirname(fileURLToPath(import.meta.url));
13
- export const DATA_DIR = join(__dirname, '..', '..', 'data');
14
- export const MCP_DATA_DIR = join(__dirname, '..', '..', 'mcp-server', 'data');
15
- export const ENV_PATH = join(__dirname, '..', '..', '.env');
16
- export const HOOK_FEATURES_PATH = join(DATA_DIR, 'hook-features.json');
17
- export const PENDING_REMEMBER_DIR = join(DATA_DIR, 'pending-remember');
18
-
19
- // --- Stdin ---
20
-
21
- export function readStdin() {
22
- return new Promise((resolve) => {
23
- if (process.stdin.isTTY) return resolve('{}');
24
- let data = '';
25
- process.stdin.setEncoding('utf8');
26
- process.stdin.on('data', (chunk) => { data += chunk; });
27
- process.stdin.on('end', () => resolve(data));
28
- setTimeout(() => resolve(data || '{}'), 2000);
29
- });
30
- }
31
-
32
- // --- Hook features ---
33
-
34
- export function getHookFeatures() {
35
- try {
36
- if (!existsSync(HOOK_FEATURES_PATH)) return {};
37
- return JSON.parse(readFileSync(HOOK_FEATURES_PATH, 'utf-8'));
38
- } catch { return {}; }
39
- }
40
-
41
- // --- Environment / connection ---
42
-
43
- function parseEnvFile(filePath) {
44
- try {
45
- const content = readFileSync(filePath, 'utf-8');
46
- const vars = {};
47
- for (const line of content.split('\n')) {
48
- const trimmed = line.trim();
49
- if (!trimmed || trimmed.startsWith('#')) continue;
50
- const eq = trimmed.indexOf('=');
51
- if (eq === -1) continue;
52
- vars[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
53
- }
54
- return vars;
55
- } catch { return {}; }
56
- }
57
-
58
- export function getActiveConnectionId() {
59
- const vars = parseEnvFile(ENV_PATH);
60
- return vars.SYNABUN_ACTIVE_CONNECTION || vars.QDRANT_ACTIVE || 'default';
61
- }
62
-
63
- export function getCategoriesPath() {
64
- const connId = getActiveConnectionId();
65
- return join(MCP_DATA_DIR, `custom-categories-${connId}.json`);
66
- }
67
-
68
- /**
69
- * Returns the path to the MCP server's categories file (no connection suffix).
70
- * This is the file the MCP server watches — hooks that need the MCP server to
71
- * pick up changes (e.g., schema refresh) MUST write to this file.
72
- */
73
- export function getMcpCategoriesPath() {
74
- return join(MCP_DATA_DIR, 'custom-categories.json');
75
- }
76
-
77
- // --- Project detection (reads from claude-code-projects.json) ---
78
-
79
- const PROJECTS_PATH = join(DATA_DIR, 'claude-code-projects.json');
80
-
81
- export function loadRegisteredProjects() {
82
- try {
83
- if (!existsSync(PROJECTS_PATH)) return [];
84
- return JSON.parse(readFileSync(PROJECTS_PATH, 'utf-8'));
85
- } catch { return []; }
86
- }
87
-
88
- export function normalizeLabel(label) {
89
- return label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
90
- }
91
-
92
- export function detectProject(cwd) {
93
- if (!cwd) return 'global';
94
- const lower = cwd.toLowerCase().replace(/\\/g, '/');
95
- const projects = loadRegisteredProjects();
96
-
97
- // Sort by path length descending — most specific match wins
98
- const sorted = projects
99
- .map((p) => ({
100
- path: p.path.toLowerCase().replace(/\\/g, '/'),
101
- label: normalizeLabel(p.label),
102
- }))
103
- .sort((a, b) => b.path.length - a.path.length);
104
-
105
- // 1. Exact path prefix match (cwd is inside a registered project)
106
- for (const p of sorted) {
107
- if (lower.startsWith(p.path + '/') || lower === p.path) return p.label;
108
- }
109
-
110
- // 2. Substring match — cwd folder name contains a registered project's folder name
111
- // e.g. "SynaBunWebsite" contains "synabun" → matches Synabun project
112
- for (const p of sorted) {
113
- const projFolder = basename(p.path).toLowerCase();
114
- if (lower.includes(projFolder)) return p.label;
115
- }
116
-
117
- // 3. Fallback to directory basename
118
- const base = basename(cwd).toLowerCase().replace(/[^a-z0-9-]/g, '-');
119
- return base || 'global';
120
- }
121
-
122
- // --- Category tree ---
123
-
124
- export function loadCategories() {
125
- try {
126
- const data = JSON.parse(readFileSync(getCategoriesPath(), 'utf-8'));
127
- if (data.version === 1 && Array.isArray(data.categories)) {
128
- return data.categories;
129
- }
130
- } catch { /* no categories available */ }
131
- return [];
132
- }
133
-
134
- export function buildCategoryTree(categories) {
135
- if (!categories || categories.length === 0) {
136
- return '(No categories defined yet. Use category_create to set up your first category.)';
137
- }
138
-
139
- const parents = categories.filter((c) => c.is_parent);
140
- const children = categories.filter((c) => c.parent);
141
- const standalone = categories.filter((c) => !c.is_parent && !c.parent);
142
-
143
- const lines = [];
144
-
145
- for (const parent of parents) {
146
- const kids = children.filter((c) => c.parent === parent.name);
147
- lines.push(`${parent.name} (PARENT) — ${parent.description}`);
148
- for (const kid of kids) {
149
- lines.push(` └─ ${kid.name} — ${kid.description}`);
150
- }
151
- if (kids.length === 0) {
152
- lines.push(` (no children yet)`);
153
- }
154
- }
155
-
156
- if (standalone.length > 0) {
157
- for (const cat of standalone) {
158
- lines.push(`${cat.name} — ${cat.description}`);
159
- }
160
- }
161
-
162
- return lines.join('\n');
163
- }
164
-
165
- // --- Auto-category creation ---
166
-
167
- /**
168
- * Standalone categories required by hooks. Always created regardless of projects.
169
- */
170
- const STANDALONE_DEFAULTS = [
171
- { name: 'conversations', description: 'Session summaries and conversation indexing' },
172
- { name: 'communication-style', description: 'User communication patterns and preferences' },
173
- { name: 'plans', description: 'Implementation plans stored after plan mode approval', is_parent: true, color: '#06d6a0' },
174
- ];
175
-
176
- /**
177
- * Default child categories for every registered project.
178
- * Created under a project parent, never standalone.
179
- */
180
- const PROJECT_CHILDREN = [
181
- { suffix: 'project', description: 'General project knowledge, decisions, and milestones' },
182
- { suffix: 'architecture', description: 'System design, tech stack, data flow, and component architecture' },
183
- { suffix: 'bugs', description: 'Bug fixes, debugging sessions, and known issues' },
184
- { suffix: 'config', description: 'Configuration, deployment, environment, and infrastructure' },
185
- ];
186
-
187
- /**
188
- * Ensure all registered projects have a parent category with default children,
189
- * and that standalone hook-required categories exist.
190
- *
191
- * Writes to the MCP server's categories file (no connection suffix) so the
192
- * MCP server's file watcher picks up changes and refreshes tool schemas.
193
- *
194
- * Idempotent: only adds missing categories, never modifies or removes existing ones.
195
- *
196
- * @returns {{ created: string[], total: number }}
197
- */
198
- export function ensureProjectCategories() {
199
- const projects = loadRegisteredProjects();
200
- const catPath = getMcpCategoriesPath();
201
-
202
- let data;
203
- try {
204
- data = JSON.parse(readFileSync(catPath, 'utf-8'));
205
- } catch {
206
- data = { version: 1, categories: [] };
207
- }
208
-
209
- const categories = data.categories || [];
210
- const existingNames = new Set(categories.map((c) => c.name));
211
- const created = [];
212
- const now = new Date().toISOString();
213
-
214
- // 1. Ensure standalone defaults exist
215
- for (const def of STANDALONE_DEFAULTS) {
216
- if (!existingNames.has(def.name)) {
217
- const cat = { name: def.name, description: def.description, created_at: now };
218
- if (def.is_parent) cat.is_parent = true;
219
- if (def.color) cat.color = def.color;
220
- categories.push(cat);
221
- existingNames.add(def.name);
222
- created.push(def.name);
223
- }
224
- }
225
-
226
- // 2. Ensure project parents + children exist
227
- for (const proj of projects) {
228
- let label = normalizeLabel(proj.label);
229
- // Truncate to 16 chars so children stay within 30-char limit (16 + 1 + 12 = 29)
230
- if (label.length > 16) label = label.slice(0, 16).replace(/-$/, '');
231
-
232
- // Ensure parent exists
233
- if (!existingNames.has(label)) {
234
- categories.push({
235
- name: label,
236
- description: `Knowledge and context for the ${proj.label} project`,
237
- is_parent: true,
238
- created_at: now,
239
- });
240
- existingNames.add(label);
241
- created.push(label);
242
- } else {
243
- // Parent exists — ensure it has is_parent flag
244
- const existing = categories.find((c) => c.name === label);
245
- if (existing && !existing.is_parent) existing.is_parent = true;
246
- }
247
-
248
- // Ensure each default child exists
249
- for (const child of PROJECT_CHILDREN) {
250
- const childName = `${label}-${child.suffix}`;
251
- if (!existingNames.has(childName)) {
252
- categories.push({
253
- name: childName,
254
- description: `${child.description} for ${proj.label}`,
255
- parent: label,
256
- created_at: now,
257
- });
258
- existingNames.add(childName);
259
- created.push(childName);
260
- }
261
- }
262
- }
263
-
264
- // Only write if we created something new
265
- if (created.length > 0) {
266
- data.categories = categories;
267
- writeFileSync(catPath, JSON.stringify(data, null, 2), 'utf-8');
268
- }
269
-
270
- return { created, total: categories.length };
271
- }
272
-
273
- /**
274
- * Build the full category reference block for lazy injection.
275
- * Includes tree, available names, selection rules, project scoping, and tool notes.
276
- */
277
- export function buildCategoryReference(categories, project) {
278
- const tree = buildCategoryTree(categories);
279
- const names = categories.map((c) => c.name).join(', ');
280
-
281
- return [
282
- `## SynaBun Category Reference`,
283
- ``,
284
- `### Project: ${project}`,
285
- ``,
286
- tree,
287
- ``,
288
- `Available names: ${names || '(none)'}`,
289
- ``,
290
- `### Category Selection`,
291
- ``,
292
- `1. Match existing child category by description → use it.`,
293
- `2. No child fits but parent does → \`category_create\` new child under that parent → use it.`,
294
- `3. No parent fits → \`category_create\` new parent + child → use it.`,
295
- `4. Uncertain → \`AskUserQuestion\` with 2-3 options + "Create new category" → use what user picks.`,
296
- ``,
297
- `NEVER guess. NEVER use parent directly. NEVER skip categorization.`,
298
- ``,
299
- `### Project Scoping`,
300
- ``,
301
- `- Project-specific (bugs, architecture, config) → "${project}"`,
302
- `- Universal (language features, library APIs) → "global"`,
303
- `- Cross-project (shared infra) → "shared"`,
304
- ].join('\n');
305
- }
306
-
307
- // --- Loop staleness cleanup ---
308
-
309
- const LOOP_STALE_INACTIVITY_MS = 45 * 60 * 1000; // 45 min — browser iterations can be long
310
-
311
- /**
312
- * Scan loop directory and delete stale/inactive loop files.
313
- * No history keeping — ended loops are removed immediately.
314
- *
315
- * A loop is stale if:
316
- * 1. active + exceeded its maxMinutes + 5min grace
317
- * 2. active + no iteration activity for 10 minutes (session died)
318
- *
319
- * @param {string} loopDir - path to data/loop/
320
- * @returns {{ deactivated: string[], deleted: string[] }}
321
- */
322
- export function cleanupStaleLoops(loopDir) {
323
- const deactivated = [];
324
- const deleted = [];
325
- if (!existsSync(loopDir)) return { deactivated, deleted };
326
-
327
- const now = Date.now();
328
- try {
329
- const files = readdirSync(loopDir).filter(f => f.endsWith('.json') && !f.startsWith('pending-'));
330
- for (const f of files) {
331
- const fp = join(loopDir, f);
332
- try {
333
- const loop = JSON.parse(readFileSync(fp, 'utf-8'));
334
-
335
- if (loop.active) {
336
- const lastActivity = new Date(loop.lastIterationAt || loop.startedAt || 0).getTime();
337
- const noRecentActivity = (now - lastActivity) >= LOOP_STALE_INACTIVITY_MS;
338
-
339
- if (noRecentActivity) {
340
- // Stale loop (no activity for 45 min) — delete immediately
341
- try { unlinkSync(fp); deleted.push(f); } catch { /* ok */ }
342
- }
343
- } else {
344
- // Inactive loop — delete immediately (no history keeping)
345
- try { unlinkSync(fp); deleted.push(f); } catch { /* ok */ }
346
- }
347
- } catch { /* skip corrupt files */ }
348
- }
349
- } catch { /* ok */ }
350
-
351
- // Delete stale pending-*.json files (client never claimed them)
352
- try {
353
- const pendingFiles = readdirSync(loopDir)
354
- .filter(f => f.startsWith('pending-') && f.endsWith('.json'));
355
- for (const f of pendingFiles) {
356
- const fp = join(loopDir, f);
357
- try {
358
- const loop = JSON.parse(readFileSync(fp, 'utf-8'));
359
- const lastActivity = new Date(loop.lastIterationAt || loop.startedAt || 0).getTime();
360
- if ((now - lastActivity) >= LOOP_STALE_INACTIVITY_MS) {
361
- try { unlinkSync(fp); deleted.push(f); } catch { /* ok */ }
362
- }
363
- } catch { /* skip corrupt */ }
364
- }
365
- } catch { /* ok */ }
366
-
367
- return { deactivated, deleted };
368
- }
1
+ /**
2
+ * Shared utilities for SynaBun Claude Code hooks.
3
+ *
4
+ * Extracted to avoid duplication across session-start, prompt-submit,
5
+ * post-remember, pre-compact, and stop hooks.
6
+ */
7
+
8
+ import { readFileSync, existsSync, writeFileSync, readdirSync, unlinkSync, appendFileSync, mkdirSync, statSync, renameSync, openSync, readSync, closeSync, fstatSync } from 'node:fs';
9
+ import { dirname, join, basename, resolve as pathResolve } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { getDataHome } from '../../lib/paths.js';
12
+ import { classifyPrompt, isTrivialPrompt } from './prompt-origin.mjs';
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+
16
+ // --- Neural Interface endpoint, TypeSafe kill switch, hook budgets ---
17
+
18
+ export function niUrl() {
19
+ return (process.env.SYNABUN_NI_URL || 'http://localhost:3344').replace(/\/+$/, '');
20
+ }
21
+
22
+ /**
23
+ * SYNABUN_TYPESAFE=off in the hook's own environment (the skin test and any
24
+ * CLI spawned with `typesafe:'off'` set it). Read on every call, never cached:
25
+ * no /api/hook-judge calls, recall with judge:false, no edit notices, no
26
+ * digest or plan-conflict requests.
27
+ */
28
+ export function typesafeOff() {
29
+ return String(process.env.SYNABUN_TYPESAFE ?? '').trim().toLowerCase() === 'off' || isTemporaryChat();
30
+ }
31
+
32
+ /**
33
+ * SYNABUN_TEMPORARY_CHAT=1 in the hook's own environment: a temporary chat of
34
+ * the Claude sidepanel (docs/claude-sidepanel.md), set by its bridge for that
35
+ * session's process. Nothing of such a session is stored, indexed, judged or
36
+ * asked for, and it is named to nobody: every hook takes one early branch on
37
+ * this, and a session without the marker never does. Read on every call,
38
+ * never cached.
39
+ */
40
+ export function isTemporaryChat() {
41
+ return String(process.env.SYNABUN_TEMPORARY_CHAT ?? '').trim() === '1';
42
+ }
43
+
44
+ /** A network call whose timeout would be shorter than this is skipped. */
45
+ export const MIN_CALL_MS = 250;
46
+
47
+ /**
48
+ * Deadline arithmetic for a hook with a fixed Claude Code timeout. The clock
49
+ * starts at process start (node startup and imports count against the budget),
50
+ * and `safetyMs` is left for writing the output and exiting.
51
+ * remaining() ms until the deadline
52
+ * callTimeout(reserve) timeout for a call that must leave `reserve` ms of
53
+ * work after it (150 ms of slack is always kept)
54
+ */
55
+ export function hookBudget(totalMs, safetyMs = 250) {
56
+ const startedAt = Date.now() - process.uptime() * 1000;
57
+ const deadline = startedAt + totalMs - safetyMs;
58
+ return {
59
+ deadline,
60
+ remaining: () => Math.max(0, Math.floor(deadline - Date.now())),
61
+ callTimeout: (reserveMs = 0) => Math.max(0, Math.floor(deadline - Date.now() - 150 - reserveMs)),
62
+ };
63
+ }
64
+
65
+ /**
66
+ * The server refuses judgment text over 16000 characters, and prompts that
67
+ * long used to get neither a judgment nor a recall. Keep the head and the
68
+ * tail: requests usually open with the ask and close with the specifics.
69
+ */
70
+ export function clipForJudge(text, max = 12000, head = 9000) {
71
+ const s = typeof text === 'string' ? text : '';
72
+ if (s.length <= max) return s;
73
+ const marker = '\n[…]\n';
74
+ return s.slice(0, head) + marker + s.slice(s.length - (max - head - marker.length));
75
+ }
76
+
77
+ /** Recall queries embed the head of the prompt only. */
78
+ export function clipQuery(text, max = 2000) {
79
+ return (typeof text === 'string' ? text : '').slice(0, max);
80
+ }
81
+
82
+ function clipLine(value, max) {
83
+ const s = String(value ?? '').replace(/\s+/g, ' ').trim();
84
+ return s.length > max ? `${s.slice(0, Math.max(0, max - 1))}…` : s;
85
+ }
86
+
87
+ function tailText(value, max) {
88
+ const s = String(value ?? '').trim();
89
+ return s.length > max ? `…${s.slice(s.length - (max - 1))}` : s;
90
+ }
91
+ const DATA_HOME = getDataHome();
92
+ export const DATA_DIR = join(DATA_HOME, 'data');
93
+ export const MCP_DATA_DIR = join(DATA_HOME, 'mcp-data');
94
+ export const ENV_PATH = join(DATA_HOME, '.env');
95
+ export const HOOK_FEATURES_PATH = join(DATA_DIR, 'hook-features.json');
96
+ export const PENDING_REMEMBER_DIR = join(DATA_DIR, 'pending-remember');
97
+ export const LOOP_LOG_DIR = join(DATA_DIR, 'logs');
98
+
99
+ // --- Loop logging (mirrors neural-interface/lib/loop-logger.js) ---
100
+ // Writes per-loop entries from inside hooks into the same logfile the server uses.
101
+
102
+ let _loopLogDirEnsured = false;
103
+ function ensureLoopLogDir() {
104
+ if (_loopLogDirEnsured) return;
105
+ try {
106
+ if (!existsSync(LOOP_LOG_DIR)) mkdirSync(LOOP_LOG_DIR, { recursive: true });
107
+ _loopLogDirEnsured = true;
108
+ } catch { /* ok */ }
109
+ }
110
+
111
+ function safeJsonInline(obj) {
112
+ try {
113
+ return JSON.stringify(obj, (_k, v) => {
114
+ if (typeof v === 'string' && v.length > 800) return v.slice(0, 800) + `…(+${v.length - 800})`;
115
+ return v;
116
+ });
117
+ } catch { return '<unserializable>'; }
118
+ }
119
+
120
+ /**
121
+ * Append a line to a logfile, rotating it once if it exceeds maxBytes.
122
+ * Hook debug logs (compact-debug.log, user-learning-debug.log, …) were plain
123
+ * appendFileSync with no bound and grew to tens of MB. This keeps one `.1`
124
+ * backup and starts fresh, capping on-disk size at ~2x maxBytes.
125
+ */
126
+ export function appendCapped(filePath, line, maxBytes = 10 * 1024 * 1024) {
127
+ try {
128
+ if (existsSync(filePath) && statSync(filePath).size > maxBytes) {
129
+ try { renameSync(filePath, filePath + '.1'); } catch { /* fall through to append */ }
130
+ }
131
+ } catch { /* stat failed — just append */ }
132
+ try { appendFileSync(filePath, line); } catch { /* best effort */ }
133
+ }
134
+
135
+ /**
136
+ * Delete pending-remember flag files older than maxAgeDays. These per-session
137
+ * flags are created on first user message and never deleted, so they pile up
138
+ * (hundreds of stale files). Safe to sweep by mtime — a live session rewrites
139
+ * its flag on every message, keeping mtime fresh.
140
+ */
141
+ export function cleanupStalePendingRemember(maxAgeDays = 30) {
142
+ let deleted = 0;
143
+ try {
144
+ if (!existsSync(PENDING_REMEMBER_DIR)) return { deleted };
145
+ const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
146
+ for (const f of readdirSync(PENDING_REMEMBER_DIR)) {
147
+ if (!f.endsWith('.json')) continue;
148
+ const fp = join(PENDING_REMEMBER_DIR, f);
149
+ try {
150
+ if (statSync(fp).mtimeMs < cutoff) { unlinkSync(fp); deleted++; }
151
+ } catch { /* skip racing/unreadable */ }
152
+ }
153
+ } catch { /* ok */ }
154
+ return { deleted };
155
+ }
156
+
157
+ /**
158
+ * Delete the files in `dir` last modified more than `maxAgeMs` ago (per-session
159
+ * caches such as data/subagent-dispatch/ that nothing else removes).
160
+ */
161
+ export function cleanupStaleFiles(dir, maxAgeMs) {
162
+ let deleted = 0;
163
+ try {
164
+ if (!existsSync(dir)) return { deleted };
165
+ const cutoff = Date.now() - maxAgeMs;
166
+ for (const f of readdirSync(dir)) {
167
+ const fp = join(dir, f);
168
+ try {
169
+ if (statSync(fp).mtimeMs < cutoff) { unlinkSync(fp); deleted++; }
170
+ } catch { /* skip racing/unreadable */ }
171
+ }
172
+ } catch { /* ok */ }
173
+ return { deleted };
174
+ }
175
+
176
+ /**
177
+ * Append one log entry to the per-loop logfile and stderr.
178
+ * tsid may be null/undefined; will fall back to SYNABUN_TERMINAL_SESSION env.
179
+ */
180
+ export function appendLoopLog(tsid, tag, msg, meta = undefined) {
181
+ const sid = tsid || process.env.SYNABUN_TERMINAL_SESSION || null;
182
+ const line = `${new Date().toISOString()} | hook:${tag} | ${msg}${meta ? ' | ' + safeJsonInline(meta) : ''}\n`;
183
+ try { process.stderr.write(`[loop:hook:${tag}] ${msg}${meta ? ' ' + safeJsonInline(meta) : ''}\n`); } catch { /* ok */ }
184
+ if (!sid) return;
185
+ ensureLoopLogDir();
186
+ try {
187
+ appendFileSync(pathResolve(LOOP_LOG_DIR, `loop-${sid}.log`), line);
188
+ } catch { /* ok */ }
189
+ }
190
+
191
+ // --- Stdin ---
192
+
193
+ export function readStdin() {
194
+ return new Promise((resolve) => {
195
+ if (process.stdin.isTTY) return resolve('{}');
196
+ let data = '';
197
+ process.stdin.setEncoding('utf8');
198
+ process.stdin.on('data', (chunk) => { data += chunk; });
199
+ process.stdin.on('end', () => { clearTimeout(guard); resolve(data); });
200
+ // unref + clearTimeout: without both, this timer keeps the event loop alive
201
+ // for its full duration AFTER 'end' already resolved, so every hook
202
+ // invocation stalled ~2s against a 3s configured timeout.
203
+ const guard = setTimeout(() => resolve(data || '{}'), 2000);
204
+ guard.unref?.();
205
+ });
206
+ }
207
+
208
+ // --- Transcript reading (session-scoped plan storage) ---
209
+
210
+ const CLAUDE_PROJECTS_DIR = join(process.env.USERPROFILE || process.env.HOME || '', '.claude', 'projects');
211
+
212
+ /**
213
+ * Convert a cwd to Claude Code's transcript project-dir key.
214
+ * Claude Code replaces every non-alphanumeric char with a hyphen, so
215
+ * /Users/foo/Apps/Synabun → -Users-foo-Apps-Synabun.
216
+ */
217
+ function cwdToProjectKey(cwd) {
218
+ if (!cwd) return '';
219
+ return cwd.replace(/[^a-zA-Z0-9]/g, '-');
220
+ }
221
+
222
+ /**
223
+ * Resolve a readable transcript path for a session. Prefers the explicit
224
+ * transcriptPath from the hook input; falls back to deriving it from
225
+ * sessionId + cwd, then to scanning ~/.claude/projects/ for <sessionId>.jsonl
226
+ * (covers sessions that cd'd into a subdir after launch).
227
+ *
228
+ * Returns an absolute path or null. Never guesses across sessions — the
229
+ * returned transcript belongs to exactly the requested sessionId.
230
+ */
231
+ export function resolveTranscriptPath(transcriptPath, sessionId, cwd) {
232
+ try {
233
+ if (transcriptPath && existsSync(transcriptPath)) return transcriptPath;
234
+ } catch { /* fall through */ }
235
+
236
+ if (!sessionId) return null;
237
+
238
+ // Fast path: derive ~/.claude/projects/<key>/<sessionId>.jsonl from cwd.
239
+ try {
240
+ const key = cwdToProjectKey(cwd);
241
+ if (key) {
242
+ const derived = join(CLAUDE_PROJECTS_DIR, key, `${sessionId}.jsonl`);
243
+ if (existsSync(derived)) return derived;
244
+ }
245
+ } catch { /* fall through */ }
246
+
247
+ // Robust path: scan every project dir for <sessionId>.jsonl.
248
+ try {
249
+ if (existsSync(CLAUDE_PROJECTS_DIR)) {
250
+ for (const dir of readdirSync(CLAUDE_PROJECTS_DIR)) {
251
+ const candidate = join(CLAUDE_PROJECTS_DIR, dir, `${sessionId}.jsonl`);
252
+ try { if (existsSync(candidate)) return candidate; } catch { /* skip */ }
253
+ }
254
+ }
255
+ } catch { /* ok */ }
256
+
257
+ return null;
258
+ }
259
+
260
+ /**
261
+ * Read up to maxBytes from the END of a file as UTF-8. Bounded so a multi-MB
262
+ * transcript never blows the Stop hook's short timeout. The first line of the
263
+ * returned slice may be truncated — callers must tolerate unparseable lines.
264
+ */
265
+ function readFileTail(filePath, maxBytes = 512 * 1024) {
266
+ let fd = null;
267
+ try {
268
+ fd = openSync(filePath, 'r');
269
+ const size = fstatSync(fd).size;
270
+ const readBytes = Math.min(size, maxBytes);
271
+ if (readBytes <= 0) return '';
272
+ const start = size - readBytes;
273
+ const buf = Buffer.allocUnsafe(readBytes);
274
+ readSync(fd, buf, 0, readBytes, start);
275
+ return buf.toString('utf-8');
276
+ } catch {
277
+ return '';
278
+ } finally {
279
+ if (fd !== null) { try { closeSync(fd); } catch { /* ok */ } }
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Scan a session transcript for the LAST ExitPlanMode tool_use and return its
285
+ * authoritative plan payload. Because the transcript belongs to exactly one
286
+ * session, this is session-scoped by construction — it can never surface
287
+ * another session's plan (the root cause of the cross-session leak).
288
+ *
289
+ * Returns { plan, planFilePath, slug } or null when the session has no
290
+ * ExitPlanMode (i.e. it never exited plan mode).
291
+ */
292
+ export function readLastExitPlanMode(transcriptPath) {
293
+ if (!transcriptPath) return null;
294
+ const tail = readFileTail(transcriptPath);
295
+ if (!tail) return null;
296
+
297
+ const lines = tail.split('\n');
298
+ // Walk backwards: the first ExitPlanMode found = the LAST in the file,
299
+ // which is the final approved plan when a session re-plans. A declined one
300
+ // (the user kept planning, the approval card expired, the stream closed) is
301
+ // skipped: only an approved plan is owed to memory.
302
+ for (let i = lines.length - 1; i >= 0; i--) {
303
+ const line = lines[i];
304
+ if (!line || line.indexOf('ExitPlanMode') === -1) continue;
305
+ let entry;
306
+ try { entry = JSON.parse(line); } catch { continue; }
307
+ const content = entry?.message?.content;
308
+ if (!Array.isArray(content)) continue;
309
+ for (const block of content) {
310
+ if (block && block.type === 'tool_use' && block.name === 'ExitPlanMode') {
311
+ const result = exitPlanResult(lines, i, block.id);
312
+ if (result?.declined) break;
313
+ const input = block.input || {};
314
+ return {
315
+ plan: typeof input.plan === 'string' ? input.plan : '',
316
+ planFilePath: typeof input.planFilePath === 'string' ? input.planFilePath : '',
317
+ slug: typeof entry.slug === 'string' ? entry.slug : '',
318
+ // Where the CLI says the plan file is (it may never have been written).
319
+ resultFilePath: result?.filePath || '',
320
+ };
321
+ }
322
+ }
323
+ }
324
+ return null;
325
+ }
326
+
327
+ /**
328
+ * The result of the ExitPlanMode call on line `at`: { declined, filePath }, or
329
+ * null when none is there yet. An error result is a decline. "You are not in
330
+ * plan mode" is one too, unless the transcript shows an approval anyway: the
331
+ * session was in plan mode for that turn and the approval left it before the
332
+ * call ran (older CLIs did that to most approvals), or the Claude sidepanel's
333
+ * simulated plan mode was approved with its "Continue with implementation"
334
+ * prompt. Without either, the session was never in plan mode.
335
+ */
336
+ function exitPlanResult(lines, at, id) {
337
+ if (!id) return null;
338
+ const needle = `"tool_use_id":"${id}"`;
339
+ for (let j = at + 1; j < lines.length; j++) {
340
+ if (lines[j].indexOf(needle) === -1) continue;
341
+ let entry;
342
+ try { entry = JSON.parse(lines[j]); } catch { continue; }
343
+ const content = Array.isArray(entry?.message?.content) ? entry.message.content : [];
344
+ const block = content.find((b) => b && b.type === 'tool_result' && b.tool_use_id === id);
345
+ if (!block) continue;
346
+ const text = typeof block.content === 'string' ? block.content : JSON.stringify(block.content ?? '');
347
+ const approvedAnyway = /not in plan mode/i.test(text) && (planModeTurn(lines, at) || sidepanelContinued(lines, j + 1));
348
+ return {
349
+ declined: block.is_error === true && !approvedAnyway,
350
+ filePath: typeof entry.toolUseResult?.filePath === 'string' ? entry.toolUseResult.filePath : '',
351
+ };
352
+ }
353
+ return null;
354
+ }
355
+
356
+ // Transcript lines parsed only when they can decide (a plan-mode reminder, a
357
+ // mode change, a user entry).
358
+ function parsedIf(line, ...needles) {
359
+ if (!line || !needles.some((needle) => line.indexOf(needle) !== -1)) return null;
360
+ try { return JSON.parse(line); } catch { return null; }
361
+ }
362
+ // What the user sent, as the transcript records it: not a tool result, a hook's
363
+ // injected text (isMeta) or a compaction summary. Stamped or not, it starts a turn.
364
+ function isPrompt(entry) {
365
+ if (entry?.type !== 'user' || entry.isMeta || entry.isCompactSummary) return false;
366
+ const content = entry.message?.content;
367
+ return !(Array.isArray(content) && content.some((b) => b && b.type === 'tool_result'));
368
+ }
369
+ function promptText(entry) {
370
+ const content = entry.message?.content;
371
+ return typeof content === 'string' ? content
372
+ : Array.isArray(content) ? content.map((b) => (b && b.type === 'text' ? b.text || '' : '')).join('\n') : '';
373
+ }
374
+
375
+ /**
376
+ * Whether the session was in plan mode during the turn of the entry on line
377
+ * `at`, walking back no further than the prompt that started it. The CLI writes
378
+ * a `plan_mode` attachment (its plan-mode reminder) only while the permission
379
+ * mode is plan, a `permission-mode` entry when the mode changes, and stamps a
380
+ * prompt with `permissionMode`. A `plan_mode_exit` attachment first means plan
381
+ * mode had already ended. The turn's prompt ends the walk: an unstamped one
382
+ * leaves the mode unknown, so nothing was approved, and nothing before it
383
+ * counts. The sidepanel's simulated plan mode runs in default and leaves none
384
+ * of them.
385
+ */
386
+ function planModeTurn(lines, at) {
387
+ for (let k = at - 1; k >= 0; k--) {
388
+ const entry = parsedIf(lines[k], '"plan_mode', '"permission-mode"', '"type":"user"');
389
+ if (!entry) continue;
390
+ if (entry.type === 'attachment') {
391
+ if (entry.attachment?.type === 'plan_mode_exit') return false;
392
+ if (entry.attachment?.type === 'plan_mode') return true;
393
+ continue;
394
+ }
395
+ if (entry.type === 'permission-mode') return entry.permissionMode === 'plan';
396
+ if (isPrompt(entry)) return entry.permissionMode === 'plan';
397
+ }
398
+ return false;
399
+ }
400
+
401
+ // The Claude sidepanel's "Continue with implementation" prompts (ui-claude-panel.js
402
+ // renderPostPlanActions): the approval of a plan made in its simulated plan mode.
403
+ const SIDEPANEL_PLAN_APPROVAL = /(?:^|\n)(?:Continue with the implementation based on the approved plan\.|The user has reviewed and approved this updated plan:)/;
404
+
405
+ // A slash command and its output (the PLAN COMPLETE card's "Compact context" runs /compact).
406
+ const COMMAND_ECHO = /^\s*<(?:command-|local-command-)/;
407
+
408
+ /**
409
+ * Whether the first prompt from line `from` on is the sidepanel's approval of
410
+ * the plan before it. A slash command or its output in between is not an answer.
411
+ */
412
+ function sidepanelContinued(lines, from) {
413
+ for (let j = from; j < lines.length; j++) {
414
+ const entry = parsedIf(lines[j], '"type":"user"');
415
+ if (!isPrompt(entry)) continue;
416
+ const text = promptText(entry);
417
+ if (COMMAND_ECHO.test(text)) continue;
418
+ return SIDEPANEL_PLAN_APPROVAL.test(text);
419
+ }
420
+ return false;
421
+ }
422
+
423
+ // --- Transcript entries (turn activity, recent prompts) ---
424
+ //
425
+ // Transcript lines are {type:'user'|'assistant', message:{role, content},
426
+ // isMeta?, isSidechain?, promptId?, promptSource?, toolUseResult?}. Other line
427
+ // types (attachment, file-history, ai-title, …) can be megabytes each, so a
428
+ // line is only parsed when its head carries a `"message":` key and no
429
+ // `"attachment":` key (checked on 7,878 live entries: `"message":` always sits
430
+ // in the first 180 characters).
431
+
432
+ const MEMORY_WRITE_TOOL = /(?:^|__)Syna[Bb]un_+(?:remember|reflect)$/;
433
+ const MEMORY_TOOL = /(?:^|__)Syna[Bb]un_+(?:remember|reflect|recall|memories|forget|restore|sync)$/;
434
+ const EDIT_TOOL_NAMES = new Set(['Edit', 'Write', 'NotebookEdit', 'MultiEdit']);
435
+ // Results that say nothing about whether the work succeeded.
436
+ const QUIET_TOOLS = new Set(['Read', 'Grep', 'Glob', 'LS', 'ToolSearch', 'TodoWrite', 'NotebookRead',
437
+ 'EnterPlanMode', 'ExitPlanMode', 'AskUserQuestion', 'Edit', 'Write', 'NotebookEdit', 'MultiEdit']);
438
+
439
+ export function transcriptEntries(filePath, maxBytes = 2 * 1024 * 1024) {
440
+ if (!filePath) return [];
441
+ let fd = null;
442
+ let text = '';
443
+ let truncated = false;
444
+ try {
445
+ fd = openSync(filePath, 'r');
446
+ const size = fstatSync(fd).size;
447
+ const readBytes = Math.min(size, maxBytes);
448
+ if (readBytes <= 0) return [];
449
+ truncated = readBytes < size;
450
+ const buf = Buffer.allocUnsafe(readBytes);
451
+ readSync(fd, buf, 0, readBytes, size - readBytes);
452
+ text = buf.toString('utf-8');
453
+ } catch {
454
+ return [];
455
+ } finally {
456
+ if (fd !== null) { try { closeSync(fd); } catch { /* ok */ } }
457
+ }
458
+ const lines = text.split('\n');
459
+ if (truncated) lines.shift(); // started mid-line
460
+ const entries = [];
461
+ for (const line of lines) {
462
+ if (!line) continue;
463
+ const head = line.slice(0, 600);
464
+ if (head.includes('"attachment":') || !head.includes('"message":')) continue;
465
+ let entry;
466
+ try { entry = JSON.parse(line); } catch { continue; }
467
+ if (entry && (entry.type === 'user' || entry.type === 'assistant')) entries.push(entry);
468
+ }
469
+ return entries;
470
+ }
471
+
472
+ function contentBlocks(entry) {
473
+ const content = entry?.message?.content;
474
+ if (Array.isArray(content)) return content;
475
+ if (typeof content === 'string') return [{ type: 'text', text: content }];
476
+ return [];
477
+ }
478
+
479
+ function blockText(content) {
480
+ if (typeof content === 'string') return content;
481
+ if (Array.isArray(content)) return content.map((b) => (b && b.type === 'text' ? b.text || '' : '')).filter(Boolean).join('\n');
482
+ return '';
483
+ }
484
+
485
+ /** The text a user entry carries (tool results excluded). */
486
+ export function userEntryText(entry) {
487
+ const content = entry?.message?.content;
488
+ if (typeof content === 'string') return content;
489
+ if (!Array.isArray(content)) return '';
490
+ return content.map((b) => (b && b.type === 'text' ? b.text || '' : '')).filter(Boolean).join('\n');
491
+ }
492
+
493
+ /**
494
+ * A user entry that starts a turn: typed (or injected) text, not a tool result,
495
+ * not Claude Code's own meta line (Stop-hook feedback, caveats), not a sidechain.
496
+ */
497
+ export function isTurnStart(entry) {
498
+ if (!entry || entry.type !== 'user' || entry.isMeta || entry.isSidechain) return false;
499
+ const content = entry.message?.content;
500
+ if (typeof content === 'string') return true;
501
+ if (!Array.isArray(content) || content.length === 0) return false;
502
+ return !content.some((b) => b && b.type === 'tool_result');
503
+ }
504
+
505
+ /** Human-typed prompt text of a turn-start entry, or '' for system-injected ones. */
506
+ export function humanPromptText(entry) {
507
+ if (!isTurnStart(entry) || entry.promptSource === 'system') return '';
508
+ const cls = classifyPrompt(userEntryText(entry));
509
+ return cls.origin === 'human' ? cls.text : '';
510
+ }
511
+
512
+ // Checks that could back a claim like "tests pass" or "it builds".
513
+ const TEST_OR_BUILD = /\b(?:(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?[\w:.-]*(?:test|build|check|lint|typecheck|verify)[\w:.-]*|npx\s+(?:vitest|jest|mocha|tsc|playwright|eslint)|node\s+(?:--test\b|\S*(?:test|spec|bench|verify)\S*)|vitest|jest|mocha|pytest|tsc|go\s+(?:test|build|vet)|cargo\s+(?:test|build|check|clippy)|make\b|mvn\b|gradle\w*|dotnet\s+(?:test|build)|swift\s+(?:test|build)|xcodebuild|ruff|eslint|playwright\s+test)/i;
514
+
515
+ export function isTestOrBuildCommand(command) {
516
+ return TEST_OR_BUILD.test(String(command || ''));
517
+ }
518
+
519
+ const READ_ONLY_COMMAND = /^(?:ls|ll|cat|bat|head|tail|less|more|grep|egrep|fgrep|rg|ag|find|fd|pwd|echo|printf|wc|which|type|whereis|stat|file|du|df|tree|sort|uniq|cut|tr|awk|jq|column|diff|cmp|date|true|basename|dirname|realpath|readlink|env|printenv|cd|sed\s+-n|git\s+(?:status|log|diff|show|branch|rev-parse|remote|ls-files|blame|shortlog|describe|config\s+--get))\b/i;
520
+
521
+ /**
522
+ * True when every segment of a shell command only reads. A redirect into a
523
+ * file makes a segment a write. Used to decide whether a Bash-only turn did
524
+ * anything that could be worth a memory; the judgment makes the final call.
525
+ */
526
+ export function isReadOnlyCommand(command) {
527
+ const segments = String(command || '').split(/&&|\|\||;|\||\n/).map((s) => s.trim()).filter(Boolean);
528
+ if (!segments.length) return true;
529
+ return segments.every((segment) => {
530
+ const bare = segment.replace(/^(?:[A-Za-z_][A-Za-z0-9_]*=\S*\s+)+/, '');
531
+ const withoutHarmless = bare.replace(/\d?>>?\s*\/dev\/null/g, '').replace(/\d?>&\d/g, '');
532
+ if (/>/.test(withoutHarmless)) return false;
533
+ return READ_ONLY_COMMAND.test(bare);
534
+ });
535
+ }
536
+
537
+ function bashResultText(result) {
538
+ if (!result) return '';
539
+ const fromBlock = blockText(result.block?.content);
540
+ if (fromBlock) return fromBlock;
541
+ const tur = result.tur;
542
+ if (tur && typeof tur === 'object') return [tur.stdout, tur.stderr].filter((s) => typeof s === 'string' && s).join('\n');
543
+ return typeof tur === 'string' ? tur : '';
544
+ }
545
+
546
+ function bashExit(input, result) {
547
+ if (!result) return input?.run_in_background ? 'background' : 'interrupted';
548
+ const tur = result.tur;
549
+ if (tur && typeof tur === 'object') {
550
+ if (tur.interrupted === true) return 'interrupted';
551
+ if (tur.backgroundTaskId) return 'background';
552
+ }
553
+ if (result.block?.is_error === true) {
554
+ return /interrupted by user/i.test(bashResultText(result)) ? 'interrupted' : 'error';
555
+ }
556
+ return input?.run_in_background ? 'background' : 'ok';
557
+ }
558
+
559
+ /**
560
+ * What the current turn did, read from the transcript: everything after the
561
+ * last turn-start user entry (see isTurnStart).
562
+ *
563
+ * bash_commands ≤12 most recent Bash calls of the WHOLE turn:
564
+ * {command ≤240, description ≤100, exit, output_tail ≤300,
565
+ * after_memory_write}. The turn usually runs its tests, then
566
+ * calls `remember`, then reports — the claim check must see
567
+ * the test run, so the window is the turn, not the part
568
+ * after the last memory write.
569
+ * checks every test/build command of the turn (≤10 most recent)
570
+ * tool_results ≤6 non-Bash results that can show success or failure
571
+ * edited_files files edited this turn
572
+ * tool_counts tool name → calls this turn
573
+ * memory_written_this_turn a SynaBun remember/reflect ran this turn
574
+ * prompt_id promptId of the turn-start entry, when recorded
575
+ */
576
+ export function readTurnActivity(transcriptPath, { maxBytes = 4 * 1024 * 1024 } = {}) {
577
+ const activity = {
578
+ prompt_id: null, bash_commands: [], checks: [], tool_results: [], edited_files: [],
579
+ tool_counts: {}, memory_written_this_turn: false, bash_total: 0,
580
+ };
581
+ const entries = transcriptEntries(transcriptPath, maxBytes);
582
+ if (!entries.length) return activity;
583
+ let start = 0;
584
+ for (let i = entries.length - 1; i >= 0; i--) {
585
+ if (isTurnStart(entries[i])) {
586
+ start = i + 1;
587
+ activity.prompt_id = typeof entries[i].promptId === 'string' ? entries[i].promptId : null;
588
+ break;
589
+ }
590
+ }
591
+ const turn = entries.slice(start);
592
+ const results = new Map();
593
+ const uses = [];
594
+ for (const entry of turn) {
595
+ for (const block of contentBlocks(entry)) {
596
+ if (!block || typeof block !== 'object') continue;
597
+ if (entry.type === 'user' && block.type === 'tool_result' && block.tool_use_id) {
598
+ results.set(block.tool_use_id, { block, tur: entry.toolUseResult });
599
+ } else if (entry.type === 'assistant' && block.type === 'tool_use') {
600
+ uses.push(block);
601
+ }
602
+ }
603
+ }
604
+ let lastWrite = -1;
605
+ uses.forEach((use, i) => { if (MEMORY_WRITE_TOOL.test(use.name || '')) lastWrite = i; });
606
+ activity.memory_written_this_turn = lastWrite >= 0;
607
+
608
+ const bash = [];
609
+ const others = [];
610
+ const edited = new Set();
611
+ uses.forEach((use, i) => {
612
+ const name = String(use.name || '');
613
+ activity.tool_counts[name] = (activity.tool_counts[name] || 0) + 1;
614
+ const input = use.input && typeof use.input === 'object' ? use.input : {};
615
+ const result = results.get(use.id);
616
+ if (name === 'Bash') {
617
+ const command = String(input.command || '');
618
+ const entry = {
619
+ command: clipLine(command, 240),
620
+ description: clipLine(input.description, 100),
621
+ exit: bashExit(input, result),
622
+ output_tail: tailText(bashResultText(result), 300),
623
+ after_memory_write: i > lastWrite,
624
+ };
625
+ bash.push(entry);
626
+ if (isTestOrBuildCommand(command)) activity.checks.push({ command: entry.command, exit: entry.exit, after_memory_write: entry.after_memory_write });
627
+ return;
628
+ }
629
+ if (EDIT_TOOL_NAMES.has(name)) {
630
+ const file = input.file_path || input.notebook_path;
631
+ if (typeof file === 'string' && file) edited.add(file);
632
+ }
633
+ if (QUIET_TOOLS.has(name) || MEMORY_TOOL.test(name) || !result) return;
634
+ others.push({ order: i, tool: name, is_error: result.block?.is_error === true, tail: tailText(blockText(result.block?.content) || (typeof result.tur === 'string' ? result.tur : ''), 300) });
635
+ });
636
+ activity.bash_total = bash.length;
637
+ activity.bash_commands = bash.slice(-12);
638
+ activity.checks = activity.checks.slice(-10);
639
+ activity.edited_files = [...edited].slice(0, 50);
640
+ // ≤6 results, errors first (they are the evidence that matters), then the
641
+ // most recent; reported in the order they happened.
642
+ const errors = others.filter((r) => r.is_error).slice(-6);
643
+ const rest = others.filter((r) => !r.is_error).slice(-(6 - errors.length));
644
+ activity.tool_results = [...errors, ...rest].sort((a, b) => a.order - b.order)
645
+ .map(({ tool, is_error, tail }) => ({ tool, is_error, tail }));
646
+ return activity;
647
+ }
648
+
649
+ /** The last n non-trivial human prompts in the transcript, most recent first. */
650
+ export function readRecentHumanPrompts(transcriptPath, n = 2, maxBytes = 512 * 1024) {
651
+ const out = [];
652
+ const entries = transcriptEntries(transcriptPath, maxBytes);
653
+ for (let i = entries.length - 1; i >= 0 && out.length < n; i--) {
654
+ const text = humanPromptText(entries[i]);
655
+ if (text && !isTrivialPrompt(text)) out.push(text);
656
+ }
657
+ return out;
658
+ }
659
+
660
+ /** Text of the last assistant text block in the transcript, clipped. */
661
+ export function readLastAssistantText(transcriptPath, maxChars = 1200, maxBytes = 512 * 1024) {
662
+ const entries = transcriptEntries(transcriptPath, maxBytes);
663
+ for (let i = entries.length - 1; i >= 0; i--) {
664
+ if (entries[i].type !== 'assistant') continue;
665
+ const blocks = contentBlocks(entries[i]).filter((b) => b && b.type === 'text' && typeof b.text === 'string' && b.text.trim());
666
+ if (blocks.length) return blocks[blocks.length - 1].text.trim().slice(0, maxChars);
667
+ }
668
+ return '';
669
+ }
670
+
671
+ // --- Hook features ---
672
+
673
+ export function getHookFeatures() {
674
+ try {
675
+ if (!existsSync(HOOK_FEATURES_PATH)) return {};
676
+ return JSON.parse(readFileSync(HOOK_FEATURES_PATH, 'utf-8'));
677
+ } catch { return {}; }
678
+ }
679
+
680
+ // --- Memory recall (shared by prompt-submit and subagent-start hooks) ---
681
+
682
+ function formatMemoryAge(isoDate) {
683
+ const diffMs = Date.now() - new Date(isoDate).getTime();
684
+ const days = Math.floor(diffMs / (1000 * 60 * 60 * 24));
685
+ if (days === 0) return 'today';
686
+ if (days === 1) return '1 day ago';
687
+ if (days < 30) return `${days} days ago`;
688
+ const months = Math.floor(days / 30);
689
+ return months === 1 ? '1 month ago' : `${months} months ago`;
690
+ }
691
+
692
+ export const MEMORY_BLOCK_HEADER = '=== SynaBun: Related Memories ===';
693
+ export const MEMORY_BLOCK_END = '=== End Memories ===';
694
+
695
+ /**
696
+ * The `=== SynaBun: Related Memories ===` block (the header is load-bearing:
697
+ * the assistant persona and tests look for it). When the server ranked the
698
+ * shortlist, the closing line says so, with the number of candidates the
699
+ * relevance floor removed.
700
+ */
701
+ export function formatMemoryBlock(data, { ranked = false, dropped = 0 } = {}) {
702
+ if (!data || !Array.isArray(data.results) || data.results.length === 0) return '';
703
+ const n = Number(dropped) || 0;
704
+ const tag = ranked ? ` [ranked by Jev${n > 0 ? `; ${n} low-relevance ${n === 1 ? 'match' : 'matches'} dropped` : ''}]` : '';
705
+ if (data.context) {
706
+ return [MEMORY_BLOCK_HEADER, data.context,
707
+ `Use these as evidence; fetch full UUIDs only when more detail is needed.${tag}`, MEMORY_BLOCK_END].join('\n');
708
+ }
709
+ const lines = data.results.map((r, i) => {
710
+ const score = (r.score * 100).toFixed(0);
711
+ const age = formatMemoryAge(r.created_at);
712
+ const tags = r.tags?.length ? r.tags.join(', ') : 'none';
713
+ const files = r.related_files?.length ? r.related_files.slice(0, 3).join(', ') : 'none';
714
+ return `${i + 1}. [${r.category} | importance ${r.importance}, ${age}, ${score}% match] ${r.content}\n Tags: ${tags} | Files: ${files}`;
715
+ });
716
+ return [MEMORY_BLOCK_HEADER, ...lines,
717
+ `These memories may be relevant. Use as context — call recall for deeper search if needed.${tag}`, MEMORY_BLOCK_END].join('\n');
718
+ }
719
+
720
+ /**
721
+ * Fetch relevant memories from the Neural Interface server and format them
722
+ * into the `=== SynaBun: Related Memories ===` block used for additionalContext.
723
+ *
724
+ * @param {object} opts
725
+ * @param {string} opts.query Text to search memories with.
726
+ * @param {string} [opts.project] Resolved project label (omit/'global' searches globally).
727
+ * @param {number} [opts.limit] Max results (default 3).
728
+ * @param {number} [opts.minScore] Minimum cosine score (default 0.4).
729
+ * @param {number} [opts.timeoutMs] Fetch timeout (default 1500).
730
+ * @param {number} [opts.tokenBudget] Compact context budget (default 600).
731
+ * @param {boolean} [opts.returnMeta] Return { context, alreadyPresent, ranked, rank_surface, dropped, stale }.
732
+ * @param {string} [opts.sessionId] Claude session: injection dedup ledger AND log attribution.
733
+ * @param {string} [opts.logSessionId] Log attribution only (a subagent must not be
734
+ * deduplicated against memories that live in its parent's context).
735
+ * @param {boolean} [opts.rank] Legacy flag: rank with surface brief-rank.
736
+ * @param {'rerank'|'brief-rank'} [opts.surface] Rank with this surface (implies ranking).
737
+ * @param {boolean} [opts.floor] Drop judged candidates under the surface's minScore.
738
+ * @param {number} [opts.budgetMs] Remaining hook budget for this call; also the fetch timeout.
739
+ * @param {boolean} [opts.wantStale] Return (and mark delivered) this session's stale verdicts.
740
+ * @param {string[]} [opts.staleAck] Verdicts already shown elsewhere: mark delivered, do not return.
741
+ * @returns {Promise<string|object>} Empty on failure.
742
+ */
743
+ export async function recallMemories({ query, project, limit = 3, minScore = 0.4, timeoutMs = 1500, tokenBudget = 600,
744
+ returnMeta = false, sessionId, logSessionId, contextGeneration, rank = false, surface, floor = false, budgetMs,
745
+ wantStale = false, staleAck } = {}) {
746
+ // responded: the server answered (so staleAck was received and applied).
747
+ const meta = { responded: false, ranked: false, rank_surface: null, dropped: [], stale: [] };
748
+ const result = (context = '', alreadyPresent = false) => returnMeta ? { context, alreadyPresent, ...meta } : context;
749
+ const trimmed = (query || '').trim();
750
+ if (!trimmed) return result();
751
+ const effective = Math.floor(Math.min(Number(timeoutMs) || 0, budgetMs === undefined ? Infinity : Number(budgetMs) || 0));
752
+ if (!(effective >= MIN_CALL_MS)) return result();
753
+ const off = typesafeOff();
754
+ const ranking = !off && (rank || !!surface);
755
+ try {
756
+ const resp = await fetch(`${niUrl()}/api/hook-recall`, {
757
+ method: 'POST',
758
+ headers: { 'Content-Type': 'application/json' },
759
+ body: JSON.stringify({
760
+ query: trimmed,
761
+ project: project && project !== 'global' ? project : undefined,
762
+ limit,
763
+ min_score: minScore,
764
+ token_budget: tokenBudget, format: 'compact',
765
+ caller: 'claude-code-hook', claudeSessionId: sessionId, context_generation: contextGeneration,
766
+ session_id: sessionId || logSessionId || undefined,
767
+ // Ranking judges the shortlist server-side and must fit this hook's
768
+ // deadline. `rank:true` is what an older server understands (surface
769
+ // brief-rank); a `surface` alone implies ranking on a current server,
770
+ // so the prompt path stays unranked on an old one.
771
+ rank: (rank && !off) || undefined,
772
+ rank_timeout_ms: ranking ? Math.max(300, effective - 400) : undefined,
773
+ surface: surface || undefined,
774
+ floor: floor || undefined,
775
+ judge: off ? false : undefined,
776
+ budget_ms: budgetMs === undefined ? undefined : effective,
777
+ want_stale: wantStale || undefined,
778
+ stale_ack: Array.isArray(staleAck) && staleAck.length ? staleAck.slice(0, 50) : undefined,
779
+ }),
780
+ signal: AbortSignal.timeout(effective),
781
+ });
782
+
783
+ if (!resp.ok) return result();
784
+ const data = await resp.json();
785
+ if (!data || typeof data !== 'object') return result();
786
+ meta.responded = true;
787
+ meta.ranked = data.ranked === true;
788
+ meta.rank_surface = typeof data.rank_surface === 'string' ? data.rank_surface : null;
789
+ meta.dropped = Array.isArray(data.dropped) ? data.dropped : [];
790
+ meta.stale = Array.isArray(data.stale) ? data.stale.filter((v) => v && typeof v.memory_id === 'string') : [];
791
+ if (data.already_present) return result('', true);
792
+ if (!data.results || data.results.length === 0) return result();
793
+ return result(formatMemoryBlock(data, { ranked: meta.ranked, dropped: meta.dropped.length }));
794
+ } catch {
795
+ // NI down, timeout, or error — silently skip
796
+ return result();
797
+ }
798
+ }
799
+
800
+ /**
801
+ * POST /api/hook-judge. Hooks never hold the API key and never call the vendor
802
+ * directly. Always resolves; {} means "not judged" and every caller falls back
803
+ * to its own rule. No network at all when SYNABUN_TYPESAFE=off, when the
804
+ * request says judge:false, or when the budget left is under MIN_CALL_MS.
805
+ */
806
+ export async function hookJudge(kind, fields = {}, { timeoutMs = 1500 } = {}) {
807
+ const body = fields && typeof fields === 'object' ? fields : {};
808
+ if (typesafeOff() || body.judge === false) return {};
809
+ const t = Math.floor(Number(timeoutMs) || 0);
810
+ if (!(t >= MIN_CALL_MS)) return {};
811
+ try {
812
+ const resp = await fetch(`${niUrl()}/api/hook-judge`, {
813
+ method: 'POST',
814
+ headers: { 'Content-Type': 'application/json' },
815
+ body: JSON.stringify({ ...body, kind, budget_ms: body.budget_ms ?? t }),
816
+ signal: AbortSignal.timeout(t),
817
+ });
818
+ if (!resp.ok) return {};
819
+ const data = await resp.json();
820
+ return data && typeof data === 'object' && !Array.isArray(data) ? data : {};
821
+ } catch {
822
+ return {};
823
+ }
824
+ }
825
+
826
+ /**
827
+ * Ask the Neural Interface for a typed judgment of a text (TypeSafe, server-side).
828
+ *
829
+ * @param {'prompt'|'agent-message'|'stop-turn'|'loop-goal'} kind
830
+ * @param {string} text
831
+ * @param {{project?:string, timeoutMs?:number, extra?:Record<string, any>,
832
+ * budget?:ReturnType<typeof hookBudget>, reserveMs?:number}} [opts]
833
+ * `extra` carries kind-specific state (files edited, loop task and journal).
834
+ * With `budget`, the timeout is also bounded by budget.callTimeout(reserveMs).
835
+ * @returns {Promise<Record<string, any>>}
836
+ */
837
+ export async function judgeText(kind, text, { project, timeoutMs = 1500, extra = {}, budget, reserveMs = 0 } = {}) {
838
+ const trimmed = (text || '').trim();
839
+ if (!trimmed) return {};
840
+ const t = budget ? Math.min(timeoutMs, budget.callTimeout(reserveMs)) : timeoutMs;
841
+ return hookJudge(kind, { ...extra, text: trimmed, project }, { timeoutMs: t });
842
+ }
843
+
844
+ /**
845
+ * Fire-and-forget POST that may wait at most `waitMs` for the answer and never
846
+ * keeps the process alive past that. Resolves true only on a 2xx.
847
+ */
848
+ export async function postNotice(path, body, waitMs = 200) {
849
+ let timer;
850
+ try {
851
+ const request = fetch(`${niUrl()}${path}`, {
852
+ method: 'POST',
853
+ headers: { 'Content-Type': 'application/json' },
854
+ body: JSON.stringify(body),
855
+ signal: AbortSignal.timeout(Math.max(50, waitMs)),
856
+ }).then((resp) => resp.ok, () => false);
857
+ const giveUp = new Promise((resolve) => { timer = setTimeout(() => resolve(false), waitMs); timer.unref?.(); });
858
+ return await Promise.race([request, giveUp]);
859
+ } catch {
860
+ return false;
861
+ } finally {
862
+ clearTimeout(timer);
863
+ }
864
+ }
865
+
866
+ /**
867
+ * Edit-time stale verdicts (StaleVerdict[] from /api/hook-recall or stop-turn)
868
+ * as one short advisory block: at most 3, excerpts clipped to 160 characters.
869
+ */
870
+ export function formatStaleNotices(list) {
871
+ const items = (Array.isArray(list) ? list : []).filter((v) => v && typeof v.memory_id === 'string').slice(0, 3);
872
+ if (!items.length) return '';
873
+ const lines = items.map((v) => {
874
+ const parts = [];
875
+ if (v.category) parts.push(String(v.category));
876
+ const p = Number(v.probability);
877
+ if (v.probability !== undefined && v.probability !== null && Number.isFinite(p)) parts.push(`p(still accurate)=${p.toFixed(2)}`);
878
+ if (v.file) parts.push(String(v.file));
879
+ const excerpt = clipLine(v.excerpt, 160);
880
+ return `- [${v.memory_id}]${parts.length ? ` (${parts.join(', ')})` : ''}${excerpt ? ` "${excerpt}"` : ''}`;
881
+ });
882
+ return [
883
+ 'SynaBun [Jev] stale-memory check: a recent edit may have made these memories inaccurate.',
884
+ ...lines,
885
+ 'Check each against the current code, then `reflect` to correct it or `forget` it if it no longer holds.',
886
+ ].join('\n');
887
+ }
888
+
889
+ // --- Environment / connection ---
890
+
891
+ function parseEnvFile(filePath) {
892
+ try {
893
+ const content = readFileSync(filePath, 'utf-8');
894
+ const vars = {};
895
+ for (const line of content.split('\n')) {
896
+ const trimmed = line.trim();
897
+ if (!trimmed || trimmed.startsWith('#')) continue;
898
+ const eq = trimmed.indexOf('=');
899
+ if (eq === -1) continue;
900
+ vars[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
901
+ }
902
+ return vars;
903
+ } catch { return {}; }
904
+ }
905
+
906
+ export function getActiveConnectionId() {
907
+ const vars = parseEnvFile(ENV_PATH);
908
+ return vars.SYNABUN_ACTIVE_CONNECTION || vars.QDRANT_ACTIVE || 'default';
909
+ }
910
+
911
+ export function getCategoriesPath() {
912
+ const connId = getActiveConnectionId();
913
+ return join(MCP_DATA_DIR, `custom-categories-${connId}.json`);
914
+ }
915
+
916
+ /**
917
+ * Returns the path to the MCP server's categories file (no connection suffix).
918
+ * This is the file the MCP server watches — hooks that need the MCP server to
919
+ * pick up changes (e.g., schema refresh) MUST write to this file.
920
+ */
921
+ export function getMcpCategoriesPath() {
922
+ return join(MCP_DATA_DIR, 'custom-categories.json');
923
+ }
924
+
925
+ // --- Project detection (reads from claude-code-projects.json) ---
926
+
927
+ const PROJECTS_PATH = join(DATA_DIR, 'claude-code-projects.json');
928
+
929
+ export function loadRegisteredProjects() {
930
+ try {
931
+ if (!existsSync(PROJECTS_PATH)) return [];
932
+ return JSON.parse(readFileSync(PROJECTS_PATH, 'utf-8'));
933
+ } catch { return []; }
934
+ }
935
+
936
+ export function normalizeLabel(label) {
937
+ return label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
938
+ }
939
+
940
+ export function detectProject(cwd) {
941
+ if (!cwd) return 'global';
942
+ const lower = cwd.toLowerCase().replace(/\\/g, '/');
943
+ const projects = loadRegisteredProjects();
944
+
945
+ // Sort by path length descending — most specific match wins
946
+ const sorted = projects
947
+ .map((p) => ({
948
+ path: p.path.toLowerCase().replace(/\\/g, '/'),
949
+ label: normalizeLabel(p.label),
950
+ }))
951
+ .sort((a, b) => b.path.length - a.path.length);
952
+
953
+ // 1. Exact path prefix match (cwd is inside a registered project)
954
+ for (const p of sorted) {
955
+ if (lower.startsWith(p.path + '/') || lower === p.path) return p.label;
956
+ }
957
+
958
+ // 2. Substring match — cwd folder name contains a registered project's folder name
959
+ // e.g. "MyAppWebsite" contains "myapp" → matches the MyApp project
960
+ for (const p of sorted) {
961
+ const projFolder = basename(p.path).toLowerCase();
962
+ if (lower.includes(projFolder)) return p.label;
963
+ }
964
+
965
+ // 3. Fallback to directory basename
966
+ const base = basename(cwd).toLowerCase().replace(/[^a-z0-9-]/g, '-');
967
+ return base || 'global';
968
+ }
969
+
970
+ // --- Category tree ---
971
+
972
+ export function loadCategories() {
973
+ try {
974
+ const data = JSON.parse(readFileSync(getCategoriesPath(), 'utf-8'));
975
+ if (data.version === 1 && Array.isArray(data.categories)) {
976
+ return data.categories;
977
+ }
978
+ } catch { /* no categories available */ }
979
+ return [];
980
+ }
981
+
982
+ export function buildCategoryTree(categories) {
983
+ if (!categories || categories.length === 0) {
984
+ return '(No categories defined yet. Use `category` with action "create" to set up your first category.)';
985
+ }
986
+
987
+ const parents = categories.filter((c) => c.is_parent);
988
+ const children = categories.filter((c) => c.parent);
989
+ const standalone = categories.filter((c) => !c.is_parent && !c.parent);
990
+
991
+ const lines = [];
992
+
993
+ for (const parent of parents) {
994
+ const kids = children.filter((c) => c.parent === parent.name);
995
+ lines.push(`${parent.name} (PARENT) — ${parent.description}`);
996
+ for (const kid of kids) {
997
+ lines.push(` └─ ${kid.name} — ${kid.description}`);
998
+ }
999
+ if (kids.length === 0) {
1000
+ lines.push(` (no children yet)`);
1001
+ }
1002
+ }
1003
+
1004
+ if (standalone.length > 0) {
1005
+ for (const cat of standalone) {
1006
+ lines.push(`${cat.name} — ${cat.description}`);
1007
+ }
1008
+ }
1009
+
1010
+ return lines.join('\n');
1011
+ }
1012
+
1013
+ // --- Auto-category creation ---
1014
+
1015
+ /**
1016
+ * Standalone categories required by hooks. Always created regardless of projects.
1017
+ */
1018
+ const STANDALONE_DEFAULTS = [
1019
+ { name: 'conversations', description: 'Session summaries and conversation indexing' },
1020
+ { name: 'communication-style', description: 'User communication patterns and preferences' },
1021
+ { name: 'plans', description: 'Implementation plans stored after plan mode approval', is_parent: true, color: '#06d6a0' },
1022
+ ];
1023
+
1024
+ /**
1025
+ * Default child categories for every registered project.
1026
+ * Created under a project parent, never standalone.
1027
+ */
1028
+ const PROJECT_CHILDREN = [
1029
+ { suffix: 'project', description: 'General project knowledge, decisions, and milestones' },
1030
+ { suffix: 'architecture', description: 'System design, tech stack, data flow, and component architecture' },
1031
+ { suffix: 'bugs', description: 'Bug fixes, debugging sessions, and known issues' },
1032
+ { suffix: 'config', description: 'Configuration, deployment, environment, and infrastructure' },
1033
+ ];
1034
+
1035
+ /**
1036
+ * Ensure all registered projects have a parent category with default children,
1037
+ * and that standalone hook-required categories exist.
1038
+ *
1039
+ * Writes to the MCP server's categories file (no connection suffix) so the
1040
+ * MCP server's file watcher picks up changes and refreshes tool schemas.
1041
+ *
1042
+ * Idempotent: only adds missing categories, never modifies or removes existing ones.
1043
+ *
1044
+ * @returns {{ created: string[], total: number }}
1045
+ */
1046
+ export function ensureProjectCategories() {
1047
+ const projects = loadRegisteredProjects();
1048
+ const catPath = getMcpCategoriesPath();
1049
+
1050
+ let data;
1051
+ try {
1052
+ data = JSON.parse(readFileSync(catPath, 'utf-8'));
1053
+ } catch {
1054
+ data = { version: 1, categories: [] };
1055
+ }
1056
+
1057
+ const categories = data.categories || [];
1058
+ const existingNames = new Set(categories.map((c) => c.name));
1059
+ const created = [];
1060
+ const now = new Date().toISOString();
1061
+
1062
+ // 1. Ensure standalone defaults exist
1063
+ for (const def of STANDALONE_DEFAULTS) {
1064
+ if (!existingNames.has(def.name)) {
1065
+ const cat = { name: def.name, description: def.description, created_at: now };
1066
+ if (def.is_parent) cat.is_parent = true;
1067
+ if (def.color) cat.color = def.color;
1068
+ categories.push(cat);
1069
+ existingNames.add(def.name);
1070
+ created.push(def.name);
1071
+ }
1072
+ }
1073
+
1074
+ // 2. Ensure project parents + children exist
1075
+ for (const proj of projects) {
1076
+ let label = normalizeLabel(proj.label);
1077
+ // Truncate to 16 chars so children stay within 30-char limit (16 + 1 + 12 = 29)
1078
+ if (label.length > 16) label = label.slice(0, 16).replace(/-$/, '');
1079
+
1080
+ // Ensure parent exists
1081
+ if (!existingNames.has(label)) {
1082
+ categories.push({
1083
+ name: label,
1084
+ description: `Knowledge and context for the ${proj.label} project`,
1085
+ is_parent: true,
1086
+ created_at: now,
1087
+ });
1088
+ existingNames.add(label);
1089
+ created.push(label);
1090
+ } else {
1091
+ // Parent exists — ensure it has is_parent flag
1092
+ const existing = categories.find((c) => c.name === label);
1093
+ if (existing && !existing.is_parent) existing.is_parent = true;
1094
+ }
1095
+
1096
+ // Ensure each default child exists
1097
+ for (const child of PROJECT_CHILDREN) {
1098
+ const childName = `${label}-${child.suffix}`;
1099
+ if (!existingNames.has(childName)) {
1100
+ categories.push({
1101
+ name: childName,
1102
+ description: `${child.description} for ${proj.label}`,
1103
+ parent: label,
1104
+ created_at: now,
1105
+ });
1106
+ existingNames.add(childName);
1107
+ created.push(childName);
1108
+ }
1109
+ }
1110
+ }
1111
+
1112
+ // Only write if we created something new
1113
+ if (created.length > 0) {
1114
+ data.categories = categories;
1115
+ writeFileSync(catPath, JSON.stringify(data, null, 2), 'utf-8');
1116
+ }
1117
+
1118
+ return { created, total: categories.length };
1119
+ }
1120
+
1121
+ /**
1122
+ * Build the full category reference block for lazy injection.
1123
+ * Includes tree, available names, selection rules, project scoping, and tool notes.
1124
+ */
1125
+ export function buildCategoryReference(categories, project) {
1126
+ const tree = buildCategoryTree(categories);
1127
+ const names = categories.map((c) => c.name).join(', ');
1128
+
1129
+ return [
1130
+ `## SynaBun Category Reference`,
1131
+ ``,
1132
+ `### Project: ${project}`,
1133
+ ``,
1134
+ tree,
1135
+ ``,
1136
+ `Available names: ${names || '(none)'}`,
1137
+ ``,
1138
+ `### Category Selection`,
1139
+ ``,
1140
+ `1. Match existing child category by description → use it.`,
1141
+ `2. No child fits but parent does → \`category\` with action "create" for a new child under that parent → use it.`,
1142
+ `3. No parent fits → \`category\` with action "create" for a new parent + child → use it.`,
1143
+ `4. Uncertain → \`AskUserQuestion\` with 2-3 options + "Create new category" → use what user picks.`,
1144
+ ``,
1145
+ `NEVER guess. NEVER use parent directly. NEVER skip categorization.`,
1146
+ ``,
1147
+ `### Project Scoping`,
1148
+ ``,
1149
+ `- Project-specific (bugs, architecture, config) → "${project}"`,
1150
+ `- Universal (language features, library APIs) → "global"`,
1151
+ `- Cross-project (shared infra) → "shared"`,
1152
+ ].join('\n');
1153
+ }
1154
+
1155
+ // --- Loop staleness cleanup ---
1156
+
1157
+ const LOOP_STALE_INACTIVITY_MS = 45 * 60 * 1000; // 45 min — browser iterations can be long
1158
+
1159
+ /**
1160
+ * Scan loop directory and delete stale/inactive loop files.
1161
+ * No history keeping — ended loops are removed immediately.
1162
+ *
1163
+ * A loop is stale if:
1164
+ * 1. active + exceeded its maxMinutes + 5min grace
1165
+ * 2. active + no iteration activity for 10 minutes (session died)
1166
+ *
1167
+ * @param {string} loopDir - path to data/loop/
1168
+ * @returns {{ deactivated: string[], deleted: string[] }}
1169
+ */
1170
+ export function cleanupStaleLoops(loopDir) {
1171
+ const deactivated = [];
1172
+ const deleted = [];
1173
+ if (!existsSync(loopDir)) return { deactivated, deleted };
1174
+
1175
+ const now = Date.now();
1176
+ try {
1177
+ const files = readdirSync(loopDir).filter(f => f.endsWith('.json') && !f.startsWith('pending-') && f !== 'active-pointer.json');
1178
+ for (const f of files) {
1179
+ const fp = join(loopDir, f);
1180
+ try {
1181
+ const loop = JSON.parse(readFileSync(fp, 'utf-8'));
1182
+
1183
+ if (loop.active) {
1184
+ const lastActivity = new Date(loop.lastIterationAt || loop.startedAt || 0).getTime();
1185
+ const noRecentActivity = (now - lastActivity) >= LOOP_STALE_INACTIVITY_MS;
1186
+
1187
+ if (noRecentActivity) {
1188
+ // Stale loop (no activity for 45 min) — delete immediately
1189
+ try { unlinkSync(fp); deleted.push(f); } catch { /* ok */ }
1190
+ }
1191
+ } else {
1192
+ // Inactive loop — delete immediately (no history keeping)
1193
+ try { unlinkSync(fp); deleted.push(f); } catch { /* ok */ }
1194
+ }
1195
+ } catch { /* skip corrupt files */ }
1196
+ }
1197
+ } catch { /* ok */ }
1198
+
1199
+ // Drop active-pointer.json if its target flag no longer exists
1200
+ try {
1201
+ const ptrPath = join(loopDir, 'active-pointer.json');
1202
+ if (existsSync(ptrPath)) {
1203
+ const ptr = JSON.parse(readFileSync(ptrPath, 'utf-8'));
1204
+ const targetPath = ptr?.sessionId ? join(loopDir, `${ptr.sessionId}.json`) : null;
1205
+ if (!targetPath || !existsSync(targetPath)) {
1206
+ try { unlinkSync(ptrPath); } catch { /* ok */ }
1207
+ }
1208
+ }
1209
+ } catch { /* ok */ }
1210
+
1211
+ // Delete stale pending-*.json files (client never claimed them)
1212
+ try {
1213
+ const pendingFiles = readdirSync(loopDir)
1214
+ .filter(f => f.startsWith('pending-') && f.endsWith('.json'));
1215
+ for (const f of pendingFiles) {
1216
+ const fp = join(loopDir, f);
1217
+ try {
1218
+ const loop = JSON.parse(readFileSync(fp, 'utf-8'));
1219
+ const lastActivity = new Date(loop.lastIterationAt || loop.startedAt || 0).getTime();
1220
+ if ((now - lastActivity) >= LOOP_STALE_INACTIVITY_MS) {
1221
+ try { unlinkSync(fp); deleted.push(f); } catch { /* ok */ }
1222
+ }
1223
+ } catch { /* skip corrupt */ }
1224
+ }
1225
+ } catch { /* ok */ }
1226
+
1227
+ return { deactivated, deleted };
1228
+ }