jfl 0.4.4 → 0.6.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 (533) hide show
  1. package/dist/commands/context-hub.d.ts +1 -0
  2. package/dist/commands/context-hub.d.ts.map +1 -1
  3. package/dist/commands/context-hub.js +1064 -41
  4. package/dist/commands/context-hub.js.map +1 -1
  5. package/dist/commands/eval.d.ts +1 -1
  6. package/dist/commands/eval.d.ts.map +1 -1
  7. package/dist/commands/eval.js +192 -1
  8. package/dist/commands/eval.js.map +1 -1
  9. package/dist/commands/findings.d.ts +6 -0
  10. package/dist/commands/findings.d.ts.map +1 -0
  11. package/dist/commands/findings.js +203 -0
  12. package/dist/commands/findings.js.map +1 -0
  13. package/dist/commands/hud.d.ts.map +1 -1
  14. package/dist/commands/hud.js +47 -9
  15. package/dist/commands/hud.js.map +1 -1
  16. package/dist/commands/ide.d.ts +27 -0
  17. package/dist/commands/ide.d.ts.map +1 -0
  18. package/dist/commands/ide.js +546 -0
  19. package/dist/commands/ide.js.map +1 -0
  20. package/dist/commands/onboard.d.ts.map +1 -1
  21. package/dist/commands/onboard.js +212 -2
  22. package/dist/commands/onboard.js.map +1 -1
  23. package/dist/commands/openclaw.d.ts +3 -0
  24. package/dist/commands/openclaw.d.ts.map +1 -1
  25. package/dist/commands/openclaw.js +76 -2
  26. package/dist/commands/openclaw.js.map +1 -1
  27. package/dist/commands/peter.d.ts +3 -0
  28. package/dist/commands/peter.d.ts.map +1 -1
  29. package/dist/commands/peter.js +1168 -58
  30. package/dist/commands/peter.js.map +1 -1
  31. package/dist/commands/pi-fleet.d.ts +18 -0
  32. package/dist/commands/pi-fleet.d.ts.map +1 -0
  33. package/dist/commands/pi-fleet.js +382 -0
  34. package/dist/commands/pi-fleet.js.map +1 -0
  35. package/dist/commands/pi.d.ts.map +1 -1
  36. package/dist/commands/pi.js +18 -3
  37. package/dist/commands/pi.js.map +1 -1
  38. package/dist/commands/scope.d.ts.map +1 -1
  39. package/dist/commands/scope.js +90 -1
  40. package/dist/commands/scope.js.map +1 -1
  41. package/dist/commands/services.d.ts.map +1 -1
  42. package/dist/commands/services.js +18 -0
  43. package/dist/commands/services.js.map +1 -1
  44. package/dist/commands/setup.d.ts +12 -0
  45. package/dist/commands/setup.d.ts.map +1 -0
  46. package/dist/commands/setup.js +322 -0
  47. package/dist/commands/setup.js.map +1 -0
  48. package/dist/commands/status.d.ts.map +1 -1
  49. package/dist/commands/status.js +22 -4
  50. package/dist/commands/status.js.map +1 -1
  51. package/dist/commands/train.d.ts +33 -0
  52. package/dist/commands/train.d.ts.map +1 -0
  53. package/dist/commands/train.js +510 -0
  54. package/dist/commands/train.js.map +1 -0
  55. package/dist/commands/verify.d.ts +14 -0
  56. package/dist/commands/verify.d.ts.map +1 -0
  57. package/dist/commands/verify.js +276 -0
  58. package/dist/commands/verify.js.map +1 -0
  59. package/dist/commands/viz.d.ts.map +1 -1
  60. package/dist/commands/viz.js +417 -0
  61. package/dist/commands/viz.js.map +1 -1
  62. package/dist/dashboard-static/assets/index-CW9ZxqX8.css +1 -0
  63. package/dist/dashboard-static/assets/index-DNN__p4K.js +121 -0
  64. package/dist/dashboard-static/index.html +2 -2
  65. package/dist/index.js +324 -64
  66. package/dist/index.js.map +1 -1
  67. package/dist/lib/agent-config.d.ts +52 -0
  68. package/dist/lib/agent-config.d.ts.map +1 -0
  69. package/dist/lib/agent-config.js +231 -0
  70. package/dist/lib/agent-config.js.map +1 -0
  71. package/dist/lib/agent-generator.d.ts +10 -0
  72. package/dist/lib/agent-generator.d.ts.map +1 -1
  73. package/dist/lib/agent-generator.js +64 -10
  74. package/dist/lib/agent-generator.js.map +1 -1
  75. package/dist/lib/agent-session.d.ts +104 -0
  76. package/dist/lib/agent-session.d.ts.map +1 -0
  77. package/dist/lib/agent-session.js +635 -0
  78. package/dist/lib/agent-session.js.map +1 -0
  79. package/dist/lib/eval-snapshot.d.ts +47 -0
  80. package/dist/lib/eval-snapshot.d.ts.map +1 -0
  81. package/dist/lib/eval-snapshot.js +315 -0
  82. package/dist/lib/eval-snapshot.js.map +1 -0
  83. package/dist/lib/eval-store.d.ts +5 -0
  84. package/dist/lib/eval-store.d.ts.map +1 -1
  85. package/dist/lib/eval-store.js +33 -3
  86. package/dist/lib/eval-store.js.map +1 -1
  87. package/dist/lib/findings-engine.d.ts +51 -0
  88. package/dist/lib/findings-engine.d.ts.map +1 -0
  89. package/dist/lib/findings-engine.js +338 -0
  90. package/dist/lib/findings-engine.js.map +1 -0
  91. package/dist/lib/flow-engine.d.ts +8 -0
  92. package/dist/lib/flow-engine.d.ts.map +1 -1
  93. package/dist/lib/flow-engine.js +84 -2
  94. package/dist/lib/flow-engine.js.map +1 -1
  95. package/dist/lib/hub-client.d.ts +1 -0
  96. package/dist/lib/hub-client.d.ts.map +1 -1
  97. package/dist/lib/hub-client.js +33 -6
  98. package/dist/lib/hub-client.js.map +1 -1
  99. package/dist/lib/ide-panes.d.ts +58 -0
  100. package/dist/lib/ide-panes.d.ts.map +1 -0
  101. package/dist/lib/ide-panes.js +508 -0
  102. package/dist/lib/ide-panes.js.map +1 -0
  103. package/dist/lib/memory-db.js +4 -4
  104. package/dist/lib/memory-db.js.map +1 -1
  105. package/dist/lib/memory-indexer.d.ts.map +1 -1
  106. package/dist/lib/memory-indexer.js +3 -0
  107. package/dist/lib/memory-indexer.js.map +1 -1
  108. package/dist/lib/memory-search.d.ts +148 -4
  109. package/dist/lib/memory-search.d.ts.map +1 -1
  110. package/dist/lib/memory-search.js +496 -58
  111. package/dist/lib/memory-search.js.map +1 -1
  112. package/dist/lib/meta-orchestrator.d.ts +104 -0
  113. package/dist/lib/meta-orchestrator.d.ts.map +1 -0
  114. package/dist/lib/meta-orchestrator.js +373 -0
  115. package/dist/lib/meta-orchestrator.js.map +1 -0
  116. package/dist/lib/peer-agent-generator.d.ts.map +1 -1
  117. package/dist/lib/peer-agent-generator.js +43 -19
  118. package/dist/lib/peer-agent-generator.js.map +1 -1
  119. package/dist/lib/pi-sky/bridge.d.ts +55 -0
  120. package/dist/lib/pi-sky/bridge.d.ts.map +1 -0
  121. package/dist/lib/pi-sky/bridge.js +264 -0
  122. package/dist/lib/pi-sky/bridge.js.map +1 -0
  123. package/dist/lib/pi-sky/cost-monitor.d.ts +21 -0
  124. package/dist/lib/pi-sky/cost-monitor.d.ts.map +1 -0
  125. package/dist/lib/pi-sky/cost-monitor.js +126 -0
  126. package/dist/lib/pi-sky/cost-monitor.js.map +1 -0
  127. package/dist/lib/pi-sky/eval-sweep.d.ts +27 -0
  128. package/dist/lib/pi-sky/eval-sweep.d.ts.map +1 -0
  129. package/dist/lib/pi-sky/eval-sweep.js +141 -0
  130. package/dist/lib/pi-sky/eval-sweep.js.map +1 -0
  131. package/dist/lib/pi-sky/event-router.d.ts +32 -0
  132. package/dist/lib/pi-sky/event-router.d.ts.map +1 -0
  133. package/dist/lib/pi-sky/event-router.js +176 -0
  134. package/dist/lib/pi-sky/event-router.js.map +1 -0
  135. package/dist/lib/pi-sky/experiment.d.ts +9 -0
  136. package/dist/lib/pi-sky/experiment.d.ts.map +1 -0
  137. package/dist/lib/pi-sky/experiment.js +83 -0
  138. package/dist/lib/pi-sky/experiment.js.map +1 -0
  139. package/dist/lib/pi-sky/index.d.ts +16 -0
  140. package/dist/lib/pi-sky/index.d.ts.map +1 -0
  141. package/dist/lib/pi-sky/index.js +16 -0
  142. package/dist/lib/pi-sky/index.js.map +1 -0
  143. package/dist/lib/pi-sky/stratus-gate.d.ts +28 -0
  144. package/dist/lib/pi-sky/stratus-gate.d.ts.map +1 -0
  145. package/dist/lib/pi-sky/stratus-gate.js +61 -0
  146. package/dist/lib/pi-sky/stratus-gate.js.map +1 -0
  147. package/dist/lib/pi-sky/swarm.d.ts +28 -0
  148. package/dist/lib/pi-sky/swarm.d.ts.map +1 -0
  149. package/dist/lib/pi-sky/swarm.js +208 -0
  150. package/dist/lib/pi-sky/swarm.js.map +1 -0
  151. package/dist/lib/pi-sky/types.d.ts +139 -0
  152. package/dist/lib/pi-sky/types.d.ts.map +1 -0
  153. package/dist/lib/pi-sky/types.js +2 -0
  154. package/dist/lib/pi-sky/types.js.map +1 -0
  155. package/dist/lib/pi-sky/voice-bridge.d.ts +20 -0
  156. package/dist/lib/pi-sky/voice-bridge.d.ts.map +1 -0
  157. package/dist/lib/pi-sky/voice-bridge.js +91 -0
  158. package/dist/lib/pi-sky/voice-bridge.js.map +1 -0
  159. package/dist/lib/policy-head.d.ts +40 -0
  160. package/dist/lib/policy-head.d.ts.map +1 -0
  161. package/dist/lib/policy-head.js +234 -0
  162. package/dist/lib/policy-head.js.map +1 -0
  163. package/dist/lib/predictor.d.ts +10 -0
  164. package/dist/lib/predictor.d.ts.map +1 -1
  165. package/dist/lib/predictor.js +46 -7
  166. package/dist/lib/predictor.js.map +1 -1
  167. package/dist/lib/replay-buffer.d.ts +93 -0
  168. package/dist/lib/replay-buffer.d.ts.map +1 -0
  169. package/dist/lib/replay-buffer.js +302 -0
  170. package/dist/lib/replay-buffer.js.map +1 -0
  171. package/dist/lib/sentinel-rl.d.ts +97 -0
  172. package/dist/lib/sentinel-rl.d.ts.map +1 -0
  173. package/dist/lib/sentinel-rl.js +430 -0
  174. package/dist/lib/sentinel-rl.js.map +1 -0
  175. package/dist/lib/session-lock.d.ts +61 -0
  176. package/dist/lib/session-lock.d.ts.map +1 -0
  177. package/dist/lib/session-lock.js +438 -0
  178. package/dist/lib/session-lock.js.map +1 -0
  179. package/dist/lib/setup/agent-generator.d.ts +18 -0
  180. package/dist/lib/setup/agent-generator.d.ts.map +1 -0
  181. package/dist/lib/setup/agent-generator.js +114 -0
  182. package/dist/lib/setup/agent-generator.js.map +1 -0
  183. package/dist/lib/setup/context-analyzer.d.ts +16 -0
  184. package/dist/lib/setup/context-analyzer.d.ts.map +1 -0
  185. package/dist/lib/setup/context-analyzer.js +112 -0
  186. package/dist/lib/setup/context-analyzer.js.map +1 -0
  187. package/dist/lib/setup/doc-auditor.d.ts +54 -0
  188. package/dist/lib/setup/doc-auditor.d.ts.map +1 -0
  189. package/dist/lib/setup/doc-auditor.js +629 -0
  190. package/dist/lib/setup/doc-auditor.js.map +1 -0
  191. package/dist/lib/setup/domain-generator.d.ts +7 -0
  192. package/dist/lib/setup/domain-generator.d.ts.map +1 -0
  193. package/dist/lib/setup/domain-generator.js +58 -0
  194. package/dist/lib/setup/domain-generator.js.map +1 -0
  195. package/dist/lib/setup/smart-eval-generator.d.ts +38 -0
  196. package/dist/lib/setup/smart-eval-generator.d.ts.map +1 -0
  197. package/dist/lib/setup/smart-eval-generator.js +378 -0
  198. package/dist/lib/setup/smart-eval-generator.js.map +1 -0
  199. package/dist/lib/setup/smart-recommender.d.ts +63 -0
  200. package/dist/lib/setup/smart-recommender.d.ts.map +1 -0
  201. package/dist/lib/setup/smart-recommender.js +329 -0
  202. package/dist/lib/setup/smart-recommender.js.map +1 -0
  203. package/dist/lib/setup/spec-generator.d.ts +63 -0
  204. package/dist/lib/setup/spec-generator.d.ts.map +1 -0
  205. package/dist/lib/setup/spec-generator.js +310 -0
  206. package/dist/lib/setup/spec-generator.js.map +1 -0
  207. package/dist/lib/setup/violation-agent-generator.d.ts +32 -0
  208. package/dist/lib/setup/violation-agent-generator.d.ts.map +1 -0
  209. package/dist/lib/setup/violation-agent-generator.js +255 -0
  210. package/dist/lib/setup/violation-agent-generator.js.map +1 -0
  211. package/dist/lib/stratus-client.d.ts +1 -0
  212. package/dist/lib/stratus-client.d.ts.map +1 -1
  213. package/dist/lib/stratus-client.js +24 -2
  214. package/dist/lib/stratus-client.js.map +1 -1
  215. package/dist/lib/telemetry-agent-v2.d.ts +128 -0
  216. package/dist/lib/telemetry-agent-v2.d.ts.map +1 -0
  217. package/dist/lib/telemetry-agent-v2.js +1042 -0
  218. package/dist/lib/telemetry-agent-v2.js.map +1 -0
  219. package/dist/lib/telemetry-agent.d.ts.map +1 -1
  220. package/dist/lib/telemetry-agent.js +27 -6
  221. package/dist/lib/telemetry-agent.js.map +1 -1
  222. package/dist/lib/telemetry-digest.d.ts.map +1 -1
  223. package/dist/lib/telemetry-digest.js +27 -5
  224. package/dist/lib/telemetry-digest.js.map +1 -1
  225. package/dist/lib/telemetry.d.ts.map +1 -1
  226. package/dist/lib/telemetry.js +29 -4
  227. package/dist/lib/telemetry.js.map +1 -1
  228. package/dist/lib/text-preprocessing.d.ts +83 -0
  229. package/dist/lib/text-preprocessing.d.ts.map +1 -0
  230. package/dist/lib/text-preprocessing.js +261 -0
  231. package/dist/lib/text-preprocessing.js.map +1 -0
  232. package/dist/lib/training-buffer.d.ts +86 -0
  233. package/dist/lib/training-buffer.d.ts.map +1 -0
  234. package/dist/lib/training-buffer.js +139 -0
  235. package/dist/lib/training-buffer.js.map +1 -0
  236. package/dist/lib/tuple-miner.d.ts +30 -0
  237. package/dist/lib/tuple-miner.d.ts.map +1 -0
  238. package/dist/lib/tuple-miner.js +427 -0
  239. package/dist/lib/tuple-miner.js.map +1 -0
  240. package/dist/lib/vm-backend.d.ts +72 -0
  241. package/dist/lib/vm-backend.d.ts.map +1 -0
  242. package/dist/lib/vm-backend.js +175 -0
  243. package/dist/lib/vm-backend.js.map +1 -0
  244. package/dist/lib/workspace/backend.d.ts +53 -0
  245. package/dist/lib/workspace/backend.d.ts.map +1 -0
  246. package/dist/lib/workspace/backend.js +37 -0
  247. package/dist/lib/workspace/backend.js.map +1 -0
  248. package/dist/lib/workspace/cmux-adapter.d.ts +46 -0
  249. package/dist/lib/workspace/cmux-adapter.d.ts.map +1 -0
  250. package/dist/lib/workspace/cmux-adapter.js +261 -0
  251. package/dist/lib/workspace/cmux-adapter.js.map +1 -0
  252. package/dist/lib/workspace/data-pipeline.d.ts +35 -0
  253. package/dist/lib/workspace/data-pipeline.d.ts.map +1 -0
  254. package/dist/lib/workspace/data-pipeline.js +463 -0
  255. package/dist/lib/workspace/data-pipeline.js.map +1 -0
  256. package/dist/lib/workspace/engine.d.ts +64 -0
  257. package/dist/lib/workspace/engine.d.ts.map +1 -0
  258. package/dist/lib/workspace/engine.js +397 -0
  259. package/dist/lib/workspace/engine.js.map +1 -0
  260. package/dist/lib/workspace/notifications.d.ts +14 -0
  261. package/dist/lib/workspace/notifications.d.ts.map +1 -0
  262. package/dist/lib/workspace/notifications.js +41 -0
  263. package/dist/lib/workspace/notifications.js.map +1 -0
  264. package/dist/lib/workspace/surface-registry.d.ts +49 -0
  265. package/dist/lib/workspace/surface-registry.d.ts.map +1 -0
  266. package/dist/lib/workspace/surface-registry.js +217 -0
  267. package/dist/lib/workspace/surface-registry.js.map +1 -0
  268. package/dist/lib/workspace/surface-type.d.ts +153 -0
  269. package/dist/lib/workspace/surface-type.d.ts.map +1 -0
  270. package/dist/lib/workspace/surface-type.js +9 -0
  271. package/dist/lib/workspace/surface-type.js.map +1 -0
  272. package/dist/lib/workspace/surfaces/agent-overview.d.ts +16 -0
  273. package/dist/lib/workspace/surfaces/agent-overview.d.ts.map +1 -0
  274. package/dist/lib/workspace/surfaces/agent-overview.js +116 -0
  275. package/dist/lib/workspace/surfaces/agent-overview.js.map +1 -0
  276. package/dist/lib/workspace/surfaces/agent.d.ts +16 -0
  277. package/dist/lib/workspace/surfaces/agent.d.ts.map +1 -0
  278. package/dist/lib/workspace/surfaces/agent.js +112 -0
  279. package/dist/lib/workspace/surfaces/agent.js.map +1 -0
  280. package/dist/lib/workspace/surfaces/claude.d.ts +15 -0
  281. package/dist/lib/workspace/surfaces/claude.d.ts.map +1 -0
  282. package/dist/lib/workspace/surfaces/claude.js +23 -0
  283. package/dist/lib/workspace/surfaces/claude.js.map +1 -0
  284. package/dist/lib/workspace/surfaces/dashboard.d.ts +21 -0
  285. package/dist/lib/workspace/surfaces/dashboard.d.ts.map +1 -0
  286. package/dist/lib/workspace/surfaces/dashboard.js +32 -0
  287. package/dist/lib/workspace/surfaces/dashboard.js.map +1 -0
  288. package/dist/lib/workspace/surfaces/eval.d.ts +15 -0
  289. package/dist/lib/workspace/surfaces/eval.d.ts.map +1 -0
  290. package/dist/lib/workspace/surfaces/eval.js +42 -0
  291. package/dist/lib/workspace/surfaces/eval.js.map +1 -0
  292. package/dist/lib/workspace/surfaces/event-stream.d.ts +16 -0
  293. package/dist/lib/workspace/surfaces/event-stream.d.ts.map +1 -0
  294. package/dist/lib/workspace/surfaces/event-stream.js +40 -0
  295. package/dist/lib/workspace/surfaces/event-stream.js.map +1 -0
  296. package/dist/lib/workspace/surfaces/flow.d.ts +16 -0
  297. package/dist/lib/workspace/surfaces/flow.d.ts.map +1 -0
  298. package/dist/lib/workspace/surfaces/flow.js +49 -0
  299. package/dist/lib/workspace/surfaces/flow.js.map +1 -0
  300. package/dist/lib/workspace/surfaces/index.d.ts +16 -0
  301. package/dist/lib/workspace/surfaces/index.d.ts.map +1 -0
  302. package/dist/lib/workspace/surfaces/index.js +16 -0
  303. package/dist/lib/workspace/surfaces/index.js.map +1 -0
  304. package/dist/lib/workspace/surfaces/portfolio.d.ts +16 -0
  305. package/dist/lib/workspace/surfaces/portfolio.d.ts.map +1 -0
  306. package/dist/lib/workspace/surfaces/portfolio.js +102 -0
  307. package/dist/lib/workspace/surfaces/portfolio.js.map +1 -0
  308. package/dist/lib/workspace/surfaces/service.d.ts +16 -0
  309. package/dist/lib/workspace/surfaces/service.d.ts.map +1 -0
  310. package/dist/lib/workspace/surfaces/service.js +45 -0
  311. package/dist/lib/workspace/surfaces/service.js.map +1 -0
  312. package/dist/lib/workspace/surfaces/shell.d.ts +15 -0
  313. package/dist/lib/workspace/surfaces/shell.d.ts.map +1 -0
  314. package/dist/lib/workspace/surfaces/shell.js +19 -0
  315. package/dist/lib/workspace/surfaces/shell.js.map +1 -0
  316. package/dist/lib/workspace/surfaces/telemetry.d.ts +16 -0
  317. package/dist/lib/workspace/surfaces/telemetry.d.ts.map +1 -0
  318. package/dist/lib/workspace/surfaces/telemetry.js +48 -0
  319. package/dist/lib/workspace/surfaces/telemetry.js.map +1 -0
  320. package/dist/lib/workspace/surfaces/topology.d.ts +15 -0
  321. package/dist/lib/workspace/surfaces/topology.d.ts.map +1 -0
  322. package/dist/lib/workspace/surfaces/topology.js +19 -0
  323. package/dist/lib/workspace/surfaces/topology.js.map +1 -0
  324. package/dist/lib/workspace/surfaces/training.d.ts +16 -0
  325. package/dist/lib/workspace/surfaces/training.d.ts.map +1 -0
  326. package/dist/lib/workspace/surfaces/training.js +22 -0
  327. package/dist/lib/workspace/surfaces/training.js.map +1 -0
  328. package/dist/lib/workspace/tmux-adapter.d.ts +27 -0
  329. package/dist/lib/workspace/tmux-adapter.d.ts.map +1 -0
  330. package/dist/lib/workspace/tmux-adapter.js +106 -0
  331. package/dist/lib/workspace/tmux-adapter.js.map +1 -0
  332. package/dist/mcp/context-hub-mcp.js +7 -24
  333. package/dist/mcp/context-hub-mcp.js.map +1 -1
  334. package/dist/types/flows.d.ts +2 -0
  335. package/dist/types/flows.d.ts.map +1 -1
  336. package/dist/types/ide.d.ts +49 -0
  337. package/dist/types/ide.d.ts.map +1 -0
  338. package/dist/types/ide.js +5 -0
  339. package/dist/types/ide.js.map +1 -0
  340. package/dist/types/platform-digest.d.ts +228 -0
  341. package/dist/types/platform-digest.d.ts.map +1 -0
  342. package/dist/types/platform-digest.js +5 -0
  343. package/dist/types/platform-digest.js.map +1 -0
  344. package/dist/types/telemetry-digest.d.ts +2 -0
  345. package/dist/types/telemetry-digest.d.ts.map +1 -1
  346. package/dist/utils/ensure-project.d.ts +1 -0
  347. package/dist/utils/ensure-project.d.ts.map +1 -1
  348. package/dist/utils/ensure-project.js +19 -7
  349. package/dist/utils/ensure-project.js.map +1 -1
  350. package/dist/utils/jfl-config.d.ts +1 -0
  351. package/dist/utils/jfl-config.d.ts.map +1 -1
  352. package/dist/utils/jfl-config.js +19 -1
  353. package/dist/utils/jfl-config.js.map +1 -1
  354. package/dist/utils/jfl-paths.d.ts +5 -0
  355. package/dist/utils/jfl-paths.d.ts.map +1 -1
  356. package/dist/utils/jfl-paths.js +25 -3
  357. package/dist/utils/jfl-paths.js.map +1 -1
  358. package/package.json +3 -2
  359. package/packages/pi/AGENTS.md +112 -0
  360. package/packages/pi/extensions/agent-grid.ts +191 -0
  361. package/packages/pi/extensions/agent-names.ts +178 -0
  362. package/packages/pi/extensions/autoresearch.ts +427 -0
  363. package/packages/pi/extensions/bookmarks.ts +85 -0
  364. package/packages/pi/extensions/context.ts +184 -0
  365. package/packages/pi/extensions/crm-tool.ts +61 -0
  366. package/packages/pi/extensions/eval-tool.ts +224 -0
  367. package/packages/pi/extensions/eval.ts +60 -0
  368. package/packages/pi/extensions/footer.ts +239 -0
  369. package/packages/pi/extensions/hub-resolver.ts +63 -0
  370. package/packages/pi/extensions/hud-tool.ts +145 -0
  371. package/packages/pi/extensions/index.ts +405 -0
  372. package/packages/pi/extensions/journal.ts +224 -0
  373. package/packages/pi/extensions/map-bridge.ts +178 -0
  374. package/packages/pi/extensions/memory-tool.ts +73 -0
  375. package/packages/pi/extensions/notifications.ts +73 -0
  376. package/packages/pi/extensions/peter-parker.ts +202 -0
  377. package/packages/pi/extensions/policy-head-tool.ts +276 -0
  378. package/packages/pi/extensions/portfolio-bridge.ts +90 -0
  379. package/packages/pi/extensions/session.ts +142 -0
  380. package/packages/pi/extensions/shortcuts.ts +259 -0
  381. package/packages/pi/extensions/stratus-bridge.ts +115 -0
  382. package/packages/pi/extensions/synopsis-tool.ts +83 -0
  383. package/packages/pi/extensions/tool-renderers.ts +353 -0
  384. package/packages/pi/extensions/training-buffer-tool.ts +368 -0
  385. package/packages/pi/extensions/types.ts +163 -0
  386. package/packages/pi/package-lock.json +346 -0
  387. package/packages/pi/package.json +44 -0
  388. package/packages/pi/skills/agent-browser/SKILL.md +116 -0
  389. package/packages/pi/skills/brand-architect/SKILL.md +240 -0
  390. package/packages/pi/skills/brand-architect/config.yaml +137 -0
  391. package/packages/pi/skills/campaign-hud/config.yaml +112 -0
  392. package/packages/pi/skills/content-creator/SKILL.md +294 -0
  393. package/packages/pi/skills/context/SKILL.md +65 -0
  394. package/packages/pi/skills/debug/MULTI_AGENT.md +360 -0
  395. package/packages/pi/skills/debug/SKILL.md +554 -0
  396. package/packages/pi/skills/end/SKILL.md +1782 -0
  397. package/packages/pi/skills/eval/SKILL.md +75 -0
  398. package/packages/pi/skills/fly-deploy/SKILL.md +676 -0
  399. package/packages/pi/skills/founder-video/SKILL.md +467 -0
  400. package/packages/pi/skills/hud/SKILL.md +160 -0
  401. package/packages/pi/skills/orchestrate/SKILL.md +74 -0
  402. package/packages/pi/skills/pi-agents/SKILL.md +78 -0
  403. package/packages/pi/skills/react-best-practices/AGENTS.md +2249 -0
  404. package/packages/pi/skills/react-best-practices/README.md +123 -0
  405. package/packages/pi/skills/react-best-practices/SKILL.md +125 -0
  406. package/packages/pi/skills/react-best-practices/metadata.json +15 -0
  407. package/packages/pi/skills/react-best-practices/rules/_sections.md +46 -0
  408. package/packages/pi/skills/react-best-practices/rules/_template.md +28 -0
  409. package/packages/pi/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  410. package/packages/pi/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
  411. package/packages/pi/skills/react-best-practices/rules/async-api-routes.md +38 -0
  412. package/packages/pi/skills/react-best-practices/rules/async-defer-await.md +80 -0
  413. package/packages/pi/skills/react-best-practices/rules/async-dependencies.md +36 -0
  414. package/packages/pi/skills/react-best-practices/rules/async-parallel.md +28 -0
  415. package/packages/pi/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
  416. package/packages/pi/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
  417. package/packages/pi/skills/react-best-practices/rules/bundle-conditional.md +31 -0
  418. package/packages/pi/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
  419. package/packages/pi/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  420. package/packages/pi/skills/react-best-practices/rules/bundle-preload.md +50 -0
  421. package/packages/pi/skills/react-best-practices/rules/client-event-listeners.md +74 -0
  422. package/packages/pi/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
  423. package/packages/pi/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
  424. package/packages/pi/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
  425. package/packages/pi/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
  426. package/packages/pi/skills/react-best-practices/rules/js-cache-storage.md +70 -0
  427. package/packages/pi/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
  428. package/packages/pi/skills/react-best-practices/rules/js-early-exit.md +50 -0
  429. package/packages/pi/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
  430. package/packages/pi/skills/react-best-practices/rules/js-index-maps.md +37 -0
  431. package/packages/pi/skills/react-best-practices/rules/js-length-check-first.md +49 -0
  432. package/packages/pi/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
  433. package/packages/pi/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
  434. package/packages/pi/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
  435. package/packages/pi/skills/react-best-practices/rules/rendering-activity.md +26 -0
  436. package/packages/pi/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  437. package/packages/pi/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
  438. package/packages/pi/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
  439. package/packages/pi/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  440. package/packages/pi/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  441. package/packages/pi/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
  442. package/packages/pi/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
  443. package/packages/pi/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
  444. package/packages/pi/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
  445. package/packages/pi/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
  446. package/packages/pi/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  447. package/packages/pi/skills/react-best-practices/rules/rerender-memo.md +44 -0
  448. package/packages/pi/skills/react-best-practices/rules/rerender-transitions.md +40 -0
  449. package/packages/pi/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
  450. package/packages/pi/skills/react-best-practices/rules/server-cache-lru.md +41 -0
  451. package/packages/pi/skills/react-best-practices/rules/server-cache-react.md +26 -0
  452. package/packages/pi/skills/react-best-practices/rules/server-parallel-fetching.md +79 -0
  453. package/packages/pi/skills/react-best-practices/rules/server-serialization.md +38 -0
  454. package/packages/pi/skills/remotion-best-practices/SKILL.md +43 -0
  455. package/packages/pi/skills/remotion-best-practices/rules/3d.md +86 -0
  456. package/packages/pi/skills/remotion-best-practices/rules/animations.md +29 -0
  457. package/packages/pi/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
  458. package/packages/pi/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
  459. package/packages/pi/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +108 -0
  460. package/packages/pi/skills/remotion-best-practices/rules/assets.md +78 -0
  461. package/packages/pi/skills/remotion-best-practices/rules/audio.md +172 -0
  462. package/packages/pi/skills/remotion-best-practices/rules/calculate-metadata.md +104 -0
  463. package/packages/pi/skills/remotion-best-practices/rules/can-decode.md +75 -0
  464. package/packages/pi/skills/remotion-best-practices/rules/charts.md +58 -0
  465. package/packages/pi/skills/remotion-best-practices/rules/compositions.md +146 -0
  466. package/packages/pi/skills/remotion-best-practices/rules/display-captions.md +126 -0
  467. package/packages/pi/skills/remotion-best-practices/rules/extract-frames.md +229 -0
  468. package/packages/pi/skills/remotion-best-practices/rules/fonts.md +152 -0
  469. package/packages/pi/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
  470. package/packages/pi/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
  471. package/packages/pi/skills/remotion-best-practices/rules/get-video-duration.md +58 -0
  472. package/packages/pi/skills/remotion-best-practices/rules/gifs.md +138 -0
  473. package/packages/pi/skills/remotion-best-practices/rules/images.md +130 -0
  474. package/packages/pi/skills/remotion-best-practices/rules/import-srt-captions.md +67 -0
  475. package/packages/pi/skills/remotion-best-practices/rules/lottie.md +68 -0
  476. package/packages/pi/skills/remotion-best-practices/rules/measuring-dom-nodes.md +35 -0
  477. package/packages/pi/skills/remotion-best-practices/rules/measuring-text.md +143 -0
  478. package/packages/pi/skills/remotion-best-practices/rules/sequencing.md +106 -0
  479. package/packages/pi/skills/remotion-best-practices/rules/tailwind.md +11 -0
  480. package/packages/pi/skills/remotion-best-practices/rules/text-animations.md +20 -0
  481. package/packages/pi/skills/remotion-best-practices/rules/timing.md +179 -0
  482. package/packages/pi/skills/remotion-best-practices/rules/transcribe-captions.md +19 -0
  483. package/packages/pi/skills/remotion-best-practices/rules/transitions.md +122 -0
  484. package/packages/pi/skills/remotion-best-practices/rules/trimming.md +53 -0
  485. package/packages/pi/skills/remotion-best-practices/rules/videos.md +171 -0
  486. package/packages/pi/skills/search/SKILL.md +220 -0
  487. package/packages/pi/skills/spec/SKILL.md +377 -0
  488. package/packages/pi/skills/startup/SKILL.md +315 -0
  489. package/packages/pi/skills/web-architect/SKILL.md +309 -0
  490. package/packages/pi/skills/x-algorithm/SKILL.md +305 -0
  491. package/packages/pi/teams/dev-team.yaml +63 -0
  492. package/packages/pi/teams/gtm-team.yaml +79 -0
  493. package/packages/pi/themes/jfl.theme.json +76 -0
  494. package/packages/pi/tsconfig.json +21 -0
  495. package/scripts/collect-tuples.sh +124 -0
  496. package/scripts/destroy-fleet.sh +37 -0
  497. package/scripts/jfl-ide.sh +48 -0
  498. package/scripts/session/session-cleanup.sh +4 -11
  499. package/scripts/session/session-init.sh +6 -0
  500. package/scripts/session/session-sync.sh +25 -0
  501. package/scripts/setup-branch-protection.sh +106 -0
  502. package/scripts/spawn-fleet.sh +144 -0
  503. package/scripts/train/requirements.txt +5 -0
  504. package/scripts/train/train-policy-head.py +477 -0
  505. package/scripts/train/v2/dataset.py +81 -0
  506. package/scripts/train/v2/domain.json +18 -0
  507. package/scripts/train/v2/eval.py +196 -0
  508. package/scripts/train/v2/generate_data.py +219 -0
  509. package/scripts/train/v2/infer.py +188 -0
  510. package/scripts/train/v2/model.py +112 -0
  511. package/scripts/train/v2/precompute.py +132 -0
  512. package/scripts/train/v2/train.py +302 -0
  513. package/scripts/train/v2/transform_buffer.py +227 -0
  514. package/scripts/train/v2/validate_data.py +115 -0
  515. package/scripts/train-policy-head.py +434 -0
  516. package/scripts/vm-swarm/README.md +301 -0
  517. package/scripts/vm-swarm/collect-tuples.sh +331 -0
  518. package/scripts/vm-swarm/create-base-template.sh +339 -0
  519. package/scripts/vm-swarm/kill-fleet.sh +204 -0
  520. package/scripts/vm-swarm/monitor-fleet.sh +346 -0
  521. package/scripts/vm-swarm/spawn-fleet.sh +304 -0
  522. package/template/.claude/settings.json +2 -15
  523. package/template/.github/workflows/jfl-eval.yml +6 -1
  524. package/template/.github/workflows/jfl-review.yml +4 -0
  525. package/template/scripts/session/session-cleanup.sh +2 -11
  526. package/template/scripts/session/session-end-hub.sh +72 -0
  527. package/template/scripts/session/session-end.sh +69 -6
  528. package/template/scripts/session/session-init.sh +55 -30
  529. package/template/scripts/session/session-lock.sh +464 -0
  530. package/template/scripts/session/session-start-hub.sh +105 -0
  531. package/template/templates/service-agent/workflows/jfl-eval.yml +19 -0
  532. package/dist/dashboard-static/assets/index-B6kRK9Rq.js +0 -116
  533. package/dist/dashboard-static/assets/index-BpdKJPLu.css +0 -1
