jfl 0.4.4 → 0.5.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 (427) hide show
  1. package/dist/commands/context-hub.d.ts.map +1 -1
  2. package/dist/commands/context-hub.js +818 -39
  3. package/dist/commands/context-hub.js.map +1 -1
  4. package/dist/commands/eval.d.ts +1 -1
  5. package/dist/commands/eval.d.ts.map +1 -1
  6. package/dist/commands/eval.js +192 -1
  7. package/dist/commands/eval.js.map +1 -1
  8. package/dist/commands/findings.d.ts +6 -0
  9. package/dist/commands/findings.d.ts.map +1 -0
  10. package/dist/commands/findings.js +203 -0
  11. package/dist/commands/findings.js.map +1 -0
  12. package/dist/commands/hud.d.ts.map +1 -1
  13. package/dist/commands/hud.js +47 -9
  14. package/dist/commands/hud.js.map +1 -1
  15. package/dist/commands/ide.d.ts +27 -0
  16. package/dist/commands/ide.d.ts.map +1 -0
  17. package/dist/commands/ide.js +546 -0
  18. package/dist/commands/ide.js.map +1 -0
  19. package/dist/commands/onboard.d.ts.map +1 -1
  20. package/dist/commands/onboard.js +212 -2
  21. package/dist/commands/onboard.js.map +1 -1
  22. package/dist/commands/openclaw.d.ts +3 -0
  23. package/dist/commands/openclaw.d.ts.map +1 -1
  24. package/dist/commands/openclaw.js +76 -2
  25. package/dist/commands/openclaw.js.map +1 -1
  26. package/dist/commands/peter.d.ts +1 -0
  27. package/dist/commands/peter.d.ts.map +1 -1
  28. package/dist/commands/peter.js +935 -15
  29. package/dist/commands/peter.js.map +1 -1
  30. package/dist/commands/pi-fleet.d.ts +18 -0
  31. package/dist/commands/pi-fleet.d.ts.map +1 -0
  32. package/dist/commands/pi-fleet.js +382 -0
  33. package/dist/commands/pi-fleet.js.map +1 -0
  34. package/dist/commands/pi.d.ts.map +1 -1
  35. package/dist/commands/pi.js +18 -3
  36. package/dist/commands/pi.js.map +1 -1
  37. package/dist/commands/scope.d.ts.map +1 -1
  38. package/dist/commands/scope.js +90 -1
  39. package/dist/commands/scope.js.map +1 -1
  40. package/dist/commands/services.d.ts.map +1 -1
  41. package/dist/commands/services.js +18 -0
  42. package/dist/commands/services.js.map +1 -1
  43. package/dist/commands/status.d.ts.map +1 -1
  44. package/dist/commands/status.js +22 -4
  45. package/dist/commands/status.js.map +1 -1
  46. package/dist/commands/viz.d.ts.map +1 -1
  47. package/dist/commands/viz.js +417 -0
  48. package/dist/commands/viz.js.map +1 -1
  49. package/dist/dashboard-static/assets/index-B6b867Pv.js +121 -0
  50. package/dist/dashboard-static/assets/index-Y4BrqxV-.css +1 -0
  51. package/dist/dashboard-static/index.html +2 -2
  52. package/dist/index.js +225 -61
  53. package/dist/index.js.map +1 -1
  54. package/dist/lib/agent-config.d.ts +52 -0
  55. package/dist/lib/agent-config.d.ts.map +1 -0
  56. package/dist/lib/agent-config.js +231 -0
  57. package/dist/lib/agent-config.js.map +1 -0
  58. package/dist/lib/agent-generator.d.ts +10 -0
  59. package/dist/lib/agent-generator.d.ts.map +1 -1
  60. package/dist/lib/agent-generator.js +64 -10
  61. package/dist/lib/agent-generator.js.map +1 -1
  62. package/dist/lib/agent-session.d.ts +104 -0
  63. package/dist/lib/agent-session.d.ts.map +1 -0
  64. package/dist/lib/agent-session.js +627 -0
  65. package/dist/lib/agent-session.js.map +1 -0
  66. package/dist/lib/eval-snapshot.d.ts +47 -0
  67. package/dist/lib/eval-snapshot.d.ts.map +1 -0
  68. package/dist/lib/eval-snapshot.js +315 -0
  69. package/dist/lib/eval-snapshot.js.map +1 -0
  70. package/dist/lib/eval-store.d.ts +5 -0
  71. package/dist/lib/eval-store.d.ts.map +1 -1
  72. package/dist/lib/eval-store.js +33 -3
  73. package/dist/lib/eval-store.js.map +1 -1
  74. package/dist/lib/findings-engine.d.ts +51 -0
  75. package/dist/lib/findings-engine.d.ts.map +1 -0
  76. package/dist/lib/findings-engine.js +338 -0
  77. package/dist/lib/findings-engine.js.map +1 -0
  78. package/dist/lib/flow-engine.d.ts +8 -0
  79. package/dist/lib/flow-engine.d.ts.map +1 -1
  80. package/dist/lib/flow-engine.js +84 -2
  81. package/dist/lib/flow-engine.js.map +1 -1
  82. package/dist/lib/hub-client.d.ts +1 -0
  83. package/dist/lib/hub-client.d.ts.map +1 -1
  84. package/dist/lib/hub-client.js +33 -6
  85. package/dist/lib/hub-client.js.map +1 -1
  86. package/dist/lib/ide-panes.d.ts +58 -0
  87. package/dist/lib/ide-panes.d.ts.map +1 -0
  88. package/dist/lib/ide-panes.js +508 -0
  89. package/dist/lib/ide-panes.js.map +1 -0
  90. package/dist/lib/memory-db.js +4 -4
  91. package/dist/lib/memory-db.js.map +1 -1
  92. package/dist/lib/memory-indexer.d.ts.map +1 -1
  93. package/dist/lib/memory-indexer.js +3 -0
  94. package/dist/lib/memory-indexer.js.map +1 -1
  95. package/dist/lib/memory-search.d.ts +148 -4
  96. package/dist/lib/memory-search.d.ts.map +1 -1
  97. package/dist/lib/memory-search.js +496 -58
  98. package/dist/lib/memory-search.js.map +1 -1
  99. package/dist/lib/meta-orchestrator.d.ts +104 -0
  100. package/dist/lib/meta-orchestrator.d.ts.map +1 -0
  101. package/dist/lib/meta-orchestrator.js +373 -0
  102. package/dist/lib/meta-orchestrator.js.map +1 -0
  103. package/dist/lib/peer-agent-generator.d.ts.map +1 -1
  104. package/dist/lib/peer-agent-generator.js +43 -19
  105. package/dist/lib/peer-agent-generator.js.map +1 -1
  106. package/dist/lib/policy-head.d.ts +25 -0
  107. package/dist/lib/policy-head.d.ts.map +1 -0
  108. package/dist/lib/policy-head.js +136 -0
  109. package/dist/lib/policy-head.js.map +1 -0
  110. package/dist/lib/replay-buffer.d.ts +93 -0
  111. package/dist/lib/replay-buffer.d.ts.map +1 -0
  112. package/dist/lib/replay-buffer.js +302 -0
  113. package/dist/lib/replay-buffer.js.map +1 -0
  114. package/dist/lib/sentinel-rl.d.ts +97 -0
  115. package/dist/lib/sentinel-rl.d.ts.map +1 -0
  116. package/dist/lib/sentinel-rl.js +430 -0
  117. package/dist/lib/sentinel-rl.js.map +1 -0
  118. package/dist/lib/session-lock.d.ts +61 -0
  119. package/dist/lib/session-lock.d.ts.map +1 -0
  120. package/dist/lib/session-lock.js +438 -0
  121. package/dist/lib/session-lock.js.map +1 -0
  122. package/dist/lib/stratus-client.d.ts +1 -0
  123. package/dist/lib/stratus-client.d.ts.map +1 -1
  124. package/dist/lib/stratus-client.js +24 -2
  125. package/dist/lib/stratus-client.js.map +1 -1
  126. package/dist/lib/telemetry-agent-v2.d.ts +128 -0
  127. package/dist/lib/telemetry-agent-v2.d.ts.map +1 -0
  128. package/dist/lib/telemetry-agent-v2.js +1042 -0
  129. package/dist/lib/telemetry-agent-v2.js.map +1 -0
  130. package/dist/lib/telemetry-agent.d.ts.map +1 -1
  131. package/dist/lib/telemetry-agent.js +27 -6
  132. package/dist/lib/telemetry-agent.js.map +1 -1
  133. package/dist/lib/telemetry-digest.d.ts.map +1 -1
  134. package/dist/lib/telemetry-digest.js +27 -5
  135. package/dist/lib/telemetry-digest.js.map +1 -1
  136. package/dist/lib/telemetry.d.ts.map +1 -1
  137. package/dist/lib/telemetry.js +29 -4
  138. package/dist/lib/telemetry.js.map +1 -1
  139. package/dist/lib/text-preprocessing.d.ts +83 -0
  140. package/dist/lib/text-preprocessing.d.ts.map +1 -0
  141. package/dist/lib/text-preprocessing.js +261 -0
  142. package/dist/lib/text-preprocessing.js.map +1 -0
  143. package/dist/lib/training-buffer.d.ts +86 -0
  144. package/dist/lib/training-buffer.d.ts.map +1 -0
  145. package/dist/lib/training-buffer.js +139 -0
  146. package/dist/lib/training-buffer.js.map +1 -0
  147. package/dist/lib/tuple-miner.d.ts +30 -0
  148. package/dist/lib/tuple-miner.d.ts.map +1 -0
  149. package/dist/lib/tuple-miner.js +427 -0
  150. package/dist/lib/tuple-miner.js.map +1 -0
  151. package/dist/lib/vm-backend.d.ts +72 -0
  152. package/dist/lib/vm-backend.d.ts.map +1 -0
  153. package/dist/lib/vm-backend.js +175 -0
  154. package/dist/lib/vm-backend.js.map +1 -0
  155. package/dist/lib/workspace/backend.d.ts +53 -0
  156. package/dist/lib/workspace/backend.d.ts.map +1 -0
  157. package/dist/lib/workspace/backend.js +37 -0
  158. package/dist/lib/workspace/backend.js.map +1 -0
  159. package/dist/lib/workspace/cmux-adapter.d.ts +46 -0
  160. package/dist/lib/workspace/cmux-adapter.d.ts.map +1 -0
  161. package/dist/lib/workspace/cmux-adapter.js +261 -0
  162. package/dist/lib/workspace/cmux-adapter.js.map +1 -0
  163. package/dist/lib/workspace/data-pipeline.d.ts +35 -0
  164. package/dist/lib/workspace/data-pipeline.d.ts.map +1 -0
  165. package/dist/lib/workspace/data-pipeline.js +463 -0
  166. package/dist/lib/workspace/data-pipeline.js.map +1 -0
  167. package/dist/lib/workspace/engine.d.ts +64 -0
  168. package/dist/lib/workspace/engine.d.ts.map +1 -0
  169. package/dist/lib/workspace/engine.js +397 -0
  170. package/dist/lib/workspace/engine.js.map +1 -0
  171. package/dist/lib/workspace/notifications.d.ts +14 -0
  172. package/dist/lib/workspace/notifications.d.ts.map +1 -0
  173. package/dist/lib/workspace/notifications.js +41 -0
  174. package/dist/lib/workspace/notifications.js.map +1 -0
  175. package/dist/lib/workspace/surface-registry.d.ts +49 -0
  176. package/dist/lib/workspace/surface-registry.d.ts.map +1 -0
  177. package/dist/lib/workspace/surface-registry.js +217 -0
  178. package/dist/lib/workspace/surface-registry.js.map +1 -0
  179. package/dist/lib/workspace/surface-type.d.ts +153 -0
  180. package/dist/lib/workspace/surface-type.d.ts.map +1 -0
  181. package/dist/lib/workspace/surface-type.js +9 -0
  182. package/dist/lib/workspace/surface-type.js.map +1 -0
  183. package/dist/lib/workspace/surfaces/agent-overview.d.ts +16 -0
  184. package/dist/lib/workspace/surfaces/agent-overview.d.ts.map +1 -0
  185. package/dist/lib/workspace/surfaces/agent-overview.js +116 -0
  186. package/dist/lib/workspace/surfaces/agent-overview.js.map +1 -0
  187. package/dist/lib/workspace/surfaces/agent.d.ts +16 -0
  188. package/dist/lib/workspace/surfaces/agent.d.ts.map +1 -0
  189. package/dist/lib/workspace/surfaces/agent.js +112 -0
  190. package/dist/lib/workspace/surfaces/agent.js.map +1 -0
  191. package/dist/lib/workspace/surfaces/claude.d.ts +15 -0
  192. package/dist/lib/workspace/surfaces/claude.d.ts.map +1 -0
  193. package/dist/lib/workspace/surfaces/claude.js +23 -0
  194. package/dist/lib/workspace/surfaces/claude.js.map +1 -0
  195. package/dist/lib/workspace/surfaces/dashboard.d.ts +21 -0
  196. package/dist/lib/workspace/surfaces/dashboard.d.ts.map +1 -0
  197. package/dist/lib/workspace/surfaces/dashboard.js +32 -0
  198. package/dist/lib/workspace/surfaces/dashboard.js.map +1 -0
  199. package/dist/lib/workspace/surfaces/eval.d.ts +15 -0
  200. package/dist/lib/workspace/surfaces/eval.d.ts.map +1 -0
  201. package/dist/lib/workspace/surfaces/eval.js +42 -0
  202. package/dist/lib/workspace/surfaces/eval.js.map +1 -0
  203. package/dist/lib/workspace/surfaces/event-stream.d.ts +16 -0
  204. package/dist/lib/workspace/surfaces/event-stream.d.ts.map +1 -0
  205. package/dist/lib/workspace/surfaces/event-stream.js +40 -0
  206. package/dist/lib/workspace/surfaces/event-stream.js.map +1 -0
  207. package/dist/lib/workspace/surfaces/flow.d.ts +16 -0
  208. package/dist/lib/workspace/surfaces/flow.d.ts.map +1 -0
  209. package/dist/lib/workspace/surfaces/flow.js +49 -0
  210. package/dist/lib/workspace/surfaces/flow.js.map +1 -0
  211. package/dist/lib/workspace/surfaces/index.d.ts +16 -0
  212. package/dist/lib/workspace/surfaces/index.d.ts.map +1 -0
  213. package/dist/lib/workspace/surfaces/index.js +16 -0
  214. package/dist/lib/workspace/surfaces/index.js.map +1 -0
  215. package/dist/lib/workspace/surfaces/portfolio.d.ts +16 -0
  216. package/dist/lib/workspace/surfaces/portfolio.d.ts.map +1 -0
  217. package/dist/lib/workspace/surfaces/portfolio.js +102 -0
  218. package/dist/lib/workspace/surfaces/portfolio.js.map +1 -0
  219. package/dist/lib/workspace/surfaces/service.d.ts +16 -0
  220. package/dist/lib/workspace/surfaces/service.d.ts.map +1 -0
  221. package/dist/lib/workspace/surfaces/service.js +45 -0
  222. package/dist/lib/workspace/surfaces/service.js.map +1 -0
  223. package/dist/lib/workspace/surfaces/shell.d.ts +15 -0
  224. package/dist/lib/workspace/surfaces/shell.d.ts.map +1 -0
  225. package/dist/lib/workspace/surfaces/shell.js +19 -0
  226. package/dist/lib/workspace/surfaces/shell.js.map +1 -0
  227. package/dist/lib/workspace/surfaces/telemetry.d.ts +16 -0
  228. package/dist/lib/workspace/surfaces/telemetry.d.ts.map +1 -0
  229. package/dist/lib/workspace/surfaces/telemetry.js +48 -0
  230. package/dist/lib/workspace/surfaces/telemetry.js.map +1 -0
  231. package/dist/lib/workspace/surfaces/topology.d.ts +15 -0
  232. package/dist/lib/workspace/surfaces/topology.d.ts.map +1 -0
  233. package/dist/lib/workspace/surfaces/topology.js +19 -0
  234. package/dist/lib/workspace/surfaces/topology.js.map +1 -0
  235. package/dist/lib/workspace/surfaces/training.d.ts +16 -0
  236. package/dist/lib/workspace/surfaces/training.d.ts.map +1 -0
  237. package/dist/lib/workspace/surfaces/training.js +22 -0
  238. package/dist/lib/workspace/surfaces/training.js.map +1 -0
  239. package/dist/lib/workspace/tmux-adapter.d.ts +27 -0
  240. package/dist/lib/workspace/tmux-adapter.d.ts.map +1 -0
  241. package/dist/lib/workspace/tmux-adapter.js +106 -0
  242. package/dist/lib/workspace/tmux-adapter.js.map +1 -0
  243. package/dist/mcp/context-hub-mcp.js +7 -24
  244. package/dist/mcp/context-hub-mcp.js.map +1 -1
  245. package/dist/types/flows.d.ts +2 -0
  246. package/dist/types/flows.d.ts.map +1 -1
  247. package/dist/types/ide.d.ts +49 -0
  248. package/dist/types/ide.d.ts.map +1 -0
  249. package/dist/types/ide.js +5 -0
  250. package/dist/types/ide.js.map +1 -0
  251. package/dist/types/platform-digest.d.ts +228 -0
  252. package/dist/types/platform-digest.d.ts.map +1 -0
  253. package/dist/types/platform-digest.js +5 -0
  254. package/dist/types/platform-digest.js.map +1 -0
  255. package/dist/types/telemetry-digest.d.ts +2 -0
  256. package/dist/types/telemetry-digest.d.ts.map +1 -1
  257. package/dist/utils/ensure-project.d.ts +1 -0
  258. package/dist/utils/ensure-project.d.ts.map +1 -1
  259. package/dist/utils/ensure-project.js +19 -7
  260. package/dist/utils/ensure-project.js.map +1 -1
  261. package/dist/utils/jfl-config.d.ts +1 -0
  262. package/dist/utils/jfl-config.d.ts.map +1 -1
  263. package/dist/utils/jfl-config.js +19 -1
  264. package/dist/utils/jfl-config.js.map +1 -1
  265. package/dist/utils/jfl-paths.d.ts +5 -0
  266. package/dist/utils/jfl-paths.d.ts.map +1 -1
  267. package/dist/utils/jfl-paths.js +25 -3
  268. package/dist/utils/jfl-paths.js.map +1 -1
  269. package/package.json +3 -2
  270. package/packages/pi/AGENTS.md +112 -0
  271. package/packages/pi/extensions/agent-grid.ts +191 -0
  272. package/packages/pi/extensions/agent-names.ts +178 -0
  273. package/packages/pi/extensions/autoresearch.ts +427 -0
  274. package/packages/pi/extensions/bookmarks.ts +85 -0
  275. package/packages/pi/extensions/context.ts +151 -0
  276. package/packages/pi/extensions/crm-tool.ts +61 -0
  277. package/packages/pi/extensions/eval-tool.ts +224 -0
  278. package/packages/pi/extensions/eval.ts +60 -0
  279. package/packages/pi/extensions/footer.ts +239 -0
  280. package/packages/pi/extensions/hud-tool.ts +145 -0
  281. package/packages/pi/extensions/index.ts +392 -0
  282. package/packages/pi/extensions/journal.ts +224 -0
  283. package/packages/pi/extensions/map-bridge.ts +178 -0
  284. package/packages/pi/extensions/memory-tool.ts +68 -0
  285. package/packages/pi/extensions/notifications.ts +73 -0
  286. package/packages/pi/extensions/peter-parker.ts +202 -0
  287. package/packages/pi/extensions/policy-head-tool.ts +276 -0
  288. package/packages/pi/extensions/portfolio-bridge.ts +90 -0
  289. package/packages/pi/extensions/session.ts +90 -0
  290. package/packages/pi/extensions/shortcuts.ts +259 -0
  291. package/packages/pi/extensions/stratus-bridge.ts +115 -0
  292. package/packages/pi/extensions/synopsis-tool.ts +83 -0
  293. package/packages/pi/extensions/tool-renderers.ts +352 -0
  294. package/packages/pi/extensions/training-buffer-tool.ts +368 -0
  295. package/packages/pi/extensions/types.ts +163 -0
  296. package/packages/pi/package-lock.json +346 -0
  297. package/packages/pi/package.json +44 -0
  298. package/packages/pi/skills/agent-browser/SKILL.md +116 -0
  299. package/packages/pi/skills/brand-architect/SKILL.md +240 -0
  300. package/packages/pi/skills/brand-architect/config.yaml +137 -0
  301. package/packages/pi/skills/campaign-hud/config.yaml +112 -0
  302. package/packages/pi/skills/content-creator/SKILL.md +294 -0
  303. package/packages/pi/skills/context/SKILL.md +65 -0
  304. package/packages/pi/skills/debug/MULTI_AGENT.md +360 -0
  305. package/packages/pi/skills/debug/SKILL.md +554 -0
  306. package/packages/pi/skills/end/SKILL.md +1782 -0
  307. package/packages/pi/skills/eval/SKILL.md +75 -0
  308. package/packages/pi/skills/fly-deploy/SKILL.md +676 -0
  309. package/packages/pi/skills/founder-video/SKILL.md +467 -0
  310. package/packages/pi/skills/hud/SKILL.md +160 -0
  311. package/packages/pi/skills/orchestrate/SKILL.md +74 -0
  312. package/packages/pi/skills/pi-agents/SKILL.md +78 -0
  313. package/packages/pi/skills/react-best-practices/AGENTS.md +2249 -0
  314. package/packages/pi/skills/react-best-practices/README.md +123 -0
  315. package/packages/pi/skills/react-best-practices/SKILL.md +125 -0
  316. package/packages/pi/skills/react-best-practices/metadata.json +15 -0
  317. package/packages/pi/skills/react-best-practices/rules/_sections.md +46 -0
  318. package/packages/pi/skills/react-best-practices/rules/_template.md +28 -0
  319. package/packages/pi/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  320. package/packages/pi/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
  321. package/packages/pi/skills/react-best-practices/rules/async-api-routes.md +38 -0
  322. package/packages/pi/skills/react-best-practices/rules/async-defer-await.md +80 -0
  323. package/packages/pi/skills/react-best-practices/rules/async-dependencies.md +36 -0
  324. package/packages/pi/skills/react-best-practices/rules/async-parallel.md +28 -0
  325. package/packages/pi/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
  326. package/packages/pi/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
  327. package/packages/pi/skills/react-best-practices/rules/bundle-conditional.md +31 -0
  328. package/packages/pi/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
  329. package/packages/pi/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  330. package/packages/pi/skills/react-best-practices/rules/bundle-preload.md +50 -0
  331. package/packages/pi/skills/react-best-practices/rules/client-event-listeners.md +74 -0
  332. package/packages/pi/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
  333. package/packages/pi/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
  334. package/packages/pi/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
  335. package/packages/pi/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
  336. package/packages/pi/skills/react-best-practices/rules/js-cache-storage.md +70 -0
  337. package/packages/pi/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
  338. package/packages/pi/skills/react-best-practices/rules/js-early-exit.md +50 -0
  339. package/packages/pi/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
  340. package/packages/pi/skills/react-best-practices/rules/js-index-maps.md +37 -0
  341. package/packages/pi/skills/react-best-practices/rules/js-length-check-first.md +49 -0
  342. package/packages/pi/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
  343. package/packages/pi/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
  344. package/packages/pi/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
  345. package/packages/pi/skills/react-best-practices/rules/rendering-activity.md +26 -0
  346. package/packages/pi/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  347. package/packages/pi/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
  348. package/packages/pi/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
  349. package/packages/pi/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  350. package/packages/pi/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  351. package/packages/pi/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
  352. package/packages/pi/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
  353. package/packages/pi/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
  354. package/packages/pi/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
  355. package/packages/pi/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
  356. package/packages/pi/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  357. package/packages/pi/skills/react-best-practices/rules/rerender-memo.md +44 -0
  358. package/packages/pi/skills/react-best-practices/rules/rerender-transitions.md +40 -0
  359. package/packages/pi/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
  360. package/packages/pi/skills/react-best-practices/rules/server-cache-lru.md +41 -0
  361. package/packages/pi/skills/react-best-practices/rules/server-cache-react.md +26 -0
  362. package/packages/pi/skills/react-best-practices/rules/server-parallel-fetching.md +79 -0
  363. package/packages/pi/skills/react-best-practices/rules/server-serialization.md +38 -0
  364. package/packages/pi/skills/remotion-best-practices/SKILL.md +43 -0
  365. package/packages/pi/skills/remotion-best-practices/rules/3d.md +86 -0
  366. package/packages/pi/skills/remotion-best-practices/rules/animations.md +29 -0
  367. package/packages/pi/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
  368. package/packages/pi/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
  369. package/packages/pi/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +108 -0
  370. package/packages/pi/skills/remotion-best-practices/rules/assets.md +78 -0
  371. package/packages/pi/skills/remotion-best-practices/rules/audio.md +172 -0
  372. package/packages/pi/skills/remotion-best-practices/rules/calculate-metadata.md +104 -0
  373. package/packages/pi/skills/remotion-best-practices/rules/can-decode.md +75 -0
  374. package/packages/pi/skills/remotion-best-practices/rules/charts.md +58 -0
  375. package/packages/pi/skills/remotion-best-practices/rules/compositions.md +146 -0
  376. package/packages/pi/skills/remotion-best-practices/rules/display-captions.md +126 -0
  377. package/packages/pi/skills/remotion-best-practices/rules/extract-frames.md +229 -0
  378. package/packages/pi/skills/remotion-best-practices/rules/fonts.md +152 -0
  379. package/packages/pi/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
  380. package/packages/pi/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
  381. package/packages/pi/skills/remotion-best-practices/rules/get-video-duration.md +58 -0
  382. package/packages/pi/skills/remotion-best-practices/rules/gifs.md +138 -0
  383. package/packages/pi/skills/remotion-best-practices/rules/images.md +130 -0
  384. package/packages/pi/skills/remotion-best-practices/rules/import-srt-captions.md +67 -0
  385. package/packages/pi/skills/remotion-best-practices/rules/lottie.md +68 -0
  386. package/packages/pi/skills/remotion-best-practices/rules/measuring-dom-nodes.md +35 -0
  387. package/packages/pi/skills/remotion-best-practices/rules/measuring-text.md +143 -0
  388. package/packages/pi/skills/remotion-best-practices/rules/sequencing.md +106 -0
  389. package/packages/pi/skills/remotion-best-practices/rules/tailwind.md +11 -0
  390. package/packages/pi/skills/remotion-best-practices/rules/text-animations.md +20 -0
  391. package/packages/pi/skills/remotion-best-practices/rules/timing.md +179 -0
  392. package/packages/pi/skills/remotion-best-practices/rules/transcribe-captions.md +19 -0
  393. package/packages/pi/skills/remotion-best-practices/rules/transitions.md +122 -0
  394. package/packages/pi/skills/remotion-best-practices/rules/trimming.md +53 -0
  395. package/packages/pi/skills/remotion-best-practices/rules/videos.md +171 -0
  396. package/packages/pi/skills/search/SKILL.md +220 -0
  397. package/packages/pi/skills/spec/SKILL.md +377 -0
  398. package/packages/pi/skills/startup/SKILL.md +315 -0
  399. package/packages/pi/skills/web-architect/SKILL.md +309 -0
  400. package/packages/pi/skills/x-algorithm/SKILL.md +305 -0
  401. package/packages/pi/teams/dev-team.yaml +63 -0
  402. package/packages/pi/teams/gtm-team.yaml +79 -0
  403. package/packages/pi/themes/jfl.theme.json +76 -0
  404. package/packages/pi/tsconfig.json +21 -0
  405. package/scripts/collect-tuples.sh +124 -0
  406. package/scripts/destroy-fleet.sh +37 -0
  407. package/scripts/jfl-ide.sh +48 -0
  408. package/scripts/session/session-cleanup.sh +4 -11
  409. package/scripts/session/session-init.sh +6 -0
  410. package/scripts/session/session-sync.sh +25 -0
  411. package/scripts/setup-branch-protection.sh +106 -0
  412. package/scripts/spawn-fleet.sh +144 -0
  413. package/scripts/train-policy-head.py +434 -0
  414. package/scripts/vm-swarm/README.md +301 -0
  415. package/scripts/vm-swarm/collect-tuples.sh +331 -0
  416. package/scripts/vm-swarm/create-base-template.sh +339 -0
  417. package/scripts/vm-swarm/kill-fleet.sh +204 -0
  418. package/scripts/vm-swarm/monitor-fleet.sh +346 -0
  419. package/scripts/vm-swarm/spawn-fleet.sh +304 -0
  420. package/template/.github/workflows/jfl-eval.yml +6 -1
  421. package/template/.github/workflows/jfl-review.yml +4 -0
  422. package/template/scripts/session/session-end.sh +69 -6
  423. package/template/scripts/session/session-init.sh +55 -30
  424. package/template/scripts/session/session-lock.sh +464 -0
  425. package/template/templates/service-agent/workflows/jfl-eval.yml +19 -0
  426. package/dist/dashboard-static/assets/index-B6kRK9Rq.js +0 -116
  427. package/dist/dashboard-static/assets/index-BpdKJPLu.css +0 -1
