@10et/cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1401) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +79 -0
  3. package/clawdbot-plugin/clawdbot.plugin.json +20 -0
  4. package/clawdbot-plugin/index.js +555 -0
  5. package/clawdbot-plugin/index.ts +582 -0
  6. package/clawdbot-skill/README.md +328 -0
  7. package/clawdbot-skill/SKILL.md +59 -0
  8. package/clawdbot-skill/index.ts +683 -0
  9. package/clawdbot-skill/package.json +28 -0
  10. package/clawdbot-skill/skill.json +19 -0
  11. package/dist/commands/agent.d.ts +7 -0
  12. package/dist/commands/agent.d.ts.map +1 -0
  13. package/dist/commands/agent.js +170 -0
  14. package/dist/commands/agent.js.map +1 -0
  15. package/dist/commands/agents.d.ts +5 -0
  16. package/dist/commands/agents.d.ts.map +1 -0
  17. package/dist/commands/agents.js +400 -0
  18. package/dist/commands/agents.js.map +1 -0
  19. package/dist/commands/ci-setup.d.ts +5 -0
  20. package/dist/commands/ci-setup.d.ts.map +1 -0
  21. package/dist/commands/ci-setup.js +82 -0
  22. package/dist/commands/ci-setup.js.map +1 -0
  23. package/dist/commands/clawdbot.d.ts +11 -0
  24. package/dist/commands/clawdbot.d.ts.map +1 -0
  25. package/dist/commands/clawdbot.js +215 -0
  26. package/dist/commands/clawdbot.js.map +1 -0
  27. package/dist/commands/context-hub.d.ts +22 -0
  28. package/dist/commands/context-hub.d.ts.map +1 -0
  29. package/dist/commands/context-hub.js +3505 -0
  30. package/dist/commands/context-hub.js.map +1 -0
  31. package/dist/commands/deploy.d.ts +5 -0
  32. package/dist/commands/deploy.d.ts.map +1 -0
  33. package/dist/commands/deploy.js +371 -0
  34. package/dist/commands/deploy.js.map +1 -0
  35. package/dist/commands/digest.d.ts +12 -0
  36. package/dist/commands/digest.d.ts.map +1 -0
  37. package/dist/commands/digest.js +128 -0
  38. package/dist/commands/digest.js.map +1 -0
  39. package/dist/commands/doctor.d.ts +8 -0
  40. package/dist/commands/doctor.d.ts.map +1 -0
  41. package/dist/commands/doctor.js +265 -0
  42. package/dist/commands/doctor.js.map +1 -0
  43. package/dist/commands/eval.d.ts +46 -0
  44. package/dist/commands/eval.d.ts.map +1 -0
  45. package/dist/commands/eval.js +427 -0
  46. package/dist/commands/eval.js.map +1 -0
  47. package/dist/commands/feedback.d.ts +2 -0
  48. package/dist/commands/feedback.d.ts.map +1 -0
  49. package/dist/commands/feedback.js +179 -0
  50. package/dist/commands/feedback.js.map +1 -0
  51. package/dist/commands/findings.d.ts +13 -0
  52. package/dist/commands/findings.d.ts.map +1 -0
  53. package/dist/commands/findings.js +203 -0
  54. package/dist/commands/findings.js.map +1 -0
  55. package/dist/commands/flows.d.ts +10 -0
  56. package/dist/commands/flows.d.ts.map +1 -0
  57. package/dist/commands/flows.js +423 -0
  58. package/dist/commands/flows.js.map +1 -0
  59. package/dist/commands/gtm-process-update.d.ts +10 -0
  60. package/dist/commands/gtm-process-update.d.ts.map +1 -0
  61. package/dist/commands/gtm-process-update.js +101 -0
  62. package/dist/commands/gtm-process-update.js.map +1 -0
  63. package/dist/commands/hooks.d.ts +13 -0
  64. package/dist/commands/hooks.d.ts.map +1 -0
  65. package/dist/commands/hooks.js +304 -0
  66. package/dist/commands/hooks.js.map +1 -0
  67. package/dist/commands/hud.d.ts +4 -0
  68. package/dist/commands/hud.d.ts.map +1 -0
  69. package/dist/commands/hud.js +300 -0
  70. package/dist/commands/hud.js.map +1 -0
  71. package/dist/commands/ide.d.ts +28 -0
  72. package/dist/commands/ide.d.ts.map +1 -0
  73. package/dist/commands/ide.js +628 -0
  74. package/dist/commands/ide.js.map +1 -0
  75. package/dist/commands/improve.d.ts +11 -0
  76. package/dist/commands/improve.d.ts.map +1 -0
  77. package/dist/commands/improve.js +77 -0
  78. package/dist/commands/improve.js.map +1 -0
  79. package/dist/commands/init-from-service.d.ts +15 -0
  80. package/dist/commands/init-from-service.d.ts.map +1 -0
  81. package/dist/commands/init-from-service.js +540 -0
  82. package/dist/commands/init-from-service.js.map +1 -0
  83. package/dist/commands/init.d.ts +15 -0
  84. package/dist/commands/init.d.ts.map +1 -0
  85. package/dist/commands/init.js +940 -0
  86. package/dist/commands/init.js.map +1 -0
  87. package/dist/commands/kanban.d.ts +34 -0
  88. package/dist/commands/kanban.d.ts.map +1 -0
  89. package/dist/commands/kanban.js +225 -0
  90. package/dist/commands/kanban.js.map +1 -0
  91. package/dist/commands/linear.d.ts +41 -0
  92. package/dist/commands/linear.d.ts.map +1 -0
  93. package/dist/commands/linear.js +740 -0
  94. package/dist/commands/linear.js.map +1 -0
  95. package/dist/commands/login.d.ts +12 -0
  96. package/dist/commands/login.d.ts.map +1 -0
  97. package/dist/commands/login.js +780 -0
  98. package/dist/commands/login.js.map +1 -0
  99. package/dist/commands/memory.d.ts +38 -0
  100. package/dist/commands/memory.d.ts.map +1 -0
  101. package/dist/commands/memory.js +229 -0
  102. package/dist/commands/memory.js.map +1 -0
  103. package/dist/commands/migrate-services.d.ts +8 -0
  104. package/dist/commands/migrate-services.d.ts.map +1 -0
  105. package/dist/commands/migrate-services.js +182 -0
  106. package/dist/commands/migrate-services.js.map +1 -0
  107. package/dist/commands/migrate-tenet.d.ts +25 -0
  108. package/dist/commands/migrate-tenet.d.ts.map +1 -0
  109. package/dist/commands/migrate-tenet.js +252 -0
  110. package/dist/commands/migrate-tenet.js.map +1 -0
  111. package/dist/commands/onboard.d.ts +24 -0
  112. package/dist/commands/onboard.d.ts.map +1 -0
  113. package/dist/commands/onboard.js +873 -0
  114. package/dist/commands/onboard.js.map +1 -0
  115. package/dist/commands/openclaw.d.ts +59 -0
  116. package/dist/commands/openclaw.d.ts.map +1 -0
  117. package/dist/commands/openclaw.js +775 -0
  118. package/dist/commands/openclaw.js.map +1 -0
  119. package/dist/commands/orchestrate.d.ts +14 -0
  120. package/dist/commands/orchestrate.d.ts.map +1 -0
  121. package/dist/commands/orchestrate.js +270 -0
  122. package/dist/commands/orchestrate.js.map +1 -0
  123. package/dist/commands/organize.d.ts +16 -0
  124. package/dist/commands/organize.d.ts.map +1 -0
  125. package/dist/commands/organize.js +334 -0
  126. package/dist/commands/organize.js.map +1 -0
  127. package/dist/commands/peter.d.ts +21 -0
  128. package/dist/commands/peter.d.ts.map +1 -0
  129. package/dist/commands/peter.js +2778 -0
  130. package/dist/commands/peter.js.map +1 -0
  131. package/dist/commands/pi-fleet.d.ts +18 -0
  132. package/dist/commands/pi-fleet.d.ts.map +1 -0
  133. package/dist/commands/pi-fleet.js +382 -0
  134. package/dist/commands/pi-fleet.js.map +1 -0
  135. package/dist/commands/pi.d.ts +25 -0
  136. package/dist/commands/pi.d.ts.map +1 -0
  137. package/dist/commands/pi.js +218 -0
  138. package/dist/commands/pi.js.map +1 -0
  139. package/dist/commands/pivot.d.ts +28 -0
  140. package/dist/commands/pivot.d.ts.map +1 -0
  141. package/dist/commands/pivot.js +216 -0
  142. package/dist/commands/pivot.js.map +1 -0
  143. package/dist/commands/portfolio.d.ts +11 -0
  144. package/dist/commands/portfolio.d.ts.map +1 -0
  145. package/dist/commands/portfolio.js +239 -0
  146. package/dist/commands/portfolio.js.map +1 -0
  147. package/dist/commands/predict.d.ts +25 -0
  148. package/dist/commands/predict.d.ts.map +1 -0
  149. package/dist/commands/predict.js +234 -0
  150. package/dist/commands/predict.js.map +1 -0
  151. package/dist/commands/profile.d.ts +46 -0
  152. package/dist/commands/profile.d.ts.map +1 -0
  153. package/dist/commands/profile.js +498 -0
  154. package/dist/commands/profile.js.map +1 -0
  155. package/dist/commands/ralph.d.ts +11 -0
  156. package/dist/commands/ralph.d.ts.map +1 -0
  157. package/dist/commands/ralph.js +102 -0
  158. package/dist/commands/ralph.js.map +1 -0
  159. package/dist/commands/repair.d.ts +7 -0
  160. package/dist/commands/repair.d.ts.map +1 -0
  161. package/dist/commands/repair.js +356 -0
  162. package/dist/commands/repair.js.map +1 -0
  163. package/dist/commands/scope.d.ts +8 -0
  164. package/dist/commands/scope.d.ts.map +1 -0
  165. package/dist/commands/scope.js +503 -0
  166. package/dist/commands/scope.js.map +1 -0
  167. package/dist/commands/service-agent.d.ts +16 -0
  168. package/dist/commands/service-agent.d.ts.map +1 -0
  169. package/dist/commands/service-agent.js +375 -0
  170. package/dist/commands/service-agent.js.map +1 -0
  171. package/dist/commands/service-manager.d.ts +12 -0
  172. package/dist/commands/service-manager.d.ts.map +1 -0
  173. package/dist/commands/service-manager.js +969 -0
  174. package/dist/commands/service-manager.js.map +1 -0
  175. package/dist/commands/service-validate.d.ts +12 -0
  176. package/dist/commands/service-validate.d.ts.map +1 -0
  177. package/dist/commands/service-validate.js +619 -0
  178. package/dist/commands/service-validate.js.map +1 -0
  179. package/dist/commands/services-create.d.ts +15 -0
  180. package/dist/commands/services-create.d.ts.map +1 -0
  181. package/dist/commands/services-create.js +1800 -0
  182. package/dist/commands/services-create.js.map +1 -0
  183. package/dist/commands/services-scan.d.ts +13 -0
  184. package/dist/commands/services-scan.d.ts.map +1 -0
  185. package/dist/commands/services-scan.js +251 -0
  186. package/dist/commands/services-scan.js.map +1 -0
  187. package/dist/commands/services-sync-agents.d.ts +23 -0
  188. package/dist/commands/services-sync-agents.d.ts.map +1 -0
  189. package/dist/commands/services-sync-agents.js +207 -0
  190. package/dist/commands/services-sync-agents.js.map +1 -0
  191. package/dist/commands/services.d.ts +19 -0
  192. package/dist/commands/services.d.ts.map +1 -0
  193. package/dist/commands/services.js +906 -0
  194. package/dist/commands/services.js.map +1 -0
  195. package/dist/commands/session.d.ts +7 -0
  196. package/dist/commands/session.d.ts.map +1 -0
  197. package/dist/commands/session.js +597 -0
  198. package/dist/commands/session.js.map +1 -0
  199. package/dist/commands/setup.d.ts +12 -0
  200. package/dist/commands/setup.d.ts.map +1 -0
  201. package/dist/commands/setup.js +727 -0
  202. package/dist/commands/setup.js.map +1 -0
  203. package/dist/commands/skills.d.ts +31 -0
  204. package/dist/commands/skills.d.ts.map +1 -0
  205. package/dist/commands/skills.js +314 -0
  206. package/dist/commands/skills.js.map +1 -0
  207. package/dist/commands/start.d.ts +25 -0
  208. package/dist/commands/start.d.ts.map +1 -0
  209. package/dist/commands/start.js +251 -0
  210. package/dist/commands/start.js.map +1 -0
  211. package/dist/commands/status.d.ts +2 -0
  212. package/dist/commands/status.d.ts.map +1 -0
  213. package/dist/commands/status.js +163 -0
  214. package/dist/commands/status.js.map +1 -0
  215. package/dist/commands/synopsis.d.ts +54 -0
  216. package/dist/commands/synopsis.d.ts.map +1 -0
  217. package/dist/commands/synopsis.js +277 -0
  218. package/dist/commands/synopsis.js.map +1 -0
  219. package/dist/commands/telemetry-monitor.d.ts +11 -0
  220. package/dist/commands/telemetry-monitor.d.ts.map +1 -0
  221. package/dist/commands/telemetry-monitor.js +224 -0
  222. package/dist/commands/telemetry-monitor.js.map +1 -0
  223. package/dist/commands/telemetry-test.d.ts +11 -0
  224. package/dist/commands/telemetry-test.d.ts.map +1 -0
  225. package/dist/commands/telemetry-test.js +67 -0
  226. package/dist/commands/telemetry-test.js.map +1 -0
  227. package/dist/commands/tenet-agents.d.ts +13 -0
  228. package/dist/commands/tenet-agents.d.ts.map +1 -0
  229. package/dist/commands/tenet-agents.js +191 -0
  230. package/dist/commands/tenet-agents.js.map +1 -0
  231. package/dist/commands/tenet-setup.d.ts +20 -0
  232. package/dist/commands/tenet-setup.d.ts.map +1 -0
  233. package/dist/commands/tenet-setup.js +135 -0
  234. package/dist/commands/tenet-setup.js.map +1 -0
  235. package/dist/commands/train.d.ts +51 -0
  236. package/dist/commands/train.d.ts.map +1 -0
  237. package/dist/commands/train.js +692 -0
  238. package/dist/commands/train.js.map +1 -0
  239. package/dist/commands/update.d.ts +12 -0
  240. package/dist/commands/update.d.ts.map +1 -0
  241. package/dist/commands/update.js +559 -0
  242. package/dist/commands/update.js.map +1 -0
  243. package/dist/commands/validate-settings.d.ts +37 -0
  244. package/dist/commands/validate-settings.d.ts.map +1 -0
  245. package/dist/commands/validate-settings.js +197 -0
  246. package/dist/commands/validate-settings.js.map +1 -0
  247. package/dist/commands/verify.d.ts +14 -0
  248. package/dist/commands/verify.d.ts.map +1 -0
  249. package/dist/commands/verify.js +304 -0
  250. package/dist/commands/verify.js.map +1 -0
  251. package/dist/commands/viz.d.ts +40 -0
  252. package/dist/commands/viz.d.ts.map +1 -0
  253. package/dist/commands/viz.js +877 -0
  254. package/dist/commands/viz.js.map +1 -0
  255. package/dist/commands/voice.d.ts +409 -0
  256. package/dist/commands/voice.d.ts.map +1 -0
  257. package/dist/commands/voice.js +4765 -0
  258. package/dist/commands/voice.js.map +1 -0
  259. package/dist/commands/whoami.d.ts +2 -0
  260. package/dist/commands/whoami.d.ts.map +1 -0
  261. package/dist/commands/whoami.js +24 -0
  262. package/dist/commands/whoami.js.map +1 -0
  263. package/dist/dashboard/index.d.ts +11 -0
  264. package/dist/dashboard/index.d.ts.map +1 -0
  265. package/dist/dashboard/index.js +70 -0
  266. package/dist/dashboard/index.js.map +1 -0
  267. package/dist/dashboard-static/assets/index-BVrmW-ZI.js +154 -0
  268. package/dist/dashboard-static/assets/index-DtruPD44.css +1 -0
  269. package/dist/dashboard-static/index.html +16 -0
  270. package/dist/index.d.ts +4 -0
  271. package/dist/index.d.ts.map +1 -0
  272. package/dist/index.js +1707 -0
  273. package/dist/index.js.map +1 -0
  274. package/dist/lib/advanced-setup.d.ts +78 -0
  275. package/dist/lib/advanced-setup.d.ts.map +1 -0
  276. package/dist/lib/advanced-setup.js +433 -0
  277. package/dist/lib/advanced-setup.js.map +1 -0
  278. package/dist/lib/agent-config.d.ts +86 -0
  279. package/dist/lib/agent-config.d.ts.map +1 -0
  280. package/dist/lib/agent-config.js +281 -0
  281. package/dist/lib/agent-config.js.map +1 -0
  282. package/dist/lib/agent-generator.d.ts +36 -0
  283. package/dist/lib/agent-generator.d.ts.map +1 -0
  284. package/dist/lib/agent-generator.js +400 -0
  285. package/dist/lib/agent-generator.js.map +1 -0
  286. package/dist/lib/agent-guards.d.ts +67 -0
  287. package/dist/lib/agent-guards.d.ts.map +1 -0
  288. package/dist/lib/agent-guards.js +229 -0
  289. package/dist/lib/agent-guards.js.map +1 -0
  290. package/dist/lib/agent-manifest.d.ts +35 -0
  291. package/dist/lib/agent-manifest.d.ts.map +1 -0
  292. package/dist/lib/agent-manifest.js +75 -0
  293. package/dist/lib/agent-manifest.js.map +1 -0
  294. package/dist/lib/agent-runtime-api.d.ts +32 -0
  295. package/dist/lib/agent-runtime-api.d.ts.map +1 -0
  296. package/dist/lib/agent-runtime-api.js +270 -0
  297. package/dist/lib/agent-runtime-api.js.map +1 -0
  298. package/dist/lib/agent-session.d.ts +104 -0
  299. package/dist/lib/agent-session.d.ts.map +1 -0
  300. package/dist/lib/agent-session.js +954 -0
  301. package/dist/lib/agent-session.js.map +1 -0
  302. package/dist/lib/build-supervisor.d.ts +44 -0
  303. package/dist/lib/build-supervisor.d.ts.map +1 -0
  304. package/dist/lib/build-supervisor.js +79 -0
  305. package/dist/lib/build-supervisor.js.map +1 -0
  306. package/dist/lib/connectors/index.d.ts +19 -0
  307. package/dist/lib/connectors/index.d.ts.map +1 -0
  308. package/dist/lib/connectors/index.js +23 -0
  309. package/dist/lib/connectors/index.js.map +1 -0
  310. package/dist/lib/counterfactual-engine.d.ts +136 -0
  311. package/dist/lib/counterfactual-engine.d.ts.map +1 -0
  312. package/dist/lib/counterfactual-engine.js +417 -0
  313. package/dist/lib/counterfactual-engine.js.map +1 -0
  314. package/dist/lib/counterfactual-training-bridge.d.ts +114 -0
  315. package/dist/lib/counterfactual-training-bridge.d.ts.map +1 -0
  316. package/dist/lib/counterfactual-training-bridge.js +322 -0
  317. package/dist/lib/counterfactual-training-bridge.js.map +1 -0
  318. package/dist/lib/discovery-agent.d.ts +48 -0
  319. package/dist/lib/discovery-agent.d.ts.map +1 -0
  320. package/dist/lib/discovery-agent.js +111 -0
  321. package/dist/lib/discovery-agent.js.map +1 -0
  322. package/dist/lib/domain/engine.d.ts +11 -0
  323. package/dist/lib/domain/engine.d.ts.map +1 -0
  324. package/dist/lib/domain/engine.js +23 -0
  325. package/dist/lib/domain/engine.js.map +1 -0
  326. package/dist/lib/domain/index.d.ts +3 -0
  327. package/dist/lib/domain/index.d.ts.map +1 -0
  328. package/dist/lib/domain/index.js +3 -0
  329. package/dist/lib/domain/index.js.map +1 -0
  330. package/dist/lib/domain/template-loader.d.ts +9 -0
  331. package/dist/lib/domain/template-loader.d.ts.map +1 -0
  332. package/dist/lib/domain/template-loader.js +29 -0
  333. package/dist/lib/domain/template-loader.js.map +1 -0
  334. package/dist/lib/domain/types.d.ts +29 -0
  335. package/dist/lib/domain/types.d.ts.map +1 -0
  336. package/dist/lib/domain/types.js +2 -0
  337. package/dist/lib/domain/types.js.map +1 -0
  338. package/dist/lib/dynamics-model.d.ts +107 -0
  339. package/dist/lib/dynamics-model.d.ts.map +1 -0
  340. package/dist/lib/dynamics-model.js +363 -0
  341. package/dist/lib/dynamics-model.js.map +1 -0
  342. package/dist/lib/eval-snapshot.d.ts +47 -0
  343. package/dist/lib/eval-snapshot.d.ts.map +1 -0
  344. package/dist/lib/eval-snapshot.js +326 -0
  345. package/dist/lib/eval-snapshot.js.map +1 -0
  346. package/dist/lib/eval-store.d.ts +20 -0
  347. package/dist/lib/eval-store.d.ts.map +1 -0
  348. package/dist/lib/eval-store.js +209 -0
  349. package/dist/lib/eval-store.js.map +1 -0
  350. package/dist/lib/findings-engine.d.ts +51 -0
  351. package/dist/lib/findings-engine.d.ts.map +1 -0
  352. package/dist/lib/findings-engine.js +338 -0
  353. package/dist/lib/findings-engine.js.map +1 -0
  354. package/dist/lib/flow-engine.d.ts +57 -0
  355. package/dist/lib/flow-engine.d.ts.map +1 -0
  356. package/dist/lib/flow-engine.js +717 -0
  357. package/dist/lib/flow-engine.js.map +1 -0
  358. package/dist/lib/gtm-generator.d.ts +29 -0
  359. package/dist/lib/gtm-generator.d.ts.map +1 -0
  360. package/dist/lib/gtm-generator.js +250 -0
  361. package/dist/lib/gtm-generator.js.map +1 -0
  362. package/dist/lib/hook-transformer.d.ts +11 -0
  363. package/dist/lib/hook-transformer.d.ts.map +1 -0
  364. package/dist/lib/hook-transformer.js +74 -0
  365. package/dist/lib/hook-transformer.js.map +1 -0
  366. package/dist/lib/hub-client.d.ts +81 -0
  367. package/dist/lib/hub-client.d.ts.map +1 -0
  368. package/dist/lib/hub-client.js +73 -0
  369. package/dist/lib/hub-client.js.map +1 -0
  370. package/dist/lib/hub-health.d.ts +40 -0
  371. package/dist/lib/hub-health.d.ts.map +1 -0
  372. package/dist/lib/hub-health.js +101 -0
  373. package/dist/lib/hub-health.js.map +1 -0
  374. package/dist/lib/ide-panes.d.ts +58 -0
  375. package/dist/lib/ide-panes.d.ts.map +1 -0
  376. package/dist/lib/ide-panes.js +508 -0
  377. package/dist/lib/ide-panes.js.map +1 -0
  378. package/dist/lib/invariant-monitor.d.ts +54 -0
  379. package/dist/lib/invariant-monitor.d.ts.map +1 -0
  380. package/dist/lib/invariant-monitor.js +487 -0
  381. package/dist/lib/invariant-monitor.js.map +1 -0
  382. package/dist/lib/journal-analyzer.d.ts +71 -0
  383. package/dist/lib/journal-analyzer.d.ts.map +1 -0
  384. package/dist/lib/journal-analyzer.js +306 -0
  385. package/dist/lib/journal-analyzer.js.map +1 -0
  386. package/dist/lib/kanban-github.d.ts +81 -0
  387. package/dist/lib/kanban-github.d.ts.map +1 -0
  388. package/dist/lib/kanban-github.js +318 -0
  389. package/dist/lib/kanban-github.js.map +1 -0
  390. package/dist/lib/kanban.d.ts +131 -0
  391. package/dist/lib/kanban.d.ts.map +1 -0
  392. package/dist/lib/kanban.js +340 -0
  393. package/dist/lib/kanban.js.map +1 -0
  394. package/dist/lib/kuva.d.ts +45 -0
  395. package/dist/lib/kuva.d.ts.map +1 -0
  396. package/dist/lib/kuva.js +131 -0
  397. package/dist/lib/kuva.js.map +1 -0
  398. package/dist/lib/linear-client.d.ts +73 -0
  399. package/dist/lib/linear-client.d.ts.map +1 -0
  400. package/dist/lib/linear-client.js +112 -0
  401. package/dist/lib/linear-client.js.map +1 -0
  402. package/dist/lib/linear-id-map.d.ts +20 -0
  403. package/dist/lib/linear-id-map.d.ts.map +1 -0
  404. package/dist/lib/linear-id-map.js +59 -0
  405. package/dist/lib/linear-id-map.js.map +1 -0
  406. package/dist/lib/linear-kanban.d.ts +66 -0
  407. package/dist/lib/linear-kanban.d.ts.map +1 -0
  408. package/dist/lib/linear-kanban.js +175 -0
  409. package/dist/lib/linear-kanban.js.map +1 -0
  410. package/dist/lib/linear-webhook.d.ts +50 -0
  411. package/dist/lib/linear-webhook.d.ts.map +1 -0
  412. package/dist/lib/linear-webhook.js +92 -0
  413. package/dist/lib/linear-webhook.js.map +1 -0
  414. package/dist/lib/map-event-bus.d.ts +50 -0
  415. package/dist/lib/map-event-bus.d.ts.map +1 -0
  416. package/dist/lib/map-event-bus.js +366 -0
  417. package/dist/lib/map-event-bus.js.map +1 -0
  418. package/dist/lib/memory-db.d.ts +136 -0
  419. package/dist/lib/memory-db.d.ts.map +1 -0
  420. package/dist/lib/memory-db.js +429 -0
  421. package/dist/lib/memory-db.js.map +1 -0
  422. package/dist/lib/memory-indexer.d.ts +61 -0
  423. package/dist/lib/memory-indexer.d.ts.map +1 -0
  424. package/dist/lib/memory-indexer.js +418 -0
  425. package/dist/lib/memory-indexer.js.map +1 -0
  426. package/dist/lib/memory-search.d.ts +185 -0
  427. package/dist/lib/memory-search.d.ts.map +1 -0
  428. package/dist/lib/memory-search.js +678 -0
  429. package/dist/lib/memory-search.js.map +1 -0
  430. package/dist/lib/meta-orchestrator.d.ts +141 -0
  431. package/dist/lib/meta-orchestrator.d.ts.map +1 -0
  432. package/dist/lib/meta-orchestrator.js +552 -0
  433. package/dist/lib/meta-orchestrator.js.map +1 -0
  434. package/dist/lib/model-pricing.d.ts +11 -0
  435. package/dist/lib/model-pricing.d.ts.map +1 -0
  436. package/dist/lib/model-pricing.js +27 -0
  437. package/dist/lib/model-pricing.js.map +1 -0
  438. package/dist/lib/onboarding.d.ts +40 -0
  439. package/dist/lib/onboarding.d.ts.map +1 -0
  440. package/dist/lib/onboarding.js +213 -0
  441. package/dist/lib/onboarding.js.map +1 -0
  442. package/dist/lib/openclaw-registry.d.ts +48 -0
  443. package/dist/lib/openclaw-registry.d.ts.map +1 -0
  444. package/dist/lib/openclaw-registry.js +181 -0
  445. package/dist/lib/openclaw-registry.js.map +1 -0
  446. package/dist/lib/openclaw-sdk.d.ts +115 -0
  447. package/dist/lib/openclaw-sdk.d.ts.map +1 -0
  448. package/dist/lib/openclaw-sdk.js +220 -0
  449. package/dist/lib/openclaw-sdk.js.map +1 -0
  450. package/dist/lib/peer-agent-generator.d.ts +44 -0
  451. package/dist/lib/peer-agent-generator.d.ts.map +1 -0
  452. package/dist/lib/peer-agent-generator.js +310 -0
  453. package/dist/lib/peer-agent-generator.js.map +1 -0
  454. package/dist/lib/peter-parker-bridge.d.ts +70 -0
  455. package/dist/lib/peter-parker-bridge.d.ts.map +1 -0
  456. package/dist/lib/peter-parker-bridge.js +345 -0
  457. package/dist/lib/peter-parker-bridge.js.map +1 -0
  458. package/dist/lib/peter-parker-config.d.ts +13 -0
  459. package/dist/lib/peter-parker-config.d.ts.map +1 -0
  460. package/dist/lib/peter-parker-config.js +86 -0
  461. package/dist/lib/peter-parker-config.js.map +1 -0
  462. package/dist/lib/physical-world-model.d.ts +50 -0
  463. package/dist/lib/physical-world-model.d.ts.map +1 -0
  464. package/dist/lib/physical-world-model.js +251 -0
  465. package/dist/lib/physical-world-model.js.map +1 -0
  466. package/dist/lib/pi-sky/bridge.d.ts +55 -0
  467. package/dist/lib/pi-sky/bridge.d.ts.map +1 -0
  468. package/dist/lib/pi-sky/bridge.js +264 -0
  469. package/dist/lib/pi-sky/bridge.js.map +1 -0
  470. package/dist/lib/pi-sky/cost-monitor.d.ts +21 -0
  471. package/dist/lib/pi-sky/cost-monitor.d.ts.map +1 -0
  472. package/dist/lib/pi-sky/cost-monitor.js +126 -0
  473. package/dist/lib/pi-sky/cost-monitor.js.map +1 -0
  474. package/dist/lib/pi-sky/eval-sweep.d.ts +27 -0
  475. package/dist/lib/pi-sky/eval-sweep.d.ts.map +1 -0
  476. package/dist/lib/pi-sky/eval-sweep.js +141 -0
  477. package/dist/lib/pi-sky/eval-sweep.js.map +1 -0
  478. package/dist/lib/pi-sky/event-router.d.ts +32 -0
  479. package/dist/lib/pi-sky/event-router.d.ts.map +1 -0
  480. package/dist/lib/pi-sky/event-router.js +176 -0
  481. package/dist/lib/pi-sky/event-router.js.map +1 -0
  482. package/dist/lib/pi-sky/experiment.d.ts +9 -0
  483. package/dist/lib/pi-sky/experiment.d.ts.map +1 -0
  484. package/dist/lib/pi-sky/experiment.js +83 -0
  485. package/dist/lib/pi-sky/experiment.js.map +1 -0
  486. package/dist/lib/pi-sky/index.d.ts +16 -0
  487. package/dist/lib/pi-sky/index.d.ts.map +1 -0
  488. package/dist/lib/pi-sky/index.js +16 -0
  489. package/dist/lib/pi-sky/index.js.map +1 -0
  490. package/dist/lib/pi-sky/stratus-gate.d.ts +28 -0
  491. package/dist/lib/pi-sky/stratus-gate.d.ts.map +1 -0
  492. package/dist/lib/pi-sky/stratus-gate.js +61 -0
  493. package/dist/lib/pi-sky/stratus-gate.js.map +1 -0
  494. package/dist/lib/pi-sky/swarm.d.ts +28 -0
  495. package/dist/lib/pi-sky/swarm.d.ts.map +1 -0
  496. package/dist/lib/pi-sky/swarm.js +208 -0
  497. package/dist/lib/pi-sky/swarm.js.map +1 -0
  498. package/dist/lib/pi-sky/types.d.ts +139 -0
  499. package/dist/lib/pi-sky/types.d.ts.map +1 -0
  500. package/dist/lib/pi-sky/types.js +2 -0
  501. package/dist/lib/pi-sky/types.js.map +1 -0
  502. package/dist/lib/pi-sky/voice-bridge.d.ts +20 -0
  503. package/dist/lib/pi-sky/voice-bridge.d.ts.map +1 -0
  504. package/dist/lib/pi-sky/voice-bridge.js +91 -0
  505. package/dist/lib/pi-sky/voice-bridge.js.map +1 -0
  506. package/dist/lib/planning-loop.d.ts +157 -0
  507. package/dist/lib/planning-loop.d.ts.map +1 -0
  508. package/dist/lib/planning-loop.js +537 -0
  509. package/dist/lib/planning-loop.js.map +1 -0
  510. package/dist/lib/policy-head.d.ts +53 -0
  511. package/dist/lib/policy-head.d.ts.map +1 -0
  512. package/dist/lib/policy-head.js +400 -0
  513. package/dist/lib/policy-head.js.map +1 -0
  514. package/dist/lib/predictor.d.ts +109 -0
  515. package/dist/lib/predictor.d.ts.map +1 -0
  516. package/dist/lib/predictor.js +433 -0
  517. package/dist/lib/predictor.js.map +1 -0
  518. package/dist/lib/replay-buffer.d.ts +93 -0
  519. package/dist/lib/replay-buffer.d.ts.map +1 -0
  520. package/dist/lib/replay-buffer.js +302 -0
  521. package/dist/lib/replay-buffer.js.map +1 -0
  522. package/dist/lib/resource-optimizer-middleware.d.ts +39 -0
  523. package/dist/lib/resource-optimizer-middleware.d.ts.map +1 -0
  524. package/dist/lib/resource-optimizer-middleware.js +228 -0
  525. package/dist/lib/resource-optimizer-middleware.js.map +1 -0
  526. package/dist/lib/resource-optimizer.d.ts +71 -0
  527. package/dist/lib/resource-optimizer.d.ts.map +1 -0
  528. package/dist/lib/resource-optimizer.js +228 -0
  529. package/dist/lib/resource-optimizer.js.map +1 -0
  530. package/dist/lib/rewards/index.d.ts +14 -0
  531. package/dist/lib/rewards/index.d.ts.map +1 -0
  532. package/dist/lib/rewards/index.js +15 -0
  533. package/dist/lib/rewards/index.js.map +1 -0
  534. package/dist/lib/rl-manager.d.ts +74 -0
  535. package/dist/lib/rl-manager.d.ts.map +1 -0
  536. package/dist/lib/rl-manager.js +245 -0
  537. package/dist/lib/rl-manager.js.map +1 -0
  538. package/dist/lib/sentinel-rl.d.ts +97 -0
  539. package/dist/lib/sentinel-rl.d.ts.map +1 -0
  540. package/dist/lib/sentinel-rl.js +430 -0
  541. package/dist/lib/sentinel-rl.js.map +1 -0
  542. package/dist/lib/service-analyzer.d.ts +76 -0
  543. package/dist/lib/service-analyzer.d.ts.map +1 -0
  544. package/dist/lib/service-analyzer.js +704 -0
  545. package/dist/lib/service-analyzer.js.map +1 -0
  546. package/dist/lib/service-dependencies.d.ts +44 -0
  547. package/dist/lib/service-dependencies.d.ts.map +1 -0
  548. package/dist/lib/service-dependencies.js +314 -0
  549. package/dist/lib/service-dependencies.js.map +1 -0
  550. package/dist/lib/service-detector.d.ts +61 -0
  551. package/dist/lib/service-detector.d.ts.map +1 -0
  552. package/dist/lib/service-detector.js +541 -0
  553. package/dist/lib/service-detector.js.map +1 -0
  554. package/dist/lib/service-gtm.d.ts +208 -0
  555. package/dist/lib/service-gtm.d.ts.map +1 -0
  556. package/dist/lib/service-gtm.js +1006 -0
  557. package/dist/lib/service-gtm.js.map +1 -0
  558. package/dist/lib/service-mcp-base.d.ts +103 -0
  559. package/dist/lib/service-mcp-base.d.ts.map +1 -0
  560. package/dist/lib/service-mcp-base.js +263 -0
  561. package/dist/lib/service-mcp-base.js.map +1 -0
  562. package/dist/lib/service-questionnaire.d.ts +11 -0
  563. package/dist/lib/service-questionnaire.d.ts.map +1 -0
  564. package/dist/lib/service-questionnaire.js +89 -0
  565. package/dist/lib/service-questionnaire.js.map +1 -0
  566. package/dist/lib/service-utils.d.ts +103 -0
  567. package/dist/lib/service-utils.d.ts.map +1 -0
  568. package/dist/lib/service-utils.js +385 -0
  569. package/dist/lib/service-utils.js.map +1 -0
  570. package/dist/lib/session-lock.d.ts +61 -0
  571. package/dist/lib/session-lock.d.ts.map +1 -0
  572. package/dist/lib/session-lock.js +438 -0
  573. package/dist/lib/session-lock.js.map +1 -0
  574. package/dist/lib/setup/agent-generator.d.ts +25 -0
  575. package/dist/lib/setup/agent-generator.d.ts.map +1 -0
  576. package/dist/lib/setup/agent-generator.js +444 -0
  577. package/dist/lib/setup/agent-generator.js.map +1 -0
  578. package/dist/lib/setup/context-analyzer.d.ts +16 -0
  579. package/dist/lib/setup/context-analyzer.d.ts.map +1 -0
  580. package/dist/lib/setup/context-analyzer.js +112 -0
  581. package/dist/lib/setup/context-analyzer.js.map +1 -0
  582. package/dist/lib/setup/doc-auditor.d.ts +54 -0
  583. package/dist/lib/setup/doc-auditor.d.ts.map +1 -0
  584. package/dist/lib/setup/doc-auditor.js +629 -0
  585. package/dist/lib/setup/doc-auditor.js.map +1 -0
  586. package/dist/lib/setup/domain-generator.d.ts +7 -0
  587. package/dist/lib/setup/domain-generator.d.ts.map +1 -0
  588. package/dist/lib/setup/domain-generator.js +58 -0
  589. package/dist/lib/setup/domain-generator.js.map +1 -0
  590. package/dist/lib/setup/flow-generator.d.ts +10 -0
  591. package/dist/lib/setup/flow-generator.d.ts.map +1 -0
  592. package/dist/lib/setup/flow-generator.js +113 -0
  593. package/dist/lib/setup/flow-generator.js.map +1 -0
  594. package/dist/lib/setup/invariant-bridge.d.ts +91 -0
  595. package/dist/lib/setup/invariant-bridge.d.ts.map +1 -0
  596. package/dist/lib/setup/invariant-bridge.js +384 -0
  597. package/dist/lib/setup/invariant-bridge.js.map +1 -0
  598. package/dist/lib/setup/smart-eval-generator.d.ts +38 -0
  599. package/dist/lib/setup/smart-eval-generator.d.ts.map +1 -0
  600. package/dist/lib/setup/smart-eval-generator.js +378 -0
  601. package/dist/lib/setup/smart-eval-generator.js.map +1 -0
  602. package/dist/lib/setup/smart-recommender.d.ts +63 -0
  603. package/dist/lib/setup/smart-recommender.d.ts.map +1 -0
  604. package/dist/lib/setup/smart-recommender.js +329 -0
  605. package/dist/lib/setup/smart-recommender.js.map +1 -0
  606. package/dist/lib/setup/spec-generator.d.ts +99 -0
  607. package/dist/lib/setup/spec-generator.d.ts.map +1 -0
  608. package/dist/lib/setup/spec-generator.js +784 -0
  609. package/dist/lib/setup/spec-generator.js.map +1 -0
  610. package/dist/lib/setup/starter-intelligence.d.ts +25 -0
  611. package/dist/lib/setup/starter-intelligence.d.ts.map +1 -0
  612. package/dist/lib/setup/starter-intelligence.js +309 -0
  613. package/dist/lib/setup/starter-intelligence.js.map +1 -0
  614. package/dist/lib/setup/violation-agent-generator.d.ts +32 -0
  615. package/dist/lib/setup/violation-agent-generator.d.ts.map +1 -0
  616. package/dist/lib/setup/violation-agent-generator.js +255 -0
  617. package/dist/lib/setup/violation-agent-generator.js.map +1 -0
  618. package/dist/lib/skill-generator.d.ts +21 -0
  619. package/dist/lib/skill-generator.d.ts.map +1 -0
  620. package/dist/lib/skill-generator.js +253 -0
  621. package/dist/lib/skill-generator.js.map +1 -0
  622. package/dist/lib/state-capture.d.ts +36 -0
  623. package/dist/lib/state-capture.d.ts.map +1 -0
  624. package/dist/lib/state-capture.js +541 -0
  625. package/dist/lib/state-capture.js.map +1 -0
  626. package/dist/lib/stealth-onboarding.d.ts +40 -0
  627. package/dist/lib/stealth-onboarding.d.ts.map +1 -0
  628. package/dist/lib/stealth-onboarding.js +213 -0
  629. package/dist/lib/stealth-onboarding.js.map +1 -0
  630. package/dist/lib/storage/cloud.d.ts +27 -0
  631. package/dist/lib/storage/cloud.d.ts.map +1 -0
  632. package/dist/lib/storage/cloud.js +75 -0
  633. package/dist/lib/storage/cloud.js.map +1 -0
  634. package/dist/lib/storage/index.d.ts +15 -0
  635. package/dist/lib/storage/index.d.ts.map +1 -0
  636. package/dist/lib/storage/index.js +15 -0
  637. package/dist/lib/storage/index.js.map +1 -0
  638. package/dist/lib/storage/interface.d.ts +56 -0
  639. package/dist/lib/storage/interface.d.ts.map +1 -0
  640. package/dist/lib/storage/interface.js +2 -0
  641. package/dist/lib/storage/interface.js.map +1 -0
  642. package/dist/lib/storage/local.d.ts +26 -0
  643. package/dist/lib/storage/local.d.ts.map +1 -0
  644. package/dist/lib/storage/local.js +164 -0
  645. package/dist/lib/storage/local.js.map +1 -0
  646. package/dist/lib/stratus-client.d.ts +178 -0
  647. package/dist/lib/stratus-client.d.ts.map +1 -0
  648. package/dist/lib/stratus-client.js +739 -0
  649. package/dist/lib/stratus-client.js.map +1 -0
  650. package/dist/lib/stratus-rollout-test.d.ts +10 -0
  651. package/dist/lib/stratus-rollout-test.d.ts.map +1 -0
  652. package/dist/lib/stratus-rollout-test.js +412 -0
  653. package/dist/lib/stratus-rollout-test.js.map +1 -0
  654. package/dist/lib/surface-agent.d.ts +78 -0
  655. package/dist/lib/surface-agent.d.ts.map +1 -0
  656. package/dist/lib/surface-agent.js +105 -0
  657. package/dist/lib/surface-agent.js.map +1 -0
  658. package/dist/lib/surface-coordination-example.d.ts +30 -0
  659. package/dist/lib/surface-coordination-example.d.ts.map +1 -0
  660. package/dist/lib/surface-coordination-example.js +164 -0
  661. package/dist/lib/surface-coordination-example.js.map +1 -0
  662. package/dist/lib/telemetry/physical-world-collector.d.ts +15 -0
  663. package/dist/lib/telemetry/physical-world-collector.d.ts.map +1 -0
  664. package/dist/lib/telemetry/physical-world-collector.js +177 -0
  665. package/dist/lib/telemetry/physical-world-collector.js.map +1 -0
  666. package/dist/lib/telemetry/training-bridge.d.ts +51 -0
  667. package/dist/lib/telemetry/training-bridge.d.ts.map +1 -0
  668. package/dist/lib/telemetry/training-bridge.js +185 -0
  669. package/dist/lib/telemetry/training-bridge.js.map +1 -0
  670. package/dist/lib/telemetry-agent-v2.d.ts +128 -0
  671. package/dist/lib/telemetry-agent-v2.d.ts.map +1 -0
  672. package/dist/lib/telemetry-agent-v2.js +1043 -0
  673. package/dist/lib/telemetry-agent-v2.js.map +1 -0
  674. package/dist/lib/telemetry-agent.d.ts +57 -0
  675. package/dist/lib/telemetry-agent.d.ts.map +1 -0
  676. package/dist/lib/telemetry-agent.js +289 -0
  677. package/dist/lib/telemetry-agent.js.map +1 -0
  678. package/dist/lib/telemetry-digest.d.ts +10 -0
  679. package/dist/lib/telemetry-digest.d.ts.map +1 -0
  680. package/dist/lib/telemetry-digest.js +381 -0
  681. package/dist/lib/telemetry-digest.js.map +1 -0
  682. package/dist/lib/telemetry.d.ts +37 -0
  683. package/dist/lib/telemetry.d.ts.map +1 -0
  684. package/dist/lib/telemetry.js +376 -0
  685. package/dist/lib/telemetry.js.map +1 -0
  686. package/dist/lib/tenet-board-agent.d.ts +52 -0
  687. package/dist/lib/tenet-board-agent.d.ts.map +1 -0
  688. package/dist/lib/tenet-board-agent.js +226 -0
  689. package/dist/lib/tenet-board-agent.js.map +1 -0
  690. package/dist/lib/tenet-ide-agent.d.ts +40 -0
  691. package/dist/lib/tenet-ide-agent.d.ts.map +1 -0
  692. package/dist/lib/tenet-ide-agent.js +199 -0
  693. package/dist/lib/tenet-ide-agent.js.map +1 -0
  694. package/dist/lib/text-preprocessing.d.ts +83 -0
  695. package/dist/lib/text-preprocessing.d.ts.map +1 -0
  696. package/dist/lib/text-preprocessing.js +261 -0
  697. package/dist/lib/text-preprocessing.js.map +1 -0
  698. package/dist/lib/tool-schemas.d.ts +35 -0
  699. package/dist/lib/tool-schemas.d.ts.map +1 -0
  700. package/dist/lib/tool-schemas.js +246 -0
  701. package/dist/lib/tool-schemas.js.map +1 -0
  702. package/dist/lib/training-buffer.d.ts +86 -0
  703. package/dist/lib/training-buffer.d.ts.map +1 -0
  704. package/dist/lib/training-buffer.js +139 -0
  705. package/dist/lib/training-buffer.js.map +1 -0
  706. package/dist/lib/training-tuples.d.ts +33 -0
  707. package/dist/lib/training-tuples.d.ts.map +1 -0
  708. package/dist/lib/training-tuples.js +273 -0
  709. package/dist/lib/training-tuples.js.map +1 -0
  710. package/dist/lib/trajectory-loader.d.ts +82 -0
  711. package/dist/lib/trajectory-loader.d.ts.map +1 -0
  712. package/dist/lib/trajectory-loader.js +406 -0
  713. package/dist/lib/trajectory-loader.js.map +1 -0
  714. package/dist/lib/tuple-miner.d.ts +30 -0
  715. package/dist/lib/tuple-miner.d.ts.map +1 -0
  716. package/dist/lib/tuple-miner.js +427 -0
  717. package/dist/lib/tuple-miner.js.map +1 -0
  718. package/dist/lib/vm-backend.d.ts +72 -0
  719. package/dist/lib/vm-backend.d.ts.map +1 -0
  720. package/dist/lib/vm-backend.js +175 -0
  721. package/dist/lib/vm-backend.js.map +1 -0
  722. package/dist/lib/workspace/backend.d.ts +53 -0
  723. package/dist/lib/workspace/backend.d.ts.map +1 -0
  724. package/dist/lib/workspace/backend.js +37 -0
  725. package/dist/lib/workspace/backend.js.map +1 -0
  726. package/dist/lib/workspace/cmux-adapter.d.ts +46 -0
  727. package/dist/lib/workspace/cmux-adapter.d.ts.map +1 -0
  728. package/dist/lib/workspace/cmux-adapter.js +261 -0
  729. package/dist/lib/workspace/cmux-adapter.js.map +1 -0
  730. package/dist/lib/workspace/data-pipeline.d.ts +35 -0
  731. package/dist/lib/workspace/data-pipeline.d.ts.map +1 -0
  732. package/dist/lib/workspace/data-pipeline.js +494 -0
  733. package/dist/lib/workspace/data-pipeline.js.map +1 -0
  734. package/dist/lib/workspace/engine.d.ts +65 -0
  735. package/dist/lib/workspace/engine.d.ts.map +1 -0
  736. package/dist/lib/workspace/engine.js +407 -0
  737. package/dist/lib/workspace/engine.js.map +1 -0
  738. package/dist/lib/workspace/notifications.d.ts +14 -0
  739. package/dist/lib/workspace/notifications.d.ts.map +1 -0
  740. package/dist/lib/workspace/notifications.js +41 -0
  741. package/dist/lib/workspace/notifications.js.map +1 -0
  742. package/dist/lib/workspace/sidebar-runner.d.ts +13 -0
  743. package/dist/lib/workspace/sidebar-runner.d.ts.map +1 -0
  744. package/dist/lib/workspace/sidebar-runner.js +419 -0
  745. package/dist/lib/workspace/sidebar-runner.js.map +1 -0
  746. package/dist/lib/workspace/surface-registry.d.ts +49 -0
  747. package/dist/lib/workspace/surface-registry.d.ts.map +1 -0
  748. package/dist/lib/workspace/surface-registry.js +225 -0
  749. package/dist/lib/workspace/surface-registry.js.map +1 -0
  750. package/dist/lib/workspace/surface-type.d.ts +153 -0
  751. package/dist/lib/workspace/surface-type.d.ts.map +1 -0
  752. package/dist/lib/workspace/surface-type.js +9 -0
  753. package/dist/lib/workspace/surface-type.js.map +1 -0
  754. package/dist/lib/workspace/surfaces/agent-overview.d.ts +16 -0
  755. package/dist/lib/workspace/surfaces/agent-overview.d.ts.map +1 -0
  756. package/dist/lib/workspace/surfaces/agent-overview.js +116 -0
  757. package/dist/lib/workspace/surfaces/agent-overview.js.map +1 -0
  758. package/dist/lib/workspace/surfaces/agent.d.ts +16 -0
  759. package/dist/lib/workspace/surfaces/agent.d.ts.map +1 -0
  760. package/dist/lib/workspace/surfaces/agent.js +112 -0
  761. package/dist/lib/workspace/surfaces/agent.js.map +1 -0
  762. package/dist/lib/workspace/surfaces/claude.d.ts +15 -0
  763. package/dist/lib/workspace/surfaces/claude.d.ts.map +1 -0
  764. package/dist/lib/workspace/surfaces/claude.js +23 -0
  765. package/dist/lib/workspace/surfaces/claude.js.map +1 -0
  766. package/dist/lib/workspace/surfaces/dashboard.d.ts +21 -0
  767. package/dist/lib/workspace/surfaces/dashboard.d.ts.map +1 -0
  768. package/dist/lib/workspace/surfaces/dashboard.js +32 -0
  769. package/dist/lib/workspace/surfaces/dashboard.js.map +1 -0
  770. package/dist/lib/workspace/surfaces/eval.d.ts +15 -0
  771. package/dist/lib/workspace/surfaces/eval.d.ts.map +1 -0
  772. package/dist/lib/workspace/surfaces/eval.js +42 -0
  773. package/dist/lib/workspace/surfaces/eval.js.map +1 -0
  774. package/dist/lib/workspace/surfaces/event-stream.d.ts +16 -0
  775. package/dist/lib/workspace/surfaces/event-stream.d.ts.map +1 -0
  776. package/dist/lib/workspace/surfaces/event-stream.js +40 -0
  777. package/dist/lib/workspace/surfaces/event-stream.js.map +1 -0
  778. package/dist/lib/workspace/surfaces/flow.d.ts +16 -0
  779. package/dist/lib/workspace/surfaces/flow.d.ts.map +1 -0
  780. package/dist/lib/workspace/surfaces/flow.js +49 -0
  781. package/dist/lib/workspace/surfaces/flow.js.map +1 -0
  782. package/dist/lib/workspace/surfaces/index.d.ts +19 -0
  783. package/dist/lib/workspace/surfaces/index.d.ts.map +1 -0
  784. package/dist/lib/workspace/surfaces/index.js +19 -0
  785. package/dist/lib/workspace/surfaces/index.js.map +1 -0
  786. package/dist/lib/workspace/surfaces/kanban.d.ts +15 -0
  787. package/dist/lib/workspace/surfaces/kanban.d.ts.map +1 -0
  788. package/dist/lib/workspace/surfaces/kanban.js +43 -0
  789. package/dist/lib/workspace/surfaces/kanban.js.map +1 -0
  790. package/dist/lib/workspace/surfaces/physical-world.d.ts +15 -0
  791. package/dist/lib/workspace/surfaces/physical-world.d.ts.map +1 -0
  792. package/dist/lib/workspace/surfaces/physical-world.js +37 -0
  793. package/dist/lib/workspace/surfaces/physical-world.js.map +1 -0
  794. package/dist/lib/workspace/surfaces/portfolio.d.ts +16 -0
  795. package/dist/lib/workspace/surfaces/portfolio.d.ts.map +1 -0
  796. package/dist/lib/workspace/surfaces/portfolio.js +102 -0
  797. package/dist/lib/workspace/surfaces/portfolio.js.map +1 -0
  798. package/dist/lib/workspace/surfaces/service.d.ts +16 -0
  799. package/dist/lib/workspace/surfaces/service.d.ts.map +1 -0
  800. package/dist/lib/workspace/surfaces/service.js +45 -0
  801. package/dist/lib/workspace/surfaces/service.js.map +1 -0
  802. package/dist/lib/workspace/surfaces/shell.d.ts +15 -0
  803. package/dist/lib/workspace/surfaces/shell.d.ts.map +1 -0
  804. package/dist/lib/workspace/surfaces/shell.js +19 -0
  805. package/dist/lib/workspace/surfaces/shell.js.map +1 -0
  806. package/dist/lib/workspace/surfaces/sidebar.d.ts +22 -0
  807. package/dist/lib/workspace/surfaces/sidebar.d.ts.map +1 -0
  808. package/dist/lib/workspace/surfaces/sidebar.js +94 -0
  809. package/dist/lib/workspace/surfaces/sidebar.js.map +1 -0
  810. package/dist/lib/workspace/surfaces/telemetry.d.ts +16 -0
  811. package/dist/lib/workspace/surfaces/telemetry.d.ts.map +1 -0
  812. package/dist/lib/workspace/surfaces/telemetry.js +48 -0
  813. package/dist/lib/workspace/surfaces/telemetry.js.map +1 -0
  814. package/dist/lib/workspace/surfaces/topology.d.ts +15 -0
  815. package/dist/lib/workspace/surfaces/topology.d.ts.map +1 -0
  816. package/dist/lib/workspace/surfaces/topology.js +19 -0
  817. package/dist/lib/workspace/surfaces/topology.js.map +1 -0
  818. package/dist/lib/workspace/surfaces/training.d.ts +16 -0
  819. package/dist/lib/workspace/surfaces/training.d.ts.map +1 -0
  820. package/dist/lib/workspace/surfaces/training.js +22 -0
  821. package/dist/lib/workspace/surfaces/training.js.map +1 -0
  822. package/dist/lib/workspace/tmux-adapter.d.ts +30 -0
  823. package/dist/lib/workspace/tmux-adapter.d.ts.map +1 -0
  824. package/dist/lib/workspace/tmux-adapter.js +137 -0
  825. package/dist/lib/workspace/tmux-adapter.js.map +1 -0
  826. package/dist/lib/workspace/tmux-sidebar.d.ts +14 -0
  827. package/dist/lib/workspace/tmux-sidebar.d.ts.map +1 -0
  828. package/dist/lib/workspace/tmux-sidebar.js +230 -0
  829. package/dist/lib/workspace/tmux-sidebar.js.map +1 -0
  830. package/dist/lib/world-model-store.d.ts +172 -0
  831. package/dist/lib/world-model-store.d.ts.map +1 -0
  832. package/dist/lib/world-model-store.js +487 -0
  833. package/dist/lib/world-model-store.js.map +1 -0
  834. package/dist/mcp/context-hub-mcp.d.ts +11 -0
  835. package/dist/mcp/context-hub-mcp.d.ts.map +1 -0
  836. package/dist/mcp/context-hub-mcp.js +797 -0
  837. package/dist/mcp/context-hub-mcp.js.map +1 -0
  838. package/dist/mcp/service-mcp-server.d.ts +12 -0
  839. package/dist/mcp/service-mcp-server.d.ts.map +1 -0
  840. package/dist/mcp/service-mcp-server.js +434 -0
  841. package/dist/mcp/service-mcp-server.js.map +1 -0
  842. package/dist/mcp/service-peer-mcp.d.ts +36 -0
  843. package/dist/mcp/service-peer-mcp.d.ts.map +1 -0
  844. package/dist/mcp/service-peer-mcp.js +220 -0
  845. package/dist/mcp/service-peer-mcp.js.map +1 -0
  846. package/dist/mcp/service-registry-mcp.d.ts +13 -0
  847. package/dist/mcp/service-registry-mcp.d.ts.map +1 -0
  848. package/dist/mcp/service-registry-mcp.js +330 -0
  849. package/dist/mcp/service-registry-mcp.js.map +1 -0
  850. package/dist/telegram/voice.d.ts +146 -0
  851. package/dist/telegram/voice.d.ts.map +1 -0
  852. package/dist/telegram/voice.js +351 -0
  853. package/dist/telegram/voice.js.map +1 -0
  854. package/dist/types/eval.d.ts +18 -0
  855. package/dist/types/eval.d.ts.map +1 -0
  856. package/dist/types/eval.js +5 -0
  857. package/dist/types/eval.js.map +1 -0
  858. package/dist/types/flows.d.ts +72 -0
  859. package/dist/types/flows.d.ts.map +1 -0
  860. package/dist/types/flows.js +10 -0
  861. package/dist/types/flows.js.map +1 -0
  862. package/dist/types/ide.d.ts +49 -0
  863. package/dist/types/ide.d.ts.map +1 -0
  864. package/dist/types/ide.js +5 -0
  865. package/dist/types/ide.js.map +1 -0
  866. package/dist/types/journal.d.ts +133 -0
  867. package/dist/types/journal.d.ts.map +1 -0
  868. package/dist/types/journal.js +59 -0
  869. package/dist/types/journal.js.map +1 -0
  870. package/dist/types/map.d.ts +42 -0
  871. package/dist/types/map.d.ts.map +1 -0
  872. package/dist/types/map.js +39 -0
  873. package/dist/types/map.js.map +1 -0
  874. package/dist/types/physical-world-model.d.ts +65 -0
  875. package/dist/types/physical-world-model.d.ts.map +1 -0
  876. package/dist/types/physical-world-model.js +43 -0
  877. package/dist/types/physical-world-model.js.map +1 -0
  878. package/dist/types/platform-digest.d.ts +228 -0
  879. package/dist/types/platform-digest.d.ts.map +1 -0
  880. package/dist/types/platform-digest.js +5 -0
  881. package/dist/types/platform-digest.js.map +1 -0
  882. package/dist/types/skills.d.ts +44 -0
  883. package/dist/types/skills.d.ts.map +1 -0
  884. package/dist/types/skills.js +5 -0
  885. package/dist/types/skills.js.map +1 -0
  886. package/dist/types/telemetry-digest.d.ts +75 -0
  887. package/dist/types/telemetry-digest.d.ts.map +1 -0
  888. package/dist/types/telemetry-digest.js +5 -0
  889. package/dist/types/telemetry-digest.js.map +1 -0
  890. package/dist/types/telemetry.d.ts +107 -0
  891. package/dist/types/telemetry.d.ts.map +1 -0
  892. package/dist/types/telemetry.js +5 -0
  893. package/dist/types/telemetry.js.map +1 -0
  894. package/dist/types/world-model.d.ts +478 -0
  895. package/dist/types/world-model.d.ts.map +1 -0
  896. package/dist/types/world-model.js +87 -0
  897. package/dist/types/world-model.js.map +1 -0
  898. package/dist/ui/banner.d.ts +18 -0
  899. package/dist/ui/banner.d.ts.map +1 -0
  900. package/dist/ui/banner.js +323 -0
  901. package/dist/ui/banner.js.map +1 -0
  902. package/dist/ui/context-hub-logs.d.ts +10 -0
  903. package/dist/ui/context-hub-logs.d.ts.map +1 -0
  904. package/dist/ui/context-hub-logs.js +175 -0
  905. package/dist/ui/context-hub-logs.js.map +1 -0
  906. package/dist/ui/event-dashboard.d.ts +12 -0
  907. package/dist/ui/event-dashboard.d.ts.map +1 -0
  908. package/dist/ui/event-dashboard.js +342 -0
  909. package/dist/ui/event-dashboard.js.map +1 -0
  910. package/dist/ui/index.d.ts +8 -0
  911. package/dist/ui/index.d.ts.map +1 -0
  912. package/dist/ui/index.js +8 -0
  913. package/dist/ui/index.js.map +1 -0
  914. package/dist/ui/prompts.d.ts +52 -0
  915. package/dist/ui/prompts.d.ts.map +1 -0
  916. package/dist/ui/prompts.js +72 -0
  917. package/dist/ui/prompts.js.map +1 -0
  918. package/dist/ui/service-dashboard.d.ts +11 -0
  919. package/dist/ui/service-dashboard.d.ts.map +1 -0
  920. package/dist/ui/service-dashboard.js +357 -0
  921. package/dist/ui/service-dashboard.js.map +1 -0
  922. package/dist/ui/services-manager.d.ts +11 -0
  923. package/dist/ui/services-manager.d.ts.map +1 -0
  924. package/dist/ui/services-manager.js +507 -0
  925. package/dist/ui/services-manager.js.map +1 -0
  926. package/dist/ui/theme.d.ts +82 -0
  927. package/dist/ui/theme.d.ts.map +1 -0
  928. package/dist/ui/theme.js +142 -0
  929. package/dist/ui/theme.js.map +1 -0
  930. package/dist/utils/auth-guard.d.ts +66 -0
  931. package/dist/utils/auth-guard.d.ts.map +1 -0
  932. package/dist/utils/auth-guard.js +347 -0
  933. package/dist/utils/auth-guard.js.map +1 -0
  934. package/dist/utils/auth-status.d.ts +21 -0
  935. package/dist/utils/auth-status.d.ts.map +1 -0
  936. package/dist/utils/auth-status.js +53 -0
  937. package/dist/utils/auth-status.js.map +1 -0
  938. package/dist/utils/claude-md-generator.d.ts +10 -0
  939. package/dist/utils/claude-md-generator.d.ts.map +1 -0
  940. package/dist/utils/claude-md-generator.js +215 -0
  941. package/dist/utils/claude-md-generator.js.map +1 -0
  942. package/dist/utils/context-hub-port.d.ts +33 -0
  943. package/dist/utils/context-hub-port.d.ts.map +1 -0
  944. package/dist/utils/context-hub-port.js +118 -0
  945. package/dist/utils/context-hub-port.js.map +1 -0
  946. package/dist/utils/ensure-context-hub.d.ts +20 -0
  947. package/dist/utils/ensure-context-hub.d.ts.map +1 -0
  948. package/dist/utils/ensure-context-hub.js +66 -0
  949. package/dist/utils/ensure-context-hub.js.map +1 -0
  950. package/dist/utils/ensure-project.d.ts +12 -0
  951. package/dist/utils/ensure-project.d.ts.map +1 -0
  952. package/dist/utils/ensure-project.js +81 -0
  953. package/dist/utils/ensure-project.js.map +1 -0
  954. package/dist/utils/git.d.ts +73 -0
  955. package/dist/utils/git.d.ts.map +1 -0
  956. package/dist/utils/git.js +222 -0
  957. package/dist/utils/git.js.map +1 -0
  958. package/dist/utils/github-auth.d.ts +54 -0
  959. package/dist/utils/github-auth.d.ts.map +1 -0
  960. package/dist/utils/github-auth.js +376 -0
  961. package/dist/utils/github-auth.js.map +1 -0
  962. package/dist/utils/github-repo.d.ts +30 -0
  963. package/dist/utils/github-repo.d.ts.map +1 -0
  964. package/dist/utils/github-repo.js +219 -0
  965. package/dist/utils/github-repo.js.map +1 -0
  966. package/dist/utils/jfl-config.d.ts +30 -0
  967. package/dist/utils/jfl-config.d.ts.map +1 -0
  968. package/dist/utils/jfl-config.js +153 -0
  969. package/dist/utils/jfl-config.js.map +1 -0
  970. package/dist/utils/jfl-migration.d.ts +29 -0
  971. package/dist/utils/jfl-migration.d.ts.map +1 -0
  972. package/dist/utils/jfl-migration.js +142 -0
  973. package/dist/utils/jfl-migration.js.map +1 -0
  974. package/dist/utils/jfl-paths.d.ts +71 -0
  975. package/dist/utils/jfl-paths.d.ts.map +1 -0
  976. package/dist/utils/jfl-paths.js +157 -0
  977. package/dist/utils/jfl-paths.js.map +1 -0
  978. package/dist/utils/platform-auth.d.ts +81 -0
  979. package/dist/utils/platform-auth.d.ts.map +1 -0
  980. package/dist/utils/platform-auth.js +192 -0
  981. package/dist/utils/platform-auth.js.map +1 -0
  982. package/dist/utils/project-config.d.ts +43 -0
  983. package/dist/utils/project-config.d.ts.map +1 -0
  984. package/dist/utils/project-config.js +97 -0
  985. package/dist/utils/project-config.js.map +1 -0
  986. package/dist/utils/provenance.d.ts +65 -0
  987. package/dist/utils/provenance.d.ts.map +1 -0
  988. package/dist/utils/provenance.js +213 -0
  989. package/dist/utils/provenance.js.map +1 -0
  990. package/dist/utils/settings-validator.d.ts +74 -0
  991. package/dist/utils/settings-validator.d.ts.map +1 -0
  992. package/dist/utils/settings-validator.js +241 -0
  993. package/dist/utils/settings-validator.js.map +1 -0
  994. package/dist/utils/skill-registry.d.ts +49 -0
  995. package/dist/utils/skill-registry.d.ts.map +1 -0
  996. package/dist/utils/skill-registry.js +192 -0
  997. package/dist/utils/skill-registry.js.map +1 -0
  998. package/dist/utils/tenet-env.d.ts +34 -0
  999. package/dist/utils/tenet-env.d.ts.map +1 -0
  1000. package/dist/utils/tenet-env.js +42 -0
  1001. package/dist/utils/tenet-env.js.map +1 -0
  1002. package/dist/utils/wallet.d.ts +62 -0
  1003. package/dist/utils/wallet.d.ts.map +1 -0
  1004. package/dist/utils/wallet.js +253 -0
  1005. package/dist/utils/wallet.js.map +1 -0
  1006. package/dist/utils/x402-client.d.ts +86 -0
  1007. package/dist/utils/x402-client.d.ts.map +1 -0
  1008. package/dist/utils/x402-client.js +266 -0
  1009. package/dist/utils/x402-client.js.map +1 -0
  1010. package/package.json +105 -0
  1011. package/packages/pi/AGENTS.md +112 -0
  1012. package/packages/pi/assets/boot.mp3 +0 -0
  1013. package/packages/pi/dist/agent-grid.d.ts +24 -0
  1014. package/packages/pi/dist/agent-grid.d.ts.map +1 -0
  1015. package/packages/pi/dist/agent-grid.js +162 -0
  1016. package/packages/pi/dist/agent-grid.js.map +1 -0
  1017. package/packages/pi/dist/agent-names.d.ts +43 -0
  1018. package/packages/pi/dist/agent-names.d.ts.map +1 -0
  1019. package/packages/pi/dist/agent-names.js +156 -0
  1020. package/packages/pi/dist/agent-names.js.map +1 -0
  1021. package/packages/pi/dist/autoresearch.d.ts +15 -0
  1022. package/packages/pi/dist/autoresearch.d.ts.map +1 -0
  1023. package/packages/pi/dist/autoresearch.js +372 -0
  1024. package/packages/pi/dist/autoresearch.js.map +1 -0
  1025. package/packages/pi/dist/bookmarks.d.ts +15 -0
  1026. package/packages/pi/dist/bookmarks.d.ts.map +1 -0
  1027. package/packages/pi/dist/bookmarks.js +77 -0
  1028. package/packages/pi/dist/bookmarks.js.map +1 -0
  1029. package/packages/pi/dist/context.d.ts +17 -0
  1030. package/packages/pi/dist/context.d.ts.map +1 -0
  1031. package/packages/pi/dist/context.js +152 -0
  1032. package/packages/pi/dist/context.js.map +1 -0
  1033. package/packages/pi/dist/crm-tool.d.ts +12 -0
  1034. package/packages/pi/dist/crm-tool.d.ts.map +1 -0
  1035. package/packages/pi/dist/crm-tool.js +58 -0
  1036. package/packages/pi/dist/crm-tool.js.map +1 -0
  1037. package/packages/pi/dist/eval-tool.d.ts +11 -0
  1038. package/packages/pi/dist/eval-tool.d.ts.map +1 -0
  1039. package/packages/pi/dist/eval-tool.js +188 -0
  1040. package/packages/pi/dist/eval-tool.js.map +1 -0
  1041. package/packages/pi/dist/eval.d.ts +12 -0
  1042. package/packages/pi/dist/eval.d.ts.map +1 -0
  1043. package/packages/pi/dist/eval.js +43 -0
  1044. package/packages/pi/dist/eval.js.map +1 -0
  1045. package/packages/pi/dist/footer.d.ts +20 -0
  1046. package/packages/pi/dist/footer.d.ts.map +1 -0
  1047. package/packages/pi/dist/footer.js +222 -0
  1048. package/packages/pi/dist/footer.js.map +1 -0
  1049. package/packages/pi/dist/header.d.ts +17 -0
  1050. package/packages/pi/dist/header.d.ts.map +1 -0
  1051. package/packages/pi/dist/header.js +156 -0
  1052. package/packages/pi/dist/header.js.map +1 -0
  1053. package/packages/pi/dist/hub-resolver.d.ts +11 -0
  1054. package/packages/pi/dist/hub-resolver.d.ts.map +1 -0
  1055. package/packages/pi/dist/hub-resolver.js +58 -0
  1056. package/packages/pi/dist/hub-resolver.js.map +1 -0
  1057. package/packages/pi/dist/hub-tools.d.ts +14 -0
  1058. package/packages/pi/dist/hub-tools.d.ts.map +1 -0
  1059. package/packages/pi/dist/hub-tools.js +266 -0
  1060. package/packages/pi/dist/hub-tools.js.map +1 -0
  1061. package/packages/pi/dist/hud-tool.d.ts +17 -0
  1062. package/packages/pi/dist/hud-tool.d.ts.map +1 -0
  1063. package/packages/pi/dist/hud-tool.js +297 -0
  1064. package/packages/pi/dist/hud-tool.js.map +1 -0
  1065. package/packages/pi/dist/index.d.ts +12 -0
  1066. package/packages/pi/dist/index.d.ts.map +1 -0
  1067. package/packages/pi/dist/index.js +556 -0
  1068. package/packages/pi/dist/index.js.map +1 -0
  1069. package/packages/pi/dist/jfl-resolve.d.ts +29 -0
  1070. package/packages/pi/dist/jfl-resolve.d.ts.map +1 -0
  1071. package/packages/pi/dist/jfl-resolve.js +89 -0
  1072. package/packages/pi/dist/jfl-resolve.js.map +1 -0
  1073. package/packages/pi/dist/journal.d.ts +23 -0
  1074. package/packages/pi/dist/journal.d.ts.map +1 -0
  1075. package/packages/pi/dist/journal.js +250 -0
  1076. package/packages/pi/dist/journal.js.map +1 -0
  1077. package/packages/pi/dist/map-bridge.d.ts +20 -0
  1078. package/packages/pi/dist/map-bridge.d.ts.map +1 -0
  1079. package/packages/pi/dist/map-bridge.js +181 -0
  1080. package/packages/pi/dist/map-bridge.js.map +1 -0
  1081. package/packages/pi/dist/memory-tool.d.ts +11 -0
  1082. package/packages/pi/dist/memory-tool.d.ts.map +1 -0
  1083. package/packages/pi/dist/memory-tool.js +162 -0
  1084. package/packages/pi/dist/memory-tool.js.map +1 -0
  1085. package/packages/pi/dist/notifications.d.ts +15 -0
  1086. package/packages/pi/dist/notifications.d.ts.map +1 -0
  1087. package/packages/pi/dist/notifications.js +65 -0
  1088. package/packages/pi/dist/notifications.js.map +1 -0
  1089. package/packages/pi/dist/onboarding-v1.d.ts +15 -0
  1090. package/packages/pi/dist/onboarding-v1.d.ts.map +1 -0
  1091. package/packages/pi/dist/onboarding-v1.js +417 -0
  1092. package/packages/pi/dist/onboarding-v1.js.map +1 -0
  1093. package/packages/pi/dist/onboarding-v2.d.ts +18 -0
  1094. package/packages/pi/dist/onboarding-v2.d.ts.map +1 -0
  1095. package/packages/pi/dist/onboarding-v2.js +402 -0
  1096. package/packages/pi/dist/onboarding-v2.js.map +1 -0
  1097. package/packages/pi/dist/onboarding-v3.d.ts +13 -0
  1098. package/packages/pi/dist/onboarding-v3.d.ts.map +1 -0
  1099. package/packages/pi/dist/onboarding-v3.js +581 -0
  1100. package/packages/pi/dist/onboarding-v3.js.map +1 -0
  1101. package/packages/pi/dist/peter-parker.d.ts +12 -0
  1102. package/packages/pi/dist/peter-parker.d.ts.map +1 -0
  1103. package/packages/pi/dist/peter-parker.js +162 -0
  1104. package/packages/pi/dist/peter-parker.js.map +1 -0
  1105. package/packages/pi/dist/pivot-tool.d.ts +11 -0
  1106. package/packages/pi/dist/pivot-tool.d.ts.map +1 -0
  1107. package/packages/pi/dist/pivot-tool.js +56 -0
  1108. package/packages/pi/dist/pivot-tool.js.map +1 -0
  1109. package/packages/pi/dist/policy-head-tool.d.ts +15 -0
  1110. package/packages/pi/dist/policy-head-tool.d.ts.map +1 -0
  1111. package/packages/pi/dist/policy-head-tool.js +220 -0
  1112. package/packages/pi/dist/policy-head-tool.js.map +1 -0
  1113. package/packages/pi/dist/portfolio-bridge.d.ts +12 -0
  1114. package/packages/pi/dist/portfolio-bridge.d.ts.map +1 -0
  1115. package/packages/pi/dist/portfolio-bridge.js +81 -0
  1116. package/packages/pi/dist/portfolio-bridge.js.map +1 -0
  1117. package/packages/pi/dist/service-skills.d.ts +15 -0
  1118. package/packages/pi/dist/service-skills.d.ts.map +1 -0
  1119. package/packages/pi/dist/service-skills.js +198 -0
  1120. package/packages/pi/dist/service-skills.js.map +1 -0
  1121. package/packages/pi/dist/session.d.ts +28 -0
  1122. package/packages/pi/dist/session.d.ts.map +1 -0
  1123. package/packages/pi/dist/session.js +649 -0
  1124. package/packages/pi/dist/session.js.map +1 -0
  1125. package/packages/pi/dist/shortcuts.d.ts +11 -0
  1126. package/packages/pi/dist/shortcuts.d.ts.map +1 -0
  1127. package/packages/pi/dist/shortcuts.js +231 -0
  1128. package/packages/pi/dist/shortcuts.js.map +1 -0
  1129. package/packages/pi/dist/startup-briefing.d.ts +13 -0
  1130. package/packages/pi/dist/startup-briefing.d.ts.map +1 -0
  1131. package/packages/pi/dist/startup-briefing.js +432 -0
  1132. package/packages/pi/dist/startup-briefing.js.map +1 -0
  1133. package/packages/pi/dist/stratus-bridge.d.ts +14 -0
  1134. package/packages/pi/dist/stratus-bridge.d.ts.map +1 -0
  1135. package/packages/pi/dist/stratus-bridge.js +104 -0
  1136. package/packages/pi/dist/stratus-bridge.js.map +1 -0
  1137. package/packages/pi/dist/subway-mesh.d.ts +88 -0
  1138. package/packages/pi/dist/subway-mesh.d.ts.map +1 -0
  1139. package/packages/pi/dist/subway-mesh.js +813 -0
  1140. package/packages/pi/dist/subway-mesh.js.map +1 -0
  1141. package/packages/pi/dist/synopsis-tool.d.ts +12 -0
  1142. package/packages/pi/dist/synopsis-tool.d.ts.map +1 -0
  1143. package/packages/pi/dist/synopsis-tool.js +84 -0
  1144. package/packages/pi/dist/synopsis-tool.js.map +1 -0
  1145. package/packages/pi/dist/tool-renderers.d.ts +55 -0
  1146. package/packages/pi/dist/tool-renderers.d.ts.map +1 -0
  1147. package/packages/pi/dist/tool-renderers.js +349 -0
  1148. package/packages/pi/dist/tool-renderers.js.map +1 -0
  1149. package/packages/pi/dist/training-buffer-tool.d.ts +16 -0
  1150. package/packages/pi/dist/training-buffer-tool.d.ts.map +1 -0
  1151. package/packages/pi/dist/training-buffer-tool.js +319 -0
  1152. package/packages/pi/dist/training-buffer-tool.js.map +1 -0
  1153. package/packages/pi/dist/types.d.ts +195 -0
  1154. package/packages/pi/dist/types.d.ts.map +1 -0
  1155. package/packages/pi/dist/types.js +11 -0
  1156. package/packages/pi/dist/types.js.map +1 -0
  1157. package/packages/pi/extensions/agent-grid.ts +191 -0
  1158. package/packages/pi/extensions/agent-names.ts +178 -0
  1159. package/packages/pi/extensions/autoresearch.ts +428 -0
  1160. package/packages/pi/extensions/bookmarks.ts +85 -0
  1161. package/packages/pi/extensions/context.ts +158 -0
  1162. package/packages/pi/extensions/crm-tool.ts +61 -0
  1163. package/packages/pi/extensions/eval-tool.ts +224 -0
  1164. package/packages/pi/extensions/eval.ts +61 -0
  1165. package/packages/pi/extensions/footer.ts +239 -0
  1166. package/packages/pi/extensions/header.ts +171 -0
  1167. package/packages/pi/extensions/hub-resolver.ts +63 -0
  1168. package/packages/pi/extensions/hub-tools.ts +267 -0
  1169. package/packages/pi/extensions/hud-tool.ts +294 -0
  1170. package/packages/pi/extensions/index.ts +601 -0
  1171. package/packages/pi/extensions/jfl-resolve.ts +98 -0
  1172. package/packages/pi/extensions/journal.ts +309 -0
  1173. package/packages/pi/extensions/map-bridge.ts +209 -0
  1174. package/packages/pi/extensions/memory-tool.ts +170 -0
  1175. package/packages/pi/extensions/notifications.ts +73 -0
  1176. package/packages/pi/extensions/onboarding-v1.ts +455 -0
  1177. package/packages/pi/extensions/onboarding-v2.ts +374 -0
  1178. package/packages/pi/extensions/onboarding-v3.ts +686 -0
  1179. package/packages/pi/extensions/peter-parker.ts +203 -0
  1180. package/packages/pi/extensions/pivot-tool.ts +59 -0
  1181. package/packages/pi/extensions/policy-head-tool.ts +277 -0
  1182. package/packages/pi/extensions/portfolio-bridge.ts +89 -0
  1183. package/packages/pi/extensions/service-skills.ts +219 -0
  1184. package/packages/pi/extensions/session.ts +684 -0
  1185. package/packages/pi/extensions/shortcuts.ts +259 -0
  1186. package/packages/pi/extensions/startup-briefing.ts +482 -0
  1187. package/packages/pi/extensions/stratus-bridge.ts +116 -0
  1188. package/packages/pi/extensions/subway-mesh.ts +893 -0
  1189. package/packages/pi/extensions/synopsis-tool.ts +88 -0
  1190. package/packages/pi/extensions/tool-renderers.ts +366 -0
  1191. package/packages/pi/extensions/training-buffer-tool.ts +376 -0
  1192. package/packages/pi/extensions/types.ts +169 -0
  1193. package/packages/pi/package-lock.json +346 -0
  1194. package/packages/pi/package.json +42 -0
  1195. package/packages/pi/skills/agent-browser/SKILL.md +116 -0
  1196. package/packages/pi/skills/brand-architect/SKILL.md +240 -0
  1197. package/packages/pi/skills/brand-architect/config.yaml +137 -0
  1198. package/packages/pi/skills/campaign-hud/config.yaml +112 -0
  1199. package/packages/pi/skills/content-creator/SKILL.md +294 -0
  1200. package/packages/pi/skills/context/SKILL.md +65 -0
  1201. package/packages/pi/skills/debug/MULTI_AGENT.md +360 -0
  1202. package/packages/pi/skills/debug/SKILL.md +554 -0
  1203. package/packages/pi/skills/end/SKILL.md +1790 -0
  1204. package/packages/pi/skills/eval/SKILL.md +75 -0
  1205. package/packages/pi/skills/fly-deploy/SKILL.md +676 -0
  1206. package/packages/pi/skills/founder-video/SKILL.md +467 -0
  1207. package/packages/pi/skills/hud/SKILL.md +160 -0
  1208. package/packages/pi/skills/orchestrate/SKILL.md +74 -0
  1209. package/packages/pi/skills/pi-agents/SKILL.md +78 -0
  1210. package/packages/pi/skills/pivot/SKILL.md +91 -0
  1211. package/packages/pi/skills/react-best-practices/AGENTS.md +2249 -0
  1212. package/packages/pi/skills/react-best-practices/README.md +123 -0
  1213. package/packages/pi/skills/react-best-practices/SKILL.md +125 -0
  1214. package/packages/pi/skills/react-best-practices/metadata.json +15 -0
  1215. package/packages/pi/skills/react-best-practices/rules/_sections.md +46 -0
  1216. package/packages/pi/skills/react-best-practices/rules/_template.md +28 -0
  1217. package/packages/pi/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  1218. package/packages/pi/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
  1219. package/packages/pi/skills/react-best-practices/rules/async-api-routes.md +38 -0
  1220. package/packages/pi/skills/react-best-practices/rules/async-defer-await.md +80 -0
  1221. package/packages/pi/skills/react-best-practices/rules/async-dependencies.md +36 -0
  1222. package/packages/pi/skills/react-best-practices/rules/async-parallel.md +28 -0
  1223. package/packages/pi/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
  1224. package/packages/pi/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
  1225. package/packages/pi/skills/react-best-practices/rules/bundle-conditional.md +31 -0
  1226. package/packages/pi/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
  1227. package/packages/pi/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  1228. package/packages/pi/skills/react-best-practices/rules/bundle-preload.md +50 -0
  1229. package/packages/pi/skills/react-best-practices/rules/client-event-listeners.md +74 -0
  1230. package/packages/pi/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
  1231. package/packages/pi/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
  1232. package/packages/pi/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
  1233. package/packages/pi/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
  1234. package/packages/pi/skills/react-best-practices/rules/js-cache-storage.md +70 -0
  1235. package/packages/pi/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
  1236. package/packages/pi/skills/react-best-practices/rules/js-early-exit.md +50 -0
  1237. package/packages/pi/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
  1238. package/packages/pi/skills/react-best-practices/rules/js-index-maps.md +37 -0
  1239. package/packages/pi/skills/react-best-practices/rules/js-length-check-first.md +49 -0
  1240. package/packages/pi/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
  1241. package/packages/pi/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
  1242. package/packages/pi/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
  1243. package/packages/pi/skills/react-best-practices/rules/rendering-activity.md +26 -0
  1244. package/packages/pi/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  1245. package/packages/pi/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
  1246. package/packages/pi/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
  1247. package/packages/pi/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  1248. package/packages/pi/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  1249. package/packages/pi/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
  1250. package/packages/pi/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
  1251. package/packages/pi/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
  1252. package/packages/pi/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
  1253. package/packages/pi/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
  1254. package/packages/pi/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  1255. package/packages/pi/skills/react-best-practices/rules/rerender-memo.md +44 -0
  1256. package/packages/pi/skills/react-best-practices/rules/rerender-transitions.md +40 -0
  1257. package/packages/pi/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
  1258. package/packages/pi/skills/react-best-practices/rules/server-cache-lru.md +41 -0
  1259. package/packages/pi/skills/react-best-practices/rules/server-cache-react.md +26 -0
  1260. package/packages/pi/skills/react-best-practices/rules/server-parallel-fetching.md +79 -0
  1261. package/packages/pi/skills/react-best-practices/rules/server-serialization.md +38 -0
  1262. package/packages/pi/skills/remotion-best-practices/SKILL.md +43 -0
  1263. package/packages/pi/skills/remotion-best-practices/rules/3d.md +86 -0
  1264. package/packages/pi/skills/remotion-best-practices/rules/animations.md +29 -0
  1265. package/packages/pi/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
  1266. package/packages/pi/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
  1267. package/packages/pi/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +108 -0
  1268. package/packages/pi/skills/remotion-best-practices/rules/assets.md +78 -0
  1269. package/packages/pi/skills/remotion-best-practices/rules/audio.md +172 -0
  1270. package/packages/pi/skills/remotion-best-practices/rules/calculate-metadata.md +104 -0
  1271. package/packages/pi/skills/remotion-best-practices/rules/can-decode.md +75 -0
  1272. package/packages/pi/skills/remotion-best-practices/rules/charts.md +58 -0
  1273. package/packages/pi/skills/remotion-best-practices/rules/compositions.md +146 -0
  1274. package/packages/pi/skills/remotion-best-practices/rules/display-captions.md +126 -0
  1275. package/packages/pi/skills/remotion-best-practices/rules/extract-frames.md +229 -0
  1276. package/packages/pi/skills/remotion-best-practices/rules/fonts.md +152 -0
  1277. package/packages/pi/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
  1278. package/packages/pi/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
  1279. package/packages/pi/skills/remotion-best-practices/rules/get-video-duration.md +58 -0
  1280. package/packages/pi/skills/remotion-best-practices/rules/gifs.md +138 -0
  1281. package/packages/pi/skills/remotion-best-practices/rules/images.md +130 -0
  1282. package/packages/pi/skills/remotion-best-practices/rules/import-srt-captions.md +67 -0
  1283. package/packages/pi/skills/remotion-best-practices/rules/lottie.md +68 -0
  1284. package/packages/pi/skills/remotion-best-practices/rules/measuring-dom-nodes.md +35 -0
  1285. package/packages/pi/skills/remotion-best-practices/rules/measuring-text.md +143 -0
  1286. package/packages/pi/skills/remotion-best-practices/rules/sequencing.md +106 -0
  1287. package/packages/pi/skills/remotion-best-practices/rules/tailwind.md +11 -0
  1288. package/packages/pi/skills/remotion-best-practices/rules/text-animations.md +20 -0
  1289. package/packages/pi/skills/remotion-best-practices/rules/timing.md +179 -0
  1290. package/packages/pi/skills/remotion-best-practices/rules/transcribe-captions.md +19 -0
  1291. package/packages/pi/skills/remotion-best-practices/rules/transitions.md +122 -0
  1292. package/packages/pi/skills/remotion-best-practices/rules/trimming.md +53 -0
  1293. package/packages/pi/skills/remotion-best-practices/rules/videos.md +171 -0
  1294. package/packages/pi/skills/search/SKILL.md +220 -0
  1295. package/packages/pi/skills/spec/SKILL.md +377 -0
  1296. package/packages/pi/skills/startup/SKILL.md +315 -0
  1297. package/packages/pi/skills/subway-browser/SKILL.md +292 -0
  1298. package/packages/pi/skills/viz/SKILL.md +204 -0
  1299. package/packages/pi/skills/web-architect/SKILL.md +309 -0
  1300. package/packages/pi/skills/x-algorithm/SKILL.md +305 -0
  1301. package/packages/pi/teams/dev-team.yaml +63 -0
  1302. package/packages/pi/teams/gtm-team.yaml +79 -0
  1303. package/packages/pi/themes/jfl.theme.json +76 -0
  1304. package/packages/pi/tsconfig.json +21 -0
  1305. package/scripts/__pycache__/train-policy-head.cpython-314.pyc +0 -0
  1306. package/scripts/collect-tuples.sh +124 -0
  1307. package/scripts/commit-gtm.sh +56 -0
  1308. package/scripts/commit-product.sh +68 -0
  1309. package/scripts/context-query.sh +45 -0
  1310. package/scripts/destroy-fleet.sh +37 -0
  1311. package/scripts/generate-changesets.sh +113 -0
  1312. package/scripts/jfl-ide.sh +48 -0
  1313. package/scripts/migrate-to-branch-sessions.sh +201 -0
  1314. package/scripts/postinstall.js +146 -0
  1315. package/scripts/pp-branch-pr.sh +133 -0
  1316. package/scripts/pp-branch-pr.sh.bak +115 -0
  1317. package/scripts/session/auto-commit.sh +297 -0
  1318. package/scripts/session/fix-tracked-logs.sh +97 -0
  1319. package/scripts/session/jfl-doctor.sh +707 -0
  1320. package/scripts/session/session-cleanup.sh +292 -0
  1321. package/scripts/session/session-end.sh +198 -0
  1322. package/scripts/session/session-init.sh +356 -0
  1323. package/scripts/session/session-init.sh.backup +292 -0
  1324. package/scripts/session/session-sync.sh +192 -0
  1325. package/scripts/session/test-context-preservation.sh +160 -0
  1326. package/scripts/session/test-critical-infrastructure.sh +293 -0
  1327. package/scripts/session/test-experience-level.sh +336 -0
  1328. package/scripts/session/test-session-cleanup.sh +268 -0
  1329. package/scripts/session/test-session-sync.sh +320 -0
  1330. package/scripts/setup-branch-protection.sh +106 -0
  1331. package/scripts/spawn-fleet.sh +144 -0
  1332. package/scripts/telemetry-dashboard.sh +44 -0
  1333. package/scripts/test-map-eventbus.sh +357 -0
  1334. package/scripts/test-onboarding.sh +121 -0
  1335. package/scripts/test-planning-loop-e2e.ts +181 -0
  1336. package/scripts/test-server-inference.ts +49 -0
  1337. package/scripts/test-state-sensitivity.ts +32 -0
  1338. package/scripts/train/requirements.txt +5 -0
  1339. package/scripts/train/train-policy-head.py +477 -0
  1340. package/scripts/train/v2/__pycache__/dataset.cpython-314.pyc +0 -0
  1341. package/scripts/train/v2/__pycache__/eval.cpython-314.pyc +0 -0
  1342. package/scripts/train/v2/__pycache__/generate_data.cpython-314.pyc +0 -0
  1343. package/scripts/train/v2/__pycache__/infer.cpython-314.pyc +0 -0
  1344. package/scripts/train/v2/__pycache__/model.cpython-314.pyc +0 -0
  1345. package/scripts/train/v2/__pycache__/precompute.cpython-314.pyc +0 -0
  1346. package/scripts/train/v2/__pycache__/train.cpython-314.pyc +0 -0
  1347. package/scripts/train/v2/__pycache__/transform_buffer.cpython-314.pyc +0 -0
  1348. package/scripts/train/v2/__pycache__/validate_data.cpython-314.pyc +0 -0
  1349. package/scripts/train/v2/benchmark.py +661 -0
  1350. package/scripts/train/v2/dataset.py +81 -0
  1351. package/scripts/train/v2/domain.json +66 -0
  1352. package/scripts/train/v2/eval.py +196 -0
  1353. package/scripts/train/v2/generate_balanced.py +439 -0
  1354. package/scripts/train/v2/generate_data.py +219 -0
  1355. package/scripts/train/v2/generate_hard_negatives.py +219 -0
  1356. package/scripts/train/v2/infer.py +301 -0
  1357. package/scripts/train/v2/infer_server.py +224 -0
  1358. package/scripts/train/v2/model.py +112 -0
  1359. package/scripts/train/v2/online_train.py +576 -0
  1360. package/scripts/train/v2/precompute.py +150 -0
  1361. package/scripts/train/v2/train.py +302 -0
  1362. package/scripts/train/v2/transform_buffer.py +227 -0
  1363. package/scripts/train/v2/validate_data.py +115 -0
  1364. package/scripts/train-policy-head.py +434 -0
  1365. package/scripts/vm-swarm/README.md +301 -0
  1366. package/scripts/vm-swarm/collect-tuples.sh +331 -0
  1367. package/scripts/vm-swarm/create-base-template.sh +339 -0
  1368. package/scripts/vm-swarm/kill-fleet.sh +204 -0
  1369. package/scripts/vm-swarm/monitor-fleet.sh +346 -0
  1370. package/scripts/vm-swarm/spawn-fleet.sh +304 -0
  1371. package/scripts/voice-start.sh +156 -0
  1372. package/scripts/voice-stop.sh +33 -0
  1373. package/scripts/where-am-i.sh +78 -0
  1374. package/templates/QUICKSTART_SKILL_TO_PRODUCT.md +242 -0
  1375. package/templates/brand/BRAND_BRIEF.md +124 -0
  1376. package/templates/brand/BRAND_DECISIONS.md +168 -0
  1377. package/templates/brand/BRAND_GUIDELINES.md +251 -0
  1378. package/templates/brand/VOICE_AND_TONE.md +146 -0
  1379. package/templates/brand/global.css +240 -0
  1380. package/templates/collaboration/CONTRIBUTOR.md +74 -0
  1381. package/templates/collaboration/CRM.md +97 -0
  1382. package/templates/collaboration/TASKS.md +83 -0
  1383. package/templates/dating/FUNNEL.md +29 -0
  1384. package/templates/dating/REWARDS.md +24 -0
  1385. package/templates/dating/SIGNALS.md +18 -0
  1386. package/templates/dating/anti_patterns.md +23 -0
  1387. package/templates/dating/connectors/index.md +16 -0
  1388. package/templates/dating/modes.md +21 -0
  1389. package/templates/dating/psychology.md +18 -0
  1390. package/templates/default/README.md +19 -0
  1391. package/templates/service-agent/.claude/settings.json +32 -0
  1392. package/templates/service-agent/CLAUDE.md +334 -0
  1393. package/templates/service-agent/knowledge/ARCHITECTURE.md +115 -0
  1394. package/templates/service-agent/knowledge/DEPLOYMENT.md +199 -0
  1395. package/templates/service-agent/knowledge/RUNBOOK.md +412 -0
  1396. package/templates/service-agent/knowledge/SERVICE_SPEC.md +77 -0
  1397. package/templates/service-mcp-template.js +325 -0
  1398. package/templates/strategic/NARRATIVE.md +114 -0
  1399. package/templates/strategic/ROADMAP.md +128 -0
  1400. package/templates/strategic/THESIS.md +108 -0
  1401. package/templates/strategic/VISION.md +74 -0