@@ -25,6 +25,9 @@ import { FlowEngine } from "../lib/flow-engine.js";
25
25
  import { WebSocketServer } from "ws";
26
26
  import { telemetry } from "../lib/telemetry.js";
27
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";
28
31
  const PID_FILE = ".jfl/context-hub.pid";
29
32
  const LOG_FILE = ".jfl/logs/context-hub.log";
30
33
  const TOKEN_FILE = ".jfl/context-hub.token";
@@ -152,7 +155,11 @@ function readKnowledgeDocs(projectRoot) {
152
155
  "NARRATIVE.md",
153
156
  "THESIS.md",
154
157
  "BRAND_DECISIONS.md",
155
- "TASKS.md"
158
+ "TASKS.md",
159
+ "ARCHITECTURE.md",
160
+ "SERVICE_SPEC.md",
161
+ "DEPLOYMENT.md",
162
+ "RUNBOOK.md",
156
163
  ];
157
164
  for (const filename of priorityFiles) {
158
165
  const filePath = path.join(knowledgeDir, filename);
@@ -163,7 +170,7 @@ function readKnowledgeDocs(projectRoot) {
163
170
  source: "knowledge",
164
171
  type: "doc",
165
172
  title,
166
- content: content.slice(0, 2000), // Truncate for context
173
+ content: content.slice(0, 2000),
167
174
  path: filePath
168
175
  });