@@ -0,0 +1,427 @@
1
+ /**
2
+ * Autoresearch Extension
3
+ *
4
+ * Self-driving research loop as a Pi command. Runs N rounds of:
5
+ * branch → propose experiment → execute → eval → keep or revert
6
+ * Only the winning experiment gets a PR.
7
+ *
8
+ * Uses the policy head to rank proposals when available, falls back to
9
+ * heuristic dimension analysis when not.
10
+ *
11
+ * @purpose Pi command for autonomous experiment loop — /autoresearch
12
+ */
13
+
14
+ import { execSync, spawnSync } from "child_process"
15
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, appendFileSync } from "fs"
16
+ import { join, dirname } from "path"
17
+ import type { PiContext, JflConfig } from "./types.js"
18
+ import { emitCustomEvent } from "./map-bridge.js"
19
+
20
+ let projectRoot = ""
21
+
22
+ interface ExperimentProposal {
23
+ task: string
24
+ predicted_delta: number
25
+ reasoning: string
26
+ risk: string
27
+ }
28
+
29
+ interface ExperimentResult {
30
+ round: number
31
+ task: string
32
+ score: number
33
+ delta: number
34
+ testsPassing: number
35
+ testsTotal: number
36
+ branch: string
37
+ }
38
+
39
+ function git(args: string[]): { ok: boolean; output: string } {
40
+ try {
41
+ const out = execSync(`git ${args.join(" ")}`, {
42
+ cwd: projectRoot,
43
+ encoding: "utf-8",
44
+ stdio: ["pipe", "pipe", "pipe"],
45
+ }).trim()
46
+ return { ok: true, output: out }
47
+ } catch (err: any) {
48
+ return { ok: false, output: err.stderr?.trim() || err.message }
49
+ }
50
+ }
51
+
52
+ function gh(args: string[]): { ok: boolean; output: string } {
53
+ try {
54
+ const out = execSync(`gh ${args.join(" ")}`, {
55
+ cwd: projectRoot,
56
+ encoding: "utf-8",
57
+ stdio: ["pipe", "pipe", "pipe"],
58
+ }).trim()
59
+ return { ok: true, output: out }
60
+ } catch (err: any) {
61
+ return { ok: false, output: err.stderr?.trim() || err.message }
62
+ }
63
+ }
64
+
65
+ async function getPolicyHead(): Promise<any> {
66
+ try {
67
+ // @ts-ignore — resolved from jfl package at runtime
68
+ const { PolicyHeadInference } = await import("../../src/lib/policy-head.js")
69
+ return new PolicyHeadInference(projectRoot)
70
+ } catch {
71
+ return null
72
+ }
73
+ }
74
+
75
+ async function getTrainingBuffer(): Promise<any> {
76
+ try {
77
+ // @ts-ignore — resolved from jfl package at runtime
78
+ const { TrainingBuffer } = await import("../../src/lib/training-buffer.js")
79
+ return new TrainingBuffer(projectRoot)
80
+ } catch {
81
+ return null
82
+ }
83
+ }
84
+
85
+ function readEvalHistory(): Array<{ ts: string; composite: number; metrics: Record<string, any> }> {
86
+ const evalPath = join(projectRoot, ".jfl", "eval", "eval.jsonl")
87
+ if (!existsSync(evalPath)) return []
88
+ const entries: any[] = []
89
+ for (const line of readFileSync(evalPath, "utf-8").split("\n")) {
90
+ if (!line.trim()) continue
91
+ try { entries.push(JSON.parse(line)) } catch {}
92
+ }
93
+ return entries
94
+ }
95
+
96
+ function readJournalEntries(): Array<{ type: string; title: string; ts: string }> {
97
+ const journalDir = join(projectRoot, ".jfl", "journal")
98
+ if (!existsSync(journalDir)) return []
99
+ const entries: any[] = []
100
+ try {
101
+ const { readdirSync } = require("fs")
102
+ for (const f of readdirSync(journalDir)) {
103
+ if (!f.endsWith(".jsonl")) continue
104
+ for (const line of readFileSync(join(journalDir, f), "utf-8").split("\n")) {
105
+ if (!line.trim()) continue
106
+ try { entries.push(JSON.parse(line)) } catch {}
107
+ }
108
+ }
109
+ } catch {}
110
+ return entries.sort((a, b) => (a.ts || "").localeCompare(b.ts || ""))
111
+ }
112
+
113
+ function writeJournalEntry(entry: Record<string, any>): void {
114
+ const journalDir = join(projectRoot, ".jfl", "journal")
115
+ mkdirSync(journalDir, { recursive: true })
116
+ const branch = git(["branch", "--show-current"]).output || "main"
117
+ const file = join(journalDir, `${branch}.jsonl`)
118
+ appendFileSync(file, JSON.stringify(entry) + "\n")
119
+ }
120
+
121
+ function buildFallbackProposal(
122
+ evals: Array<{ ts: string; composite: number; metrics: Record<string, any> }>
123
+ ): ExperimentProposal | null {
124
+ if (evals.length < 2) return null
125
+ const sorted = [...evals].sort((a, b) => b.ts.localeCompare(a.ts))
126
+ const latest = sorted[0]
127
+ const prev = sorted[1]
128
+
129
+ const metrics = latest.metrics || {}
130
+ const prevMetrics = prev.metrics || {}
131
+
132
+ let worstDim = ""
133
+ let worstDelta = 0
134
+
135
+ for (const [key, val] of Object.entries(metrics)) {
136
+ if (typeof val !== "number") continue
137
+ const prevVal = (prevMetrics[key] as number) ?? val
138
+ const delta = val - prevVal
139
+ if (delta < worstDelta) {
140
+ worstDelta = delta
141
+ worstDim = key
142
+ }
143
+ }
144
+
145
+ if (worstDim) {
146
+ return {
147
+ task: `Improve ${worstDim} score (regressed by ${worstDelta.toFixed(4)})`,
148
+ predicted_delta: Math.abs(worstDelta),
149
+ reasoning: `Recovering ${worstDim} regression`,
150
+ risk: "May not fully recover",
151
+ }
152
+ }
153
+
154
+ let lowestDim = ""
155
+ let lowestScore = Infinity
156
+ for (const [key, val] of Object.entries(metrics)) {
157
+ if (typeof val !== "number") continue
158
+ if (val < lowestScore) {
159
+ lowestScore = val
160
+ lowestDim = key
161
+ }
162
+ }
163
+
164
+ if (lowestDim) {
165
+ return {
166
+ task: `Improve ${lowestDim} score (currently ${lowestScore.toFixed(4)})`,
167
+ predicted_delta: Math.max(0.01, (1 - lowestScore) * 0.1),
168
+ reasoning: `${lowestDim} is weakest dimension`,
169
+ risk: "May trade off against others",
170
+ }
171
+ }
172
+
173
+ return null
174
+ }
175
+
176
+ function runTests(): { passing: number; total: number; score: number } {
177
+ const result = spawnSync("npx", ["jest", "--json", "--silent"], {
178
+ cwd: projectRoot,
179
+ encoding: "utf-8",
180
+ stdio: "pipe",
181
+ timeout: 120000,
182
+ })
183
+
184
+ try {
185
+ const json = JSON.parse(result.stdout || "{}")
186
+ const passing = json.numPassedTests || 0
187
+ const total = json.numTotalTests || 1
188
+ return { passing, total, score: total > 0 ? passing / total : 0 }
189
+ } catch {
190
+ return { passing: 0, total: 1, score: 0 }
191
+ }
192
+ }
193
+
194
+ export async function setupAutoresearch(ctx: PiContext, _config: JflConfig): Promise<void> {
195
+ projectRoot = ctx.session.projectRoot
196
+
197
+ ctx.registerCommand({
198
+ name: "autoresearch",
199
+ description: "Run autonomous experiment loop — branch, change, eval, keep or revert. Usage: /autoresearch [rounds]",
200
+ async handler(args, ctx) {
201
+ const rounds = parseInt(args.trim() || "3", 10)
202
+ if (rounds < 1 || rounds > 20) {
203
+ ctx.ui.notify("Rounds must be between 1 and 20.", { level: "warn" })
204
+ return
205
+ }
206
+
207
+ ctx.ui.notify(`Starting autoresearch: ${rounds} rounds`, { level: "info" })
208
+
209
+ const evals = readEvalHistory()
210
+ if (evals.length === 0) {
211
+ ctx.ui.notify("No eval history. Run an eval first to establish a baseline.", { level: "warn" })
212
+ return
213
+ }
214
+
215
+ const baseBranch = git(["branch", "--show-current"]).output || "main"
216
+ const baselineScore = [...evals].sort((a, b) => b.ts.localeCompare(a.ts))[0]?.composite ?? 0
217
+
218
+ const pastTitles = readJournalEntries()
219
+ .filter(e => e.type === "experiment")
220
+ .map(e => e.title)
221
+
222
+ const results: ExperimentResult[] = []
223
+ let bestResult: ExperimentResult | null = null
224
+
225
+ git(["stash", "--include-untracked"])
226
+
227
+ for (let round = 1; round <= rounds; round++) {
228
+ ctx.ui.notify(`── Round ${round}/${rounds} ──`, { level: "info" })
229
+
230
+ let proposal: ExperimentProposal | null = null
231
+
232
+ const policyHead = await getPolicyHead()
233
+ if (policyHead?.isLoaded) {
234
+ const fallback = buildFallbackProposal(evals)
235
+ if (fallback) proposal = fallback
236
+ }
237
+
238
+ if (!proposal) {
239
+ proposal = buildFallbackProposal(evals)
240
+ }
241
+
242
+ if (!proposal) {
243
+ ctx.ui.notify(`Round ${round}: No target found, skipping`, { level: "info" })
244
+ continue
245
+ }
246
+
247
+ if (pastTitles.some(t => t.includes(proposal!.task.slice(0, 40)))) {
248
+ proposal.task = `[Retry] ${proposal.task}`
249
+ }
250
+
251
+ const branchName = `pp/autoresearch-r${round}-${Date.now()}`
252
+ git(["fetch", "origin", baseBranch])
253
+
254
+ let checkout = git(["checkout", "-b", branchName, `origin/${baseBranch}`])
255
+ if (!checkout.ok) {
256
+ checkout = git(["checkout", "-b", branchName, baseBranch])
257
+ }
258
+ if (!checkout.ok) {
259
+ ctx.ui.notify(`Round ${round}: Failed to create branch`, { level: "warn" })
260
+ continue
261
+ }
262
+
263
+ ctx.ui.notify(`Task: ${proposal.task}`, { level: "info" })
264
+
265
+ await emitCustomEvent(ctx, "autoresearch:round:start", {
266
+ round,
267
+ task: proposal.task,
268
+ predicted_delta: proposal.predicted_delta,
269
+ })
270
+
271
+ // The agent itself will make changes via the sendUserMessage approach
272
+ // For now, we run tests on whatever's been committed
273
+ const diffCheck = git(["diff", "--quiet", "HEAD"])
274
+ const untrackedResult = spawnSync("git", ["ls-files", "--others", "--exclude-standard"], {
275
+ cwd: projectRoot, encoding: "utf-8", stdio: "pipe",
276
+ })
277
+ const hasChanges = !diffCheck.ok || (untrackedResult.stdout || "").trim().length > 0
278
+
279
+ if (hasChanges) {
280
+ git(["add", "-A"])
281
+ git(["commit", "-m", `autoresearch: round ${round} - ${proposal.task}`])
282
+ }
283
+
284
+ const { passing, total, score } = runTests()
285
+ const delta = score - baselineScore
286
+
287
+ const result: ExperimentResult = {
288
+ round,
289
+ task: proposal.task,
290
+ score,
291
+ delta,
292
+ testsPassing: passing,
293
+ testsTotal: total,
294
+ branch: branchName,
295
+ }
296
+ results.push(result)
297
+
298
+ if (!bestResult || result.score > bestResult.score) {
299
+ bestResult = result
300
+ }
301
+
302
+ writeJournalEntry({
303
+ v: 1,
304
+ ts: new Date().toISOString(),
305
+ session: "autoresearch",
306
+ type: "experiment",
307
+ status: delta > 0 ? "complete" : "incomplete",
308
+ title: `Autoresearch R${round}: ${proposal.task.slice(0, 60)}`,
309
+ summary: `Score: ${score.toFixed(4)}, delta: ${delta > 0 ? "+" : ""}${delta.toFixed(4)}`,
310
+ agent_id: "pi-autoresearch",
311
+ })
312
+
313
+ const tb = await getTrainingBuffer()
314
+ if (tb) {
315
+ tb.append({
316
+ agent: "pi-autoresearch",
317
+ state: {
318
+ composite_score: baselineScore,
319
+ dimension_scores: {},
320
+ tests_passing: 0,
321
+ tests_total: 0,
322
+ trajectory_length: results.length,
323
+ recent_deltas: results.slice(-5).map(r => r.delta),
324
+ agent: "pi-autoresearch",
325
+ },
326
+ action: {
327
+ type: "experiment",
328
+ description: proposal.task,
329
+ files_affected: [],
330
+ scope: "medium",
331
+ branch: branchName,
332
+ },
333
+ reward: {
334
+ composite_delta: delta,
335
+ dimension_deltas: {},
336
+ tests_added: 0,
337
+ quality_score: score,
338
+ improved: delta > 0,
339
+ prediction_error: Math.abs(proposal.predicted_delta - delta),
340
+ },
341
+ metadata: {
342
+ branch: branchName,
343
+ autoresearch_round: round,
344
+ source: "autoresearch",
345
+ },
346
+ })
347
+ }
348
+
349
+ await emitCustomEvent(ctx, "autoresearch:round:end", {
350
+ round,
351
+ task: proposal.task,
352
+ score,
353
+ delta,
354
+ is_best: bestResult?.round === round,
355
+ })
356
+
357
+ const sign = delta > 0 ? "+" : ""
358
+ ctx.ui.notify(
359
+ `Round ${round}: ${score.toFixed(4)} (${sign}${delta.toFixed(4)}) — ${proposal.task.slice(0, 50)}`,
360
+ { level: delta > 0 ? "info" : "warn" }
361
+ )
362
+
363
+ git(["checkout", baseBranch])
364
+ }
365
+
366
+ // Summary
367
+ const summaryLines = [
368
+ `Autoresearch complete: ${rounds} rounds`,
369
+ "",
370
+ ]
371
+ for (const r of results) {
372
+ const sign = r.delta >= 0 ? "+" : ""
373
+ const star = bestResult && r.round === bestResult.round ? " ★" : ""
374
+ summaryLines.push(` R${r.round}: ${r.score.toFixed(4)} (${sign}${r.delta.toFixed(4)}) ${r.task.slice(0, 50)}${star}`)
375
+ }
376
+
377
+ if (bestResult && bestResult.delta > 0) {
378
+ summaryLines.push("", `Winner: Round ${bestResult.round} (+${bestResult.delta.toFixed(4)})`)
379
+
380
+ git(["checkout", bestResult.branch])
381
+ const pushResult = git(["push", "-u", "origin", bestResult.branch])
382
+
383
+ if (pushResult.ok) {
384
+ const prTitle = `Autoresearch: ${bestResult.task.slice(0, 50)} (+${bestResult.delta.toFixed(4)})`
385
+ const prBody = [
386
+ "## Autoresearch Winner",
387
+ "",
388
+ `**Task:** ${bestResult.task}`,
389
+ `**Score:** ${bestResult.score.toFixed(4)} (delta: +${bestResult.delta.toFixed(4)})`,
390
+ `**Tests:** ${bestResult.testsPassing}/${bestResult.testsTotal}`,
391
+ `**Round:** ${bestResult.round}/${rounds}`,
392
+ ].join("\n")
393
+
394
+ const prResult = gh([
395
+ "pr", "create", "--title", prTitle, "--body", prBody,
396
+ "--base", baseBranch, "--head", bestResult.branch,
397
+ ])
398
+
399
+ if (prResult.ok) {
400
+ summaryLines.push(`PR created: ${prResult.output}`)
401
+ await emitCustomEvent(ctx, "autoresearch:complete", {
402
+ rounds,
403
+ winner_round: bestResult.round,
404
+ winner_task: bestResult.task,
405
+ winner_delta: bestResult.delta,
406
+ pr_url: prResult.output,
407
+ })
408
+ }
409
+ }
410
+
411
+ git(["checkout", baseBranch])
412
+ } else {
413
+ summaryLines.push("", "No improvement found across all rounds.")
414
+ }
415
+
416
+ // Cleanup non-winning branches
417
+ for (const r of results) {
418
+ if (!bestResult || r.round !== bestResult.round) {
419
+ git(["branch", "-D", r.branch])
420
+ }
421
+ }
422
+
423
+ git(["stash", "pop"])
424
+ ctx.ui.notify(summaryLines.join("\n"), { level: "info" })
425
+ },
426
+ })
427
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Bookmarks Extension
3
+ *
4
+ * Registers /bookmark and /unbookmark for marking key decisions,
5
+ * milestones, and important moments in the session tree.
6
+ * Labels show up in Pi's /tree navigation for easy jumping.
7
+ *
8
+ * Also auto-bookmarks when journal entries of type "decision" or
9
+ * "milestone" are written.
10
+ *
11
+ * @purpose Session tree bookmarks for key JFL moments
12
+ */
13
+
14
+ import type { PiContext } from "./types.js"
15
+
16
+ let latestEntryId: string | undefined
17
+
18
+ export function setupBookmarks(ctx: PiContext): void {
19
+ ctx.registerCommand({
20
+ name: "bookmark",
21
+ description: "Bookmark the last message (usage: /bookmark [label])",
22
+ async handler(args, ctx) {
23
+ const label = args.trim() || `jfl-${Date.now()}`
24
+
25
+ const sm = ctx.pi.sessionManager
26
+ if (!sm) {
27
+ ctx.ui.notify("Session manager unavailable", { level: "warn" })
28
+ return
29
+ }
30
+
31
+ const entries = sm.getEntries?.() ?? sm.getBranch?.() ?? []
32
+ for (let i = entries.length - 1; i >= 0; i--) {
33
+ const entry = entries[i]
34
+ if (entry?.type === "message" && entry?.message?.role === "assistant") {
35
+ ctx.pi.setLabel(entry.id, label)
36
+ ctx.ui.notify(`Bookmarked: ${label}`, { level: "success" })
37
+ latestEntryId = entry.id
38
+ return
39
+ }
40
+ }
41
+
42
+ ctx.ui.notify("No assistant message to bookmark", { level: "warn" })
43
+ },
44
+ })
45
+
46
+ ctx.registerCommand({
47
+ name: "unbookmark",
48
+ description: "Remove the most recent bookmark",
49
+ async handler(_args, ctx) {
50
+ const sm = ctx.pi.sessionManager
51
+ if (!sm) return
52
+
53
+ const entries = sm.getEntries?.() ?? sm.getBranch?.() ?? []
54
+ for (let i = entries.length - 1; i >= 0; i--) {
55
+ const entry = entries[i]
56
+ const label = sm.getLabel?.(entry?.id)
57
+ if (label) {
58
+ ctx.pi.setLabel(entry.id, undefined)
59
+ ctx.ui.notify(`Removed: ${label}`, { level: "info" })
60
+ return
61
+ }
62
+ }
63
+
64
+ ctx.ui.notify("No bookmarked entry found", { level: "warn" })
65
+ },
66
+ })
67
+
68
+ ctx.on("journal:written", (data: any) => {
69
+ if (!data) return
70
+ const type = data.type
71
+ if (type === "decision" || type === "milestone") {
72
+ const sm = ctx.pi.sessionManager
73
+ if (!sm) return
74
+
75
+ const entries = sm.getEntries?.() ?? []
76
+ if (entries.length > 0) {
77
+ const last = entries[entries.length - 1]
78
+ if (last?.id) {
79
+ const label = `${type}: ${(data.title ?? "").slice(0, 40)}`
80
+ ctx.pi.setLabel(last.id, label)
81
+ }
82
+ }
83
+ }
84
+ })
85
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Context Extension
3
+ *
4
+ * Ensures Context Hub is running, injects recent context before each agent turn,
5
+ * and registers the jfl_context tool with custom TUI rendering.
6
+ * Context results show type-colored headers and collapsible sections.
7
+ *
8
+ * @purpose Context Hub integration — inject context, register themed jfl_context tool
9
+ */
10
+
11
+ import { existsSync, readFileSync } from "fs"
12
+ import { join } from "path"
13
+ import { execSync } from "child_process"
14
+ import type { PiContext, JflConfig, AgentStartEvent } from "./types.js"
15
+ import { contextRenderCall, contextRenderResult } from "./tool-renderers.js"
16
+
17
+ let hubBaseUrl = "http://localhost:4242"
18
+ let hubToken: string | null = null
19
+ let projectRoot = ""
20
+
21
+ function readToken(root: string): string | null {
22
+ const tokenPath = join(root, ".jfl", "context-hub.token")
23
+ if (existsSync(tokenPath)) {
24
+ return readFileSync(tokenPath, "utf-8").trim()
25
+ }
26
+ return null
27
+ }
28
+
29
+ function getHubUrl(root: string): string {
30
+ // 1. Runtime port file (written by context-hub when it starts)
31
+ const portFile = join(root, ".jfl", "context-hub.port")
32
+ if (existsSync(portFile)) {
33
+ const port = readFileSync(portFile, "utf-8").trim()
34
+ if (port) return `http://localhost:${port}`
35
+ }
36
+
37
+ // 2. Project config (static port assignment)
38
+ const configFile = join(root, ".jfl", "config.json")
39
+ if (existsSync(configFile)) {
40
+ try {
41
+ const config = JSON.parse(readFileSync(configFile, "utf-8"))
42
+ const port = config.contextHub?.port
43
+ if (port) return `http://localhost:${port}`
44
+ } catch {}
45
+ }
46
+
47
+ return "http://localhost:4242"
48
+ }
49
+
50
+ function refreshHubUrl(): void {
51
+ hubBaseUrl = getHubUrl(projectRoot)
52
+ hubToken = readToken(projectRoot)
53
+ }
54
+
55
+ async function fetchContext(query?: string, limit = 10): Promise<string> {
56
+ // Try current URL first, then refresh and retry once on failure
57
+ for (let attempt = 0; attempt < 2; attempt++) {
58
+ try {
59
+ const params = new URLSearchParams()
60
+ if (query) params.set("query", query)
61
+ params.set("limit", String(limit))
62
+
63
+ const resp = await fetch(`${hubBaseUrl}/api/context?${params}`, {
64
+ headers: hubToken ? { Authorization: `Bearer ${hubToken}` } : {},
65
+ signal: AbortSignal.timeout(5000),
66
+ })
67
+
68
+ if (!resp.ok) {
69
+ if (attempt === 0) { refreshHubUrl(); continue }
70
+ return ""
71
+ }
72
+
73
+ const data = await resp.json() as { items?: Array<{ content: string; source?: string }> }
74
+ if (!data.items?.length) return ""
75
+
76
+ return data.items
77
+ .map((item) => {
78
+ const prefix = item.source ? `[${item.source}] ` : ""
79
+ return `${prefix}${item.content}`
80
+ })
81
+ .join("\n\n")
82
+ } catch {
83
+ if (attempt === 0) { refreshHubUrl(); continue }
84
+ return ""
85
+ }
86
+ }
87
+ return ""
88
+ }
89
+
90
+ export async function setupContext(ctx: PiContext, _config: JflConfig): Promise<void> {
91
+ const root = ctx.session.projectRoot
92
+ projectRoot = root
93
+
94
+ // Start Context Hub FIRST, then read the port it wrote
95
+ try {
96
+ execSync("jfl context-hub ensure", { cwd: root, stdio: "pipe", timeout: 15000 })
97
+ ctx.log("Context Hub ensured", "debug")
98
+ } catch (err) {
99
+ const msg = err instanceof Error ? err.message : String(err)
100
+ ctx.log(`Context Hub ensure failed: ${msg}`, "debug")
101
+ }
102
+
103
+ // Now read the port (hub may have written .jfl/context-hub.port during ensure)
104
+ hubBaseUrl = getHubUrl(root)
105
+ hubToken = readToken(root)
106
+ ctx.log(`Context Hub URL: ${hubBaseUrl}`, "debug")
107
+
108
+ ctx.registerTool({
109
+ name: "jfl_context",
110
+ description: "Search JFL project context: journal entries, knowledge docs, memory. Use this to look up what happened in previous sessions, project decisions, or any project-specific knowledge.",
111
+ promptSnippet: "Search project context: journals, knowledge docs, decisions",
112
+ inputSchema: {
113
+ type: "object",
114
+ properties: {
115
+ query: {
116
+ type: "string",
117
+ description: "Search query to find relevant context",
118
+ },
119
+ limit: {
120
+ type: "number",
121
+ description: "Maximum number of results to return (default: 10)",
122
+ },
123
+ },
124
+ required: ["query"],
125
+ },
126
+ async handler(input) {
127
+ const { query, limit } = input as { query: string; limit?: number }
128
+ const result = await fetchContext(query, limit ?? 10)
129
+ return result || "No relevant context found."
130
+ },
131
+ renderCall: contextRenderCall,
132
+ renderResult: contextRenderResult,
133
+ })
134
+ }
135
+
136
+ export async function injectContext(
137
+ _ctx: PiContext,
138
+ _event: AgentStartEvent
139
+ ): Promise<{ systemPromptAddition?: string } | void> {
140
+ const context = await fetchContext(undefined, 10)
141
+ if (!context) return
142
+
143
+ return {
144
+ systemPromptAddition: [
145
+ "## JFL Project Context",
146
+ "(Recent journal entries and project knowledge — use this to maintain continuity across sessions)",
147
+ "",
148
+ context,
149
+ ].join("\n"),
150
+ }
151
+ }