@@ -0,0 +1,3505 @@
1
+ /**
2
+ * jfl context-hub - Unified context layer for AI agents
3
+ *
4
+ * Provides context from journal, knowledge docs, and code to any AI.
5
+ * Works locally (CLI) and hosted (platform).
6
+ *
7
+ * @purpose CLI command for Context Hub daemon management
8
+ */
9
+ import chalk from "chalk";
10
+ import ora from "ora";
11
+ import { execSync, spawn } from "child_process";
12
+ import * as fs from "fs";
13
+ import * as path from "path";
14
+ import * as http from "http";
15
+ import { homedir } from "os";
16
+ import { fileURLToPath } from "url";
17
+ import { initializeDatabase, getMemoryStats, insertMemory, getLinksFrom, getLinksTo, getMemoriesByIds, addLink, } from "../lib/memory-db.js";
18
+ import { searchMemories } from "../lib/memory-search.js";
19
+ import { indexJournalEntries, startPeriodicIndexing, backfillEmbeddings, indexCodeHeaders } from "../lib/memory-indexer.js";
20
+ import { getProjectPort } from "../utils/context-hub-port.js";
21
+ import { getConfigValue, setConfig } from "../utils/jfl-config.js";
22
+ import Conf from "conf";
23
+ import { MAPEventBus } from "../lib/map-event-bus.js";
24
+ import { FlowEngine } from "../lib/flow-engine.js";
25
+ import { WebSocketServer } from "ws";
26
+ import { telemetry } from "../lib/telemetry.js";
27
+ import { transformHookPayload } from "../lib/hook-transformer.js";
28
+ import { loadAllAgentConfigs } from "../lib/agent-config.js";
29
+ import { TrainingBuffer } from "../lib/training-buffer.js";
30
+ import { FindingsEngine } from "../lib/findings-engine.js";
31
+ const PID_FILE = ".jfl/context-hub.pid";
32
+ const LOG_FILE = ".jfl/logs/context-hub.log";
33
+ const TOKEN_FILE = ".jfl/context-hub.token";
34
+ // ============================================================================
35
+ // Security
36
+ // ============================================================================
37
+ function generateToken() {
38
+ return Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join('');
39
+ }
40
+ function getTokenFile(projectRoot) {
41
+ return path.join(projectRoot, TOKEN_FILE);
42
+ }
43
+ function getOrCreateToken(projectRoot) {
44
+ const tokenFile = getTokenFile(projectRoot);
45
+ if (fs.existsSync(tokenFile)) {
46
+ return fs.readFileSync(tokenFile, 'utf-8').trim();
47
+ }
48
+ const token = generateToken();
49
+ fs.writeFileSync(tokenFile, token, { mode: 0o600 }); // Owner read/write only
50
+ return token;
51
+ }
52
+ function validateAuth(req, projectRoot, url) {
53
+ const tokenFile = getTokenFile(projectRoot);
54
+ // If no token file exists, allow access (backwards compatibility during migration)
55
+ if (!fs.existsSync(tokenFile)) {
56
+ return true;
57
+ }
58
+ const expectedToken = fs.readFileSync(tokenFile, 'utf-8').trim();
59
+ // Check Authorization header first
60
+ const authHeader = req.headers['authorization'];
61
+ if (authHeader) {
62
+ const providedToken = authHeader.startsWith('Bearer ')
63
+ ? authHeader.slice(7)
64
+ : authHeader;
65
+ if (providedToken === expectedToken)
66
+ return true;
67
+ }
68
+ // Fall back to ?token= query param (needed for SSE/EventSource which can't set headers)
69
+ if (url) {
70
+ const queryToken = url.searchParams.get('token');
71
+ if (queryToken && queryToken === expectedToken)
72
+ return true;
73
+ }
74
+ return false;
75
+ }
76
+ function isPortInUse(port) {
77
+ return new Promise((resolve) => {
78
+ const server = http.createServer();
79
+ server.once('error', (err) => {
80
+ if (err.code === 'EADDRINUSE') {
81
+ resolve(true);
82
+ }
83
+ else {
84
+ resolve(false);
85
+ }
86
+ });
87
+ server.once('listening', () => {
88
+ server.close();
89
+ resolve(false);
90
+ });
91
+ server.listen(port);
92
+ });
93
+ }
94
+ // ============================================================================
95
+ // Journal Reader
96
+ // ============================================================================
97
+ function readJournalEntries(projectRoot, limit = 50) {
98
+ const journalDir = path.join(projectRoot, ".jfl", "journal");
99
+ const items = [];
100
+ if (!fs.existsSync(journalDir)) {
101
+ return items;
102
+ }
103
+ // Sort by modification time (newest first) so recent entries aren't buried
104
+ const files = fs.readdirSync(journalDir)
105
+ .filter(f => f.endsWith(".jsonl"))
106
+ .map(f => ({
107
+ name: f,
108
+ mtime: fs.statSync(path.join(journalDir, f)).mtimeMs
109
+ }))
110
+ .sort((a, b) => b.mtime - a.mtime)
111
+ .map(f => f.name);
112
+ // Read all entries from all files, then sort globally by timestamp
113
+ const allEntries = [];
114
+ for (const file of files) {
115
+ const filePath = path.join(journalDir, file);
116
+ const content = fs.readFileSync(filePath, "utf-8");
117
+ const lines = content.trim().split("\n").filter(l => l.trim());
118
+ for (const line of lines) {
119
+ try {
120
+ const entry = JSON.parse(line);
121
+ allEntries.push({
122
+ source: "journal",
123
+ type: entry.type || "entry",
124
+ title: entry.title || "Untitled",
125
+ content: entry.summary || entry.detail || "",
126
+ timestamp: entry.ts,
127
+ path: filePath
128
+ });
129
+ }
130
+ catch {
131
+ // Skip malformed lines
132
+ }
133
+ }
134
+ }
135
+ // Sort all entries by timestamp descending, then take the limit
136
+ allEntries.sort((a, b) => {
137
+ const ta = a.timestamp || "";
138
+ const tb = b.timestamp || "";
139
+ return tb.localeCompare(ta);
140
+ });
141
+ return allEntries.slice(0, limit);
142
+ }
143
+ // ============================================================================
144
+ // Knowledge Reader
145
+ // ============================================================================
146
+ function readKnowledgeDocs(projectRoot) {
147
+ const knowledgeDir = path.join(projectRoot, "knowledge");
148
+ const items = [];
149
+ if (!fs.existsSync(knowledgeDir)) {
150
+ return items;
151
+ }
152
+ const priorityFiles = [
153
+ "VISION.md",
154
+ "ROADMAP.md",
155
+ "NARRATIVE.md",
156
+ "THESIS.md",
157
+ "BRAND_DECISIONS.md",
158
+ "TASKS.md",
159
+ "ARCHITECTURE.md",
160
+ "SERVICE_SPEC.md",
161
+ "DEPLOYMENT.md",
162
+ "RUNBOOK.md",
163
+ ];
164
+ for (const filename of priorityFiles) {
165
+ const filePath = path.join(knowledgeDir, filename);
166
+ if (fs.existsSync(filePath)) {
167
+ const content = fs.readFileSync(filePath, "utf-8");
168
+ const title = filename.replace(".md", "").replace(/_/g, " ");
169
+ items.push({
170
+ source: "knowledge",
171
+ type: "doc",
172
+ title,
173
+ content: content.slice(0, 2000),
174
+ path: filePath
175
+ });
176
+ }
177
+ }
178
+ return items;
179
+ }
180
+ function discoverServices(projectRoot) {
181
+ const services = {};
182
+ // Look for .jfl/services.json in current project and parent directories
183
+ // Also search sibling GTM directories
184
+ const searchPaths = [
185
+ projectRoot,
186
+ path.join(projectRoot, ".."),
187
+ path.join(projectRoot, "../.."),
188
+ ];
189
+ // Add sibling directories (e.g., if in jfl-cli, check ../JFL-GTM)
190
+ const parentDir = path.join(projectRoot, "..");
191
+ if (fs.existsSync(parentDir)) {
192
+ try {
193
+ const siblings = fs.readdirSync(parentDir, { withFileTypes: true });
194
+ for (const sibling of siblings) {
195
+ if (sibling.isDirectory() && (sibling.name.includes("GTM") || sibling.name.includes("gtm"))) {
196
+ searchPaths.push(path.join(parentDir, sibling.name));
197
+ }
198
+ }
199
+ }
200
+ catch {
201
+ // Ignore read errors
202
+ }
203
+ }
204
+ for (const searchPath of searchPaths) {
205
+ if (!fs.existsSync(searchPath))
206
+ continue;
207
+ const servicesFile = path.join(searchPath, ".jfl/services.json");
208
+ if (fs.existsSync(servicesFile)) {
209
+ try {
210
+ const servicesData = JSON.parse(fs.readFileSync(servicesFile, "utf-8"));
211
+ for (const [serviceName, serviceData] of Object.entries(servicesData)) {
212
+ const data = serviceData;
213
+ // Check if service has service.json with MCP info
214
+ let mcpInfo;
215
+ const serviceJsonPath = path.join(data.path, "service.json");
216
+ if (fs.existsSync(serviceJsonPath)) {
217
+ try {
218
+ const serviceJson = JSON.parse(fs.readFileSync(serviceJsonPath, "utf-8"));
219
+ if (serviceJson.mcp) {
220
+ mcpInfo = {
221
+ enabled: serviceJson.mcp.enabled || false,
222
+ transport: serviceJson.mcp.transport || "stdio"
223
+ };
224
+ }
225
+ }
226
+ catch {
227
+ // Ignore parse errors
228
+ }
229
+ }
230
+ // Determine service status (basic check - could be enhanced)
231
+ let status = "unknown";
232
+ if (data.port) {
233
+ // Could check if port is in use
234
+ status = "stopped";
235
+ }
236
+ services[serviceName] = {
237
+ name: serviceName,
238
+ type: data.type || "unknown",
239
+ description: data.description || "",
240
+ path: data.path,
241
+ status,
242
+ mcp: mcpInfo,
243
+ commands: data.commands,
244
+ healthcheck: data.healthcheck,
245
+ port: data.port,
246
+ dependencies: data.dependencies
247
+ };
248
+ }
249
+ }
250
+ catch (err) {
251
+ console.error(`Failed to read services from ${servicesFile}:`, err);
252
+ }
253
+ }
254
+ }
255
+ return services;
256
+ }
257
+ // ============================================================================
258
+ // Code Context Reader
259
+ // ============================================================================
260
+ function extractPurpose(content) {
261
+ const match = content.match(/@purpose\s+(.+?)(?:\n|\*)/i);
262
+ return match ? match[1].trim() : null;
263
+ }
264
+ function readCodeContext(projectRoot, limit = 30) {
265
+ const items = [];
266
+ // Look for files with @purpose in common locations
267
+ const searchDirs = [
268
+ path.join(projectRoot, "src"),
269
+ path.join(projectRoot, "app"),
270
+ path.join(projectRoot, "lib"),
271
+ path.join(projectRoot, "components"),
272
+ path.join(projectRoot, "product", "src"),
273
+ path.join(projectRoot, "product", "packages")
274
+ ];
275
+ const extensions = [".ts", ".tsx", ".js", ".jsx"];
276
+ function scanDir(dir, depth = 0) {
277
+ if (depth > 4 || items.length >= limit)
278
+ return;
279
+ if (!fs.existsSync(dir))
280
+ return;
281
+ try {
282
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
283
+ for (const entry of entries) {
284
+ if (items.length >= limit)
285
+ break;
286
+ if (entry.name.startsWith(".") || entry.name === "node_modules")
287
+ continue;
288
+ const fullPath = path.join(dir, entry.name);
289
+ if (entry.isDirectory()) {
290
+ scanDir(fullPath, depth + 1);
291
+ }
292
+ else if (extensions.some(ext => entry.name.endsWith(ext))) {
293
+ try {
294
+ const content = fs.readFileSync(fullPath, "utf-8");
295
+ const purpose = extractPurpose(content);
296
+ if (purpose) {
297
+ items.push({
298
+ source: "code",
299
+ type: "file",
300
+ title: entry.name,
301
+ content: purpose,
302
+ path: fullPath
303
+ });
304
+ }
305
+ }
306
+ catch {
307
+ // Skip unreadable files
308
+ }
309
+ }
310
+ }
311
+ }
312
+ catch {
313
+ // Skip unreadable directories
314
+ }
315
+ }
316
+ for (const dir of searchDirs) {
317
+ scanDir(dir);
318
+ }
319
+ return items;
320
+ }
321
+ /**
322
+ * Read GTM content artifacts — drafts, specs, content, scripts.
323
+ * Used instead of code indexing for GTM-type projects.
324
+ */
325
+ function readGtmContent(projectRoot, limit = 40) {
326
+ const items = [];
327
+ const gtmDirs = [
328
+ { dir: "content", type: "content" },
329
+ { dir: "drafts", type: "draft" },
330
+ { dir: "specs", type: "spec" },
331
+ { dir: "scripts", type: "script" },
332
+ ];
333
+ const mdExtensions = [".md", ".txt"];
334
+ const scriptExtensions = [".sh", ".py", ".mjs", ".js"];
335
+ for (const { dir, type } of gtmDirs) {
336
+ const fullDir = path.join(projectRoot, dir);
337
+ if (!fs.existsSync(fullDir))
338
+ continue;
339
+ function scanDir(d, depth = 0) {
340
+ if (depth > 3 || items.length >= limit)
341
+ return;
342
+ try {
343
+ const entries = fs.readdirSync(d, { withFileTypes: true });
344
+ for (const entry of entries) {
345
+ if (items.length >= limit)
346
+ break;
347
+ if (entry.name.startsWith(".") || entry.name === "node_modules")
348
+ continue;
349
+ const fullPath = path.join(d, entry.name);
350
+ if (entry.isDirectory()) {
351
+ scanDir(fullPath, depth + 1);
352
+ }
353
+ else {
354
+ const isMarkdown = mdExtensions.some(ext => entry.name.endsWith(ext));
355
+ const isScript = type === "script" && scriptExtensions.some(ext => entry.name.endsWith(ext));
356
+ if (isMarkdown || isScript) {
357
+ try {
358
+ const raw = fs.readFileSync(fullPath, "utf-8");
359
+ // Extract first heading + first paragraph as summary
360
+ const lines = raw.split("\n").filter(l => l.trim());
361
+ const heading = lines.find(l => l.startsWith("#"))?.replace(/^#+\s*/, "") || entry.name;
362
+ const bodyLines = lines.filter(l => !l.startsWith("#") && !l.startsWith("---"));
363
+ const summary = bodyLines.slice(0, 5).join(" ").slice(0, 500);
364
+ if (summary.length > 20) {
365
+ items.push({
366
+ source: "content",
367
+ type,
368
+ title: heading,
369
+ content: summary,
370
+ path: fullPath
371
+ });
372
+ }
373
+ }
374
+ catch {
375
+ // Skip unreadable files
376
+ }
377
+ }
378
+ }
379
+ }
380
+ }
381
+ catch {
382
+ // Skip unreadable directories
383
+ }
384
+ }
385
+ scanDir(fullDir);
386
+ }
387
+ return items;
388
+ }
389
+ // ============================================================================
390
+ // Search & Scoring (TF-IDF style)
391
+ // ============================================================================
392
+ function tokenize(text) {
393
+ return text
394
+ .toLowerCase()
395
+ .replace(/[^\w\s]/g, " ")
396
+ .split(/\s+/)
397
+ .filter(t => t.length > 2);
398
+ }
399
+ function computeTF(tokens) {
400
+ const tf = new Map();
401
+ for (const token of tokens) {
402
+ tf.set(token, (tf.get(token) || 0) + 1);
403
+ }
404
+ // Normalize by document length
405
+ for (const [term, count] of tf) {
406
+ tf.set(term, count / tokens.length);
407
+ }
408
+ return tf;
409
+ }
410
+ function computeIDF(documents) {
411
+ const idf = new Map();
412
+ const N = documents.length;
413
+ // Count documents containing each term
414
+ const docCount = new Map();
415
+ for (const doc of documents) {
416
+ const uniqueTerms = new Set(doc);
417
+ for (const term of uniqueTerms) {
418
+ docCount.set(term, (docCount.get(term) || 0) + 1);
419
+ }
420
+ }
421
+ // Compute IDF
422
+ for (const [term, count] of docCount) {
423
+ idf.set(term, Math.log((N + 1) / (count + 1)) + 1);
424
+ }
425
+ return idf;
426
+ }
427
+ function scoreItem(item, queryTokens, idf) {
428
+ const text = `${item.title} ${item.content}`;
429
+ const tokens = tokenize(text);
430
+ const tf = computeTF(tokens);
431
+ let score = 0;
432
+ for (const queryTerm of queryTokens) {
433
+ const termTF = tf.get(queryTerm) || 0;
434
+ const termIDF = idf.get(queryTerm) || 1;
435
+ score += termTF * termIDF;
436
+ }
437
+ // Boost title matches
438
+ const titleTokens = new Set(tokenize(item.title));
439
+ for (const queryTerm of queryTokens) {
440
+ if (titleTokens.has(queryTerm)) {
441
+ score *= 1.5;
442
+ }
443
+ }
444
+ // Boost recent items (journal)
445
+ if (item.source === "journal" && item.timestamp) {
446
+ const age = Date.now() - new Date(item.timestamp).getTime();
447
+ const daysSinceUpdate = age / (1000 * 60 * 60 * 24);
448
+ if (daysSinceUpdate < 7) {
449
+ score *= 1.3; // Boost recent entries
450
+ }
451
+ }
452
+ return score;
453
+ }
454
+ function semanticSearch(items, query) {
455
+ const queryTokens = tokenize(query);
456
+ if (queryTokens.length === 0)
457
+ return items;
458
+ // Build corpus for IDF
459
+ const documents = items.map(item => tokenize(`${item.title} ${item.content}`));
460
+ const idf = computeIDF(documents);
461
+ // Score and sort
462
+ for (const item of items) {
463
+ item.relevance = scoreItem(item, queryTokens, idf);
464
+ }
465
+ return items
466
+ .filter(item => (item.relevance || 0) > 0)
467
+ .sort((a, b) => (b.relevance || 0) - (a.relevance || 0));
468
+ }
469
+ // ============================================================================
470
+ // Orchestrator
471
+ // ============================================================================
472
+ function getProjectType(projectRoot) {
473
+ const configPath = path.join(projectRoot, ".jfl", "config.json");
474
+ if (fs.existsSync(configPath)) {
475
+ try {
476
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
477
+ return config.type || "standalone";
478
+ }
479
+ catch { }
480
+ }
481
+ return "standalone";
482
+ }
483
+ function getUnifiedContext(projectRoot, query, taskType) {
484
+ const journalItems = readJournalEntries(projectRoot);
485
+ const knowledgeItems = readKnowledgeDocs(projectRoot);
486
+ const projectType = getProjectType(projectRoot);
487
+ const codeItems = readCodeContext(projectRoot);
488
+ const contentItems = projectType === "gtm" ? readGtmContent(projectRoot) : [];
489
+ let items = [...journalItems, ...knowledgeItems, ...codeItems, ...contentItems];
490
+ // Apply semantic search if query provided
491
+ if (query) {
492
+ items = semanticSearch(items, query);
493
+ }
494
+ return {
495
+ items,
496
+ sources: {
497
+ journal: journalItems.length > 0,
498
+ knowledge: knowledgeItems.length > 0,
499
+ code: codeItems.length > 0,
500
+ content: contentItems.length > 0,
501
+ memory: fs.existsSync(path.join(projectRoot, ".jfl", "memory.db")),
502
+ },
503
+ query,
504
+ taskType
505
+ };
506
+ }
507
+ function getChildHubs(projectRoot) {
508
+ const configPath = path.join(projectRoot, ".jfl", "config.json");
509
+ if (!fs.existsSync(configPath))
510
+ return [];
511
+ try {
512
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
513
+ if (config.type !== "portfolio")
514
+ return [];
515
+ const children = [];
516
+ for (const service of config.registered_services || []) {
517
+ if (service.type === "gtm" && service.path) {
518
+ const childPort = getProjectPort(service.path);
519
+ const tokenPath = path.join(service.path, ".jfl", "context-hub.token");
520
+ const token = fs.existsSync(tokenPath)
521
+ ? fs.readFileSync(tokenPath, "utf-8").trim()
522
+ : undefined;
523
+ children.push({ name: service.name, path: service.path, port: childPort, token });
524
+ }
525
+ }
526
+ return children;
527
+ }
528
+ catch {
529
+ return [];
530
+ }
531
+ }
532
+ async function fetchChildContext(child, endpoint, body) {
533
+ try {
534
+ const headers = { "Content-Type": "application/json" };
535
+ if (child.token)
536
+ headers["Authorization"] = `Bearer ${child.token}`;
537
+ const response = await fetch(`http://localhost:${child.port}${endpoint}`, {
538
+ method: "POST",
539
+ headers,
540
+ body: JSON.stringify(body),
541
+ signal: AbortSignal.timeout(5000),
542
+ });
543
+ if (!response.ok)
544
+ return [];
545
+ const data = (await response.json());
546
+ return (data.items || []).map((item) => ({
547
+ ...item,
548
+ title: `[${child.name}] ${item.title}`,
549
+ }));
550
+ }
551
+ catch {
552
+ return readJournalEntries(child.path, 10).map((item) => ({
553
+ ...item,
554
+ title: `[${child.name}] ${item.title}`,
555
+ }));
556
+ }
557
+ }
558
+ async function getPortfolioContext(projectRoot, query, taskType, maxItems) {
559
+ const local = getUnifiedContext(projectRoot, query, taskType);
560
+ const children = getChildHubs(projectRoot);
561
+ if (children.length === 0) {
562
+ if (maxItems)
563
+ local.items = local.items.slice(0, maxItems);
564
+ return local;
565
+ }
566
+ const endpoint = query ? "/api/context/search" : "/api/context";
567
+ const body = { maxItems: maxItems || 20 };
568
+ if (query)
569
+ body.query = query;
570
+ if (taskType)
571
+ body.taskType = taskType;
572
+ const childResults = await Promise.all(children.map((child) => fetchChildContext(child, endpoint, body)));
573
+ let merged = [...local.items, ...childResults.flat()];
574
+ if (query) {
575
+ merged = semanticSearch(merged, query);
576
+ }
577
+ return {
578
+ items: maxItems ? merged.slice(0, maxItems) : merged,
579
+ sources: {
580
+ ...local.sources,
581
+ journal: true,
582
+ knowledge: true,
583
+ },
584
+ query,
585
+ taskType,
586
+ };
587
+ }
588
+ // ============================================================================
589
+ // HTTP Server
590
+ // ============================================================================
591
+ function createServer(projectRoot, port, eventBus, flowEngine) {
592
+ const server = http.createServer(async (req, res) => {
593
+ const requestStart = Date.now();
594
+ const pathname = new URL(req.url || "/", `http://localhost:${port}`).pathname;
595
+ // Intercept writeHead to capture status code for telemetry
596
+ let capturedStatus = 200;
597
+ const originalWriteHead = res.writeHead.bind(res);
598
+ res.writeHead = function (statusCode, ...args) {
599
+ capturedStatus = statusCode;
600
+ return originalWriteHead(statusCode, ...args);
601
+ };
602
+ // Track request on response finish (skip health/OPTIONS/dashboard)
603
+ const shouldTrack = req.method !== "OPTIONS"
604
+ && pathname !== "/health"
605
+ && !pathname.startsWith("/dashboard");
606
+ if (shouldTrack) {
607
+ res.on('finish', () => {
608
+ telemetry.track({
609
+ category: 'context_hub',
610
+ event: 'context_hub:request',
611
+ endpoint: pathname,
612
+ method: req.method,
613
+ status_code: capturedStatus,
614
+ duration_ms: Date.now() - requestStart,
615
+ hub_port: port,
616
+ });
617
+ });
618
+ }
619
+ // CORS - include Authorization header
620
+ res.setHeader("Access-Control-Allow-Origin", "*");
621
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
622
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
623
+ if (req.method === "OPTIONS") {
624
+ res.writeHead(200);
625
+ res.end();
626
+ return;
627
+ }
628
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
629
+ // Health check - no auth required (for monitoring)
630
+ if (url.pathname === "/health" && req.method === "GET") {
631
+ res.writeHead(200, { "Content-Type": "application/json" });
632
+ res.end(JSON.stringify({ status: "ok", port }));
633
+ return;
634
+ }
635
+ // Setup report — serves .jfl/setup-report.json for dashboard consumption
636
+ if (url.pathname === "/api/setup-report" && req.method === "GET") {
637
+ const reportPath = path.join(projectRoot, ".jfl", "setup-report.json");
638
+ if (fs.existsSync(reportPath)) {
639
+ const report = fs.readFileSync(reportPath, "utf-8");
640
+ res.writeHead(200, { "Content-Type": "application/json" });
641
+ res.end(report);
642
+ }
643
+ else {
644
+ res.writeHead(404, { "Content-Type": "application/json" });
645
+ res.end(JSON.stringify({ error: "No setup report. Run: jfl setup" }));
646
+ }
647
+ return;
648
+ }
649
+ // Dashboard - served without API auth (has its own token flow in JS)
650
+ if (url.pathname.startsWith("/dashboard")) {
651
+ import("../dashboard/index.js").then(({ handleDashboardRoutes }) => {
652
+ if (!handleDashboardRoutes(req, res, projectRoot, port)) {
653
+ res.writeHead(404, { "Content-Type": "application/json" });
654
+ res.end(JSON.stringify({ error: "Not found" }));
655
+ }
656
+ }).catch(() => {
657
+ res.writeHead(500, { "Content-Type": "application/json" });
658
+ res.end(JSON.stringify({ error: "Dashboard module failed to load" }));
659
+ });
660
+ return;
661
+ }
662
+ // Hook ingestion (Claude Code HTTP hooks)
663
+ // No auth required — localhost-only. Always returns 200.
664
+ if (url.pathname === "/api/hooks" && req.method === "POST") {
665
+ const remoteAddr = req.socket.remoteAddress || "";
666
+ const isLocalhost = remoteAddr === "127.0.0.1" || remoteAddr === "::1" || remoteAddr === "::ffff:127.0.0.1";
667
+ if (!isLocalhost) {
668
+ res.writeHead(403, { "Content-Type": "application/json" });
669
+ res.end(JSON.stringify({ error: "localhost only" }));
670
+ return;
671
+ }
672
+ let body = "";
673
+ let bodySize = 0;
674
+ const MAX_BODY = 64 * 1024;
675
+ req.on("data", (chunk) => {
676
+ bodySize += typeof chunk === "string" ? chunk.length : chunk.byteLength;
677
+ if (bodySize <= MAX_BODY) {
678
+ body += chunk;
679
+ }
680
+ });
681
+ req.on("end", () => {
682
+ try {
683
+ if (bodySize > MAX_BODY) {
684
+ console.warn(`[hooks] Dropped oversized payload: ${bodySize} bytes`);
685
+ res.writeHead(200, { "Content-Type": "application/json" });
686
+ res.end(JSON.stringify({ ok: true, dropped: true }));
687
+ return;
688
+ }
689
+ const payload = JSON.parse(body);
690
+ const partial = transformHookPayload(payload);
691
+ if (eventBus) {
692
+ eventBus.emit(partial);
693
+ }
694
+ res.writeHead(200, { "Content-Type": "application/json" });
695
+ res.end(JSON.stringify({ ok: true }));
696
+ }
697
+ catch (err) {
698
+ console.warn(`[hooks] Parse error: ${err.message}`);
699
+ res.writeHead(200, { "Content-Type": "application/json" });
700
+ res.end(JSON.stringify({ ok: true, parse_error: true }));
701
+ }
702
+ });
703
+ return;
704
+ }
705
+ // All other endpoints require auth
706
+ if (!validateAuth(req, projectRoot, url)) {
707
+ telemetry.track({
708
+ category: 'context_hub',
709
+ event: 'context_hub:auth_failed',
710
+ endpoint: pathname,
711
+ });
712
+ res.writeHead(401, { "Content-Type": "application/json" });
713
+ res.end(JSON.stringify({
714
+ error: "Unauthorized",
715
+ message: "Provide token via Authorization header: Bearer <token>",
716
+ tokenFile: ".jfl/context-hub.token"
717
+ }));
718
+ return;
719
+ }
720
+ // Status (includes child hub health for portfolio)
721
+ if (url.pathname === "/api/context/status" && req.method === "GET") {
722
+ const context = getUnifiedContext(projectRoot);
723
+ const children = getChildHubs(projectRoot);
724
+ // Read actual workspace type from config
725
+ let workspaceType = "standalone";
726
+ let workspaceConfig = {};
727
+ const configPath = path.join(projectRoot, ".jfl", "config.json");
728
+ if (fs.existsSync(configPath)) {
729
+ try {
730
+ const cfg = JSON.parse(fs.readFileSync(configPath, "utf-8"));
731
+ if (cfg.type === "portfolio" || cfg.type === "gtm" || cfg.type === "service") {
732
+ workspaceType = cfg.type;
733
+ }
734
+ workspaceConfig = {
735
+ name: cfg.name,
736
+ type: cfg.type,
737
+ description: cfg.description,
738
+ scope: cfg.context_scope || null,
739
+ registered_services: (cfg.registered_services || []).map((s) => ({
740
+ name: s.name,
741
+ path: s.path,
742
+ type: s.type,
743
+ status: s.status,
744
+ context_scope: s.context_scope || null,
745
+ })),
746
+ openclaw_agents: (cfg.openclaw_agents || []).map((a) => ({
747
+ id: a.id,
748
+ runtime: a.runtime,
749
+ registered_at: a.registered_at,
750
+ })),
751
+ gtm_parent: cfg.gtm_parent || null,
752
+ portfolio_parent: cfg.portfolio_parent || null,
753
+ };
754
+ }
755
+ catch { }
756
+ }
757
+ const childStatus = await Promise.all(children.map(async (child) => {
758
+ try {
759
+ const resp = await fetch(`http://localhost:${child.port}/health`, {
760
+ signal: AbortSignal.timeout(2000),
761
+ });
762
+ return { name: child.name, port: child.port, status: resp.ok ? "ok" : "error" };
763
+ }
764
+ catch {
765
+ return { name: child.name, port: child.port, status: "down" };
766
+ }
767
+ }));
768
+ res.writeHead(200, { "Content-Type": "application/json" });
769
+ res.end(JSON.stringify({
770
+ status: "running",
771
+ port,
772
+ type: workspaceType,
773
+ config: workspaceConfig,
774
+ sources: context.sources,
775
+ itemCount: context.items.length,
776
+ ...(children.length > 0 ? { children: childStatus } : {}),
777
+ }));
778
+ return;
779
+ }
780
+ // Get context (portfolio-aware: fans out to child hubs)
781
+ if (url.pathname === "/api/context" && req.method === "POST") {
782
+ let body = "";
783
+ req.on("data", chunk => body += chunk);
784
+ req.on("end", async () => {
785
+ try {
786
+ const { query, taskType, maxItems } = JSON.parse(body || "{}");
787
+ const context = await getPortfolioContext(projectRoot, query, taskType, maxItems);
788
+ telemetry.track({
789
+ category: 'context_hub',
790
+ event: 'context_hub:context_loaded',
791
+ item_count: context.items.length,
792
+ journal_count: context.items.filter(i => i.source === 'journal').length,
793
+ knowledge_count: context.items.filter(i => i.source === 'knowledge').length,
794
+ code_count: context.items.filter(i => i.source === 'code').length,
795
+ content_count: context.items.filter(i => i.source === 'content').length,
796
+ query_length: query ? query.length : 0,
797
+ });
798
+ res.writeHead(200, { "Content-Type": "application/json" });
799
+ res.end(JSON.stringify(context));
800
+ }
801
+ catch (err) {
802
+ res.writeHead(400, { "Content-Type": "application/json" });
803
+ res.end(JSON.stringify({ error: err.message }));
804
+ }
805
+ });
806
+ return;
807
+ }
808
+ // Search (portfolio-aware: fans out to child hubs)
809
+ if (url.pathname === "/api/context/search" && req.method === "POST") {
810
+ let body = "";
811
+ req.on("data", chunk => body += chunk);
812
+ req.on("end", async () => {
813
+ try {
814
+ const { query, maxItems = 20 } = JSON.parse(body || "{}");
815
+ if (!query) {
816
+ res.writeHead(400, { "Content-Type": "application/json" });
817
+ res.end(JSON.stringify({ error: "query required" }));
818
+ return;
819
+ }
820
+ const searchStart = Date.now();
821
+ const context = await getPortfolioContext(projectRoot, query, undefined, maxItems);
822
+ context.items = context.items
823
+ .filter(item => item.relevance && item.relevance > 0)
824
+ .slice(0, maxItems);
825
+ telemetry.track({
826
+ category: 'context_hub',
827
+ event: 'context_hub:search',
828
+ result_count: context.items.length,
829
+ duration_ms: Date.now() - searchStart,
830
+ has_query: true,
831
+ query_length: query.length,
832
+ });
833
+ res.writeHead(200, { "Content-Type": "application/json" });
834
+ res.end(JSON.stringify(context));
835
+ }
836
+ catch (err) {
837
+ res.writeHead(400, { "Content-Type": "application/json" });
838
+ res.end(JSON.stringify({ error: err.message }));
839
+ }
840
+ });
841
+ return;
842
+ }
843
+ // Services registry
844
+ if (url.pathname === "/api/services" && req.method === "GET") {
845
+ try {
846
+ const services = discoverServices(projectRoot);
847
+ res.writeHead(200, { "Content-Type": "application/json" });
848
+ res.end(JSON.stringify(services));
849
+ }
850
+ catch (err) {
851
+ res.writeHead(500, { "Content-Type": "application/json" });
852
+ res.end(JSON.stringify({ error: err.message }));
853
+ }
854
+ return;
855
+ }
856
+ // Get specific service
857
+ if (url.pathname.startsWith("/api/services/") && req.method === "GET") {
858
+ try {
859
+ const serviceName = url.pathname.replace("/api/services/", "");
860
+ const services = discoverServices(projectRoot);
861
+ const service = services[serviceName];
862
+ if (!service) {
863
+ res.writeHead(404, { "Content-Type": "application/json" });
864
+ res.end(JSON.stringify({ error: "Service not found" }));
865
+ return;
866
+ }
867
+ res.writeHead(200, { "Content-Type": "application/json" });
868
+ res.end(JSON.stringify(service));
869
+ }
870
+ catch (err) {
871
+ res.writeHead(500, { "Content-Type": "application/json" });
872
+ res.end(JSON.stringify({ error: err.message }));
873
+ }
874
+ return;
875
+ }
876
+ // RAG Chat — search context + memory, then stream LLM response
877
+ if (url.pathname === "/api/chat" && req.method === "POST") {
878
+ let body = "";
879
+ req.on("data", chunk => body += chunk);
880
+ req.on("end", async () => {
881
+ try {
882
+ const { message, history = [] } = JSON.parse(body || "{}");
883
+ if (!message) {
884
+ res.writeHead(400, { "Content-Type": "application/json" });
885
+ res.end(JSON.stringify({ error: "message required" }));
886
+ return;
887
+ }
888
+ const [memRaw, ctxResult] = await Promise.allSettled([
889
+ searchMemories(message, { maxItems: 5 }),
890
+ getPortfolioContext(projectRoot, message, undefined, 5),
891
+ ]);
892
+ const memResults = memRaw.status === "fulfilled"
893
+ ? memRaw.value.map(r => ({ title: r.memory.title, content: r.memory.content, type: r.memory.type, relevance: r.relevance }))
894
+ : [];
895
+ const ctxResults = ctxResult.status === "fulfilled"
896
+ ? (ctxResult.value.items || []).slice(0, 5).map((i) => ({ title: i.title || i.path, content: i.content?.slice(0, 500), type: i.type }))
897
+ : [];
898
+ const sources = [...memResults, ...ctxResults];
899
+ let contextBlock = "";
900
+ if (sources.length > 0) {
901
+ contextBlock = "Here is relevant context from the project:\n\n" +
902
+ sources.map((s, i) => `[${i + 1}] ${s.title} (${s.type})\n${s.content}`).join("\n\n") +
903
+ "\n\n---\n\n";
904
+ }
905
+ // Load .env from project root for API keys
906
+ const envPath = path.join(projectRoot, ".env");
907
+ if (fs.existsSync(envPath)) {
908
+ for (const line of fs.readFileSync(envPath, "utf-8").split("\n")) {
909
+ const match = line.match(/^([A-Z_]+)=(.+)$/);
910
+ if (match && !process.env[match[1]])
911
+ process.env[match[1]] = match[2].trim();
912
+ }
913
+ }
914
+ const apiKey = process.env.STRATUS_API_KEY || process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY;
915
+ if (!apiKey) {
916
+ res.writeHead(200, {
917
+ "Content-Type": "text/event-stream",
918
+ "Cache-Control": "no-cache",
919
+ "Connection": "keep-alive",
920
+ });
921
+ const fallback = sources.length > 0
922
+ ? "I found relevant context but no LLM API key is configured. Set STRATUS_API_KEY, OPENAI_API_KEY, or ANTHROPIC_API_KEY in your .env.\n\n**Sources found:**\n" +
923
+ sources.map((s, i) => `${i + 1}. **${s.title}** \`${s.type}\`\n ${s.content?.slice(0, 150)}`).join("\n")
924
+ : "No API key configured and no relevant context found.";
925
+ res.write(`data: ${JSON.stringify({ sources })}\n\n`);
926
+ res.write(`data: ${JSON.stringify({ delta: fallback })}\n\n`);
927
+ res.write("data: [DONE]\n\n");
928
+ res.end();
929
+ return;
930
+ }
931
+ const useStratus = apiKey === process.env.STRATUS_API_KEY;
932
+ const baseURL = useStratus ? "https://api.stratus.run/v1" : undefined;
933
+ const model = useStratus ? "stratus-x1ac-huge-claude-sonnet-4-6" : "gpt-4o-mini";
934
+ const OpenAI = (await import("openai")).default;
935
+ const client = new OpenAI({ apiKey, baseURL });
936
+ const messages = [
937
+ {
938
+ role: "system",
939
+ content: `You are JFL Assistant, helping the user understand their project context. Answer questions based on the provided context from journal entries, knowledge docs, and code files. Be concise and direct. If the context doesn't contain enough information, say so. Reference specific sources when possible.\n\n${contextBlock}`
940
+ },
941
+ ...history.slice(-6).map((m) => ({ role: m.role, content: m.content })),
942
+ { role: "user", content: message },
943
+ ];
944
+ res.writeHead(200, {
945
+ "Content-Type": "text/event-stream",
946
+ "Cache-Control": "no-cache",
947
+ "Connection": "keep-alive",
948
+ });
949
+ res.write(`data: ${JSON.stringify({ sources })}\n\n`);
950
+ try {
951
+ const stream = await client.chat.completions.create({
952
+ model,
953
+ messages,
954
+ stream: true,
955
+ max_tokens: 1024,
956
+ });
957
+ for await (const chunk of stream) {
958
+ const delta = chunk.choices?.[0]?.delta?.content;
959
+ if (delta) {
960
+ res.write(`data: ${JSON.stringify({ delta })}\n\n`);
961
+ }
962
+ }
963
+ }
964
+ catch (llmErr) {
965
+ res.write(`data: ${JSON.stringify({ delta: `\n\nLLM error: ${llmErr.message}` })}\n\n`);
966
+ }
967
+ res.write("data: [DONE]\n\n");
968
+ res.end();
969
+ }
970
+ catch (err) {
971
+ res.writeHead(500, { "Content-Type": "application/json" });
972
+ res.end(JSON.stringify({ error: err.message }));
973
+ }
974
+ });
975
+ return;
976
+ }
977
+ // Memory search
978
+ if (url.pathname === "/api/memory/search" && req.method === "POST") {
979
+ let body = "";
980
+ req.on("data", chunk => body += chunk);
981
+ req.on("end", async () => {
982
+ try {
983
+ const { query, type, maxItems = 10, since } = JSON.parse(body || "{}");
984
+ if (!query) {
985
+ res.writeHead(400, { "Content-Type": "application/json" });
986
+ res.end(JSON.stringify({ error: "query required" }));
987
+ return;
988
+ }
989
+ const raw = await searchMemories(query, { type, maxItems, since });
990
+ const results = raw.map(r => ({
991
+ id: r.memory.id,
992
+ title: r.memory.title,
993
+ content: r.memory.content,
994
+ summary: r.memory.summary,
995
+ type: r.memory.type,
996
+ source: r.memory.source,
997
+ ts: r.memory.created_at,
998
+ score: r.score,
999
+ relevance: r.relevance,
1000
+ }));
1001
+ // Surface linked memories (teacup ↔ insight connections)
1002
+ const linkedResults = [];
1003
+ for (const r of results) {
1004
+ if (r.id) {
1005
+ try {
1006
+ const linksFrom = await getLinksFrom(r.id);
1007
+ const linksTo = await getLinksTo(r.id);
1008
+ const linkedIds = [
1009
+ ...linksFrom.map(l => l.to_memory_id),
1010
+ ...linksTo.map(l => l.from_memory_id)
1011
+ ].filter(id => !results.some(existing => existing.id === id));
1012
+ for (const linkedId of linkedIds.slice(0, 3)) {
1013
+ try {
1014
+ const [linked] = await getMemoriesByIds([linkedId]);
1015
+ if (linked) {
1016
+ const linkType = linksFrom.find(l => l.to_memory_id === linkedId)?.link_type
1017
+ || linksTo.find(l => l.from_memory_id === linkedId)?.link_type
1018
+ || 'related_to';
1019
+ linkedResults.push({
1020
+ id: linked.id,
1021
+ title: `🔗 ${linkType === 'leads_to' ? '🫖→' : '←🫖'} ${linked.title}`,
1022
+ content: linked.content,
1023
+ summary: linked.summary,
1024
+ type: linked.type,
1025
+ source: linked.source,
1026
+ ts: linked.created_at,
1027
+ score: r.score * 0.8,
1028
+ relevance: 'medium',
1029
+ });
1030
+ }
1031
+ }
1032
+ catch { /* skip broken links */ }
1033
+ }
1034
+ }
1035
+ catch { /* links optional */ }
1036
+ }
1037
+ }
1038
+ res.writeHead(200, { "Content-Type": "application/json" });
1039
+ res.end(JSON.stringify({ results: [...results, ...linkedResults] }));
1040
+ }
1041
+ catch (err) {
1042
+ res.writeHead(500, { "Content-Type": "application/json" });
1043
+ res.end(JSON.stringify({ error: err.message }));
1044
+ }
1045
+ });
1046
+ return;
1047
+ }
1048
+ // Memory status
1049
+ if (url.pathname === "/api/memory/status" && req.method === "GET") {
1050
+ getMemoryStats()
1051
+ .then(stats => {
1052
+ res.writeHead(200, { "Content-Type": "application/json" });
1053
+ res.end(JSON.stringify(stats));
1054
+ })
1055
+ .catch((err) => {
1056
+ res.writeHead(500, { "Content-Type": "application/json" });
1057
+ res.end(JSON.stringify({ error: err.message }));
1058
+ });
1059
+ return;
1060
+ }
1061
+ // Memory add
1062
+ if (url.pathname === "/api/memory/add" && req.method === "POST") {
1063
+ let body = "";
1064
+ req.on("data", chunk => body += chunk);
1065
+ req.on("end", async () => {
1066
+ try {
1067
+ const { title, content, tags, type, linked_to } = JSON.parse(body || "{}");
1068
+ if (!title || !content) {
1069
+ res.writeHead(400, { "Content-Type": "application/json" });
1070
+ res.end(JSON.stringify({ error: "title and content required" }));
1071
+ return;
1072
+ }
1073
+ const id = await insertMemory({
1074
+ source: 'manual',
1075
+ type: type || undefined,
1076
+ title,
1077
+ content,
1078
+ created_at: new Date().toISOString()
1079
+ });
1080
+ // If this teacup/insight links to another memory, create the link
1081
+ if (linked_to && typeof linked_to === 'number') {
1082
+ try {
1083
+ const linkType = type === 'teacup' ? 'leads_to' : 'related_to';
1084
+ await addLink(id, linked_to, linkType);
1085
+ }
1086
+ catch { /* link is optional, don't fail the add */ }
1087
+ }
1088
+ res.writeHead(201, { "Content-Type": "application/json" });
1089
+ res.end(JSON.stringify({ id }));
1090
+ }
1091
+ catch (err) {
1092
+ res.writeHead(500, { "Content-Type": "application/json" });
1093
+ res.end(JSON.stringify({ error: err.message }));
1094
+ }
1095
+ });
1096
+ return;
1097
+ }
1098
+ // POST /api/memory/index — trigger journal→memory indexing + embedding backfill
1099
+ if (url.pathname === "/api/memory/index" && req.method === "POST") {
1100
+ let body = "";
1101
+ req.on("data", (chunk) => body += chunk);
1102
+ req.on("end", async () => {
1103
+ try {
1104
+ const { force, backfill } = JSON.parse(body || "{}");
1105
+ const stats = await indexJournalEntries(force ?? false);
1106
+ let embeddingStats = { updated: 0, errors: 0 };
1107
+ if (backfill !== false) {
1108
+ embeddingStats = await backfillEmbeddings();
1109
+ }
1110
+ const codeStats = await indexCodeHeaders();
1111
+ res.writeHead(200, { "Content-Type": "application/json" });
1112
+ res.end(JSON.stringify({ ok: true, ...stats, embeddings_backfilled: embeddingStats.updated, embedding_errors: embeddingStats.errors, code_headers_added: codeStats.added, code_headers_updated: codeStats.updated }));
1113
+ }
1114
+ catch (err) {
1115
+ res.writeHead(500, { "Content-Type": "application/json" });
1116
+ res.end(JSON.stringify({ error: err.message }));
1117
+ }
1118
+ });
1119
+ return;
1120
+ }
1121
+ // POST /api/memory/link — add a graph edge between two memories
1122
+ if (url.pathname === "/api/memory/link" && req.method === "POST") {
1123
+ let body = "";
1124
+ req.on("data", (chunk) => body += chunk);
1125
+ req.on("end", async () => {
1126
+ try {
1127
+ const { from, to, type } = JSON.parse(body || "{}");
1128
+ if (!from || !to || !type) {
1129
+ res.writeHead(400, { "Content-Type": "application/json" });
1130
+ res.end(JSON.stringify({ error: "from, to, and type are required" }));
1131
+ return;
1132
+ }
1133
+ const { addLink } = await import("../lib/memory-db.js");
1134
+ const id = await addLink(from, to, type);
1135
+ res.writeHead(200, { "Content-Type": "application/json" });
1136
+ res.end(JSON.stringify({ ok: true, id }));
1137
+ }
1138
+ catch (err) {
1139
+ res.writeHead(500, { "Content-Type": "application/json" });
1140
+ res.end(JSON.stringify({ error: err.message }));
1141
+ }
1142
+ });
1143
+ return;
1144
+ }
1145
+ // Eval trajectory
1146
+ if (url.pathname === "/api/eval/trajectory" && req.method === "GET") {
1147
+ try {
1148
+ const { getTrajectory } = await import("../lib/eval-store.js");
1149
+ const agent = url.searchParams.get("agent") || "";
1150
+ const metric = url.searchParams.get("metric") || "composite";
1151
+ if (!agent) {
1152
+ res.writeHead(400, { "Content-Type": "application/json" });
1153
+ res.end(JSON.stringify({ error: "agent query param required" }));
1154
+ return;
1155
+ }
1156
+ const points = getTrajectory(agent, metric, projectRoot);
1157
+ res.writeHead(200, { "Content-Type": "application/json" });
1158
+ res.end(JSON.stringify({ agent, metric, points }));
1159
+ }
1160
+ catch (err) {
1161
+ res.writeHead(500, { "Content-Type": "application/json" });
1162
+ res.end(JSON.stringify({ error: err.message }));
1163
+ }
1164
+ return;
1165
+ }
1166
+ // Eval entries (all raw entries for cycle display)
1167
+ if (url.pathname === "/api/eval/entries" && req.method === "GET") {
1168
+ try {
1169
+ const { readEvals } = await import("../lib/eval-store.js");
1170
+ const limit = parseInt(url.searchParams.get("limit") || "100", 10);
1171
+ const entries = readEvals(projectRoot)
1172
+ .sort((a, b) => b.ts.localeCompare(a.ts))
1173
+ .slice(0, limit);
1174
+ res.writeHead(200, { "Content-Type": "application/json" });
1175
+ res.end(JSON.stringify({ entries }));
1176
+ }
1177
+ catch (err) {
1178
+ res.writeHead(500, { "Content-Type": "application/json" });
1179
+ res.end(JSON.stringify({ error: err.message }));
1180
+ }
1181
+ return;
1182
+ }
1183
+ // Eval leaderboard
1184
+ if (url.pathname === "/api/eval/leaderboard" && req.method === "GET") {
1185
+ try {
1186
+ const { readEvals, listAgents, getLatestEval, getTrajectory } = await import("../lib/eval-store.js");
1187
+ const agents = listAgents(projectRoot);
1188
+ const leaderboard = agents.map(agent => {
1189
+ const latest = getLatestEval(agent, projectRoot);
1190
+ const trajectory = getTrajectory(agent, "composite", projectRoot);
1191
+ const prevPoint = trajectory.length >= 2 ? trajectory[trajectory.length - 2] : null;
1192
+ const delta = latest?.composite != null && prevPoint
1193
+ ? latest.composite - prevPoint.value
1194
+ : null;
1195
+ return {
1196
+ agent,
1197
+ composite: latest?.composite ?? null,
1198
+ metrics: latest?.metrics ?? {},
1199
+ delta,
1200
+ model_version: latest?.model_version ?? null,
1201
+ lastTs: latest?.ts ?? null,
1202
+ trajectory: trajectory.slice(-20).map(p => p.value),
1203
+ };
1204
+ }).sort((a, b) => (b.composite ?? 0) - (a.composite ?? 0));
1205
+ res.writeHead(200, { "Content-Type": "application/json" });
1206
+ res.end(JSON.stringify(leaderboard));
1207
+ }
1208
+ catch (err) {
1209
+ res.writeHead(500, { "Content-Type": "application/json" });
1210
+ res.end(JSON.stringify({ error: err.message }));
1211
+ }
1212
+ return;
1213
+ }
1214
+ // Synopsis (work summary)
1215
+ if (url.pathname === "/api/synopsis" && req.method === "GET") {
1216
+ try {
1217
+ const hours = parseInt(url.searchParams.get("hours") || "24", 10);
1218
+ const author = url.searchParams.get("author") || undefined;
1219
+ const { generateSynopsis } = await import("./synopsis.js");
1220
+ const synopsis = generateSynopsis(projectRoot, hours, author);
1221
+ res.writeHead(200, { "Content-Type": "application/json" });
1222
+ res.end(JSON.stringify(synopsis));
1223
+ }
1224
+ catch (err) {
1225
+ res.writeHead(500, { "Content-Type": "application/json" });
1226
+ res.end(JSON.stringify({ error: err.message }));
1227
+ }
1228
+ return;
1229
+ }
1230
+ // Prediction accuracy (Stratus)
1231
+ if (url.pathname === "/api/eval/predictions" && req.method === "GET") {
1232
+ try {
1233
+ const { Predictor } = await import("../lib/predictor.js");
1234
+ const predictor = new Predictor({ projectRoot });
1235
+ const accuracy = predictor.getAccuracy();
1236
+ const recent = predictor.getHistory(20).reverse();
1237
+ res.writeHead(200, { "Content-Type": "application/json" });
1238
+ res.end(JSON.stringify({ accuracy, recent }));
1239
+ }
1240
+ catch (err) {
1241
+ res.writeHead(200, { "Content-Type": "application/json" });
1242
+ res.end(JSON.stringify({ accuracy: { total: 0, resolved: 0, direction_accuracy: 0, mean_delta_error: 0, calibration: 0 }, recent: [] }));
1243
+ }
1244
+ return;
1245
+ }
1246
+ // Cross-project health
1247
+ if (url.pathname === "/api/projects" && req.method === "GET") {
1248
+ const tracked = getTrackedProjects();
1249
+ Promise.all(tracked.map(async (p) => {
1250
+ // Self-check: if this is our own port, we know we're OK
1251
+ if (p.port === port) {
1252
+ return {
1253
+ name: p.path.split("/").pop() || p.path,
1254
+ path: p.path,
1255
+ port: p.port,
1256
+ status: "OK",
1257
+ pid: process.pid,
1258
+ message: "This instance",
1259
+ };
1260
+ }
1261
+ const result = await diagnoseProject(p.path, p.port);
1262
+ return {
1263
+ name: p.path.split("/").pop() || p.path,
1264
+ path: p.path,
1265
+ port: p.port,
1266
+ status: result.status,
1267
+ pid: result.pid,
1268
+ message: result.message,
1269
+ };
1270
+ }))
1271
+ .then((results) => {
1272
+ res.writeHead(200, { "Content-Type": "application/json" });
1273
+ res.end(JSON.stringify(results));
1274
+ })
1275
+ .catch((err) => {
1276
+ res.writeHead(500, { "Content-Type": "application/json" });
1277
+ res.end(JSON.stringify({ error: err.message }));
1278
+ });
1279
+ return;
1280
+ }
1281
+ // Publish event
1282
+ if (url.pathname === "/api/events" && req.method === "POST") {
1283
+ if (!eventBus) {
1284
+ res.writeHead(503, { "Content-Type": "application/json" });
1285
+ res.end(JSON.stringify({ error: "Event bus not initialized" }));
1286
+ return;
1287
+ }
1288
+ let body = "";
1289
+ req.on("data", chunk => body += chunk);
1290
+ req.on("end", () => {
1291
+ try {
1292
+ const { type, source, target, session, data, ttl } = JSON.parse(body || "{}");
1293
+ if (!type || !source) {
1294
+ res.writeHead(400, { "Content-Type": "application/json" });
1295
+ res.end(JSON.stringify({ error: "type and source required" }));
1296
+ return;
1297
+ }
1298
+ const event = eventBus.emit({
1299
+ type: type,
1300
+ source,
1301
+ target,
1302
+ session,
1303
+ data: data || {},
1304
+ ttl,
1305
+ });
1306
+ res.writeHead(201, { "Content-Type": "application/json" });
1307
+ res.end(JSON.stringify(event));
1308
+ }
1309
+ catch (err) {
1310
+ res.writeHead(400, { "Content-Type": "application/json" });
1311
+ res.end(JSON.stringify({ error: err.message }));
1312
+ }
1313
+ });
1314
+ return;
1315
+ }
1316
+ // Get recent events
1317
+ if (url.pathname === "/api/events" && req.method === "GET") {
1318
+ if (!eventBus) {
1319
+ res.writeHead(503, { "Content-Type": "application/json" });
1320
+ res.end(JSON.stringify({ error: "Event bus not initialized" }));
1321
+ return;
1322
+ }
1323
+ const since = url.searchParams.get("since") || undefined;
1324
+ const pattern = url.searchParams.get("pattern") || undefined;
1325
+ const limit = url.searchParams.get("limit") ? parseInt(url.searchParams.get("limit"), 10) : 50;
1326
+ const events = eventBus.getEvents({ since, pattern, limit });
1327
+ res.writeHead(200, { "Content-Type": "application/json" });
1328
+ res.end(JSON.stringify({ events, count: events.length }));
1329
+ return;
1330
+ }
1331
+ // SSE event stream
1332
+ if (url.pathname === "/api/events/stream" && req.method === "GET") {
1333
+ if (!eventBus) {
1334
+ res.writeHead(503, { "Content-Type": "application/json" });
1335
+ res.end(JSON.stringify({ error: "Event bus not initialized" }));
1336
+ return;
1337
+ }
1338
+ const patterns = (url.searchParams.get("patterns") || "*").split(",");
1339
+ res.writeHead(200, {
1340
+ "Content-Type": "text/event-stream",
1341
+ "Cache-Control": "no-cache",
1342
+ Connection: "keep-alive",
1343
+ "Access-Control-Allow-Origin": "*",
1344
+ });
1345
+ res.write("retry: 3000\n\n");
1346
+ const sub = eventBus.subscribe({
1347
+ clientId: `sse-${Date.now()}`,
1348
+ patterns,
1349
+ transport: "sse",
1350
+ callback: (event) => {
1351
+ res.write(`id: ${event.id}\nevent: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`);
1352
+ },
1353
+ });
1354
+ req.on("close", () => {
1355
+ eventBus.unsubscribe(sub.id);
1356
+ });
1357
+ return;
1358
+ }
1359
+ // Telemetry digest
1360
+ if (url.pathname === "/api/telemetry/digest" && req.method === "GET") {
1361
+ try {
1362
+ const { loadLocalEvents, analyzeEvents } = await import("../lib/telemetry-digest.js");
1363
+ const hours = parseInt(url.searchParams.get("hours") || "168", 10);
1364
+ const events = loadLocalEvents();
1365
+ const digest = analyzeEvents(events, hours);
1366
+ res.writeHead(200, { "Content-Type": "application/json" });
1367
+ res.end(JSON.stringify(digest));
1368
+ }
1369
+ catch (err) {
1370
+ res.writeHead(500, { "Content-Type": "application/json" });
1371
+ res.end(JSON.stringify({ error: err.message }));
1372
+ }
1373
+ return;
1374
+ }
1375
+ // Telemetry agent status
1376
+ if (url.pathname === "/api/telemetry/agent" && req.method === "GET") {
1377
+ const agent = server.__telemetryAgent;
1378
+ if (agent) {
1379
+ res.writeHead(200, { "Content-Type": "application/json" });
1380
+ res.end(JSON.stringify(agent.getStatus()));
1381
+ }
1382
+ else {
1383
+ res.writeHead(200, { "Content-Type": "application/json" });
1384
+ res.end(JSON.stringify({ running: false, lastRun: '', runCount: 0, lastInsights: [] }));
1385
+ }
1386
+ return;
1387
+ }
1388
+ // Telemetry agent: trigger manual run
1389
+ if (url.pathname === "/api/telemetry/agent/run" && req.method === "POST") {
1390
+ const agent = server.__telemetryAgent;
1391
+ if (agent) {
1392
+ try {
1393
+ const insights = await agent.run();
1394
+ res.writeHead(200, { "Content-Type": "application/json" });
1395
+ res.end(JSON.stringify({ ok: true, insights }));
1396
+ }
1397
+ catch (err) {
1398
+ res.writeHead(500, { "Content-Type": "application/json" });
1399
+ res.end(JSON.stringify({ error: err.message }));
1400
+ }
1401
+ }
1402
+ else {
1403
+ res.writeHead(503, { "Content-Type": "application/json" });
1404
+ res.end(JSON.stringify({ error: "Telemetry agent not running" }));
1405
+ }
1406
+ return;
1407
+ }
1408
+ // ── Autoresearch / Experiments API ──────────────────────────────
1409
+ // Agent configs
1410
+ if (url.pathname === "/api/v1/agents" && req.method === "GET") {
1411
+ try {
1412
+ const { listAgentConfigs, loadAgentConfig } = await import("../lib/agent-config.js");
1413
+ const names = listAgentConfigs(projectRoot);
1414
+ const agents = names.map(name => {
1415
+ try {
1416
+ return loadAgentConfig(projectRoot, name);
1417
+ }
1418
+ catch {
1419
+ return null;
1420
+ }
1421
+ }).filter(Boolean);
1422
+ res.writeHead(200, { "Content-Type": "application/json" });
1423
+ res.end(JSON.stringify({ agents }));
1424
+ }
1425
+ catch (err) {
1426
+ res.writeHead(500, { "Content-Type": "application/json" });
1427
+ res.end(JSON.stringify({ error: err.message }));
1428
+ }
1429
+ return;
1430
+ }
1431
+ // Replay buffer (experiment history)
1432
+ if (url.pathname === "/api/v1/experiments" && req.method === "GET") {
1433
+ try {
1434
+ const bufferPath = path.join(projectRoot, ".jfl", "replay-buffer.jsonl");
1435
+ const trainingPath = path.join(projectRoot, ".jfl", "training-buffer.jsonl");
1436
+ const experiments = [];
1437
+ for (const p of [bufferPath, trainingPath]) {
1438
+ if (fs.existsSync(p)) {
1439
+ const lines = fs.readFileSync(p, "utf-8").trim().split("\n").filter(Boolean);
1440
+ for (const line of lines.slice(-100)) {
1441
+ try {
1442
+ experiments.push(JSON.parse(line));
1443
+ }
1444
+ catch { }
1445
+ }
1446
+ }
1447
+ }
1448
+ const agent = url.searchParams.get("agent");
1449
+ const filtered = agent ? experiments.filter(e => e.agent === agent) : experiments;
1450
+ res.writeHead(200, { "Content-Type": "application/json" });
1451
+ res.end(JSON.stringify({ experiments: filtered, total: filtered.length }));
1452
+ }
1453
+ catch (err) {
1454
+ res.writeHead(500, { "Content-Type": "application/json" });
1455
+ res.end(JSON.stringify({ error: err.message }));
1456
+ }
1457
+ return;
1458
+ }
1459
+ // Session results
1460
+ if (url.pathname === "/api/v1/sessions" && req.method === "GET") {
1461
+ try {
1462
+ const sessionsDir = path.join(projectRoot, ".jfl", "sessions");
1463
+ const sessions = [];
1464
+ if (fs.existsSync(sessionsDir)) {
1465
+ for (const dir of fs.readdirSync(sessionsDir)) {
1466
+ const resultsPath = path.join(sessionsDir, dir, "results.tsv");
1467
+ if (fs.existsSync(resultsPath)) {
1468
+ const content = fs.readFileSync(resultsPath, "utf-8").trim();
1469
+ const lines = content.split("\n").slice(1); // skip header
1470
+ const rounds = lines.filter(l => l.trim()).map(line => {
1471
+ const [round, task, baseline, metric, delta, kept, duration, error, timestamp] = line.split("\t");
1472
+ return { round: +round, task, baseline: +baseline, metric: +metric, delta: +delta, kept: kept === "1", duration_ms: +duration, error, timestamp };
1473
+ });
1474
+ if (rounds.length > 0) {
1475
+ sessions.push({ id: dir, rounds, agent: dir.replace(/-[a-f0-9]{8}-\d+$/, "") });
1476
+ }
1477
+ }
1478
+ }
1479
+ }
1480
+ res.writeHead(200, { "Content-Type": "application/json" });
1481
+ res.end(JSON.stringify({ sessions }));
1482
+ }
1483
+ catch (err) {
1484
+ res.writeHead(500, { "Content-Type": "application/json" });
1485
+ res.end(JSON.stringify({ error: err.message }));
1486
+ }
1487
+ return;
1488
+ }
1489
+ // Product context
1490
+ if (url.pathname === "/api/v1/product-context" && req.method === "GET") {
1491
+ const contextPath = path.join(projectRoot, ".jfl", "product-context.md");
1492
+ if (fs.existsSync(contextPath)) {
1493
+ const content = fs.readFileSync(contextPath, "utf-8");
1494
+ res.writeHead(200, { "Content-Type": "application/json" });
1495
+ res.end(JSON.stringify({ context: content, updatedAt: fs.statSync(contextPath).mtime.toISOString() }));
1496
+ }
1497
+ else {
1498
+ res.writeHead(200, { "Content-Type": "application/json" });
1499
+ res.end(JSON.stringify({ context: null, updatedAt: null }));
1500
+ }
1501
+ return;
1502
+ }
1503
+ // Product analysis (telemetry agent v2)
1504
+ if (url.pathname === "/api/v1/product-analysis" && req.method === "GET") {
1505
+ try {
1506
+ const { TelemetryAgentV2 } = await import("../lib/telemetry-agent-v2.js");
1507
+ const agent = new TelemetryAgentV2({ projectRoot });
1508
+ const analysis = await agent.analyzeProduct();
1509
+ res.writeHead(200, { "Content-Type": "application/json" });
1510
+ res.end(JSON.stringify(analysis));
1511
+ }
1512
+ catch (err) {
1513
+ res.writeHead(500, { "Content-Type": "application/json" });
1514
+ res.end(JSON.stringify({ error: err.message }));
1515
+ }
1516
+ return;
1517
+ }
1518
+ // Kanban board
1519
+ // ── Kanban API (GitHub Issues-backed) ──────────────────────
1520
+ if (url.pathname === "/api/kanban" && req.method === "GET") {
1521
+ try {
1522
+ const { GitHubKanban } = await import("../lib/kanban-github.js");
1523
+ const scope = url.searchParams.get("scope") ?? undefined;
1524
+ const source = url.searchParams.get("source") ?? undefined;
1525
+ const filter = (scope || source) ? { scope, source } : undefined;
1526
+ const kb = new GitHubKanban(projectRoot);
1527
+ const board = await kb.getBoard(filter);
1528
+ res.writeHead(200, { "Content-Type": "application/json" });
1529
+ res.end(JSON.stringify(board));
1530
+ }
1531
+ catch (err) {
1532
+ res.writeHead(500, { "Content-Type": "application/json" });
1533
+ res.end(JSON.stringify({ error: err.message }));
1534
+ }
1535
+ return;
1536
+ }
1537
+ if (url.pathname === "/api/kanban/cards" && req.method === "GET") {
1538
+ try {
1539
+ const { GitHubKanban } = await import("../lib/kanban-github.js");
1540
+ const scope = url.searchParams.get("scope") ?? undefined;
1541
+ const source = url.searchParams.get("source") ?? undefined;
1542
+ const filter = (scope || source) ? { scope, source } : undefined;
1543
+ const kb = new GitHubKanban(projectRoot);
1544
+ const cards = await kb.getCards(filter);
1545
+ res.writeHead(200, { "Content-Type": "application/json" });
1546
+ res.end(JSON.stringify(cards));
1547
+ }
1548
+ catch (err) {
1549
+ res.writeHead(500, { "Content-Type": "application/json" });
1550
+ res.end(JSON.stringify({ error: err.message }));
1551
+ }
1552
+ return;
1553
+ }
1554
+ if (url.pathname === "/api/kanban/scopes" && req.method === "GET") {
1555
+ try {
1556
+ const { GitHubKanban } = await import("../lib/kanban-github.js");
1557
+ const kb = new GitHubKanban(projectRoot);
1558
+ const workspace = kb.getWorkspaceInfo();
1559
+ const services = kb.getRegisteredServices();
1560
+ res.writeHead(200, { "Content-Type": "application/json" });
1561
+ res.end(JSON.stringify({ workspace, services }));
1562
+ }
1563
+ catch (err) {
1564
+ res.writeHead(500, { "Content-Type": "application/json" });
1565
+ res.end(JSON.stringify({ error: err.message }));
1566
+ }
1567
+ return;
1568
+ }
1569
+ if (url.pathname === "/api/kanban" && req.method === "POST") {
1570
+ let body = "";
1571
+ req.on("data", (chunk) => { body += chunk; });
1572
+ req.on("end", async () => {
1573
+ try {
1574
+ const data = JSON.parse(body || "{}");
1575
+ const { GitHubKanban } = await import("../lib/kanban-github.js");
1576
+ const kb = new GitHubKanban(projectRoot);
1577
+ if (data.action === "create") {
1578
+ const number = kb.create({
1579
+ title: data.title,
1580
+ description: data.description,
1581
+ source: data.source ?? "human",
1582
+ priority: data.priority ?? 0,
1583
+ scope: data.scope,
1584
+ labels: data.labels,
1585
+ });
1586
+ res.writeHead(201, { "Content-Type": "application/json" });
1587
+ res.end(JSON.stringify({ id: `#${number}`, number }));
1588
+ }
1589
+ else if (data.action === "move") {
1590
+ // Accept both #8 and 8 format
1591
+ const num = typeof data.id === "string" ? parseInt(data.id.replace("#", "")) : data.id;
1592
+ kb.move(num, data.column);
1593
+ res.writeHead(200, { "Content-Type": "application/json" });
1594
+ res.end(JSON.stringify({ ok: true }));
1595
+ }
1596
+ else if (data.action === "pick") {
1597
+ const num = typeof data.id === "string" ? parseInt(data.id.replace("#", "")) : data.id;
1598
+ kb.pick(num, data.agent);
1599
+ res.writeHead(200, { "Content-Type": "application/json" });
1600
+ res.end(JSON.stringify({ ok: true }));
1601
+ }
1602
+ else {
1603
+ res.writeHead(400, { "Content-Type": "application/json" });
1604
+ res.end(JSON.stringify({ error: "Unknown action" }));
1605
+ }
1606
+ }
1607
+ catch (err) {
1608
+ res.writeHead(500, { "Content-Type": "application/json" });
1609
+ res.end(JSON.stringify({ error: err.message }));
1610
+ }
1611
+ });
1612
+ return;
1613
+ }
1614
+ // Flow definitions
1615
+ if (url.pathname === "/api/flows" && req.method === "GET") {
1616
+ if (!flowEngine) {
1617
+ res.writeHead(200, { "Content-Type": "application/json" });
1618
+ res.end(JSON.stringify([]));
1619
+ return;
1620
+ }
1621
+ res.writeHead(200, { "Content-Type": "application/json" });
1622
+ res.end(JSON.stringify(flowEngine.getFlows()));
1623
+ return;
1624
+ }
1625
+ // Flow executions
1626
+ if (url.pathname === "/api/flows/executions" && req.method === "GET") {
1627
+ if (!flowEngine) {
1628
+ res.writeHead(200, { "Content-Type": "application/json" });
1629
+ res.end(JSON.stringify({ executions: [] }));
1630
+ return;
1631
+ }
1632
+ res.writeHead(200, { "Content-Type": "application/json" });
1633
+ res.end(JSON.stringify({ executions: flowEngine.getExecutions() }));
1634
+ return;
1635
+ }
1636
+ // Flow approval
1637
+ if (url.pathname.match(/^\/api\/flows\/[^/]+\/approve$/) && req.method === "POST") {
1638
+ if (!flowEngine) {
1639
+ res.writeHead(503, { "Content-Type": "application/json" });
1640
+ res.end(JSON.stringify({ error: "Flow engine not initialized" }));
1641
+ return;
1642
+ }
1643
+ const flowName = decodeURIComponent(url.pathname.split("/")[3]);
1644
+ let body = "";
1645
+ req.on("data", chunk => body += chunk);
1646
+ req.on("end", async () => {
1647
+ try {
1648
+ const { trigger_event_id } = JSON.parse(body || "{}");
1649
+ if (!trigger_event_id) {
1650
+ res.writeHead(400, { "Content-Type": "application/json" });
1651
+ res.end(JSON.stringify({ error: "trigger_event_id required" }));
1652
+ return;
1653
+ }
1654
+ const result = await flowEngine.approveGated(flowName, trigger_event_id);
1655
+ if (!result) {
1656
+ res.writeHead(404, { "Content-Type": "application/json" });
1657
+ res.end(JSON.stringify({ error: "Gated execution not found" }));
1658
+ return;
1659
+ }
1660
+ res.writeHead(200, { "Content-Type": "application/json" });
1661
+ res.end(JSON.stringify(result));
1662
+ }
1663
+ catch (err) {
1664
+ res.writeHead(500, { "Content-Type": "application/json" });
1665
+ res.end(JSON.stringify({ error: err.message }));
1666
+ }
1667
+ });
1668
+ return;
1669
+ }
1670
+ if (url.pathname.match(/^\/api\/flows\/[^/]+\/toggle$/) && req.method === "POST") {
1671
+ if (!flowEngine) {
1672
+ res.writeHead(503, { "Content-Type": "application/json" });
1673
+ res.end(JSON.stringify({ error: "Flow engine not initialized" }));
1674
+ return;
1675
+ }
1676
+ const flowName = decodeURIComponent(url.pathname.split("/")[3]);
1677
+ let body = "";
1678
+ req.on("data", chunk => body += chunk);
1679
+ req.on("end", () => {
1680
+ try {
1681
+ const { enabled } = JSON.parse(body || "{}");
1682
+ const result = flowEngine.toggleFlow(flowName, enabled);
1683
+ if (!result) {
1684
+ res.writeHead(404, { "Content-Type": "application/json" });
1685
+ res.end(JSON.stringify({ error: "Flow not found" }));
1686
+ return;
1687
+ }
1688
+ res.writeHead(200, { "Content-Type": "application/json" });
1689
+ res.end(JSON.stringify({ ok: true, flow: flowName, enabled: result.enabled }));
1690
+ }
1691
+ catch (err) {
1692
+ res.writeHead(500, { "Content-Type": "application/json" });
1693
+ res.end(JSON.stringify({ error: err.message }));
1694
+ }
1695
+ });
1696
+ return;
1697
+ }
1698
+ // Topology — returns nodes/edges for agent topology visualization
1699
+ if (url.pathname === "/api/v1/topology" && req.method === "GET") {
1700
+ try {
1701
+ const configPath = path.join(projectRoot, ".jfl", "config.json");
1702
+ let registeredServices = [];
1703
+ let workspaceType = "standalone";
1704
+ let workspaceName = "workspace";
1705
+ if (fs.existsSync(configPath)) {
1706
+ try {
1707
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
1708
+ registeredServices = config.registered_services || [];
1709
+ workspaceType = config.type || "standalone";
1710
+ workspaceName = config.name || "workspace";
1711
+ }
1712
+ catch { }
1713
+ }
1714
+ // Read event counts from map-events.jsonl
1715
+ const mapEventsPath = path.join(projectRoot, ".jfl", "map-events.jsonl");
1716
+ const eventCounts = {};
1717
+ const edgeEventCounts = {}; // "source:target:eventType" -> count
1718
+ const recentWindow = 24 * 60 * 60 * 1000; // 24 hours
1719
+ if (fs.existsSync(mapEventsPath)) {
1720
+ try {
1721
+ const lines = fs.readFileSync(mapEventsPath, "utf-8").trim().split("\n");
1722
+ const now = Date.now();
1723
+ for (const line of lines.slice(-1000)) { // Last 1000 events
1724
+ if (!line)
1725
+ continue;
1726
+ try {
1727
+ const evt = JSON.parse(line);
1728
+ const ts = new Date(evt.ts).getTime();
1729
+ if (now - ts > recentWindow)
1730
+ continue;
1731
+ // Count events by source
1732
+ if (evt.source) {
1733
+ const srcId = evt.source.toLowerCase().replace(/[^a-z0-9-]/g, "-");
1734
+ eventCounts[srcId] = (eventCounts[srcId] || 0) + 1;
1735
+ }
1736
+ // Count events by type prefix (for edge matching)
1737
+ if (evt.type) {
1738
+ const prefix = evt.type.split(":")[0];
1739
+ eventCounts[prefix] = (eventCounts[prefix] || 0) + 1;
1740
+ }
1741
+ }
1742
+ catch { }
1743
+ }
1744
+ }
1745
+ catch { }
1746
+ }
1747
+ // System agents (always present in a JFL installation)
1748
+ // Only Peter Parker is a real system agent — others come from .jfl/agents/*.toml configs
1749
+ // Stratus, eval-engine, telemetry-agent are either external infra or RL agents now
1750
+ const systemAgents = [
1751
+ { id: "peter-parker", label: "Peter Parker", type: "orchestrator", status: "running", produces: ["peter:task-completed", "peter:rollout-request", "peter:experiment-start"], consumes: ["telemetry:insight", "telemetry:metric-alert", "eval:scored", "sentinel:recommendation"] },
1752
+ ];
1753
+ const nodes = [
1754
+ ...systemAgents.map(a => ({
1755
+ id: a.id,
1756
+ label: a.label,
1757
+ type: a.type,
1758
+ status: a.status,
1759
+ eventCount: eventCounts[a.id] || eventCounts[a.id.split("-")[0]] || 0,
1760
+ produces: a.produces,
1761
+ consumes: a.consumes,
1762
+ })),
1763
+ ];
1764
+ // Add registered services as nodes
1765
+ for (const service of registeredServices) {
1766
+ if (!nodes.find(n => n.id === service.name)) {
1767
+ const nodeType = service.type === "agent" ? "agent"
1768
+ : service.type === "eval" ? "eval"
1769
+ : service.type === "gtm" ? "gtm"
1770
+ : "service";
1771
+ nodes.push({
1772
+ id: service.name,
1773
+ label: service.name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" "),
1774
+ type: nodeType,
1775
+ status: service.status === "running" ? "running" : service.status === "idle" ? "idle" : "stopped",
1776
+ eventCount: eventCounts[service.name] || 0,
1777
+ produces: service.context_scope?.produces,
1778
+ consumes: service.context_scope?.consumes,
1779
+ });
1780
+ }
1781
+ }
1782
+ // Add RL agent nodes from .jfl/agents/*.toml configs
1783
+ try {
1784
+ const rlAgentConfigs = loadAllAgentConfigs(projectRoot);
1785
+ const trainingBuffer = new TrainingBuffer(projectRoot);
1786
+ const trainingEntries = trainingBuffer.read();
1787
+ for (const config of rlAgentConfigs) {
1788
+ const nodeId = `rl-agent-${config.name}`;
1789
+ // Skip if node already exists (e.g., matches a registered service name)
1790
+ if (nodes.find(n => n.id === nodeId || n.id === config.name)) {
1791
+ continue;
1792
+ }
1793
+ // Check for recent training data (within last 24h)
1794
+ const now = Date.now();
1795
+ const recentWindow = 24 * 60 * 60 * 1000;
1796
+ const recentEntries = trainingEntries.filter(e => {
1797
+ if (e.agent !== config.name)
1798
+ return false;
1799
+ const ts = new Date(e.ts).getTime();
1800
+ return now - ts < recentWindow;
1801
+ });
1802
+ const status = recentEntries.length > 0 ? "running" : "idle";
1803
+ // Convert name to proper label (e.g., "cli-speed" -> "Cli Speed")
1804
+ const label = config.name
1805
+ .split("-")
1806
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
1807
+ .join(" ");
1808
+ nodes.push({
1809
+ id: nodeId,
1810
+ label,
1811
+ type: "agent",
1812
+ status,
1813
+ eventCount: recentEntries.length,
1814
+ produces: config.context_scope?.produces,
1815
+ consumes: config.context_scope?.consumes,
1816
+ });
1817
+ }
1818
+ }
1819
+ catch (err) {
1820
+ // Non-fatal: RL agents are optional
1821
+ }
1822
+ // For portfolio mode, fetch child GTM services and their registered services
1823
+ const childHubs = getChildHubs(projectRoot);
1824
+ const hierarchy = {
1825
+ gtms: [],
1826
+ };
1827
+ if (workspaceType === "portfolio" && childHubs.length > 0) {
1828
+ hierarchy.portfolio = workspaceName;
1829
+ for (const child of childHubs) {
1830
+ const gtmServices = [];
1831
+ // Try to read child's config for its registered services
1832
+ const childConfigPath = path.join(child.path, ".jfl", "config.json");
1833
+ if (fs.existsSync(childConfigPath)) {
1834
+ try {
1835
+ const childConfig = JSON.parse(fs.readFileSync(childConfigPath, "utf-8"));
1836
+ const childServices = childConfig.registered_services || [];
1837
+ for (const svc of childServices) {
1838
+ const svcId = `${child.name}/${svc.name}`;
1839
+ gtmServices.push(svc.name);
1840
+ // Add child services as nodes (if not already present)
1841
+ if (!nodes.find(n => n.id === svcId)) {
1842
+ nodes.push({
1843
+ id: svcId,
1844
+ label: `${svc.name}`,
1845
+ type: svc.type === "agent" ? "agent" : "service",
1846
+ status: "idle", // We don't know remote status
1847
+ eventCount: 0,
1848
+ produces: svc.context_scope?.produces,
1849
+ consumes: svc.context_scope?.consumes,
1850
+ parent: child.name,
1851
+ });
1852
+ }
1853
+ }
1854
+ }
1855
+ catch { }
1856
+ }
1857
+ hierarchy.gtms.push({
1858
+ name: child.name,
1859
+ port: child.port,
1860
+ services: gtmServices,
1861
+ });
1862
+ // Add GTM node if not present
1863
+ if (!nodes.find(n => n.id === child.name)) {
1864
+ nodes.push({
1865
+ id: child.name,
1866
+ label: child.name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" "),
1867
+ type: "gtm",
1868
+ status: "running",
1869
+ eventCount: 0,
1870
+ children: gtmServices,
1871
+ });
1872
+ }
1873
+ }
1874
+ }
1875
+ // Build edges from produces/consumes relationships
1876
+ const edges = [];
1877
+ let edgeId = 0;
1878
+ // Create edges between services based on scope patterns
1879
+ for (const producer of nodes) {
1880
+ if (!producer.produces)
1881
+ continue;
1882
+ for (const eventPattern of producer.produces) {
1883
+ for (const consumer of nodes) {
1884
+ if (producer.id === consumer.id || !consumer.consumes)
1885
+ continue;
1886
+ for (const consumePattern of consumer.consumes) {
1887
+ // Check if patterns match (simple glob matching)
1888
+ const patternMatches = (prod, cons) => {
1889
+ if (cons.endsWith(":*")) {
1890
+ return prod.startsWith(cons.slice(0, -1));
1891
+ }
1892
+ if (cons === "*")
1893
+ return true;
1894
+ return prod === cons || prod.startsWith(cons.split(":")[0] + ":");
1895
+ };
1896
+ if (patternMatches(eventPattern, consumePattern)) {
1897
+ const existing = edges.find(e => e.source === producer.id && e.target === consumer.id && e.eventType === eventPattern);
1898
+ if (!existing) {
1899
+ const edgeKey = `${producer.id}:${consumer.id}:${eventPattern}`;
1900
+ const prefix = eventPattern.split(":")[0];
1901
+ edges.push({
1902
+ id: `e${++edgeId}`,
1903
+ source: producer.id,
1904
+ target: consumer.id,
1905
+ eventType: eventPattern,
1906
+ category: eventPattern.includes("eval") || eventPattern.includes("scored") ? "success"
1907
+ : eventPattern.includes("peter") || eventPattern.includes("rollout") ? "rl"
1908
+ : "data",
1909
+ recentEvents: edgeEventCounts[edgeKey] || eventCounts[prefix] || 0,
1910
+ });
1911
+ }
1912
+ }
1913
+ }
1914
+ }
1915
+ }
1916
+ }
1917
+ // Add known system flow edges that might not be captured by scope
1918
+ const systemEdges = [
1919
+ { source: "telemetry-agent", target: "peter-parker", eventType: "telemetry:insight", category: "data" },
1920
+ { source: "peter-parker", target: "eval-engine", eventType: "peter:task-completed", category: "rl" },
1921
+ { source: "eval-engine", target: "telemetry-agent", eventType: "eval:scored", category: "success" },
1922
+ { source: "peter-parker", target: "stratus", eventType: "peter:rollout-request", category: "rl" },
1923
+ { source: "stratus", target: "eval-engine", eventType: "stratus:prediction", category: "success" },
1924
+ ];
1925
+ for (const sysEdge of systemEdges) {
1926
+ const exists = edges.find(e => e.source === sysEdge.source && e.target === sysEdge.target);
1927
+ if (!exists && nodes.find(n => n.id === sysEdge.source) && nodes.find(n => n.id === sysEdge.target)) {
1928
+ const prefix = sysEdge.eventType.split(":")[0];
1929
+ edges.push({
1930
+ id: `e${++edgeId}`,
1931
+ ...sysEdge,
1932
+ recentEvents: eventCounts[prefix] || 0,
1933
+ });
1934
+ }
1935
+ }
1936
+ res.writeHead(200, { "Content-Type": "application/json" });
1937
+ res.end(JSON.stringify({
1938
+ nodes,
1939
+ edges,
1940
+ hierarchy: workspaceType === "portfolio" ? hierarchy : undefined,
1941
+ workspaceType,
1942
+ workspaceName,
1943
+ }));
1944
+ }
1945
+ catch (err) {
1946
+ res.writeHead(500, { "Content-Type": "application/json" });
1947
+ res.end(JSON.stringify({ error: err.message }));
1948
+ }
1949
+ return;
1950
+ }
1951
+ // Autoresearch status — returns current autoresearch run state
1952
+ if (url.pathname === "/api/v1/autoresearch/status" && req.method === "GET") {
1953
+ try {
1954
+ const status = {
1955
+ running: false,
1956
+ currentRound: 0,
1957
+ totalRounds: 0,
1958
+ baselineComposite: null,
1959
+ proposals: [],
1960
+ dimensions: {},
1961
+ history: [],
1962
+ lastUpdate: null,
1963
+ };
1964
+ // Try to read from log files
1965
+ const logPaths = [
1966
+ path.join(projectRoot, ".jfl", "autoresearch-continuous.log"),
1967
+ path.join(projectRoot, ".jfl", "autoresearch-overnight.log"),
1968
+ ];
1969
+ let logContent = "";
1970
+ let logPath = "";
1971
+ for (const p of logPaths) {
1972
+ if (fs.existsSync(p)) {
1973
+ const stat = fs.statSync(p);
1974
+ // Use the most recently modified log
1975
+ if (!logPath || stat.mtimeMs > fs.statSync(logPath).mtimeMs) {
1976
+ logPath = p;
1977
+ }
1978
+ }
1979
+ }
1980
+ if (logPath) {
1981
+ logContent = fs.readFileSync(logPath, "utf-8");
1982
+ status.lastUpdate = fs.statSync(logPath).mtime.toISOString();
1983
+ // Parse total rounds from header
1984
+ const roundsMatch = logContent.match(/Autoresearch Mode \((\d+) rounds\)/);
1985
+ if (roundsMatch) {
1986
+ status.totalRounds = parseInt(roundsMatch[1], 10);
1987
+ }
1988
+ // Parse baseline
1989
+ const baselineMatch = logContent.match(/Baseline composite: ([\d.]+)/);
1990
+ if (baselineMatch) {
1991
+ status.baselineComposite = parseFloat(baselineMatch[1]);
1992
+ }
1993
+ // Parse latest round number
1994
+ const roundMatches = [...logContent.matchAll(/── Round (\d+)\/\d+ ──/g)];
1995
+ if (roundMatches.length > 0) {
1996
+ status.currentRound = parseInt(roundMatches[roundMatches.length - 1][1], 10);
1997
+ }
1998
+ // Parse policy head proposals (get the latest set)
1999
+ const proposalBlocks = [...logContent.matchAll(/Policy head re-ranked 3 proposals.*?\n([\s\S]*?)(?=\n\s*Task:|$)/g)];
2000
+ if (proposalBlocks.length > 0) {
2001
+ const latestBlock = proposalBlocks[proposalBlocks.length - 1][1];
2002
+ const proposalMatches = [...latestBlock.matchAll(/#(\d+) \[pred=([-\d.]+)\] ([^\n]+)/g)];
2003
+ status.proposals = proposalMatches.map(m => ({
2004
+ rank: parseInt(m[1], 10),
2005
+ predicted: parseFloat(m[2]),
2006
+ description: m[3].trim(),
2007
+ }));
2008
+ }
2009
+ // Parse dimensions from latest eval
2010
+ const dimMatches = [...logContent.matchAll(/Dimensions: (tests=[\d.]+ tsc=[\d.]+ lint=[\d.]+ telemetry=[\d.]+ newTests=[\d.]+)/g)];
2011
+ if (dimMatches.length > 0) {
2012
+ const latest = dimMatches[dimMatches.length - 1][1];
2013
+ for (const [, key, val] of latest.matchAll(/(tests|tsc|lint|telemetry|newTests)=([\d.]+)/g)) {
2014
+ status.dimensions[key] = parseFloat(val);
2015
+ }
2016
+ }
2017
+ // Parse round results for history
2018
+ const resultMatches = [...logContent.matchAll(/Round (\d+) result: ([\d.]+) \(([-=+][\d.]+)\)\s*\n\s*Tests: (\d+\/\d+)/g)];
2019
+ status.history = resultMatches.map(m => ({
2020
+ round: parseInt(m[1], 10),
2021
+ composite: parseFloat(m[2]),
2022
+ delta: m[3].startsWith("=") ? 0 : parseFloat(m[3]),
2023
+ tests: m[4],
2024
+ }));
2025
+ // Check if running (log modified in last 5 minutes and no completion message)
2026
+ const fiveMinAgo = Date.now() - 5 * 60 * 1000;
2027
+ const logMtime = fs.statSync(logPath).mtimeMs;
2028
+ const hasCompletionMsg = logContent.includes("All rounds complete") || logContent.includes("Autoresearch finished");
2029
+ status.running = logMtime > fiveMinAgo && !hasCompletionMsg && status.currentRound > 0;
2030
+ }
2031
+ res.writeHead(200, { "Content-Type": "application/json" });
2032
+ res.end(JSON.stringify(status));
2033
+ }
2034
+ catch (err) {
2035
+ res.writeHead(500, { "Content-Type": "application/json" });
2036
+ res.end(JSON.stringify({ error: err.message }));
2037
+ }
2038
+ return;
2039
+ }
2040
+ if (url.pathname === "/api/actions/spawn" && req.method === "POST") {
2041
+ let body = "";
2042
+ req.on("data", chunk => body += chunk);
2043
+ req.on("end", () => {
2044
+ try {
2045
+ const { command, args, cwd, event_type, event_data } = JSON.parse(body || "{}");
2046
+ if (!command) {
2047
+ res.writeHead(400, { "Content-Type": "application/json" });
2048
+ res.end(JSON.stringify({ error: "command required" }));
2049
+ return;
2050
+ }
2051
+ const env = { ...process.env };
2052
+ delete env.ANTHROPIC_API_KEY;
2053
+ delete env.CLAUDE_CODE_ENTRYPOINT;
2054
+ const child = spawn(command, args || [], {
2055
+ cwd: cwd || projectRoot,
2056
+ detached: true,
2057
+ stdio: "ignore",
2058
+ env,
2059
+ });
2060
+ child.unref();
2061
+ if (event_type && eventBus) {
2062
+ eventBus.emit({
2063
+ type: event_type,
2064
+ source: "dashboard:action",
2065
+ data: event_data || { command, args, pid: child.pid },
2066
+ });
2067
+ }
2068
+ res.writeHead(200, { "Content-Type": "application/json" });
2069
+ res.end(JSON.stringify({ ok: true, pid: child.pid }));
2070
+ }
2071
+ catch (err) {
2072
+ res.writeHead(500, { "Content-Type": "application/json" });
2073
+ res.end(JSON.stringify({ error: err.message }));
2074
+ }
2075
+ });
2076
+ return;
2077
+ }
2078
+ // ── Session Parity API ────────────────────────────────────────────
2079
+ // These endpoints provide runtime-agnostic session lifecycle.
2080
+ // Both Claude Code hooks and Pi extensions call these instead of
2081
+ // duplicating logic. Single source of truth for session init/end.
2082
+ // POST /api/session/init — sync repos, create branch, run doctor
2083
+ //
2084
+ // CONCURRENCY: Multiple Pi sessions can call this simultaneously.
2085
+ // We use a file lock + serialization to prevent branch race conditions.
2086
+ // The Hub creates the branch with `git branch` (no checkout) and returns
2087
+ // the name. The Pi extension does the actual checkout in its own context.
2088
+ if (url.pathname === "/api/session/init" && req.method === "POST") {
2089
+ let body = "";
2090
+ req.on("data", (chunk) => body += chunk);
2091
+ req.on("end", async () => {
2092
+ // Serialize session init calls to prevent git race conditions
2093
+ const lockFile = path.join(projectRoot, ".jfl", ".session-init.lock");
2094
+ const maxWait = 15000;
2095
+ const waitStart = Date.now();
2096
+ // Wait for any concurrent session init to finish
2097
+ while (fs.existsSync(lockFile) && Date.now() - waitStart < maxWait) {
2098
+ try {
2099
+ // Check if lock is stale (older than 30s)
2100
+ const lockStat = fs.statSync(lockFile);
2101
+ if (Date.now() - lockStat.mtimeMs > 30000) {
2102
+ fs.unlinkSync(lockFile);
2103
+ break;
2104
+ }
2105
+ }
2106
+ catch { }
2107
+ await new Promise(resolve => setTimeout(resolve, 200));
2108
+ }
2109
+ // Acquire lock
2110
+ try {
2111
+ fs.mkdirSync(path.join(projectRoot, ".jfl"), { recursive: true });
2112
+ fs.writeFileSync(lockFile, `${process.pid}:${Date.now()}`);
2113
+ }
2114
+ catch { }
2115
+ try {
2116
+ const { runtime } = JSON.parse(body || "{}");
2117
+ const warnings = [];
2118
+ const scriptDir = path.join(projectRoot, "scripts", "session");
2119
+ // Step 1: Sync repos
2120
+ const syncScript = path.join(scriptDir, "session-sync.sh");
2121
+ let syncOk = true;
2122
+ if (fs.existsSync(syncScript)) {
2123
+ try {
2124
+ execSync(`bash "${syncScript}"`, { cwd: projectRoot, timeout: 60000, stdio: ["pipe", "pipe", "pipe"] });
2125
+ }
2126
+ catch (err) {
2127
+ syncOk = false;
2128
+ warnings.push(`Sync warning: ${(err.message || "").split("\n")[0]}`);
2129
+ }
2130
+ }
2131
+ // Step 2: Doctor check
2132
+ const doctorScript = path.join(scriptDir, "jfl-doctor.sh");
2133
+ let doctorErrors = 0;
2134
+ let doctorWarnings = 0;
2135
+ if (fs.existsSync(doctorScript)) {
2136
+ try {
2137
+ const doctorOut = execSync(`bash "${doctorScript}"`, { cwd: projectRoot, timeout: 30000, stdio: ["pipe", "pipe", "pipe"] }).toString();
2138
+ const em = doctorOut.match(/(\d+) error\(s\)/);
2139
+ const wm = doctorOut.match(/(\d+) warning\(s\)/);
2140
+ doctorErrors = em ? parseInt(em[1]) : 0;
2141
+ doctorWarnings = wm ? parseInt(wm[1]) : 0;
2142
+ if (doctorErrors > 0)
2143
+ warnings.push(`Doctor: ${doctorErrors} errors`);
2144
+ }
2145
+ catch { }
2146
+ }
2147
+ // Step 3: Create session branch (always create a fresh one)
2148
+ let currentBranch = "";
2149
+ try {
2150
+ currentBranch = execSync("git branch --show-current", { cwd: projectRoot, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
2151
+ }
2152
+ catch { }
2153
+ // Generate session branch name first (needed regardless of path)
2154
+ const user = (() => {
2155
+ try {
2156
+ return execSync("git config user.name", { cwd: projectRoot, stdio: ["pipe", "pipe", "pipe"] })
2157
+ .toString().trim().replace(/\s+/g, "-").toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0, 30) || "user";
2158
+ }
2159
+ catch {
2160
+ return "user";
2161
+ }
2162
+ })();
2163
+ const now = new Date();
2164
+ const dateStr = now.toISOString().slice(0, 10).replace(/-/g, "");
2165
+ const timeStr = now.toISOString().slice(11, 16).replace(":", "");
2166
+ const randomId = Math.random().toString(16).slice(2, 8);
2167
+ let sessionBranch = `session-${user}-${dateStr}-${timeStr}-${randomId}`;
2168
+ // Determine working branch from config or default to main
2169
+ let workingBranch = "main";
2170
+ const configPath = path.join(projectRoot, ".jfl", "config.json");
2171
+ if (fs.existsSync(configPath)) {
2172
+ try {
2173
+ const cfg = JSON.parse(fs.readFileSync(configPath, "utf-8"));
2174
+ if (cfg.working_branch)
2175
+ workingBranch = cfg.working_branch;
2176
+ }
2177
+ catch { }
2178
+ }
2179
+ // Save any dirty state before branch operations — git add -A catches
2180
+ // both modified and untracked files. Without this, checkout fails on
2181
+ // dirty working trees (the #1 cause of "Could not create session branch").
2182
+ try {
2183
+ const status = execSync("git status --porcelain", { cwd: projectRoot, timeout: 5000, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
2184
+ if (status) {
2185
+ execSync("git add -A && git commit -m 'auto: session-init save before branch switch' --no-verify", { cwd: projectRoot, timeout: 10000, stdio: ["pipe", "pipe", "pipe"] });
2186
+ }
2187
+ }
2188
+ catch (commitErr) {
2189
+ // If commit fails (e.g. nothing to commit after add), try stash as fallback
2190
+ try {
2191
+ execSync("git stash push -m 'jfl-session-init-stash' --include-untracked", { cwd: projectRoot, timeout: 10000, stdio: ["pipe", "pipe", "pipe"] });
2192
+ warnings.push("Stashed dirty state (commit failed)");
2193
+ }
2194
+ catch {
2195
+ // Last resort: force checkout will carry dirty files to new branch
2196
+ }
2197
+ }
2198
+ // If on a stale session branch, merge it first (prevent data loss)
2199
+ if (currentBranch.startsWith("session-")) {
2200
+ try {
2201
+ // Check for unique commits
2202
+ const uniqueCommits = execSync(`git log --oneline "${workingBranch}".."${currentBranch}" 2>/dev/null | wc -l`, { cwd: projectRoot, encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] }).trim();
2203
+ if (parseInt(uniqueCommits) > 0) {
2204
+ // Merge stale branch to working branch
2205
+ execSync(`git checkout "${workingBranch}"`, { cwd: projectRoot, timeout: 10000, stdio: ["pipe", "pipe", "pipe"] });
2206
+ try {
2207
+ execSync(`git merge "${currentBranch}" --no-edit -X ours`, { cwd: projectRoot, timeout: 15000, stdio: ["pipe", "pipe", "pipe"] });
2208
+ try {
2209
+ execSync(`git branch -d "${currentBranch}"`, { cwd: projectRoot, timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
2210
+ }
2211
+ catch { }
2212
+ warnings.push(`Merged stale session ${currentBranch} (${uniqueCommits} commits) → ${workingBranch}`);
2213
+ }
2214
+ catch {
2215
+ try {
2216
+ execSync("git merge --abort", { cwd: projectRoot, timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
2217
+ }
2218
+ catch { }
2219
+ warnings.push(`Could not merge stale branch ${currentBranch} — preserved for manual merge`);
2220
+ }
2221
+ }
2222
+ else {
2223
+ execSync(`git checkout "${workingBranch}"`, { cwd: projectRoot, timeout: 10000, stdio: ["pipe", "pipe", "pipe"] });
2224
+ }
2225
+ }
2226
+ catch {
2227
+ warnings.push(`Could not switch from stale session branch ${currentBranch}`);
2228
+ }
2229
+ }
2230
+ // Ensure we're on working branch before creating session branch
2231
+ try {
2232
+ const nowBranch = execSync("git branch --show-current", { cwd: projectRoot, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
2233
+ if (nowBranch !== workingBranch) {
2234
+ execSync(`git checkout "${workingBranch}"`, { cwd: projectRoot, timeout: 10000, stdio: ["pipe", "pipe", "pipe"] });
2235
+ }
2236
+ }
2237
+ catch { }
2238
+ // Create the new session branch
2239
+ try {
2240
+ execSync(`git checkout -b "${sessionBranch}"`, { cwd: projectRoot, stdio: ["pipe", "pipe", "pipe"] });
2241
+ }
2242
+ catch {
2243
+ // If branch creation fails (name collision?), try with longer random
2244
+ const fallbackId = Math.random().toString(16).slice(2, 14);
2245
+ sessionBranch = `session-${user}-${dateStr}-${timeStr}-${fallbackId}`;
2246
+ try {
2247
+ execSync(`git checkout -b "${sessionBranch}"`, { cwd: projectRoot, stdio: ["pipe", "pipe", "pipe"] });
2248
+ }
2249
+ catch {
2250
+ sessionBranch = currentBranch || "main";
2251
+ warnings.push("Could not create session branch");
2252
+ }
2253
+ }
2254
+ // Pop stash if we stashed earlier
2255
+ try {
2256
+ const stashList = execSync("git stash list", { cwd: projectRoot, timeout: 5000, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
2257
+ if (stashList.includes("jfl-session-init-stash")) {
2258
+ execSync("git stash pop", { cwd: projectRoot, timeout: 10000, stdio: ["pipe", "pipe", "pipe"] });
2259
+ }
2260
+ }
2261
+ catch { }
2262
+ // Step 4: Save session info
2263
+ const jflDir = path.join(projectRoot, ".jfl");
2264
+ fs.mkdirSync(path.join(jflDir, "logs"), { recursive: true });
2265
+ fs.mkdirSync(path.join(jflDir, "journal"), { recursive: true });
2266
+ fs.writeFileSync(path.join(jflDir, "current-session-branch.txt"), sessionBranch);
2267
+ fs.writeFileSync(path.join(jflDir, "current-worktree.txt"), "direct");
2268
+ // Emit session start event
2269
+ if (eventBus) {
2270
+ eventBus.emit({
2271
+ type: "session:started",
2272
+ source: `hub:${runtime || "unknown"}`,
2273
+ data: { branch: sessionBranch, runtime, warnings },
2274
+ });
2275
+ }
2276
+ res.writeHead(200, { "Content-Type": "application/json" });
2277
+ res.end(JSON.stringify({
2278
+ ok: true,
2279
+ branch: sessionBranch,
2280
+ syncOk,
2281
+ doctor: { errors: doctorErrors, warnings: doctorWarnings },
2282
+ warnings,
2283
+ }));
2284
+ }
2285
+ catch (err) {
2286
+ res.writeHead(500, { "Content-Type": "application/json" });
2287
+ res.end(JSON.stringify({ error: err.message }));
2288
+ }
2289
+ finally {
2290
+ // Release session init lock
2291
+ try {
2292
+ fs.unlinkSync(lockFile);
2293
+ }
2294
+ catch { }
2295
+ }
2296
+ });
2297
+ return;
2298
+ }
2299
+ // POST /api/prompt — get system prompt injection (CLAUDE.md + context)
2300
+ if (url.pathname === "/api/prompt" && req.method === "POST") {
2301
+ let body = "";
2302
+ req.on("data", (chunk) => body += chunk);
2303
+ req.on("end", async () => {
2304
+ try {
2305
+ const { taskType, maxItems } = JSON.parse(body || "{}");
2306
+ const parts = [];
2307
+ // 1. Load CLAUDE.md
2308
+ const claudeMdPath = path.join(projectRoot, "CLAUDE.md");
2309
+ if (fs.existsSync(claudeMdPath)) {
2310
+ const content = fs.readFileSync(claudeMdPath, "utf-8");
2311
+ if (content.length > 50000) {
2312
+ const sections = content.split(/^## /m);
2313
+ const critical = sections.filter((s) => /CRITICAL|Session Sync|Journal Protocol|Immediate Decision|Working Mode|Core Architecture/i.test(s.slice(0, 100)));
2314
+ if (critical.length > 0) {
2315
+ parts.push("# Project Instructions (CLAUDE.md — critical sections)\n");
2316
+ parts.push("## " + critical.join("\n\n## "));
2317
+ }
2318
+ else {
2319
+ parts.push("# Project Instructions (CLAUDE.md — truncated)\n");
2320
+ parts.push(content.slice(0, 30000));
2321
+ }
2322
+ }
2323
+ else {
2324
+ parts.push("# Project Instructions (CLAUDE.md)\n");
2325
+ parts.push(content);
2326
+ }
2327
+ }
2328
+ // 2. Load recent context
2329
+ try {
2330
+ const contextItems = readJournalEntries(projectRoot, maxItems ?? 20);
2331
+ if (contextItems.length > 0) {
2332
+ parts.push("\n## Recent Project Context\n");
2333
+ parts.push(contextItems.map(item => {
2334
+ const prefix = item.source ? `[${item.source}] ` : "";
2335
+ return `${prefix}${item.content}`;
2336
+ }).join("\n\n"));
2337
+ }
2338
+ }
2339
+ catch { }
2340
+ // 3. Load knowledge docs summaries
2341
+ const knowledgeDir = path.join(projectRoot, "knowledge");
2342
+ if (fs.existsSync(knowledgeDir)) {
2343
+ const knowledgeDocs = ["VISION.md", "ROADMAP.md", "NARRATIVE.md", "THESIS.md"];
2344
+ const summaries = [];
2345
+ for (const doc of knowledgeDocs) {
2346
+ const docPath = path.join(knowledgeDir, doc);
2347
+ if (fs.existsSync(docPath)) {
2348
+ const content = fs.readFileSync(docPath, "utf-8");
2349
+ if (content.length > 100) {
2350
+ summaries.push(`### ${doc}\n${content.slice(0, 500)}${content.length > 500 ? "\n..." : ""}`);
2351
+ }
2352
+ }
2353
+ }
2354
+ if (summaries.length > 0) {
2355
+ parts.push("\n## Knowledge Documents\n");
2356
+ parts.push(summaries.join("\n\n"));
2357
+ }
2358
+ }
2359
+ res.writeHead(200, { "Content-Type": "application/json" });
2360
+ res.end(JSON.stringify({
2361
+ prompt: parts.join("\n"),
2362
+ claudeMdSize: fs.existsSync(path.join(projectRoot, "CLAUDE.md"))
2363
+ ? fs.statSync(path.join(projectRoot, "CLAUDE.md")).size : 0,
2364
+ taskType: taskType ?? "general",
2365
+ }));
2366
+ }
2367
+ catch (err) {
2368
+ res.writeHead(500, { "Content-Type": "application/json" });
2369
+ res.end(JSON.stringify({ error: err.message }));
2370
+ }
2371
+ });
2372
+ return;
2373
+ }
2374
+ // POST /api/session/pivot — checkpoint work without ending session
2375
+ if (url.pathname === "/api/session/pivot" && req.method === "POST") {
2376
+ let body = "";
2377
+ req.on("data", (chunk) => body += chunk);
2378
+ req.on("end", async () => {
2379
+ try {
2380
+ const { summary, push } = JSON.parse(body || "{}");
2381
+ const { pivotCommand } = await import("./pivot.js");
2382
+ const result = await pivotCommand({ summary, push, json: false });
2383
+ if (eventBus) {
2384
+ eventBus.emit({
2385
+ type: "session:pivoted",
2386
+ source: "hub:pivot",
2387
+ data: { branch: result.branch, pivotNumber: result.pivotNumber, summary: summary || "", filesChanged: result.filesChanged },
2388
+ });
2389
+ }
2390
+ res.writeHead(200, { "Content-Type": "application/json" });
2391
+ res.end(JSON.stringify(result));
2392
+ }
2393
+ catch (err) {
2394
+ res.writeHead(500, { "Content-Type": "application/json" });
2395
+ res.end(JSON.stringify({ error: err.message }));
2396
+ }
2397
+ });
2398
+ return;
2399
+ }
2400
+ // POST /api/session/end — cleanup session (merge, commit, etc)
2401
+ if (url.pathname === "/api/session/end" && req.method === "POST") {
2402
+ let body = "";
2403
+ req.on("data", (chunk) => body += chunk);
2404
+ req.on("end", async () => {
2405
+ try {
2406
+ const { runtime, skipCleanup } = JSON.parse(body || "{}");
2407
+ // Check journal exists
2408
+ let hasJournal = false;
2409
+ try {
2410
+ const branch = execSync("git branch --show-current", { cwd: projectRoot, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
2411
+ const journalPath = path.join(projectRoot, ".jfl", "journal", `${branch}.jsonl`);
2412
+ hasJournal = fs.existsSync(journalPath) && fs.statSync(journalPath).size > 0;
2413
+ }
2414
+ catch { }
2415
+ let cleanupResult = "skipped";
2416
+ if (!skipCleanup) {
2417
+ const cleanupScript = path.join(projectRoot, "scripts", "session", "session-cleanup.sh");
2418
+ if (fs.existsSync(cleanupScript)) {
2419
+ try {
2420
+ execSync(`bash "${cleanupScript}"`, { cwd: projectRoot, timeout: 60000, stdio: ["pipe", "pipe", "pipe"] });
2421
+ cleanupResult = "ok";
2422
+ }
2423
+ catch (err) {
2424
+ cleanupResult = `error: ${(err.message || "").split("\n")[0]}`;
2425
+ }
2426
+ }
2427
+ else {
2428
+ cleanupResult = "no cleanup script";
2429
+ }
2430
+ }
2431
+ // Emit session end event
2432
+ if (eventBus) {
2433
+ eventBus.emit({
2434
+ type: "session:ended",
2435
+ source: `hub:${runtime || "unknown"}`,
2436
+ data: { hasJournal, cleanupResult, runtime },
2437
+ });
2438
+ }
2439
+ res.writeHead(200, { "Content-Type": "application/json" });
2440
+ res.end(JSON.stringify({
2441
+ ok: true,
2442
+ hasJournal,
2443
+ cleanupResult,
2444
+ warnings: hasJournal ? [] : ["No journal entry for this session"],
2445
+ }));
2446
+ }
2447
+ catch (err) {
2448
+ res.writeHead(500, { "Content-Type": "application/json" });
2449
+ res.end(JSON.stringify({ error: err.message }));
2450
+ }
2451
+ });
2452
+ return;
2453
+ }
2454
+ // ── Findings API ──────────────────────────────────────────────────
2455
+ // GET /api/v1/findings — list current findings
2456
+ if (url.pathname === "/api/v1/findings" && req.method === "GET") {
2457
+ try {
2458
+ const engine = new FindingsEngine(projectRoot);
2459
+ const refresh = url.searchParams.get("refresh") === "true";
2460
+ let findings;
2461
+ if (refresh) {
2462
+ findings = await engine.analyze();
2463
+ }
2464
+ else {
2465
+ findings = engine.getFindings();
2466
+ // Auto-analyze if no findings exist
2467
+ if (findings.length === 0) {
2468
+ findings = await engine.analyze();
2469
+ }
2470
+ }
2471
+ // Filter out dismissed unless ?include_dismissed=true
2472
+ const includeDismissed = url.searchParams.get("include_dismissed") === "true";
2473
+ if (!includeDismissed) {
2474
+ findings = findings.filter(f => !f.dismissed);
2475
+ }
2476
+ res.writeHead(200, { "Content-Type": "application/json" });
2477
+ res.end(JSON.stringify({ findings, total: findings.length }));
2478
+ }
2479
+ catch (err) {
2480
+ res.writeHead(500, { "Content-Type": "application/json" });
2481
+ res.end(JSON.stringify({ error: err.message }));
2482
+ }
2483
+ return;
2484
+ }
2485
+ // POST /api/v1/findings/:id/dismiss — dismiss a finding
2486
+ if (url.pathname.match(/^\/api\/v1\/findings\/[^/]+\/dismiss$/) && req.method === "POST") {
2487
+ try {
2488
+ const findingId = decodeURIComponent(url.pathname.split("/")[4]);
2489
+ const engine = new FindingsEngine(projectRoot);
2490
+ const success = engine.dismissFinding(findingId);
2491
+ if (success) {
2492
+ res.writeHead(200, { "Content-Type": "application/json" });
2493
+ res.end(JSON.stringify({ ok: true, dismissed: findingId }));
2494
+ }
2495
+ else {
2496
+ res.writeHead(404, { "Content-Type": "application/json" });
2497
+ res.end(JSON.stringify({ error: "Finding not found" }));
2498
+ }
2499
+ }
2500
+ catch (err) {
2501
+ res.writeHead(500, { "Content-Type": "application/json" });
2502
+ res.end(JSON.stringify({ error: err.message }));
2503
+ }
2504
+ return;
2505
+ }
2506
+ // POST /api/v1/findings/:id/spawn — spawn an agent from a finding
2507
+ if (url.pathname.match(/^\/api\/v1\/findings\/[^/]+\/spawn$/) && req.method === "POST") {
2508
+ try {
2509
+ const findingId = decodeURIComponent(url.pathname.split("/")[4]);
2510
+ const engine = new FindingsEngine(projectRoot);
2511
+ const findings = engine.getFindings();
2512
+ const finding = findings.find(f => f.id === findingId);
2513
+ if (!finding) {
2514
+ res.writeHead(404, { "Content-Type": "application/json" });
2515
+ res.end(JSON.stringify({ error: "Finding not found" }));
2516
+ return;
2517
+ }
2518
+ if (!finding.agent_config) {
2519
+ res.writeHead(400, { "Content-Type": "application/json" });
2520
+ res.end(JSON.stringify({ error: "Finding has no agent config" }));
2521
+ return;
2522
+ }
2523
+ // Spawn peter-parker to fix the issue
2524
+ const env = { ...process.env };
2525
+ delete env.ANTHROPIC_API_KEY;
2526
+ delete env.CLAUDE_CODE_ENTRYPOINT;
2527
+ const agentConfig = finding.agent_config;
2528
+ const prompt = `Fix this issue: ${finding.title}\n\n${finding.description}\n\nTarget metric: ${agentConfig.metric} >= ${agentConfig.target}\nScope files: ${agentConfig.scope_files.join(", ")}`;
2529
+ const child = spawn("jfl", ["peter", "run", "--prompt", prompt], {
2530
+ cwd: projectRoot,
2531
+ detached: true,
2532
+ stdio: "ignore",
2533
+ env,
2534
+ });
2535
+ child.unref();
2536
+ // Emit event for tracking
2537
+ if (eventBus) {
2538
+ eventBus.emit({
2539
+ type: "findings:agent-spawned",
2540
+ source: "findings-engine",
2541
+ data: {
2542
+ finding_id: findingId,
2543
+ finding_type: finding.type,
2544
+ finding_title: finding.title,
2545
+ pid: child.pid,
2546
+ },
2547
+ });
2548
+ }
2549
+ res.writeHead(200, { "Content-Type": "application/json" });
2550
+ res.end(JSON.stringify({
2551
+ ok: true,
2552
+ pid: child.pid,
2553
+ finding_id: findingId,
2554
+ agent_config: agentConfig,
2555
+ }));
2556
+ }
2557
+ catch (err) {
2558
+ res.writeHead(500, { "Content-Type": "application/json" });
2559
+ res.end(JSON.stringify({ error: err.message }));
2560
+ }
2561
+ return;
2562
+ }
2563
+ // POST /api/v1/findings/analyze — force re-analyze
2564
+ if (url.pathname === "/api/v1/findings/analyze" && req.method === "POST") {
2565
+ try {
2566
+ const engine = new FindingsEngine(projectRoot);
2567
+ const findings = await engine.analyze();
2568
+ res.writeHead(200, { "Content-Type": "application/json" });
2569
+ res.end(JSON.stringify({ findings, total: findings.length }));
2570
+ }
2571
+ catch (err) {
2572
+ res.writeHead(500, { "Content-Type": "application/json" });
2573
+ res.end(JSON.stringify({ error: err.message }));
2574
+ }
2575
+ return;
2576
+ }
2577
+ // POST /api/webhooks/linear — handle Linear webhook events
2578
+ if (url.pathname === "/api/webhooks/linear" && req.method === "POST") {
2579
+ let body = "";
2580
+ req.on("data", (chunk) => { body += chunk; });
2581
+ req.on("end", async () => {
2582
+ try {
2583
+ const payload = JSON.parse(body || "{}");
2584
+ const { handleLinearWebhook } = await import("../lib/linear-webhook.js");
2585
+ const result = handleLinearWebhook(payload, projectRoot);
2586
+ if (result.handled && eventBus) {
2587
+ eventBus.emit({
2588
+ type: "linear:document-sync",
2589
+ source: "linear-webhook",
2590
+ data: {
2591
+ action: payload.action,
2592
+ documentTitle: payload.data?.title,
2593
+ journalFile: result.journalFile,
2594
+ },
2595
+ });
2596
+ }
2597
+ res.writeHead(200, { "Content-Type": "application/json" });
2598
+ res.end(JSON.stringify(result));
2599
+ }
2600
+ catch (err) {
2601
+ res.writeHead(400, { "Content-Type": "application/json" });
2602
+ res.end(JSON.stringify({ error: err.message }));
2603
+ }
2604
+ });
2605
+ return;
2606
+ }
2607
+ // 404
2608
+ res.writeHead(404, { "Content-Type": "application/json" });
2609
+ res.end(JSON.stringify({ error: "Not found" }));
2610
+ });
2611
+ // WebSocket upgrade for event streaming
2612
+ if (eventBus) {
2613
+ const wss = new WebSocketServer({ noServer: true });
2614
+ server.on("upgrade", (request, socket, head) => {
2615
+ const reqUrl = new URL(request.url || "/", `http://localhost:${port}`);
2616
+ if (reqUrl.pathname !== "/ws/events") {
2617
+ socket.destroy();
2618
+ return;
2619
+ }
2620
+ if (!validateAuth(request, projectRoot, reqUrl)) {
2621
+ socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
2622
+ socket.destroy();
2623
+ return;
2624
+ }
2625
+ wss.handleUpgrade(request, socket, head, (ws) => {
2626
+ const patterns = (reqUrl.searchParams.get("patterns") || "*").split(",");
2627
+ const sub = eventBus.subscribe({
2628
+ clientId: `ws-${Date.now()}`,
2629
+ patterns,
2630
+ transport: "websocket",
2631
+ callback: (event) => {
2632
+ if (ws.readyState === ws.OPEN) {
2633
+ ws.send(JSON.stringify(event));
2634
+ }
2635
+ },
2636
+ });
2637
+ ws.on("close", () => {
2638
+ eventBus.unsubscribe(sub.id);
2639
+ });
2640
+ ws.on("error", () => {
2641
+ eventBus.unsubscribe(sub.id);
2642
+ });
2643
+ });
2644
+ });
2645
+ }
2646
+ return server;
2647
+ }
2648
+ // ============================================================================
2649
+ // Daemon Management
2650
+ // ============================================================================
2651
+ function getPidFile(projectRoot) {
2652
+ return path.join(projectRoot, PID_FILE);
2653
+ }
2654
+ function getLogFile(projectRoot) {
2655
+ const logFile = path.join(projectRoot, LOG_FILE);
2656
+ const logDir = path.dirname(logFile);
2657
+ if (!fs.existsSync(logDir)) {
2658
+ fs.mkdirSync(logDir, { recursive: true });
2659
+ }
2660
+ return logFile;
2661
+ }
2662
+ export function isRunning(projectRoot) {
2663
+ const pidFile = getPidFile(projectRoot);
2664
+ if (!fs.existsSync(pidFile)) {
2665
+ return { running: false };
2666
+ }
2667
+ const pid = parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
2668
+ try {
2669
+ process.kill(pid, 0); // Check if process exists
2670
+ return { running: true, pid };
2671
+ }
2672
+ catch {
2673
+ // Process doesn't exist, clean up stale PID file
2674
+ fs.unlinkSync(pidFile);
2675
+ return { running: false };
2676
+ }
2677
+ }
2678
+ // ============================================================================
2679
+ // Cross-Project Helpers
2680
+ // ============================================================================
2681
+ function getTrackedProjects() {
2682
+ // Read from both config sources (Conf library + XDG config)
2683
+ const confStore = new Conf({ projectName: "jfl" });
2684
+ const confProjects = confStore.get("projects") || [];
2685
+ const xdgProjects = getConfigValue("projects") || [];
2686
+ // Deduplicate
2687
+ const allPaths = [...new Set([...confProjects, ...xdgProjects])];
2688
+ return allPaths
2689
+ .filter(p => fs.existsSync(path.join(p, ".jfl")))
2690
+ .map(p => ({ path: p, port: getProjectPort(p) }));
2691
+ }
2692
+ async function ensureForProject(projectRoot, port, quiet = false) {
2693
+ // Rule: ensure ONLY starts hubs, NEVER kills them.
2694
+ // If something is on the port, leave it alone.
2695
+ const status = isRunning(projectRoot);
2696
+ if (status.running) {
2697
+ return { status: "running", message: `Already running (PID: ${status.pid})` };
2698
+ }
2699
+ const portInUse = await isPortInUse(port);
2700
+ if (portInUse) {
2701
+ // Something is on this port. Don't kill it — could be a healthy hub
2702
+ // whose PID file was lost, or a hub started by another process.
2703
+ return { status: "running", message: `Port ${port} in use (assuming healthy)` };
2704
+ }
2705
+ // Nothing running, nothing on port — safe to start
2706
+ const result = await startDaemon(projectRoot, port);
2707
+ if (result.success) {
2708
+ return { status: "started", message: result.message };
2709
+ }
2710
+ return { status: "failed", message: result.message };
2711
+ }
2712
+ async function diagnoseProject(projectPath, port) {
2713
+ if (!fs.existsSync(projectPath)) {
2714
+ return { path: projectPath, port, status: "STALE", message: "Directory does not exist" };
2715
+ }
2716
+ const pidStatus = isRunning(projectPath);
2717
+ if (!pidStatus.running) {
2718
+ return { path: projectPath, port, status: "DOWN", pid: undefined };
2719
+ }
2720
+ try {
2721
+ const response = await fetch(`http://localhost:${port}/health`, {
2722
+ signal: AbortSignal.timeout(2000)
2723
+ });
2724
+ if (response.ok) {
2725
+ return { path: projectPath, port, status: "OK", pid: pidStatus.pid };
2726
+ }
2727
+ }
2728
+ catch {
2729
+ // Not responding
2730
+ }
2731
+ return { path: projectPath, port, status: "ZOMBIE", pid: pidStatus.pid, message: "PID exists but not responding" };
2732
+ }
2733
+ async function startDaemon(projectRoot, port) {
2734
+ const status = isRunning(projectRoot);
2735
+ if (status.running) {
2736
+ return { success: true, message: `Context Hub already running (PID: ${status.pid})` };
2737
+ }
2738
+ // Check if port is in use by another process
2739
+ const portInUse = await isPortInUse(port);
2740
+ if (portInUse) {
2741
+ return { success: false, message: `Port ${port} is already in use by another process` };
2742
+ }
2743
+ const logFile = getLogFile(projectRoot);
2744
+ const pidFile = getPidFile(projectRoot);
2745
+ // Generate auth token before starting
2746
+ const token = getOrCreateToken(projectRoot);
2747
+ // Find jfl command (prefer global install)
2748
+ let jflCmd = "jfl";
2749
+ try {
2750
+ // Try to find jfl in PATH
2751
+ execSync("which jfl", { encoding: "utf-8" }).trim();
2752
+ }
2753
+ catch {
2754
+ // Fall back to current process
2755
+ jflCmd = process.argv[1];
2756
+ }
2757
+ // Start as detached process with CONTEXT_HUB_DAEMON=1 so the serve
2758
+ // action knows to ignore SIGTERM during its startup grace period
2759
+ const child = spawn(jflCmd, ["context-hub", "serve", "--port", String(port), "--project-root", projectRoot], {
2760
+ cwd: projectRoot,
2761
+ detached: true,
2762
+ stdio: ["ignore", fs.openSync(logFile, "a"), fs.openSync(logFile, "a")],
2763
+ env: { ...process.env, NODE_ENV: "production", CONTEXT_HUB_DAEMON: "1" }
2764
+ });
2765
+ child.unref();
2766
+ // Wait a moment to ensure process started
2767
+ await new Promise(resolve => setTimeout(resolve, 500));
2768
+ // Write PID file EARLY to avoid race conditions
2769
+ if (child.pid) {
2770
+ // Write PID file immediately
2771
+ fs.writeFileSync(pidFile, String(child.pid));
2772
+ // Then verify process is still running
2773
+ try {
2774
+ process.kill(child.pid, 0);
2775
+ // Give it a bit more time to be ready
2776
+ await new Promise(resolve => setTimeout(resolve, 300));
2777
+ return { success: true, message: `Started (PID: ${child.pid}). Token: ${token.slice(0, 8)}...` };
2778
+ }
2779
+ catch {
2780
+ // Process died, clean up PID file
2781
+ fs.unlinkSync(pidFile);
2782
+ return { success: false, message: "Process started but immediately exited" };
2783
+ }
2784
+ }
2785
+ return { success: false, message: "Failed to spawn daemon process" };
2786
+ }
2787
+ async function stopDaemon(projectRoot) {
2788
+ const status = isRunning(projectRoot);
2789
+ if (!status.running || !status.pid) {
2790
+ return { success: true, message: "Context Hub is not running" };
2791
+ }
2792
+ const pidFile = getPidFile(projectRoot);
2793
+ try {
2794
+ // Send SIGTERM first (graceful)
2795
+ process.kill(status.pid, "SIGTERM");
2796
+ // Wait up to 3 seconds for graceful shutdown
2797
+ let attempts = 0;
2798
+ while (attempts < 6) {
2799
+ await new Promise(resolve => setTimeout(resolve, 500));
2800
+ try {
2801
+ process.kill(status.pid, 0); // Check if still running
2802
+ attempts++;
2803
+ }
2804
+ catch {
2805
+ // Process is gone
2806
+ break;
2807
+ }
2808
+ }
2809
+ // If still running after 3 seconds, force kill
2810
+ try {
2811
+ process.kill(status.pid, 0);
2812
+ process.kill(status.pid, "SIGKILL");
2813
+ await new Promise(resolve => setTimeout(resolve, 100));
2814
+ }
2815
+ catch {
2816
+ // Process is gone, that's fine
2817
+ }
2818
+ // Clean up PID file (preserve token for seamless restart)
2819
+ if (fs.existsSync(pidFile)) {
2820
+ fs.unlinkSync(pidFile);
2821
+ }
2822
+ return { success: true, message: "Context Hub stopped" };
2823
+ }
2824
+ catch (err) {
2825
+ return { success: false, message: `Failed to stop daemon: ${err}` };
2826
+ }
2827
+ }
2828
+ // ============================================================================
2829
+ // Auto-Install Daemon
2830
+ // ============================================================================
2831
+ export async function ensureDaemonInstalled(opts) {
2832
+ const quiet = opts?.quiet ?? false;
2833
+ if (process.platform !== "darwin") {
2834
+ return false;
2835
+ }
2836
+ const plistLabel = "com.jfl.context-hub";
2837
+ const plistDir = path.join(homedir(), "Library", "LaunchAgents");
2838
+ const plistPath = path.join(plistDir, `${plistLabel}.plist`);
2839
+ const logPath = path.join(homedir(), ".config", "jfl", "context-hub-agent.log");
2840
+ // If plist exists, check if loaded
2841
+ if (fs.existsSync(plistPath)) {
2842
+ try {
2843
+ const output = execSync("launchctl list", { encoding: "utf-8" });
2844
+ if (output.includes(plistLabel)) {
2845
+ return true;
2846
+ }
2847
+ }
2848
+ catch {
2849
+ // launchctl failed, try to reload
2850
+ }
2851
+ // Exists but not loaded — reload it
2852
+ try {
2853
+ execSync(`launchctl load "${plistPath}"`, { stdio: "ignore" });
2854
+ if (!quiet) {
2855
+ console.log(chalk.green(`\n Daemon reloaded.`));
2856
+ console.log(chalk.gray(` Plist: ${plistPath}\n`));
2857
+ }
2858
+ return true;
2859
+ }
2860
+ catch {
2861
+ // Fall through to full install
2862
+ }
2863
+ }
2864
+ // Full install
2865
+ let jflPath = "";
2866
+ try {
2867
+ jflPath = execSync("which jfl", { encoding: "utf-8" }).trim();
2868
+ }
2869
+ catch {
2870
+ jflPath = process.argv[1] || "jfl";
2871
+ }
2872
+ const logDir = path.dirname(logPath);
2873
+ if (!fs.existsSync(logDir)) {
2874
+ fs.mkdirSync(logDir, { recursive: true });
2875
+ }
2876
+ const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
2877
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2878
+ <plist version="1.0">
2879
+ <dict>
2880
+ <key>Label</key>
2881
+ <string>${plistLabel}</string>
2882
+ <key>ProgramArguments</key>
2883
+ <array>
2884
+ <string>${jflPath}</string>
2885
+ <string>context-hub</string>
2886
+ <string>ensure-all</string>
2887
+ <string>--quiet</string>
2888
+ </array>
2889
+ <key>RunAtLoad</key>
2890
+ <true/>
2891
+ <key>StartInterval</key>
2892
+ <integer>300</integer>
2893
+ <key>ProcessType</key>
2894
+ <string>Background</string>
2895
+ <key>LowPriorityIO</key>
2896
+ <true/>
2897
+ <key>Nice</key>
2898
+ <integer>10</integer>
2899
+ <key>EnvironmentVariables</key>
2900
+ <dict>
2901
+ <key>PATH</key>
2902
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
2903
+ </dict>
2904
+ <key>StandardOutPath</key>
2905
+ <string>${logPath}</string>
2906
+ <key>StandardErrorPath</key>
2907
+ <string>${logPath}</string>
2908
+ </dict>
2909
+ </plist>
2910
+ `;
2911
+ if (!fs.existsSync(plistDir)) {
2912
+ fs.mkdirSync(plistDir, { recursive: true });
2913
+ }
2914
+ // Unload if partially loaded
2915
+ try {
2916
+ execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
2917
+ }
2918
+ catch {
2919
+ // Not loaded, fine
2920
+ }
2921
+ fs.writeFileSync(plistPath, plistContent);
2922
+ try {
2923
+ execSync(`launchctl load "${plistPath}"`);
2924
+ if (!quiet) {
2925
+ console.log(chalk.green(`\n Daemon installed and loaded.`));
2926
+ console.log(chalk.gray(` Plist: ${plistPath}`));
2927
+ console.log(chalk.gray(` Log: ${logPath}`));
2928
+ console.log(chalk.gray(` Runs ensure-all every 5 minutes + on login.\n`));
2929
+ }
2930
+ return true;
2931
+ }
2932
+ catch {
2933
+ if (!quiet) {
2934
+ console.log(chalk.red(`\n Failed to load daemon.\n`));
2935
+ }
2936
+ return false;
2937
+ }
2938
+ }
2939
+ // ============================================================================
2940
+ // CLI Command
2941
+ // ============================================================================
2942
+ export async function contextHubCommand(action, options = {}) {
2943
+ const isGlobal = options.global || false;
2944
+ const projectRoot = options.projectRoot || (isGlobal ? homedir() : process.cwd());
2945
+ const port = options.port || getProjectPort(projectRoot);
2946
+ // Ensure directories exist (skip for actions that don't need local project root)
2947
+ const globalActions = ["ensure-all", "doctor", "install-daemon", "uninstall-daemon"];
2948
+ if (!globalActions.includes(action || "")) {
2949
+ if (isGlobal) {
2950
+ const { JFL_PATHS, ensureJflDirs } = await import("../utils/jfl-paths.js");
2951
+ ensureJflDirs();
2952
+ }
2953
+ else {
2954
+ const jflDir = path.join(projectRoot, ".jfl");
2955
+ if (!fs.existsSync(jflDir)) {
2956
+ fs.mkdirSync(jflDir, { recursive: true });
2957
+ }
2958
+ }
2959
+ }
2960
+ switch (action) {
2961
+ case "start": {
2962
+ const spinner = ora("Starting Context Hub...").start();
2963
+ const result = await startDaemon(projectRoot, port);
2964
+ if (result.success) {
2965
+ // Check if it was already running
2966
+ if (result.message.includes("already running")) {
2967
+ spinner.info(result.message);
2968
+ }
2969
+ else {
2970
+ // Wait for server to be ready
2971
+ await new Promise(resolve => setTimeout(resolve, 500));
2972
+ const status = isRunning(projectRoot);
2973
+ spinner.succeed(`Context Hub started on port ${port} (PID: ${status.pid})`);
2974
+ console.log(chalk.gray(` Token file: .jfl/context-hub.token`));
2975
+ }
2976
+ }
2977
+ else {
2978
+ spinner.fail(result.message);
2979
+ }
2980
+ break;
2981
+ }
2982
+ case "stop": {
2983
+ const spinner = ora("Stopping Context Hub...").start();
2984
+ const result = await stopDaemon(projectRoot);
2985
+ if (result.success) {
2986
+ spinner.succeed(result.message);
2987
+ }
2988
+ else {
2989
+ spinner.fail(result.message);
2990
+ }
2991
+ break;
2992
+ }
2993
+ case "restart": {
2994
+ await contextHubCommand("stop", options);
2995
+ await new Promise(resolve => setTimeout(resolve, 500));
2996
+ await contextHubCommand("start", options);
2997
+ break;
2998
+ }
2999
+ case "status": {
3000
+ const status = isRunning(projectRoot);
3001
+ if (status.running) {
3002
+ console.log(chalk.green(`\n Context Hub is running`));
3003
+ console.log(chalk.gray(` PID: ${status.pid}`));
3004
+ console.log(chalk.gray(` Port: ${port}`));
3005
+ // Try to get more info from the API
3006
+ try {
3007
+ const response = await fetch(`http://localhost:${port}/api/context/status`);
3008
+ const data = await response.json();
3009
+ console.log(chalk.gray(` Sources: ${Object.entries(data.sources).filter(([, v]) => v).map(([k]) => k).join(", ")}`));
3010
+ console.log(chalk.gray(` Items: ${data.itemCount}`));
3011
+ }
3012
+ catch {
3013
+ // Server might not be responding yet
3014
+ }
3015
+ console.log();
3016
+ }
3017
+ else {
3018
+ console.log(chalk.yellow("\n Context Hub is not running"));
3019
+ console.log(chalk.gray(" Run: jfl context-hub start\n"));
3020
+ }
3021
+ break;
3022
+ }
3023
+ case "ensure": {
3024
+ await ensureForProject(projectRoot, port, true);
3025
+ break;
3026
+ }
3027
+ case "ensure-all": {
3028
+ const tracked = getTrackedProjects();
3029
+ if (tracked.length === 0) {
3030
+ if (!options.quiet) {
3031
+ console.log(chalk.yellow("\n No tracked projects found.\n"));
3032
+ }
3033
+ break;
3034
+ }
3035
+ const results = [];
3036
+ for (const project of tracked) {
3037
+ const name = path.basename(project.path);
3038
+ const result = await ensureForProject(project.path, project.port, true);
3039
+ results.push({ name, result });
3040
+ }
3041
+ if (!options.quiet) {
3042
+ console.log(chalk.bold("\n Context Hub - ensure-all\n"));
3043
+ for (const { name, result } of results) {
3044
+ const icon = result.status === "failed" ? chalk.red("✗") : chalk.green("✓");
3045
+ const label = result.status === "started" ? chalk.cyan("started") :
3046
+ result.status === "running" ? chalk.green("running") :
3047
+ chalk.red("failed");
3048
+ console.log(` ${icon} ${chalk.bold(name)} — ${label}`);
3049
+ }
3050
+ const ok = results.filter(r => r.result.status !== "failed").length;
3051
+ const fail = results.filter(r => r.result.status === "failed").length;
3052
+ console.log(chalk.gray(`\n ${ok} running, ${fail} failed\n`));
3053
+ }
3054
+ break;
3055
+ }
3056
+ case "doctor": {
3057
+ const confStoreDoctor = new Conf({ projectName: "jfl" });
3058
+ const confProjectsDoctor = confStoreDoctor.get("projects") || [];
3059
+ const xdgProjectsDoctor = getConfigValue("projects") || [];
3060
+ const allProjects = [...new Set([...confProjectsDoctor, ...xdgProjectsDoctor])];
3061
+ if (allProjects.length === 0) {
3062
+ console.log(chalk.yellow("\n No tracked projects found.\n"));
3063
+ break;
3064
+ }
3065
+ const cleanMode = process.argv.includes("--clean");
3066
+ if (cleanMode) {
3067
+ const before = allProjects.length;
3068
+ const valid = allProjects.filter(p => fs.existsSync(p));
3069
+ // Write cleaned list back to both stores
3070
+ confStoreDoctor.set("projects", valid.filter(p => confProjectsDoctor.includes(p)));
3071
+ setConfig("projects", valid.filter(p => xdgProjectsDoctor.includes(p)));
3072
+ const removed = before - valid.length;
3073
+ if (removed > 0) {
3074
+ console.log(chalk.green(`\n Removed ${removed} stale project${removed > 1 ? "s" : ""} from tracker.\n`));
3075
+ }
3076
+ else {
3077
+ console.log(chalk.green("\n No stale projects found.\n"));
3078
+ }
3079
+ break;
3080
+ }
3081
+ console.log(chalk.bold("\n Context Hub - doctor\n"));
3082
+ let staleCount = 0;
3083
+ let downCount = 0;
3084
+ let zombieCount = 0;
3085
+ let okCount = 0;
3086
+ for (const projectPath of allProjects) {
3087
+ const projectPort = getProjectPort(projectPath);
3088
+ const result = await diagnoseProject(projectPath, projectPort);
3089
+ const name = path.basename(result.path);
3090
+ switch (result.status) {
3091
+ case "OK":
3092
+ console.log(` ${chalk.green("OK")} ${chalk.bold(name)} — PID ${result.pid}, port ${result.port}`);
3093
+ okCount++;
3094
+ break;
3095
+ case "ZOMBIE":
3096
+ console.log(` ${chalk.red("ZOMBIE")} ${chalk.bold(name)} — PID ${result.pid} not responding on port ${result.port}`);
3097
+ zombieCount++;
3098
+ break;
3099
+ case "DOWN":
3100
+ console.log(` ${chalk.red("DOWN")} ${chalk.bold(name)} — not running (port ${result.port})`);
3101
+ downCount++;
3102
+ break;
3103
+ case "STALE":
3104
+ console.log(` ${chalk.yellow("STALE")} ${chalk.gray(result.path)} — directory missing`);
3105
+ staleCount++;
3106
+ break;
3107
+ }
3108
+ }
3109
+ console.log();
3110
+ if (downCount > 0 || zombieCount > 0) {
3111
+ console.log(chalk.gray(` Hint: run ${chalk.cyan("jfl context-hub ensure-all")} to start all hubs`));
3112
+ }
3113
+ if (staleCount > 0) {
3114
+ console.log(chalk.gray(` Hint: run ${chalk.cyan("jfl context-hub doctor --clean")} to remove stale entries`));
3115
+ }
3116
+ if (okCount === allProjects.length) {
3117
+ console.log(chalk.green(" All projects healthy."));
3118
+ }
3119
+ console.log();
3120
+ break;
3121
+ }
3122
+ case "install-daemon": {
3123
+ const result = await ensureDaemonInstalled({ quiet: false });
3124
+ if (!result) {
3125
+ console.log(chalk.yellow("\n Daemon install skipped (non-macOS or failed).\n"));
3126
+ }
3127
+ break;
3128
+ }
3129
+ case "uninstall-daemon": {
3130
+ const plistLabel = "com.jfl.context-hub";
3131
+ const plistPath = path.join(homedir(), "Library", "LaunchAgents", `${plistLabel}.plist`);
3132
+ if (!fs.existsSync(plistPath)) {
3133
+ console.log(chalk.yellow("\n Daemon not installed.\n"));
3134
+ break;
3135
+ }
3136
+ try {
3137
+ execSync(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
3138
+ }
3139
+ catch {
3140
+ // Already unloaded
3141
+ }
3142
+ fs.unlinkSync(plistPath);
3143
+ console.log(chalk.green("\n Daemon uninstalled.\n"));
3144
+ break;
3145
+ }
3146
+ case "serve": {
3147
+ // Load .env files for API keys (hub runs as detached process)
3148
+ for (const envFile of [
3149
+ path.join(projectRoot, ".env"),
3150
+ path.join(projectRoot, ".env.local"),
3151
+ path.join(process.env.HOME || "/tmp", ".env"),
3152
+ ]) {
3153
+ if (fs.existsSync(envFile)) {
3154
+ const envContent = fs.readFileSync(envFile, "utf-8");
3155
+ for (const line of envContent.split("\n")) {
3156
+ const trimmed = line.trim();
3157
+ if (!trimmed || trimmed.startsWith("#"))
3158
+ continue;
3159
+ const eqIdx = trimmed.indexOf("=");
3160
+ if (eqIdx === -1)
3161
+ continue;
3162
+ const key = trimmed.slice(0, eqIdx).trim();
3163
+ let val = trimmed.slice(eqIdx + 1).trim();
3164
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
3165
+ val = val.slice(1, -1);
3166
+ }
3167
+ if (!process.env[key]) {
3168
+ process.env[key] = val;
3169
+ }
3170
+ }
3171
+ }
3172
+ }
3173
+ // Run server in foreground (used by daemon)
3174
+ const serviceEventsPath = path.join(projectRoot, ".jfl", "service-events.jsonl");
3175
+ const mapPersistPath = path.join(projectRoot, ".jfl", "map-events.jsonl");
3176
+ const journalDir = path.join(projectRoot, ".jfl", "journal");
3177
+ const eventBus = new MAPEventBus({
3178
+ maxSize: 1000,
3179
+ persistPath: mapPersistPath,
3180
+ serviceEventsPath,
3181
+ journalDir: fs.existsSync(journalDir) ? journalDir : null,
3182
+ });
3183
+ const flowEngine = new FlowEngine(eventBus, projectRoot);
3184
+ const server = createServer(projectRoot, port, eventBus, flowEngine);
3185
+ let isListening = false;
3186
+ // Cross-service scope impact detection (GTM/portfolio level)
3187
+ // When eval:scored fires with improved=true, detect which other services
3188
+ // are affected and emit scope:impact events for each
3189
+ const configPath = path.join(projectRoot, ".jfl", "config.json");
3190
+ const hubConfig = fs.existsSync(configPath) ? JSON.parse(fs.readFileSync(configPath, "utf-8")) : {};
3191
+ if (hubConfig.type === "gtm" || hubConfig.type === "portfolio") {
3192
+ eventBus.subscribe({
3193
+ clientId: "scope-detector",
3194
+ patterns: ["eval:scored"],
3195
+ transport: "poll",
3196
+ callback: (event) => {
3197
+ if (event.data?.improved !== "true" && event.data?.improved !== true)
3198
+ return;
3199
+ const serviceName = event.data?.service || event.source || "unknown";
3200
+ const registeredServices = hubConfig.registered_services || [];
3201
+ // Find source service's produces
3202
+ const sourceReg = registeredServices.find((s) => s.name === serviceName);
3203
+ if (!sourceReg?.path)
3204
+ return;
3205
+ try {
3206
+ const svcConfigPath = path.join(sourceReg.path, ".jfl", "config.json");
3207
+ if (!fs.existsSync(svcConfigPath))
3208
+ return;
3209
+ const svcConfig = JSON.parse(fs.readFileSync(svcConfigPath, "utf-8"));
3210
+ const produces = svcConfig.context_scope?.produces || [];
3211
+ if (produces.length === 0)
3212
+ return;
3213
+ // Check each other service for consuming matches
3214
+ for (const otherSvc of registeredServices) {
3215
+ if (otherSvc.name === serviceName || !otherSvc.path)
3216
+ continue;
3217
+ const otherConfigPath = path.join(otherSvc.path, ".jfl", "config.json");
3218
+ if (!fs.existsSync(otherConfigPath))
3219
+ continue;
3220
+ const otherConfig = JSON.parse(fs.readFileSync(otherConfigPath, "utf-8"));
3221
+ const consumes = otherConfig.context_scope?.consumes || [];
3222
+ // Match produces against consumes
3223
+ const matched = [];
3224
+ for (const p of produces) {
3225
+ for (const c of consumes) {
3226
+ if (c === "*" || p === c || (c.endsWith(":*") && p.startsWith(c.slice(0, -1))) || (c.endsWith("*") && p.startsWith(c.slice(0, -1)))) {
3227
+ matched.push(`${p} → ${c}`);
3228
+ }
3229
+ }
3230
+ }
3231
+ if (matched.length > 0) {
3232
+ const ts = new Date().toISOString();
3233
+ console.log(`[${ts}] scope:impact — ${serviceName} → ${otherSvc.name} (${matched.length} patterns)`);
3234
+ eventBus.emit({
3235
+ type: "scope:impact",
3236
+ source: "scope-detector",
3237
+ data: {
3238
+ source_service: serviceName,
3239
+ affected_service: otherSvc.name,
3240
+ affected_service_path: otherSvc.path,
3241
+ scope_patterns: matched,
3242
+ source_pr: event.data?.pr_number || "",
3243
+ change_description: event.data?.branch || "eval improvement",
3244
+ source_delta: event.data?.delta || "0",
3245
+ },
3246
+ });
3247
+ }
3248
+ }
3249
+ }
3250
+ catch (err) {
3251
+ console.error(`[scope-detector] Error checking impact for ${serviceName}:`, err.message);
3252
+ }
3253
+ },
3254
+ });
3255
+ console.log(`[scope-detector] Cross-service impact detection enabled for ${hubConfig.type} hub`);
3256
+ }
3257
+ // When spawned as daemon, ignore SIGTERM during startup grace period.
3258
+ // The parent process (hook runner) may exit and send SIGTERM to the
3259
+ // process group before we're fully detached. After grace period,
3260
+ // re-enable normal shutdown handling.
3261
+ const isDaemon = process.env.CONTEXT_HUB_DAEMON === "1";
3262
+ let startupGrace = isDaemon;
3263
+ if (isDaemon) {
3264
+ setTimeout(() => {
3265
+ startupGrace = false;
3266
+ }, 5000);
3267
+ }
3268
+ // Error handling - keep process alive
3269
+ process.on("uncaughtException", (err) => {
3270
+ console.error(`Uncaught exception: ${err.message}`);
3271
+ console.error(err.stack);
3272
+ telemetry.track({
3273
+ category: 'error',
3274
+ event: 'error:hub_crash',
3275
+ error_type: err.constructor.name,
3276
+ error_code: err.code || undefined,
3277
+ hub_port: port,
3278
+ hub_uptime_s: isListening ? Math.floor((Date.now() - hubStartTime) / 1000) : 0,
3279
+ });
3280
+ });
3281
+ process.on("unhandledRejection", (reason, promise) => {
3282
+ console.error(`Unhandled rejection at ${promise}: ${reason}`);
3283
+ // Don't exit - log and continue
3284
+ });
3285
+ server.on("error", (err) => {
3286
+ console.error(`Server error: ${err.message}`);
3287
+ telemetry.track({
3288
+ category: 'error',
3289
+ event: 'error:hub_server',
3290
+ error_type: err.constructor.name,
3291
+ error_code: err.code || undefined,
3292
+ hub_port: port,
3293
+ });
3294
+ if (err.code === "EADDRINUSE") {
3295
+ console.error(`Port ${port} is already in use. Exiting.`);
3296
+ process.exit(1);
3297
+ }
3298
+ });
3299
+ const hubStartTime = Date.now();
3300
+ server.listen(port, async () => {
3301
+ isListening = true;
3302
+ const timestamp = new Date().toISOString();
3303
+ console.log(`[${timestamp}] Context Hub listening on port ${port}`);
3304
+ console.log(`[${timestamp}] PID: ${process.pid}`);
3305
+ telemetry.track({
3306
+ category: 'context_hub',
3307
+ event: 'context_hub:started',
3308
+ hub_port: port,
3309
+ duration_ms: Date.now() - hubStartTime,
3310
+ });
3311
+ // Initialize memory system
3312
+ try {
3313
+ await initializeDatabase();
3314
+ console.log(`[${timestamp}] Memory database initialized`);
3315
+ // Index existing journal entries
3316
+ const stats = await indexJournalEntries();
3317
+ if (stats.added > 0) {
3318
+ console.log(`[${timestamp}] Indexed ${stats.added} new journal entries`);
3319
+ }
3320
+ // Start periodic indexing (every 60 seconds)
3321
+ startPeriodicIndexing(60000);
3322
+ console.log(`[${timestamp}] Periodic memory indexing started`);
3323
+ }
3324
+ catch (err) {
3325
+ console.error(`[${timestamp}] Failed to initialize memory system:`, err.message);
3326
+ // Don't exit - memory is optional
3327
+ }
3328
+ // Start flow engine (with child hub connections for portfolio mode)
3329
+ try {
3330
+ const children = getChildHubs(projectRoot);
3331
+ if (children.length > 0) {
3332
+ flowEngine.setChildren(children);
3333
+ console.log(`[${timestamp}] Portfolio mode: connecting to ${children.length} child hub(s)`);
3334
+ }
3335
+ const flowCount = await flowEngine.start();
3336
+ if (flowCount > 0) {
3337
+ console.log(`[${timestamp}] Flow engine started with ${flowCount} active flow(s)`);
3338
+ }
3339
+ }
3340
+ catch (err) {
3341
+ console.error(`[${timestamp}] Failed to start flow engine:`, err.message);
3342
+ }
3343
+ // Start telemetry agent (periodic pattern detection)
3344
+ try {
3345
+ const { TelemetryAgent } = await import("../lib/telemetry-agent.js");
3346
+ const telemetryAgent = new TelemetryAgent({
3347
+ projectRoot,
3348
+ intervalMs: 30 * 60 * 1000,
3349
+ emitEvent: (type, data, source) => {
3350
+ eventBus.emit({ type: type, data, source: source || 'telemetry-agent' });
3351
+ },
3352
+ });
3353
+ telemetryAgent.start();
3354
+ server.__telemetryAgent = telemetryAgent;
3355
+ console.log(`[${timestamp}] Telemetry agent started (interval: 30m)`);
3356
+ }
3357
+ catch (err) {
3358
+ console.error(`[${timestamp}] Failed to start telemetry agent:`, err.message);
3359
+ }
3360
+ console.log(`[${timestamp}] MAP event bus initialized (buffer: 1000, subscribers: ${eventBus.getSubscriberCount()})`);
3361
+ console.log(`[${timestamp}] Ready to serve requests`);
3362
+ });
3363
+ // Handle shutdown gracefully
3364
+ const shutdown = (signal) => {
3365
+ // During startup grace period (daemon mode), ignore SIGTERM from
3366
+ // parent process cleanup. This prevents the hook runner from
3367
+ // killing the hub before it's fully detached.
3368
+ if (startupGrace && signal === "SIGTERM") {
3369
+ const ts = new Date().toISOString();
3370
+ console.log(`[${ts}] Ignoring ${signal} during startup grace period (PID: ${process.pid}, Parent: ${process.ppid})`);
3371
+ return;
3372
+ }
3373
+ if (!isListening) {
3374
+ // Server never started, just exit
3375
+ process.exit(0);
3376
+ return;
3377
+ }
3378
+ // Log who sent the signal for debugging
3379
+ const timestamp = new Date().toISOString();
3380
+ console.log(`[${timestamp}] Received ${signal}`);
3381
+ console.log(`[${timestamp}] PID: ${process.pid}, Parent PID: ${process.ppid}`);
3382
+ console.log(`[${timestamp}] Shutting down...`);
3383
+ telemetry.track({
3384
+ category: 'context_hub',
3385
+ event: 'context_hub:stopped',
3386
+ hub_port: port,
3387
+ hub_uptime_s: Math.floor((Date.now() - hubStartTime) / 1000),
3388
+ });
3389
+ server.close(() => {
3390
+ eventBus.destroy();
3391
+ console.log(`[${new Date().toISOString()}] Server closed`);
3392
+ process.exit(0);
3393
+ });
3394
+ // Force exit after 5s if server doesn't close
3395
+ setTimeout(() => {
3396
+ console.log(`[${new Date().toISOString()}] Force exit after timeout`);
3397
+ process.exit(1);
3398
+ }, 5000);
3399
+ };
3400
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
3401
+ process.on("SIGINT", () => shutdown("SIGINT"));
3402
+ // Keep process alive with heartbeat
3403
+ const heartbeat = setInterval(() => {
3404
+ // Heartbeat - ensures event loop stays active
3405
+ // Also log periodically so we know it's alive
3406
+ if (isListening && Date.now() % 300000 < 60000) { // Every 5 minutes
3407
+ const timestamp = new Date().toISOString();
3408
+ console.log(`[${timestamp}] Heartbeat: Still running (PID: ${process.pid})`);
3409
+ }
3410
+ }, 60000);
3411
+ // Cleanup heartbeat on exit
3412
+ process.on("exit", () => {
3413
+ clearInterval(heartbeat);
3414
+ });
3415
+ break;
3416
+ }
3417
+ case "query": {
3418
+ // Quick query for testing
3419
+ const context = getUnifiedContext(projectRoot);
3420
+ console.log(chalk.bold("\n Context Hub Query\n"));
3421
+ console.log(chalk.gray(` Sources: ${Object.entries(context.sources).filter(([, v]) => v).map(([k]) => k).join(", ")}`));
3422
+ console.log(chalk.gray(` Items: ${context.items.length}\n`));
3423
+ for (const item of context.items.slice(0, 10)) {
3424
+ console.log(chalk.cyan(` [${item.source}] ${item.title}`));
3425
+ console.log(chalk.gray(` ${item.content.slice(0, 100)}${item.content.length > 100 ? "..." : ""}`));
3426
+ }
3427
+ console.log();
3428
+ break;
3429
+ }
3430
+ case "logs": {
3431
+ // Launch TUI log viewer
3432
+ const __filename = fileURLToPath(import.meta.url);
3433
+ const __dirname = path.dirname(__filename);
3434
+ const logViewer = spawn(process.execPath, [
3435
+ path.join(__dirname, "../ui/context-hub-logs.js")
3436
+ ], {
3437
+ stdio: "inherit",
3438
+ cwd: projectRoot
3439
+ });
3440
+ logViewer.on("exit", (code) => {
3441
+ process.exit(code || 0);
3442
+ });
3443
+ break;
3444
+ }
3445
+ case "dashboard": {
3446
+ const token = getOrCreateToken(projectRoot);
3447
+ const dashUrl = `http://localhost:${port}/dashboard?token=${token}`;
3448
+ try {
3449
+ execSync(`open "${dashUrl}"`);
3450
+ }
3451
+ catch {
3452
+ // open not available — print URL instead
3453
+ }
3454
+ console.log(chalk.gray(` Opening ${dashUrl}`));
3455
+ break;
3456
+ }
3457
+ case "clear-logs": {
3458
+ // Clear both global and local log files
3459
+ const { JFL_FILES } = await import("../utils/jfl-paths.js");
3460
+ const globalLogFile = path.join(JFL_FILES.servicesLogs, "context-hub.log");
3461
+ const localLogFile = getLogFile(projectRoot);
3462
+ let cleared = 0;
3463
+ if (fs.existsSync(globalLogFile)) {
3464
+ fs.writeFileSync(globalLogFile, '');
3465
+ console.log(chalk.green('✓ Global Context Hub logs cleared'));
3466
+ console.log(chalk.gray(` File: ${globalLogFile}`));
3467
+ cleared++;
3468
+ }
3469
+ if (fs.existsSync(localLogFile) && localLogFile !== globalLogFile) {
3470
+ fs.writeFileSync(localLogFile, '');
3471
+ console.log(chalk.green('✓ Local Context Hub logs cleared'));
3472
+ console.log(chalk.gray(` File: ${localLogFile}`));
3473
+ cleared++;
3474
+ }
3475
+ if (cleared === 0) {
3476
+ console.log(chalk.yellow('No log files found'));
3477
+ }
3478
+ break;
3479
+ }
3480
+ default: {
3481
+ console.log(chalk.bold("\n Context Hub - Unified context for AI agents\n"));
3482
+ console.log(chalk.gray(" Commands:"));
3483
+ console.log(" jfl context-hub start Start the daemon");
3484
+ console.log(" jfl context-hub stop Stop the daemon");
3485
+ console.log(" jfl context-hub restart Restart the daemon");
3486
+ console.log(" jfl context-hub status Check if running");
3487
+ console.log(" jfl context-hub ensure Start if not running (for hooks)");
3488
+ console.log(" jfl context-hub ensure-all Ensure all tracked projects are running");
3489
+ console.log(" jfl context-hub doctor Diagnose all tracked projects");
3490
+ console.log(" jfl context-hub doctor --clean Remove stale project entries");
3491
+ console.log(" jfl context-hub dashboard Open web dashboard in browser");
3492
+ console.log(" jfl context-hub install-daemon Install macOS launchd keepalive");
3493
+ console.log(" jfl context-hub uninstall-daemon Remove macOS launchd keepalive");
3494
+ console.log(" jfl context-hub logs Show real-time logs (TUI)");
3495
+ console.log(" jfl context-hub clear-logs Clear log file");
3496
+ console.log(" jfl context-hub query Quick context query");
3497
+ console.log();
3498
+ console.log(chalk.gray(" Options:"));
3499
+ console.log(" --port <port> Port to run on (default: per-project)");
3500
+ console.log(" --quiet Suppress output (for daemon use)");
3501
+ console.log();
3502
+ }
3503
+ }
3504
+ }
3505
+ //# sourceMappingURL=context-hub.js.map