169
176
  }
@@ -409,7 +416,7 @@ function getUnifiedContext(projectRoot, query, taskType) {
409
416
  journal: journalItems.length > 0,
410
417
  knowledge: knowledgeItems.length > 0,
411
418
  code: codeItems.length > 0,
412
- memory: false
419
+ memory: fs.existsSync(path.join(projectRoot, ".jfl", "memory.db")),
413
420
  },
414
421
  query,
415
422
  taskType
@@ -540,6 +547,20 @@ function createServer(projectRoot, port, eventBus, flowEngine) {
540
547
  res.end(JSON.stringify({ status: "ok", port }));
541
548
  return;
542
549
  }
550
+ // Setup report — serves .jfl/setup-report.json for dashboard consumption
551
+ if (url.pathname === "/api/setup-report" && req.method === "GET") {
552
+ const reportPath = path.join(projectRoot, ".jfl", "setup-report.json");
553
+ if (fs.existsSync(reportPath)) {
554
+ const report = fs.readFileSync(reportPath, "utf-8");
555
+ res.writeHead(200, { "Content-Type": "application/json" });
556
+ res.end(report);
557
+ }
558
+ else {
559
+ res.writeHead(404, { "Content-Type": "application/json" });
560
+ res.end(JSON.stringify({ error: "No setup report. Run: jfl setup" }));
561
+ }
562
+ return;
563
+ }
543
564
  // Dashboard - served without API auth (has its own token flow in JS)
544
565
  if (url.pathname.startsWith("/dashboard")) {
545
566
  import("../dashboard/index.js").then(({ handleDashboardRoutes }) => {
@@ -766,6 +787,107 @@ function createServer(projectRoot, port, eventBus, flowEngine) {
766
787
  }
767
788
  return;
768
789
  }
790
+ // RAG Chat — search context + memory, then stream LLM response
791
+ if (url.pathname === "/api/chat" && req.method === "POST") {
792
+ let body = "";
793
+ req.on("data", chunk => body += chunk);
794
+ req.on("end", async () => {
795
+ try {
796
+ const { message, history = [] } = JSON.parse(body || "{}");
797
+ if (!message) {
798
+ res.writeHead(400, { "Content-Type": "application/json" });
799
+ res.end(JSON.stringify({ error: "message required" }));
800
+ return;
801
+ }
802
+ const [memRaw, ctxResult] = await Promise.allSettled([
803
+ searchMemories(message, { maxItems: 5 }),
804
+ getPortfolioContext(projectRoot, message, undefined, 5),
805
+ ]);
806
+ const memResults = memRaw.status === "fulfilled"
807
+ ? memRaw.value.map(r => ({ title: r.memory.title, content: r.memory.content, type: r.memory.type, relevance: r.relevance }))
808
+ : [];
809
+ const ctxResults = ctxResult.status === "fulfilled"
810
+ ? (ctxResult.value.items || []).slice(0, 5).map((i) => ({ title: i.title || i.path, content: i.content?.slice(0, 500), type: i.type }))
811
+ : [];
812
+ const sources = [...memResults, ...ctxResults];
813
+ let contextBlock = "";
814
+ if (sources.length > 0) {
815
+ contextBlock = "Here is relevant context from the project:\n\n" +
816
+ sources.map((s, i) => `[${i + 1}] ${s.title} (${s.type})\n${s.content}`).join("\n\n") +
817
+ "\n\n---\n\n";
818
+ }
819
+ // Load .env from project root for API keys
820
+ const envPath = path.join(projectRoot, ".env");
821
+ if (fs.existsSync(envPath)) {
822
+ for (const line of fs.readFileSync(envPath, "utf-8").split("\n")) {
823
+ const match = line.match(/^([A-Z_]+)=(.+)$/);
824
+ if (match && !process.env[match[1]])
825
+ process.env[match[1]] = match[2].trim();
826
+ }
827
+ }
828
+ const apiKey = process.env.STRATUS_API_KEY || process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY;
829
+ if (!apiKey) {
830
+ res.writeHead(200, {
831
+ "Content-Type": "text/event-stream",
832
+ "Cache-Control": "no-cache",
833
+ "Connection": "keep-alive",
834
+ });
835
+ const fallback = sources.length > 0
836
+ ? "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" +
837
+ sources.map((s, i) => `${i + 1}. **${s.title}** \`${s.type}\`\n ${s.content?.slice(0, 150)}`).join("\n")
838
+ : "No API key configured and no relevant context found.";
839
+ res.write(`data: ${JSON.stringify({ sources })}\n\n`);
840
+ res.write(`data: ${JSON.stringify({ delta: fallback })}\n\n`);
841
+ res.write("data: [DONE]\n\n");
842
+ res.end();
843
+ return;
844
+ }
845
+ const useStratus = apiKey === process.env.STRATUS_API_KEY;
846
+ const baseURL = useStratus ? "https://api.stratus.run/v1" : undefined;
847
+ const model = useStratus ? "stratus-x1ac-huge-claude-sonnet-4-6" : "gpt-4o-mini";
848
+ const OpenAI = (await import("openai")).default;
849
+ const client = new OpenAI({ apiKey, baseURL });
850
+ const messages = [
851
+ {
852
+ role: "system",
853
+ 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}`
854
+ },
855
+ ...history.slice(-6).map((m) => ({ role: m.role, content: m.content })),
856
+ { role: "user", content: message },
857
+ ];
858
+ res.writeHead(200, {
859
+ "Content-Type": "text/event-stream",
860
+ "Cache-Control": "no-cache",
861
+ "Connection": "keep-alive",
862
+ });
863
+ res.write(`data: ${JSON.stringify({ sources })}\n\n`);
864
+ try {
865
+ const stream = await client.chat.completions.create({
866
+ model,
867
+ messages,
868
+ stream: true,
869
+ max_tokens: 1024,
870
+ });
871
+ for await (const chunk of stream) {
872
+ const delta = chunk.choices?.[0]?.delta?.content;
873
+ if (delta) {
874
+ res.write(`data: ${JSON.stringify({ delta })}\n\n`);
875
+ }
876
+ }
877
+ }
878
+ catch (llmErr) {
879
+ res.write(`data: ${JSON.stringify({ delta: `\n\nLLM error: ${llmErr.message}` })}\n\n`);
880
+ }
881
+ res.write("data: [DONE]\n\n");
882
+ res.end();
883
+ }
884
+ catch (err) {
885
+ res.writeHead(500, { "Content-Type": "application/json" });
886
+ res.end(JSON.stringify({ error: err.message }));
887
+ }
888
+ });
889
+ return;
890
+ }
769
891
  // Memory search
770
892
  if (url.pathname === "/api/memory/search" && req.method === "POST") {
771
893
  let body = "";
@@ -778,7 +900,17 @@ function createServer(projectRoot, port, eventBus, flowEngine) {
778
900
  res.end(JSON.stringify({ error: "query required" }));
779
901
  return;
780
902
  }
781
- const results = await searchMemories(query, { type, maxItems, since });
903
+ const raw = await searchMemories(query, { type, maxItems, since });
904
+ const results = raw.map(r => ({
905
+ title: r.memory.title,
906
+ content: r.memory.content,
907
+ summary: r.memory.summary,
908
+ type: r.memory.type,
909
+ source: r.memory.source,
910
+ ts: r.memory.created_at,
911
+ score: r.score,
912
+ relevance: r.relevance,
913
+ }));
782
914
  res.writeHead(200, { "Content-Type": "application/json" });
783
915
  res.end(JSON.stringify({ results }));
784
916
  }
@@ -851,6 +983,23 @@ function createServer(projectRoot, port, eventBus, flowEngine) {
851
983
  }
852
984
  return;
853
985
  }
986
+ // Eval entries (all raw entries for cycle display)
987
+ if (url.pathname === "/api/eval/entries" && req.method === "GET") {
988
+ try {
989
+ const { readEvals } = await import("../lib/eval-store.js");
990
+ const limit = parseInt(url.searchParams.get("limit") || "100", 10);
991
+ const entries = readEvals(projectRoot)
992
+ .sort((a, b) => b.ts.localeCompare(a.ts))
993
+ .slice(0, limit);
994
+ res.writeHead(200, { "Content-Type": "application/json" });
995
+ res.end(JSON.stringify({ entries }));
996
+ }
997
+ catch (err) {
998
+ res.writeHead(500, { "Content-Type": "application/json" });
999
+ res.end(JSON.stringify({ error: err.message }));
1000
+ }
1001
+ return;
1002
+ }
854
1003
  // Eval leaderboard
855
1004
  if (url.pathname === "/api/eval/leaderboard" && req.method === "GET") {
856
1005
  try {
@@ -1076,6 +1225,116 @@ function createServer(projectRoot, port, eventBus, flowEngine) {
1076
1225
  }
1077
1226
  return;
1078
1227
  }
1228
+ // ── Autoresearch / Experiments API ──────────────────────────────
1229
+ // Agent configs
1230
+ if (url.pathname === "/api/v1/agents" && req.method === "GET") {
1231
+ try {
1232
+ const { listAgentConfigs, loadAgentConfig } = await import("../lib/agent-config.js");
1233
+ const names = listAgentConfigs(projectRoot);
1234
+ const agents = names.map(name => {
1235
+ try {
1236
+ return loadAgentConfig(projectRoot, name);
1237
+ }
1238
+ catch {
1239
+ return null;
1240
+ }
1241
+ }).filter(Boolean);
1242
+ res.writeHead(200, { "Content-Type": "application/json" });
1243
+ res.end(JSON.stringify({ agents }));
1244
+ }
1245
+ catch (err) {
1246
+ res.writeHead(500, { "Content-Type": "application/json" });
1247
+ res.end(JSON.stringify({ error: err.message }));
1248
+ }
1249
+ return;
1250
+ }
1251
+ // Replay buffer (experiment history)
1252
+ if (url.pathname === "/api/v1/experiments" && req.method === "GET") {
1253
+ try {
1254
+ const bufferPath = path.join(projectRoot, ".jfl", "replay-buffer.jsonl");
1255
+ const trainingPath = path.join(projectRoot, ".jfl", "training-buffer.jsonl");
1256
+ const experiments = [];
1257
+ for (const p of [bufferPath, trainingPath]) {
1258
+ if (fs.existsSync(p)) {
1259
+ const lines = fs.readFileSync(p, "utf-8").trim().split("\n").filter(Boolean);
1260
+ for (const line of lines.slice(-100)) {
1261
+ try {
1262
+ experiments.push(JSON.parse(line));
1263
+ }
1264
+ catch { }
1265
+ }
1266
+ }
1267
+ }
1268
+ const agent = url.searchParams.get("agent");
1269
+ const filtered = agent ? experiments.filter(e => e.agent === agent) : experiments;
1270
+ res.writeHead(200, { "Content-Type": "application/json" });
1271
+ res.end(JSON.stringify({ experiments: filtered, total: filtered.length }));
1272
+ }
1273
+ catch (err) {
1274
+ res.writeHead(500, { "Content-Type": "application/json" });
1275
+ res.end(JSON.stringify({ error: err.message }));
1276
+ }
1277
+ return;
1278
+ }
1279
+ // Session results
1280
+ if (url.pathname === "/api/v1/sessions" && req.method === "GET") {
1281
+ try {
1282
+ const sessionsDir = path.join(projectRoot, ".jfl", "sessions");
1283
+ const sessions = [];
1284
+ if (fs.existsSync(sessionsDir)) {
1285
+ for (const dir of fs.readdirSync(sessionsDir)) {
1286
+ const resultsPath = path.join(sessionsDir, dir, "results.tsv");
1287
+ if (fs.existsSync(resultsPath)) {
1288
+ const content = fs.readFileSync(resultsPath, "utf-8").trim();
1289
+ const lines = content.split("\n").slice(1); // skip header
1290
+ const rounds = lines.filter(l => l.trim()).map(line => {
1291
+ const [round, task, baseline, metric, delta, kept, duration, error, timestamp] = line.split("\t");
1292
+ return { round: +round, task, baseline: +baseline, metric: +metric, delta: +delta, kept: kept === "1", duration_ms: +duration, error, timestamp };
1293
+ });
1294
+ if (rounds.length > 0) {
1295
+ sessions.push({ id: dir, rounds, agent: dir.replace(/-[a-f0-9]{8}-\d+$/, "") });
1296
+ }
1297
+ }
1298
+ }
1299
+ }
1300
+ res.writeHead(200, { "Content-Type": "application/json" });
1301
+ res.end(JSON.stringify({ sessions }));
1302
+ }
1303
+ catch (err) {
1304
+ res.writeHead(500, { "Content-Type": "application/json" });
1305
+ res.end(JSON.stringify({ error: err.message }));
1306
+ }
1307
+ return;
1308
+ }
1309
+ // Product context
1310
+ if (url.pathname === "/api/v1/product-context" && req.method === "GET") {
1311
+ const contextPath = path.join(projectRoot, ".jfl", "product-context.md");
1312
+ if (fs.existsSync(contextPath)) {
1313
+ const content = fs.readFileSync(contextPath, "utf-8");
1314
+ res.writeHead(200, { "Content-Type": "application/json" });
1315
+ res.end(JSON.stringify({ context: content, updatedAt: fs.statSync(contextPath).mtime.toISOString() }));
1316
+ }
1317
+ else {
1318
+ res.writeHead(200, { "Content-Type": "application/json" });
1319
+ res.end(JSON.stringify({ context: null, updatedAt: null }));
1320
+ }
1321
+ return;
1322
+ }
1323
+ // Product analysis (telemetry agent v2)
1324
+ if (url.pathname === "/api/v1/product-analysis" && req.method === "GET") {
1325
+ try {
1326
+ const { TelemetryAgentV2 } = await import("../lib/telemetry-agent-v2.js");
1327
+ const agent = new TelemetryAgentV2({ projectRoot });
1328
+ const analysis = await agent.analyzeProduct();
1329
+ res.writeHead(200, { "Content-Type": "application/json" });
1330
+ res.end(JSON.stringify(analysis));
1331
+ }
1332
+ catch (err) {
1333
+ res.writeHead(500, { "Content-Type": "application/json" });
1334
+ res.end(JSON.stringify({ error: err.message }));
1335
+ }
1336
+ return;
1337
+ }
1079
1338
  // Flow definitions
1080
1339
  if (url.pathname === "/api/flows" && req.method === "GET") {
1081
1340
  if (!flowEngine) {
@@ -1160,6 +1419,348 @@ function createServer(projectRoot, port, eventBus, flowEngine) {
1160
1419
  });
1161
1420
  return;
1162
1421
  }
1422
+ // Topology — returns nodes/edges for agent topology visualization
1423
+ if (url.pathname === "/api/v1/topology" && req.method === "GET") {
1424
+ try {
1425
+ const configPath = path.join(projectRoot, ".jfl", "config.json");
1426
+ let registeredServices = [];
1427
+ let workspaceType = "standalone";
1428
+ let workspaceName = "workspace";
1429
+ if (fs.existsSync(configPath)) {
1430
+ try {
1431
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
1432
+ registeredServices = config.registered_services || [];
1433
+ workspaceType = config.type || "standalone";
1434
+ workspaceName = config.name || "workspace";
1435
+ }
1436
+ catch { }
1437
+ }
1438
+ // Read event counts from map-events.jsonl
1439
+ const mapEventsPath = path.join(projectRoot, ".jfl", "map-events.jsonl");
1440
+ const eventCounts = {};
1441
+ const edgeEventCounts = {}; // "source:target:eventType" -> count
1442
+ const recentWindow = 24 * 60 * 60 * 1000; // 24 hours
1443
+ if (fs.existsSync(mapEventsPath)) {
1444
+ try {
1445
+ const lines = fs.readFileSync(mapEventsPath, "utf-8").trim().split("\n");
1446
+ const now = Date.now();
1447
+ for (const line of lines.slice(-1000)) { // Last 1000 events
1448
+ if (!line)
1449
+ continue;
1450
+ try {
1451
+ const evt = JSON.parse(line);
1452
+ const ts = new Date(evt.ts).getTime();
1453
+ if (now - ts > recentWindow)
1454
+ continue;
1455
+ // Count events by source
1456
+ if (evt.source) {
1457
+ const srcId = evt.source.toLowerCase().replace(/[^a-z0-9-]/g, "-");
1458
+ eventCounts[srcId] = (eventCounts[srcId] || 0) + 1;
1459
+ }
1460
+ // Count events by type prefix (for edge matching)
1461
+ if (evt.type) {
1462
+ const prefix = evt.type.split(":")[0];
1463
+ eventCounts[prefix] = (eventCounts[prefix] || 0) + 1;
1464
+ }
1465
+ }
1466
+ catch { }
1467
+ }
1468
+ }
1469
+ catch { }
1470
+ }
1471
+ // System agents (always present in a JFL installation)
1472
+ // Only Peter Parker is a real system agent — others come from .jfl/agents/*.toml configs
1473
+ // Stratus, eval-engine, telemetry-agent are either external infra or RL agents now
1474
+ const systemAgents = [
1475
+ { 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"] },
1476
+ ];
1477
+ const nodes = [
1478
+ ...systemAgents.map(a => ({
1479
+ id: a.id,
1480
+ label: a.label,
1481
+ type: a.type,
1482
+ status: a.status,
1483
+ eventCount: eventCounts[a.id] || eventCounts[a.id.split("-")[0]] || 0,
1484
+ produces: a.produces,
1485
+ consumes: a.consumes,
1486
+ })),
1487
+ ];
1488
+ // Add registered services as nodes
1489
+ for (const service of registeredServices) {
1490
+ if (!nodes.find(n => n.id === service.name)) {
1491
+ const nodeType = service.type === "agent" ? "agent"
1492
+ : service.type === "eval" ? "eval"
1493
+ : service.type === "gtm" ? "gtm"
1494
+ : "service";
1495
+ nodes.push({
1496
+ id: service.name,
1497
+ label: service.name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" "),
1498
+ type: nodeType,
1499
+ status: service.status === "running" ? "running" : service.status === "idle" ? "idle" : "stopped",
1500
+ eventCount: eventCounts[service.name] || 0,
1501
+ produces: service.context_scope?.produces,
1502
+ consumes: service.context_scope?.consumes,
1503
+ });
1504
+ }
1505
+ }
1506
+ // Add RL agent nodes from .jfl/agents/*.toml configs
1507
+ try {
1508
+ const rlAgentConfigs = loadAllAgentConfigs(projectRoot);
1509
+ const trainingBuffer = new TrainingBuffer(projectRoot);
1510
+ const trainingEntries = trainingBuffer.read();
1511
+ for (const config of rlAgentConfigs) {
1512
+ const nodeId = `rl-agent-${config.name}`;
1513
+ // Skip if node already exists (e.g., matches a registered service name)
1514
+ if (nodes.find(n => n.id === nodeId || n.id === config.name)) {
1515
+ continue;
1516
+ }
1517
+ // Check for recent training data (within last 24h)
1518
+ const now = Date.now();
1519
+ const recentWindow = 24 * 60 * 60 * 1000;
1520
+ const recentEntries = trainingEntries.filter(e => {
1521
+ if (e.agent !== config.name)
1522
+ return false;
1523
+ const ts = new Date(e.ts).getTime();
1524
+ return now - ts < recentWindow;
1525
+ });
1526
+ const status = recentEntries.length > 0 ? "running" : "idle";
1527
+ // Convert name to proper label (e.g., "cli-speed" -> "Cli Speed")
1528
+ const label = config.name
1529
+ .split("-")
1530
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
1531
+ .join(" ");
1532
+ nodes.push({
1533
+ id: nodeId,
1534
+ label,
1535
+ type: "agent",
1536
+ status,
1537
+ eventCount: recentEntries.length,
1538
+ produces: config.context_scope?.produces,
1539
+ consumes: config.context_scope?.consumes,
1540
+ });
1541
+ }
1542
+ }
1543
+ catch (err) {
1544
+ // Non-fatal: RL agents are optional
1545
+ }
1546
+ // For portfolio mode, fetch child GTM services and their registered services
1547
+ const childHubs = getChildHubs(projectRoot);
1548
+ const hierarchy = {
1549
+ gtms: [],
1550
+ };
1551
+ if (workspaceType === "portfolio" && childHubs.length > 0) {
1552
+ hierarchy.portfolio = workspaceName;
1553
+ for (const child of childHubs) {
1554
+ const gtmServices = [];
1555
+ // Try to read child's config for its registered services
1556
+ const childConfigPath = path.join(child.path, ".jfl", "config.json");
1557
+ if (fs.existsSync(childConfigPath)) {
1558
+ try {
1559
+ const childConfig = JSON.parse(fs.readFileSync(childConfigPath, "utf-8"));
1560
+ const childServices = childConfig.registered_services || [];
1561
+ for (const svc of childServices) {
1562
+ const svcId = `${child.name}/${svc.name}`;
1563
+ gtmServices.push(svc.name);
1564
+ // Add child services as nodes (if not already present)
1565
+ if (!nodes.find(n => n.id === svcId)) {
1566
+ nodes.push({
1567
+ id: svcId,
1568
+ label: `${svc.name}`,
1569
+ type: svc.type === "agent" ? "agent" : "service",
1570
+ status: "idle", // We don't know remote status
1571
+ eventCount: 0,
1572
+ produces: svc.context_scope?.produces,
1573
+ consumes: svc.context_scope?.consumes,
1574
+ parent: child.name,
1575
+ });
1576
+ }
1577
+ }
1578
+ }
1579
+ catch { }
1580
+ }
1581
+ hierarchy.gtms.push({
1582
+ name: child.name,
1583
+ port: child.port,
1584
+ services: gtmServices,
1585
+ });
1586
+ // Add GTM node if not present
1587
+ if (!nodes.find(n => n.id === child.name)) {
1588
+ nodes.push({
1589
+ id: child.name,
1590
+ label: child.name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" "),
1591
+ type: "gtm",
1592
+ status: "running",
1593
+ eventCount: 0,
1594
+ children: gtmServices,
1595
+ });
1596
+ }
1597
+ }
1598
+ }
1599
+ // Build edges from produces/consumes relationships
1600
+ const edges = [];
1601
+ let edgeId = 0;
1602
+ // Create edges between services based on scope patterns
1603
+ for (const producer of nodes) {
1604
+ if (!producer.produces)
1605
+ continue;
1606
+ for (const eventPattern of producer.produces) {
1607
+ for (const consumer of nodes) {
1608
+ if (producer.id === consumer.id || !consumer.consumes)
1609
+ continue;
1610
+ for (const consumePattern of consumer.consumes) {
1611
+ // Check if patterns match (simple glob matching)
1612
+ const patternMatches = (prod, cons) => {
1613
+ if (cons.endsWith(":*")) {
1614
+ return prod.startsWith(cons.slice(0, -1));
1615
+ }
1616
+ if (cons === "*")
1617
+ return true;
1618
+ return prod === cons || prod.startsWith(cons.split(":")[0] + ":");
1619
+ };
1620
+ if (patternMatches(eventPattern, consumePattern)) {
1621
+ const existing = edges.find(e => e.source === producer.id && e.target === consumer.id && e.eventType === eventPattern);
1622
+ if (!existing) {
1623
+ const edgeKey = `${producer.id}:${consumer.id}:${eventPattern}`;
1624
+ const prefix = eventPattern.split(":")[0];
1625
+ edges.push({
1626
+ id: `e${++edgeId}`,
1627
+ source: producer.id,
1628
+ target: consumer.id,
1629
+ eventType: eventPattern,
1630
+ category: eventPattern.includes("eval") || eventPattern.includes("scored") ? "success"
1631
+ : eventPattern.includes("peter") || eventPattern.includes("rollout") ? "rl"
1632
+ : "data",
1633
+ recentEvents: edgeEventCounts[edgeKey] || eventCounts[prefix] || 0,
1634
+ });
1635
+ }
1636
+ }
1637
+ }
1638
+ }
1639
+ }
1640
+ }
1641
+ // Add known system flow edges that might not be captured by scope
1642
+ const systemEdges = [
1643
+ { source: "telemetry-agent", target: "peter-parker", eventType: "telemetry:insight", category: "data" },
1644
+ { source: "peter-parker", target: "eval-engine", eventType: "peter:task-completed", category: "rl" },
1645
+ { source: "eval-engine", target: "telemetry-agent", eventType: "eval:scored", category: "success" },
1646
+ { source: "peter-parker", target: "stratus", eventType: "peter:rollout-request", category: "rl" },
1647
+ { source: "stratus", target: "eval-engine", eventType: "stratus:prediction", category: "success" },
1648
+ ];
1649
+ for (const sysEdge of systemEdges) {
1650
+ const exists = edges.find(e => e.source === sysEdge.source && e.target === sysEdge.target);
1651
+ if (!exists && nodes.find(n => n.id === sysEdge.source) && nodes.find(n => n.id === sysEdge.target)) {
1652
+ const prefix = sysEdge.eventType.split(":")[0];
1653
+ edges.push({
1654
+ id: `e${++edgeId}`,
1655
+ ...sysEdge,
1656
+ recentEvents: eventCounts[prefix] || 0,
1657
+ });
1658
+ }
1659
+ }
1660
+ res.writeHead(200, { "Content-Type": "application/json" });
1661
+ res.end(JSON.stringify({
1662
+ nodes,
1663
+ edges,
1664
+ hierarchy: workspaceType === "portfolio" ? hierarchy : undefined,
1665
+ workspaceType,
1666
+ workspaceName,
1667
+ }));
1668
+ }
1669
+ catch (err) {
1670
+ res.writeHead(500, { "Content-Type": "application/json" });
1671
+ res.end(JSON.stringify({ error: err.message }));
1672
+ }
1673
+ return;
1674
+ }
1675
+ // Autoresearch status — returns current autoresearch run state
1676
+ if (url.pathname === "/api/v1/autoresearch/status" && req.method === "GET") {
1677
+ try {
1678
+ const status = {
1679
+ running: false,
1680
+ currentRound: 0,
1681
+ totalRounds: 0,
1682
+ baselineComposite: null,
1683
+ proposals: [],
1684
+ dimensions: {},
1685
+ history: [],
1686
+ lastUpdate: null,
1687
+ };
1688
+ // Try to read from log files
1689
+ const logPaths = [
1690
+ path.join(projectRoot, ".jfl", "autoresearch-continuous.log"),
1691
+ path.join(projectRoot, ".jfl", "autoresearch-overnight.log"),
1692
+ ];
1693
+ let logContent = "";
1694
+ let logPath = "";
1695
+ for (const p of logPaths) {
1696
+ if (fs.existsSync(p)) {
1697
+ const stat = fs.statSync(p);
1698
+ // Use the most recently modified log
1699
+ if (!logPath || stat.mtimeMs > fs.statSync(logPath).mtimeMs) {
1700
+ logPath = p;
1701
+ }
1702
+ }
1703
+ }
1704
+ if (logPath) {
1705
+ logContent = fs.readFileSync(logPath, "utf-8");
1706
+ status.lastUpdate = fs.statSync(logPath).mtime.toISOString();
1707
+ // Parse total rounds from header
1708
+ const roundsMatch = logContent.match(/Autoresearch Mode \((\d+) rounds\)/);
1709
+ if (roundsMatch) {
1710
+ status.totalRounds = parseInt(roundsMatch[1], 10);
1711
+ }
1712
+ // Parse baseline
1713
+ const baselineMatch = logContent.match(/Baseline composite: ([\d.]+)/);
1714
+ if (baselineMatch) {
1715
+ status.baselineComposite = parseFloat(baselineMatch[1]);
1716
+ }
1717
+ // Parse latest round number
1718
+ const roundMatches = [...logContent.matchAll(/── Round (\d+)\/\d+ ──/g)];
1719
+ if (roundMatches.length > 0) {
1720
+ status.currentRound = parseInt(roundMatches[roundMatches.length - 1][1], 10);
1721
+ }
1722
+ // Parse policy head proposals (get the latest set)
1723
+ const proposalBlocks = [...logContent.matchAll(/Policy head re-ranked 3 proposals.*?\n([\s\S]*?)(?=\n\s*Task:|$)/g)];
1724
+ if (proposalBlocks.length > 0) {
1725
+ const latestBlock = proposalBlocks[proposalBlocks.length - 1][1];
1726
+ const proposalMatches = [...latestBlock.matchAll(/#(\d+) \[pred=([-\d.]+)\] ([^\n]+)/g)];
1727
+ status.proposals = proposalMatches.map(m => ({
1728
+ rank: parseInt(m[1], 10),
1729
+ predicted: parseFloat(m[2]),
1730
+ description: m[3].trim(),
1731
+ }));
1732
+ }
1733
+ // Parse dimensions from latest eval
1734
+ const dimMatches = [...logContent.matchAll(/Dimensions: (tests=[\d.]+ tsc=[\d.]+ lint=[\d.]+ telemetry=[\d.]+ newTests=[\d.]+)/g)];
1735
+ if (dimMatches.length > 0) {
1736
+ const latest = dimMatches[dimMatches.length - 1][1];
1737
+ for (const [, key, val] of latest.matchAll(/(tests|tsc|lint|telemetry|newTests)=([\d.]+)/g)) {
1738
+ status.dimensions[key] = parseFloat(val);
1739
+ }
1740
+ }
1741
+ // Parse round results for history
1742
+ const resultMatches = [...logContent.matchAll(/Round (\d+) result: ([\d.]+) \(([-=+][\d.]+)\)\s*\n\s*Tests: (\d+\/\d+)/g)];
1743
+ status.history = resultMatches.map(m => ({
1744
+ round: parseInt(m[1], 10),
1745
+ composite: parseFloat(m[2]),
1746
+ delta: m[3].startsWith("=") ? 0 : parseFloat(m[3]),
1747
+ tests: m[4],
1748
+ }));
1749
+ // Check if running (log modified in last 5 minutes and no completion message)
1750
+ const fiveMinAgo = Date.now() - 5 * 60 * 1000;
1751
+ const logMtime = fs.statSync(logPath).mtimeMs;
1752
+ const hasCompletionMsg = logContent.includes("All rounds complete") || logContent.includes("Autoresearch finished");
1753
+ status.running = logMtime > fiveMinAgo && !hasCompletionMsg && status.currentRound > 0;
1754
+ }
1755
+ res.writeHead(200, { "Content-Type": "application/json" });
1756
+ res.end(JSON.stringify(status));
1757
+ }
1758
+ catch (err) {
1759
+ res.writeHead(500, { "Content-Type": "application/json" });
1760
+ res.end(JSON.stringify({ error: err.message }));
1761
+ }
1762
+ return;
1763
+ }
1163
1764
  if (url.pathname === "/api/actions/spawn" && req.method === "POST") {
1164
1765
  let body = "";
1165
1766
  req.on("data", chunk => body += chunk);
@@ -1198,6 +1799,359 @@ function createServer(projectRoot, port, eventBus, flowEngine) {
1198
1799
  });
1199
1800
  return;
1200
1801
  }
1802
+ // ── Session Parity API ────────────────────────────────────────────
1803
+ // These endpoints provide runtime-agnostic session lifecycle.
1804
+ // Both Claude Code hooks and Pi extensions call these instead of
1805
+ // duplicating logic. Single source of truth for session init/end.
1806
+ // POST /api/session/init — sync repos, create branch, run doctor
1807
+ if (url.pathname === "/api/session/init" && req.method === "POST") {
1808
+ let body = "";
1809
+ req.on("data", (chunk) => body += chunk);
1810
+ req.on("end", async () => {
1811
+ try {
1812
+ const { runtime } = JSON.parse(body || "{}");
1813
+ const warnings = [];
1814
+ const scriptDir = path.join(projectRoot, "scripts", "session");
1815
+ // Step 1: Sync repos
1816
+ const syncScript = path.join(scriptDir, "session-sync.sh");
1817
+ let syncOk = true;
1818
+ if (fs.existsSync(syncScript)) {
1819
+ try {
1820
+ execSync(`bash "${syncScript}"`, { cwd: projectRoot, timeout: 60000, stdio: ["pipe", "pipe", "pipe"] });
1821
+ }
1822
+ catch (err) {
1823
+ syncOk = false;
1824
+ warnings.push(`Sync warning: ${(err.message || "").split("\n")[0]}`);
1825
+ }
1826
+ }
1827
+ // Step 2: Doctor check
1828
+ const doctorScript = path.join(scriptDir, "jfl-doctor.sh");
1829
+ let doctorErrors = 0;
1830
+ let doctorWarnings = 0;
1831
+ if (fs.existsSync(doctorScript)) {
1832
+ try {
1833
+ const doctorOut = execSync(`bash "${doctorScript}"`, { cwd: projectRoot, timeout: 30000, stdio: ["pipe", "pipe", "pipe"] }).toString();
1834
+ const em = doctorOut.match(/(\d+) error\(s\)/);
1835
+ const wm = doctorOut.match(/(\d+) warning\(s\)/);
1836
+ doctorErrors = em ? parseInt(em[1]) : 0;
1837
+ doctorWarnings = wm ? parseInt(wm[1]) : 0;
1838
+ if (doctorErrors > 0)
1839
+ warnings.push(`Doctor: ${doctorErrors} errors`);
1840
+ }
1841
+ catch { }
1842
+ }
1843
+ // Step 3: Create session branch
1844
+ let currentBranch = "";
1845
+ try {
1846
+ currentBranch = execSync("git branch --show-current", { cwd: projectRoot, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
1847
+ }
1848
+ catch { }
1849
+ let sessionBranch = currentBranch;
1850
+ if (!currentBranch.startsWith("session-")) {
1851
+ const user = (() => {
1852
+ try {
1853
+ return execSync("git config user.name", { cwd: projectRoot, stdio: ["pipe", "pipe", "pipe"] })
1854
+ .toString().trim().replace(/\s+/g, "-").toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0, 30) || "user";
1855
+ }
1856
+ catch {
1857
+ return "user";
1858
+ }
1859
+ })();
1860
+ const now = new Date();
1861
+ const dateStr = now.toISOString().slice(0, 10).replace(/-/g, "");
1862
+ const timeStr = now.toISOString().slice(11, 16).replace(":", "");
1863
+ const randomId = Math.random().toString(16).slice(2, 8);
1864
+ sessionBranch = `session-${user}-${dateStr}-${timeStr}-${randomId}`;
1865
+ try {
1866
+ execSync(`git checkout -b "${sessionBranch}"`, { cwd: projectRoot, stdio: ["pipe", "pipe", "pipe"] });
1867
+ }
1868
+ catch {
1869
+ sessionBranch = currentBranch || "main";
1870
+ warnings.push("Could not create session branch");
1871
+ }
1872
+ }
1873
+ // Step 4: Save session info
1874
+ const jflDir = path.join(projectRoot, ".jfl");
1875
+ fs.mkdirSync(path.join(jflDir, "logs"), { recursive: true });
1876
+ fs.mkdirSync(path.join(jflDir, "journal"), { recursive: true });
1877
+ fs.writeFileSync(path.join(jflDir, "current-session-branch.txt"), sessionBranch);
1878
+ fs.writeFileSync(path.join(jflDir, "current-worktree.txt"), "direct");
1879
+ // Emit session start event
1880
+ if (eventBus) {
1881
+ eventBus.emit({
1882
+ type: "session:started",
1883
+ source: `hub:${runtime || "unknown"}`,
1884
+ data: { branch: sessionBranch, runtime, warnings },
1885
+ });
1886
+ }
1887
+ res.writeHead(200, { "Content-Type": "application/json" });
1888
+ res.end(JSON.stringify({
1889
+ ok: true,
1890
+ branch: sessionBranch,
1891
+ syncOk,
1892
+ doctor: { errors: doctorErrors, warnings: doctorWarnings },
1893
+ warnings,
1894
+ }));
1895
+ }
1896
+ catch (err) {
1897
+ res.writeHead(500, { "Content-Type": "application/json" });
1898
+ res.end(JSON.stringify({ error: err.message }));
1899
+ }
1900
+ });
1901
+ return;
1902
+ }
1903
+ // POST /api/prompt — get system prompt injection (CLAUDE.md + context)
1904
+ if (url.pathname === "/api/prompt" && req.method === "POST") {
1905
+ let body = "";
1906
+ req.on("data", (chunk) => body += chunk);
1907
+ req.on("end", async () => {
1908
+ try {
1909
+ const { taskType, maxItems } = JSON.parse(body || "{}");
1910
+ const parts = [];
1911
+ // 1. Load CLAUDE.md
1912
+ const claudeMdPath = path.join(projectRoot, "CLAUDE.md");
1913
+ if (fs.existsSync(claudeMdPath)) {
1914
+ const content = fs.readFileSync(claudeMdPath, "utf-8");
1915
+ if (content.length > 50000) {
1916
+ const sections = content.split(/^## /m);
1917
+ const critical = sections.filter((s) => /CRITICAL|Session Sync|Journal Protocol|Immediate Decision|Working Mode|Core Architecture/i.test(s.slice(0, 100)));
1918
+ if (critical.length > 0) {
1919
+ parts.push("# Project Instructions (CLAUDE.md — critical sections)\n");
1920
+ parts.push("## " + critical.join("\n\n## "));
1921
+ }
1922
+ else {
1923
+ parts.push("# Project Instructions (CLAUDE.md — truncated)\n");
1924
+ parts.push(content.slice(0, 30000));
1925
+ }
1926
+ }
1927
+ else {
1928
+ parts.push("# Project Instructions (CLAUDE.md)\n");
1929
+ parts.push(content);
1930
+ }
1931
+ }
1932
+ // 2. Load recent context
1933
+ try {
1934
+ const contextItems = readJournalEntries(projectRoot, maxItems ?? 20);
1935
+ if (contextItems.length > 0) {
1936
+ parts.push("\n## Recent Project Context\n");
1937
+ parts.push(contextItems.map(item => {
1938
+ const prefix = item.source ? `[${item.source}] ` : "";
1939
+ return `${prefix}${item.content}`;
1940
+ }).join("\n\n"));
1941
+ }
1942
+ }
1943
+ catch { }
1944
+ // 3. Load knowledge docs summaries
1945
+ const knowledgeDir = path.join(projectRoot, "knowledge");
1946
+ if (fs.existsSync(knowledgeDir)) {
1947
+ const knowledgeDocs = ["VISION.md", "ROADMAP.md", "NARRATIVE.md", "THESIS.md"];
1948
+ const summaries = [];
1949
+ for (const doc of knowledgeDocs) {
1950
+ const docPath = path.join(knowledgeDir, doc);
1951
+ if (fs.existsSync(docPath)) {
1952
+ const content = fs.readFileSync(docPath, "utf-8");
1953
+ if (content.length > 100) {
1954
+ summaries.push(`### ${doc}\n${content.slice(0, 500)}${content.length > 500 ? "\n..." : ""}`);
1955
+ }
1956
+ }
1957
+ }
1958
+ if (summaries.length > 0) {
1959
+ parts.push("\n## Knowledge Documents\n");
1960
+ parts.push(summaries.join("\n\n"));
1961
+ }
1962
+ }
1963
+ res.writeHead(200, { "Content-Type": "application/json" });
1964
+ res.end(JSON.stringify({
1965
+ prompt: parts.join("\n"),
1966
+ claudeMdSize: fs.existsSync(path.join(projectRoot, "CLAUDE.md"))
1967
+ ? fs.statSync(path.join(projectRoot, "CLAUDE.md")).size : 0,
1968
+ taskType: taskType ?? "general",
1969
+ }));
1970
+ }
1971
+ catch (err) {
1972
+ res.writeHead(500, { "Content-Type": "application/json" });
1973
+ res.end(JSON.stringify({ error: err.message }));
1974
+ }
1975
+ });
1976
+ return;
1977
+ }
1978
+ // POST /api/session/end — cleanup session (merge, commit, etc)
1979
+ if (url.pathname === "/api/session/end" && req.method === "POST") {
1980
+ let body = "";
1981
+ req.on("data", (chunk) => body += chunk);
1982
+ req.on("end", async () => {
1983
+ try {
1984
+ const { runtime, skipCleanup } = JSON.parse(body || "{}");
1985
+ // Check journal exists
1986
+ let hasJournal = false;
1987
+ try {
1988
+ const branch = execSync("git branch --show-current", { cwd: projectRoot, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
1989
+ const journalPath = path.join(projectRoot, ".jfl", "journal", `${branch}.jsonl`);
1990
+ hasJournal = fs.existsSync(journalPath) && fs.statSync(journalPath).size > 0;
1991
+ }
1992
+ catch { }
1993
+ let cleanupResult = "skipped";
1994
+ if (!skipCleanup) {
1995
+ const cleanupScript = path.join(projectRoot, "scripts", "session", "session-cleanup.sh");
1996
+ if (fs.existsSync(cleanupScript)) {
1997
+ try {
1998
+ execSync(`bash "${cleanupScript}"`, { cwd: projectRoot, timeout: 60000, stdio: ["pipe", "pipe", "pipe"] });
1999
+ cleanupResult = "ok";
2000
+ }
2001
+ catch (err) {
2002
+ cleanupResult = `error: ${(err.message || "").split("\n")[0]}`;
2003
+ }
2004
+ }
2005
+ else {
2006
+ cleanupResult = "no cleanup script";
2007
+ }
2008
+ }
2009
+ // Emit session end event
2010
+ if (eventBus) {
2011
+ eventBus.emit({
2012
+ type: "session:ended",
2013
+ source: `hub:${runtime || "unknown"}`,
2014
+ data: { hasJournal, cleanupResult, runtime },
2015
+ });
2016
+ }
2017
+ res.writeHead(200, { "Content-Type": "application/json" });
2018
+ res.end(JSON.stringify({
2019
+ ok: true,
2020
+ hasJournal,
2021
+ cleanupResult,
2022
+ warnings: hasJournal ? [] : ["No journal entry for this session"],
2023
+ }));
2024
+ }
2025
+ catch (err) {
2026
+ res.writeHead(500, { "Content-Type": "application/json" });
2027
+ res.end(JSON.stringify({ error: err.message }));
2028
+ }
2029
+ });
2030
+ return;
2031
+ }
2032
+ // ── Findings API ──────────────────────────────────────────────────
2033
+ // GET /api/v1/findings — list current findings
2034
+ if (url.pathname === "/api/v1/findings" && req.method === "GET") {
2035
+ try {
2036
+ const engine = new FindingsEngine(projectRoot);
2037
+ const refresh = url.searchParams.get("refresh") === "true";
2038
+ let findings;
2039
+ if (refresh) {
2040
+ findings = await engine.analyze();
2041
+ }
2042
+ else {
2043
+ findings = engine.getFindings();
2044
+ // Auto-analyze if no findings exist
2045
+ if (findings.length === 0) {
2046
+ findings = await engine.analyze();
2047
+ }
2048
+ }
2049
+ // Filter out dismissed unless ?include_dismissed=true
2050
+ const includeDismissed = url.searchParams.get("include_dismissed") === "true";
2051
+ if (!includeDismissed) {
2052
+ findings = findings.filter(f => !f.dismissed);
2053
+ }
2054
+ res.writeHead(200, { "Content-Type": "application/json" });
2055
+ res.end(JSON.stringify({ findings, total: findings.length }));
2056
+ }
2057
+ catch (err) {
2058
+ res.writeHead(500, { "Content-Type": "application/json" });
2059
+ res.end(JSON.stringify({ error: err.message }));
2060
+ }
2061
+ return;
2062
+ }
2063
+ // POST /api/v1/findings/:id/dismiss — dismiss a finding
2064
+ if (url.pathname.match(/^\/api\/v1\/findings\/[^/]+\/dismiss$/) && req.method === "POST") {
2065
+ try {
2066
+ const findingId = decodeURIComponent(url.pathname.split("/")[4]);
2067
+ const engine = new FindingsEngine(projectRoot);
2068
+ const success = engine.dismissFinding(findingId);
2069
+ if (success) {
2070
+ res.writeHead(200, { "Content-Type": "application/json" });
2071
+ res.end(JSON.stringify({ ok: true, dismissed: findingId }));
2072
+ }
2073
+ else {
2074
+ res.writeHead(404, { "Content-Type": "application/json" });
2075
+ res.end(JSON.stringify({ error: "Finding not found" }));
2076
+ }
2077
+ }
2078
+ catch (err) {
2079
+ res.writeHead(500, { "Content-Type": "application/json" });
2080
+ res.end(JSON.stringify({ error: err.message }));
2081
+ }
2082
+ return;
2083
+ }
2084
+ // POST /api/v1/findings/:id/spawn — spawn an agent from a finding
2085
+ if (url.pathname.match(/^\/api\/v1\/findings\/[^/]+\/spawn$/) && req.method === "POST") {
2086
+ try {
2087
+ const findingId = decodeURIComponent(url.pathname.split("/")[4]);
2088
+ const engine = new FindingsEngine(projectRoot);
2089
+ const findings = engine.getFindings();
2090
+ const finding = findings.find(f => f.id === findingId);
2091
+ if (!finding) {
2092
+ res.writeHead(404, { "Content-Type": "application/json" });
2093
+ res.end(JSON.stringify({ error: "Finding not found" }));
2094
+ return;
2095
+ }
2096
+ if (!finding.agent_config) {
2097
+ res.writeHead(400, { "Content-Type": "application/json" });
2098
+ res.end(JSON.stringify({ error: "Finding has no agent config" }));
2099
+ return;
2100
+ }
2101
+ // Spawn peter-parker to fix the issue
2102
+ const env = { ...process.env };
2103
+ delete env.ANTHROPIC_API_KEY;
2104
+ delete env.CLAUDE_CODE_ENTRYPOINT;
2105
+ const agentConfig = finding.agent_config;
2106
+ const prompt = `Fix this issue: ${finding.title}\n\n${finding.description}\n\nTarget metric: ${agentConfig.metric} >= ${agentConfig.target}\nScope files: ${agentConfig.scope_files.join(", ")}`;
2107
+ const child = spawn("jfl", ["peter", "run", "--prompt", prompt], {
2108
+ cwd: projectRoot,
2109
+ detached: true,
2110
+ stdio: "ignore",
2111
+ env,
2112
+ });
2113
+ child.unref();
2114
+ // Emit event for tracking
2115
+ if (eventBus) {
2116
+ eventBus.emit({
2117
+ type: "findings:agent-spawned",
2118
+ source: "findings-engine",
2119
+ data: {
2120
+ finding_id: findingId,
2121
+ finding_type: finding.type,
2122
+ finding_title: finding.title,
2123
+ pid: child.pid,
2124
+ },
2125
+ });
2126
+ }
2127
+ res.writeHead(200, { "Content-Type": "application/json" });
2128
+ res.end(JSON.stringify({
2129
+ ok: true,
2130
+ pid: child.pid,
2131
+ finding_id: findingId,
2132
+ agent_config: agentConfig,
2133
+ }));
2134
+ }
2135
+ catch (err) {
2136
+ res.writeHead(500, { "Content-Type": "application/json" });
2137
+ res.end(JSON.stringify({ error: err.message }));
2138
+ }
2139
+ return;
2140
+ }
2141
+ // POST /api/v1/findings/analyze — force re-analyze
2142
+ if (url.pathname === "/api/v1/findings/analyze" && req.method === "POST") {
2143
+ try {
2144
+ const engine = new FindingsEngine(projectRoot);
2145
+ const findings = await engine.analyze();
2146
+ res.writeHead(200, { "Content-Type": "application/json" });
2147
+ res.end(JSON.stringify({ findings, total: findings.length }));
2148
+ }
2149
+ catch (err) {
2150
+ res.writeHead(500, { "Content-Type": "application/json" });
2151
+ res.end(JSON.stringify({ error: err.message }));
2152
+ }
2153
+ return;
2154
+ }
1201
2155
  // 404
1202
2156
  res.writeHead(404, { "Content-Type": "application/json" });
1203
2157
  res.end(JSON.stringify({ error: "Not found" }));
@@ -1284,47 +2238,19 @@ function getTrackedProjects() {
1284
2238
  .map(p => ({ path: p, port: getProjectPort(p) }));
1285
2239
  }
1286
2240
  async function ensureForProject(projectRoot, port, quiet = false) {
2241
+ // Rule: ensure ONLY starts hubs, NEVER kills them.
2242
+ // If something is on the port, leave it alone.
1287
2243
  const status = isRunning(projectRoot);
1288
2244
  if (status.running) {
1289
- try {
1290
- const response = await fetch(`http://localhost:${port}/health`, {
1291
- signal: AbortSignal.timeout(2000)
1292
- });
1293
- if (response.ok) {
1294
- return { status: "running", message: `Already running (PID: ${status.pid})` };
1295
- }
1296
- }
1297
- catch {
1298
- // Process exists but not responding, fall through
1299
- }
2245
+ return { status: "running", message: `Already running (PID: ${status.pid})` };
1300
2246
  }
1301
2247
  const portInUse = await isPortInUse(port);
1302
2248
  if (portInUse) {
1303
- try {
1304
- const response = await fetch(`http://localhost:${port}/health`, {
1305
- signal: AbortSignal.timeout(2000)
1306
- });
1307
- if (response.ok) {
1308
- return { status: "running", message: "Running (PID file missing but healthy)" };
1309
- }
1310
- }
1311
- catch {
1312
- // Not responding
1313
- }
1314
- try {
1315
- const lsofOutput = execSync(`lsof -ti :${port}`, { encoding: 'utf-8' }).trim();
1316
- if (lsofOutput) {
1317
- const orphanedPid = parseInt(lsofOutput.split('\n')[0], 10);
1318
- if (!status.pid || orphanedPid !== status.pid) {
1319
- process.kill(orphanedPid, 'SIGTERM');
1320
- await new Promise(resolve => setTimeout(resolve, 500));
1321
- }
1322
- }
1323
- }
1324
- catch {
1325
- // lsof failed or process already gone
1326
- }
2249
+ // Something is on this port. Don't kill it — could be a healthy hub
2250
+ // whose PID file was lost, or a hub started by another process.
2251
+ return { status: "running", message: `Port ${port} in use (assuming healthy)` };
1327
2252
  }
2253
+ // Nothing running, nothing on port — safe to start
1328
2254
  const result = await startDaemon(projectRoot, port);
1329
2255
  if (result.success) {
1330
2256
  return { status: "started", message: result.message };
@@ -1378,7 +2304,7 @@ async function startDaemon(projectRoot, port) {
1378
2304
  }
1379
2305
  // Start as detached process with CONTEXT_HUB_DAEMON=1 so the serve
1380
2306
  // action knows to ignore SIGTERM during its startup grace period
1381
- const child = spawn(jflCmd, ["context-hub", "serve", "--port", String(port)], {
2307
+ const child = spawn(jflCmd, ["context-hub", "serve", "--port", String(port), "--project-root", projectRoot], {
1382
2308
  cwd: projectRoot,
1383
2309
  detached: true,
1384
2310
  stdio: ["ignore", fs.openSync(logFile, "a"), fs.openSync(logFile, "a")],
@@ -1563,7 +2489,7 @@ export async function ensureDaemonInstalled(opts) {
1563
2489
  // ============================================================================
1564
2490
  export async function contextHubCommand(action, options = {}) {
1565
2491
  const isGlobal = options.global || false;
1566
- const projectRoot = isGlobal ? homedir() : process.cwd();
2492
+ const projectRoot = options.projectRoot || (isGlobal ? homedir() : process.cwd());
1567
2493
  const port = options.port || getProjectPort(projectRoot);
1568
2494
  // Ensure directories exist (skip for actions that don't need local project root)
1569
2495
  const globalActions = ["ensure-all", "doctor", "install-daemon", "uninstall-daemon"];
@@ -1766,6 +2692,32 @@ export async function contextHubCommand(action, options = {}) {
1766
2692
  break;
1767
2693
  }
1768
2694
  case "serve": {
2695
+ // Load .env files for API keys (hub runs as detached process)
2696
+ for (const envFile of [
2697
+ path.join(projectRoot, ".env"),
2698
+ path.join(projectRoot, ".env.local"),
2699
+ path.join(process.env.HOME || "/tmp", ".env"),
2700
+ ]) {
2701
+ if (fs.existsSync(envFile)) {
2702
+ const envContent = fs.readFileSync(envFile, "utf-8");
2703
+ for (const line of envContent.split("\n")) {
2704
+ const trimmed = line.trim();
2705
+ if (!trimmed || trimmed.startsWith("#"))
2706
+ continue;
2707
+ const eqIdx = trimmed.indexOf("=");
2708
+ if (eqIdx === -1)
2709
+ continue;
2710
+ const key = trimmed.slice(0, eqIdx).trim();
2711
+ let val = trimmed.slice(eqIdx + 1).trim();
2712
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
2713
+ val = val.slice(1, -1);
2714
+ }
2715
+ if (!process.env[key]) {
2716
+ process.env[key] = val;
2717
+ }
2718
+ }
2719
+ }
2720
+ }
1769
2721
  // Run server in foreground (used by daemon)
1770
2722
  const serviceEventsPath = path.join(projectRoot, ".jfl", "service-events.jsonl");
1771
2723
  const mapPersistPath = path.join(projectRoot, ".jfl", "map-events.jsonl");
@@ -1779,6 +2731,77 @@ export async function contextHubCommand(action, options = {}) {
1779
2731
  const flowEngine = new FlowEngine(eventBus, projectRoot);
1780
2732
  const server = createServer(projectRoot, port, eventBus, flowEngine);
1781
2733
  let isListening = false;
2734
+ // Cross-service scope impact detection (GTM/portfolio level)
2735
+ // When eval:scored fires with improved=true, detect which other services
2736
+ // are affected and emit scope:impact events for each
2737
+ const configPath = path.join(projectRoot, ".jfl", "config.json");
2738
+ const hubConfig = fs.existsSync(configPath) ? JSON.parse(fs.readFileSync(configPath, "utf-8")) : {};
2739
+ if (hubConfig.type === "gtm" || hubConfig.type === "portfolio") {
2740
+ eventBus.subscribe({
2741
+ clientId: "scope-detector",
2742
+ patterns: ["eval:scored"],
2743
+ transport: "poll",
2744
+ callback: (event) => {
2745
+ if (event.data?.improved !== "true" && event.data?.improved !== true)
2746
+ return;
2747
+ const serviceName = event.data?.service || event.source || "unknown";
2748
+ const registeredServices = hubConfig.registered_services || [];
2749
+ // Find source service's produces
2750
+ const sourceReg = registeredServices.find((s) => s.name === serviceName);
2751
+ if (!sourceReg?.path)
2752
+ return;
2753
+ try {
2754
+ const svcConfigPath = path.join(sourceReg.path, ".jfl", "config.json");
2755
+ if (!fs.existsSync(svcConfigPath))
2756
+ return;
2757
+ const svcConfig = JSON.parse(fs.readFileSync(svcConfigPath, "utf-8"));
2758
+ const produces = svcConfig.context_scope?.produces || [];
2759
+ if (produces.length === 0)
2760
+ return;
2761
+ // Check each other service for consuming matches
2762
+ for (const otherSvc of registeredServices) {
2763
+ if (otherSvc.name === serviceName || !otherSvc.path)
2764
+ continue;
2765
+ const otherConfigPath = path.join(otherSvc.path, ".jfl", "config.json");
2766
+ if (!fs.existsSync(otherConfigPath))
2767
+ continue;
2768
+ const otherConfig = JSON.parse(fs.readFileSync(otherConfigPath, "utf-8"));
2769
+ const consumes = otherConfig.context_scope?.consumes || [];
2770
+ // Match produces against consumes
2771
+ const matched = [];
2772
+ for (const p of produces) {
2773
+ for (const c of consumes) {
2774
+ if (c === "*" || p === c || (c.endsWith(":*") && p.startsWith(c.slice(0, -1))) || (c.endsWith("*") && p.startsWith(c.slice(0, -1)))) {
2775
+ matched.push(`${p} → ${c}`);
2776
+ }
2777
+ }
2778
+ }
2779
+ if (matched.length > 0) {
2780
+ const ts = new Date().toISOString();
2781
+ console.log(`[${ts}] scope:impact — ${serviceName} → ${otherSvc.name} (${matched.length} patterns)`);
2782
+ eventBus.emit({
2783
+ type: "scope:impact",
2784
+ source: "scope-detector",
2785
+ data: {
2786
+ source_service: serviceName,
2787
+ affected_service: otherSvc.name,
2788
+ affected_service_path: otherSvc.path,
2789
+ scope_patterns: matched,
2790
+ source_pr: event.data?.pr_number || "",
2791
+ change_description: event.data?.branch || "eval improvement",
2792
+ source_delta: event.data?.delta || "0",
2793
+ },
2794
+ });
2795
+ }
2796
+ }
2797
+ }
2798
+ catch (err) {
2799
+ console.error(`[scope-detector] Error checking impact for ${serviceName}:`, err.message);
2800
+ }
2801
+ },
2802
+ });
2803
+ console.log(`[scope-detector] Cross-service impact detection enabled for ${hubConfig.type} hub`);
2804
+ }
1782
2805
  // When spawned as daemon, ignore SIGTERM during startup grace period.
1783
2806
  // The parent process (hook runner) may exit and send SIGTERM to the
1784
2807
  // process group before we're fully detached. After grace period,