jfl 0.4.3 → 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 (428) hide show
  1. package/README.md +15 -5
  2. package/dist/commands/context-hub.d.ts.map +1 -1
  3. package/dist/commands/context-hub.js +818 -39
  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 +1218 -2
  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/status.d.ts.map +1 -1
  45. package/dist/commands/status.js +22 -4
  46. package/dist/commands/status.js.map +1 -1
  47. package/dist/commands/viz.d.ts.map +1 -1
  48. package/dist/commands/viz.js +417 -0
  49. package/dist/commands/viz.js.map +1 -1
  50. package/dist/dashboard-static/assets/index-B6b867Pv.js +121 -0
  51. package/dist/dashboard-static/assets/index-Y4BrqxV-.css +1 -0
  52. package/dist/dashboard-static/index.html +2 -2
  53. package/dist/index.js +228 -62
  54. package/dist/index.js.map +1 -1
  55. package/dist/lib/agent-config.d.ts +52 -0
  56. package/dist/lib/agent-config.d.ts.map +1 -0
  57. package/dist/lib/agent-config.js +231 -0
  58. package/dist/lib/agent-config.js.map +1 -0
  59. package/dist/lib/agent-generator.d.ts +10 -0
  60. package/dist/lib/agent-generator.d.ts.map +1 -1
  61. package/dist/lib/agent-generator.js +64 -10
  62. package/dist/lib/agent-generator.js.map +1 -1
  63. package/dist/lib/agent-session.d.ts +104 -0
  64. package/dist/lib/agent-session.d.ts.map +1 -0
  65. package/dist/lib/agent-session.js +627 -0
  66. package/dist/lib/agent-session.js.map +1 -0
  67. package/dist/lib/eval-snapshot.d.ts +47 -0
  68. package/dist/lib/eval-snapshot.d.ts.map +1 -0
  69. package/dist/lib/eval-snapshot.js +315 -0
  70. package/dist/lib/eval-snapshot.js.map +1 -0
  71. package/dist/lib/eval-store.d.ts +5 -0
  72. package/dist/lib/eval-store.d.ts.map +1 -1
  73. package/dist/lib/eval-store.js +33 -3
  74. package/dist/lib/eval-store.js.map +1 -1
  75. package/dist/lib/findings-engine.d.ts +51 -0
  76. package/dist/lib/findings-engine.d.ts.map +1 -0
  77. package/dist/lib/findings-engine.js +338 -0
  78. package/dist/lib/findings-engine.js.map +1 -0
  79. package/dist/lib/flow-engine.d.ts +8 -0
  80. package/dist/lib/flow-engine.d.ts.map +1 -1
  81. package/dist/lib/flow-engine.js +84 -2
  82. package/dist/lib/flow-engine.js.map +1 -1
  83. package/dist/lib/hub-client.d.ts +1 -0
  84. package/dist/lib/hub-client.d.ts.map +1 -1
  85. package/dist/lib/hub-client.js +33 -6
  86. package/dist/lib/hub-client.js.map +1 -1
  87. package/dist/lib/ide-panes.d.ts +58 -0
  88. package/dist/lib/ide-panes.d.ts.map +1 -0
  89. package/dist/lib/ide-panes.js +508 -0
  90. package/dist/lib/ide-panes.js.map +1 -0
  91. package/dist/lib/memory-db.js +4 -4
  92. package/dist/lib/memory-db.js.map +1 -1
  93. package/dist/lib/memory-indexer.d.ts.map +1 -1
  94. package/dist/lib/memory-indexer.js +3 -0
  95. package/dist/lib/memory-indexer.js.map +1 -1
  96. package/dist/lib/memory-search.d.ts +148 -4
  97. package/dist/lib/memory-search.d.ts.map +1 -1
  98. package/dist/lib/memory-search.js +496 -58
  99. package/dist/lib/memory-search.js.map +1 -1
  100. package/dist/lib/meta-orchestrator.d.ts +104 -0
  101. package/dist/lib/meta-orchestrator.d.ts.map +1 -0
  102. package/dist/lib/meta-orchestrator.js +373 -0
  103. package/dist/lib/meta-orchestrator.js.map +1 -0
  104. package/dist/lib/peer-agent-generator.d.ts.map +1 -1
  105. package/dist/lib/peer-agent-generator.js +43 -19
  106. package/dist/lib/peer-agent-generator.js.map +1 -1
  107. package/dist/lib/policy-head.d.ts +25 -0
  108. package/dist/lib/policy-head.d.ts.map +1 -0
  109. package/dist/lib/policy-head.js +136 -0
  110. package/dist/lib/policy-head.js.map +1 -0
  111. package/dist/lib/replay-buffer.d.ts +93 -0
  112. package/dist/lib/replay-buffer.d.ts.map +1 -0
  113. package/dist/lib/replay-buffer.js +302 -0
  114. package/dist/lib/replay-buffer.js.map +1 -0
  115. package/dist/lib/sentinel-rl.d.ts +97 -0
  116. package/dist/lib/sentinel-rl.d.ts.map +1 -0
  117. package/dist/lib/sentinel-rl.js +430 -0
  118. package/dist/lib/sentinel-rl.js.map +1 -0
  119. package/dist/lib/session-lock.d.ts +61 -0
  120. package/dist/lib/session-lock.d.ts.map +1 -0
  121. package/dist/lib/session-lock.js +438 -0
  122. package/dist/lib/session-lock.js.map +1 -0
  123. package/dist/lib/stratus-client.d.ts +1 -0
  124. package/dist/lib/stratus-client.d.ts.map +1 -1
  125. package/dist/lib/stratus-client.js +24 -2
  126. package/dist/lib/stratus-client.js.map +1 -1
  127. package/dist/lib/telemetry-agent-v2.d.ts +128 -0
  128. package/dist/lib/telemetry-agent-v2.d.ts.map +1 -0
  129. package/dist/lib/telemetry-agent-v2.js +1042 -0
  130. package/dist/lib/telemetry-agent-v2.js.map +1 -0
  131. package/dist/lib/telemetry-agent.d.ts.map +1 -1
  132. package/dist/lib/telemetry-agent.js +27 -6
  133. package/dist/lib/telemetry-agent.js.map +1 -1
  134. package/dist/lib/telemetry-digest.d.ts.map +1 -1
  135. package/dist/lib/telemetry-digest.js +27 -5
  136. package/dist/lib/telemetry-digest.js.map +1 -1
  137. package/dist/lib/telemetry.d.ts.map +1 -1
  138. package/dist/lib/telemetry.js +29 -4
  139. package/dist/lib/telemetry.js.map +1 -1
  140. package/dist/lib/text-preprocessing.d.ts +83 -0
  141. package/dist/lib/text-preprocessing.d.ts.map +1 -0
  142. package/dist/lib/text-preprocessing.js +261 -0
  143. package/dist/lib/text-preprocessing.js.map +1 -0
  144. package/dist/lib/training-buffer.d.ts +86 -0
  145. package/dist/lib/training-buffer.d.ts.map +1 -0
  146. package/dist/lib/training-buffer.js +139 -0
  147. package/dist/lib/training-buffer.js.map +1 -0
  148. package/dist/lib/tuple-miner.d.ts +30 -0
  149. package/dist/lib/tuple-miner.d.ts.map +1 -0
  150. package/dist/lib/tuple-miner.js +427 -0
  151. package/dist/lib/tuple-miner.js.map +1 -0
  152. package/dist/lib/vm-backend.d.ts +72 -0
  153. package/dist/lib/vm-backend.d.ts.map +1 -0
  154. package/dist/lib/vm-backend.js +175 -0
  155. package/dist/lib/vm-backend.js.map +1 -0
  156. package/dist/lib/workspace/backend.d.ts +53 -0
  157. package/dist/lib/workspace/backend.d.ts.map +1 -0
  158. package/dist/lib/workspace/backend.js +37 -0
  159. package/dist/lib/workspace/backend.js.map +1 -0
  160. package/dist/lib/workspace/cmux-adapter.d.ts +46 -0
  161. package/dist/lib/workspace/cmux-adapter.d.ts.map +1 -0
  162. package/dist/lib/workspace/cmux-adapter.js +261 -0
  163. package/dist/lib/workspace/cmux-adapter.js.map +1 -0
  164. package/dist/lib/workspace/data-pipeline.d.ts +35 -0
  165. package/dist/lib/workspace/data-pipeline.d.ts.map +1 -0
  166. package/dist/lib/workspace/data-pipeline.js +463 -0
  167. package/dist/lib/workspace/data-pipeline.js.map +1 -0
  168. package/dist/lib/workspace/engine.d.ts +64 -0
  169. package/dist/lib/workspace/engine.d.ts.map +1 -0
  170. package/dist/lib/workspace/engine.js +397 -0
  171. package/dist/lib/workspace/engine.js.map +1 -0
  172. package/dist/lib/workspace/notifications.d.ts +14 -0
  173. package/dist/lib/workspace/notifications.d.ts.map +1 -0
  174. package/dist/lib/workspace/notifications.js +41 -0
  175. package/dist/lib/workspace/notifications.js.map +1 -0
  176. package/dist/lib/workspace/surface-registry.d.ts +49 -0
  177. package/dist/lib/workspace/surface-registry.d.ts.map +1 -0
  178. package/dist/lib/workspace/surface-registry.js +217 -0
  179. package/dist/lib/workspace/surface-registry.js.map +1 -0
  180. package/dist/lib/workspace/surface-type.d.ts +153 -0
  181. package/dist/lib/workspace/surface-type.d.ts.map +1 -0
  182. package/dist/lib/workspace/surface-type.js +9 -0
  183. package/dist/lib/workspace/surface-type.js.map +1 -0
  184. package/dist/lib/workspace/surfaces/agent-overview.d.ts +16 -0
  185. package/dist/lib/workspace/surfaces/agent-overview.d.ts.map +1 -0
  186. package/dist/lib/workspace/surfaces/agent-overview.js +116 -0
  187. package/dist/lib/workspace/surfaces/agent-overview.js.map +1 -0
  188. package/dist/lib/workspace/surfaces/agent.d.ts +16 -0
  189. package/dist/lib/workspace/surfaces/agent.d.ts.map +1 -0
  190. package/dist/lib/workspace/surfaces/agent.js +112 -0
  191. package/dist/lib/workspace/surfaces/agent.js.map +1 -0
  192. package/dist/lib/workspace/surfaces/claude.d.ts +15 -0
  193. package/dist/lib/workspace/surfaces/claude.d.ts.map +1 -0
  194. package/dist/lib/workspace/surfaces/claude.js +23 -0
  195. package/dist/lib/workspace/surfaces/claude.js.map +1 -0
  196. package/dist/lib/workspace/surfaces/dashboard.d.ts +21 -0
  197. package/dist/lib/workspace/surfaces/dashboard.d.ts.map +1 -0
  198. package/dist/lib/workspace/surfaces/dashboard.js +32 -0
  199. package/dist/lib/workspace/surfaces/dashboard.js.map +1 -0
  200. package/dist/lib/workspace/surfaces/eval.d.ts +15 -0
  201. package/dist/lib/workspace/surfaces/eval.d.ts.map +1 -0
  202. package/dist/lib/workspace/surfaces/eval.js +42 -0
  203. package/dist/lib/workspace/surfaces/eval.js.map +1 -0
  204. package/dist/lib/workspace/surfaces/event-stream.d.ts +16 -0
  205. package/dist/lib/workspace/surfaces/event-stream.d.ts.map +1 -0
  206. package/dist/lib/workspace/surfaces/event-stream.js +40 -0
  207. package/dist/lib/workspace/surfaces/event-stream.js.map +1 -0
  208. package/dist/lib/workspace/surfaces/flow.d.ts +16 -0
  209. package/dist/lib/workspace/surfaces/flow.d.ts.map +1 -0
  210. package/dist/lib/workspace/surfaces/flow.js +49 -0
  211. package/dist/lib/workspace/surfaces/flow.js.map +1 -0
  212. package/dist/lib/workspace/surfaces/index.d.ts +16 -0
  213. package/dist/lib/workspace/surfaces/index.d.ts.map +1 -0
  214. package/dist/lib/workspace/surfaces/index.js +16 -0
  215. package/dist/lib/workspace/surfaces/index.js.map +1 -0
  216. package/dist/lib/workspace/surfaces/portfolio.d.ts +16 -0
  217. package/dist/lib/workspace/surfaces/portfolio.d.ts.map +1 -0
  218. package/dist/lib/workspace/surfaces/portfolio.js +102 -0
  219. package/dist/lib/workspace/surfaces/portfolio.js.map +1 -0
  220. package/dist/lib/workspace/surfaces/service.d.ts +16 -0
  221. package/dist/lib/workspace/surfaces/service.d.ts.map +1 -0
  222. package/dist/lib/workspace/surfaces/service.js +45 -0
  223. package/dist/lib/workspace/surfaces/service.js.map +1 -0
  224. package/dist/lib/workspace/surfaces/shell.d.ts +15 -0
  225. package/dist/lib/workspace/surfaces/shell.d.ts.map +1 -0
  226. package/dist/lib/workspace/surfaces/shell.js +19 -0
  227. package/dist/lib/workspace/surfaces/shell.js.map +1 -0
  228. package/dist/lib/workspace/surfaces/telemetry.d.ts +16 -0
  229. package/dist/lib/workspace/surfaces/telemetry.d.ts.map +1 -0
  230. package/dist/lib/workspace/surfaces/telemetry.js +48 -0
  231. package/dist/lib/workspace/surfaces/telemetry.js.map +1 -0
  232. package/dist/lib/workspace/surfaces/topology.d.ts +15 -0
  233. package/dist/lib/workspace/surfaces/topology.d.ts.map +1 -0
  234. package/dist/lib/workspace/surfaces/topology.js +19 -0
  235. package/dist/lib/workspace/surfaces/topology.js.map +1 -0
  236. package/dist/lib/workspace/surfaces/training.d.ts +16 -0
  237. package/dist/lib/workspace/surfaces/training.d.ts.map +1 -0
  238. package/dist/lib/workspace/surfaces/training.js +22 -0
  239. package/dist/lib/workspace/surfaces/training.js.map +1 -0
  240. package/dist/lib/workspace/tmux-adapter.d.ts +27 -0
  241. package/dist/lib/workspace/tmux-adapter.d.ts.map +1 -0
  242. package/dist/lib/workspace/tmux-adapter.js +106 -0
  243. package/dist/lib/workspace/tmux-adapter.js.map +1 -0
  244. package/dist/mcp/context-hub-mcp.js +7 -24
  245. package/dist/mcp/context-hub-mcp.js.map +1 -1
  246. package/dist/types/flows.d.ts +2 -0
  247. package/dist/types/flows.d.ts.map +1 -1
  248. package/dist/types/ide.d.ts +49 -0
  249. package/dist/types/ide.d.ts.map +1 -0
  250. package/dist/types/ide.js +5 -0
  251. package/dist/types/ide.js.map +1 -0
  252. package/dist/types/platform-digest.d.ts +228 -0
  253. package/dist/types/platform-digest.d.ts.map +1 -0
  254. package/dist/types/platform-digest.js +5 -0
  255. package/dist/types/platform-digest.js.map +1 -0
  256. package/dist/types/telemetry-digest.d.ts +2 -0
  257. package/dist/types/telemetry-digest.d.ts.map +1 -1
  258. package/dist/utils/ensure-project.d.ts +1 -0
  259. package/dist/utils/ensure-project.d.ts.map +1 -1
  260. package/dist/utils/ensure-project.js +19 -7
  261. package/dist/utils/ensure-project.js.map +1 -1
  262. package/dist/utils/jfl-config.d.ts +1 -0
  263. package/dist/utils/jfl-config.d.ts.map +1 -1
  264. package/dist/utils/jfl-config.js +19 -1
  265. package/dist/utils/jfl-config.js.map +1 -1
  266. package/dist/utils/jfl-paths.d.ts +5 -0
  267. package/dist/utils/jfl-paths.d.ts.map +1 -1
  268. package/dist/utils/jfl-paths.js +25 -3
  269. package/dist/utils/jfl-paths.js.map +1 -1
  270. package/package.json +3 -2
  271. package/packages/pi/AGENTS.md +112 -0
  272. package/packages/pi/extensions/agent-grid.ts +191 -0
  273. package/packages/pi/extensions/agent-names.ts +178 -0
  274. package/packages/pi/extensions/autoresearch.ts +427 -0
  275. package/packages/pi/extensions/bookmarks.ts +85 -0
  276. package/packages/pi/extensions/context.ts +151 -0
  277. package/packages/pi/extensions/crm-tool.ts +61 -0
  278. package/packages/pi/extensions/eval-tool.ts +224 -0
  279. package/packages/pi/extensions/eval.ts +60 -0
  280. package/packages/pi/extensions/footer.ts +239 -0
  281. package/packages/pi/extensions/hud-tool.ts +145 -0
  282. package/packages/pi/extensions/index.ts +392 -0
  283. package/packages/pi/extensions/journal.ts +224 -0
  284. package/packages/pi/extensions/map-bridge.ts +178 -0
  285. package/packages/pi/extensions/memory-tool.ts +68 -0
  286. package/packages/pi/extensions/notifications.ts +73 -0
  287. package/packages/pi/extensions/peter-parker.ts +202 -0
  288. package/packages/pi/extensions/policy-head-tool.ts +276 -0
  289. package/packages/pi/extensions/portfolio-bridge.ts +90 -0
  290. package/packages/pi/extensions/session.ts +90 -0
  291. package/packages/pi/extensions/shortcuts.ts +259 -0
  292. package/packages/pi/extensions/stratus-bridge.ts +115 -0
  293. package/packages/pi/extensions/synopsis-tool.ts +83 -0
  294. package/packages/pi/extensions/tool-renderers.ts +352 -0
  295. package/packages/pi/extensions/training-buffer-tool.ts +368 -0
  296. package/packages/pi/extensions/types.ts +163 -0
  297. package/packages/pi/package-lock.json +346 -0
  298. package/packages/pi/package.json +44 -0
  299. package/packages/pi/skills/agent-browser/SKILL.md +116 -0
  300. package/packages/pi/skills/brand-architect/SKILL.md +240 -0
  301. package/packages/pi/skills/brand-architect/config.yaml +137 -0
  302. package/packages/pi/skills/campaign-hud/config.yaml +112 -0
  303. package/packages/pi/skills/content-creator/SKILL.md +294 -0
  304. package/packages/pi/skills/context/SKILL.md +65 -0
  305. package/packages/pi/skills/debug/MULTI_AGENT.md +360 -0
  306. package/packages/pi/skills/debug/SKILL.md +554 -0
  307. package/packages/pi/skills/end/SKILL.md +1782 -0
  308. package/packages/pi/skills/eval/SKILL.md +75 -0
  309. package/packages/pi/skills/fly-deploy/SKILL.md +676 -0
  310. package/packages/pi/skills/founder-video/SKILL.md +467 -0
  311. package/packages/pi/skills/hud/SKILL.md +160 -0
  312. package/packages/pi/skills/orchestrate/SKILL.md +74 -0
  313. package/packages/pi/skills/pi-agents/SKILL.md +78 -0
  314. package/packages/pi/skills/react-best-practices/AGENTS.md +2249 -0
  315. package/packages/pi/skills/react-best-practices/README.md +123 -0
  316. package/packages/pi/skills/react-best-practices/SKILL.md +125 -0
  317. package/packages/pi/skills/react-best-practices/metadata.json +15 -0
  318. package/packages/pi/skills/react-best-practices/rules/_sections.md +46 -0
  319. package/packages/pi/skills/react-best-practices/rules/_template.md +28 -0
  320. package/packages/pi/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  321. package/packages/pi/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
  322. package/packages/pi/skills/react-best-practices/rules/async-api-routes.md +38 -0
  323. package/packages/pi/skills/react-best-practices/rules/async-defer-await.md +80 -0
  324. package/packages/pi/skills/react-best-practices/rules/async-dependencies.md +36 -0
  325. package/packages/pi/skills/react-best-practices/rules/async-parallel.md +28 -0
  326. package/packages/pi/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
  327. package/packages/pi/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
  328. package/packages/pi/skills/react-best-practices/rules/bundle-conditional.md +31 -0
  329. package/packages/pi/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
  330. package/packages/pi/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  331. package/packages/pi/skills/react-best-practices/rules/bundle-preload.md +50 -0
  332. package/packages/pi/skills/react-best-practices/rules/client-event-listeners.md +74 -0
  333. package/packages/pi/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
  334. package/packages/pi/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
  335. package/packages/pi/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
  336. package/packages/pi/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
  337. package/packages/pi/skills/react-best-practices/rules/js-cache-storage.md +70 -0
  338. package/packages/pi/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
  339. package/packages/pi/skills/react-best-practices/rules/js-early-exit.md +50 -0
  340. package/packages/pi/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
  341. package/packages/pi/skills/react-best-practices/rules/js-index-maps.md +37 -0
  342. package/packages/pi/skills/react-best-practices/rules/js-length-check-first.md +49 -0
  343. package/packages/pi/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
  344. package/packages/pi/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
  345. package/packages/pi/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
  346. package/packages/pi/skills/react-best-practices/rules/rendering-activity.md +26 -0
  347. package/packages/pi/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  348. package/packages/pi/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
  349. package/packages/pi/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
  350. package/packages/pi/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  351. package/packages/pi/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  352. package/packages/pi/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
  353. package/packages/pi/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
  354. package/packages/pi/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
  355. package/packages/pi/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
  356. package/packages/pi/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
  357. package/packages/pi/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  358. package/packages/pi/skills/react-best-practices/rules/rerender-memo.md +44 -0
  359. package/packages/pi/skills/react-best-practices/rules/rerender-transitions.md +40 -0
  360. package/packages/pi/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
  361. package/packages/pi/skills/react-best-practices/rules/server-cache-lru.md +41 -0
  362. package/packages/pi/skills/react-best-practices/rules/server-cache-react.md +26 -0
  363. package/packages/pi/skills/react-best-practices/rules/server-parallel-fetching.md +79 -0
  364. package/packages/pi/skills/react-best-practices/rules/server-serialization.md +38 -0
  365. package/packages/pi/skills/remotion-best-practices/SKILL.md +43 -0
  366. package/packages/pi/skills/remotion-best-practices/rules/3d.md +86 -0
  367. package/packages/pi/skills/remotion-best-practices/rules/animations.md +29 -0
  368. package/packages/pi/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
  369. package/packages/pi/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
  370. package/packages/pi/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +108 -0
  371. package/packages/pi/skills/remotion-best-practices/rules/assets.md +78 -0
  372. package/packages/pi/skills/remotion-best-practices/rules/audio.md +172 -0
  373. package/packages/pi/skills/remotion-best-practices/rules/calculate-metadata.md +104 -0
  374. package/packages/pi/skills/remotion-best-practices/rules/can-decode.md +75 -0
  375. package/packages/pi/skills/remotion-best-practices/rules/charts.md +58 -0
  376. package/packages/pi/skills/remotion-best-practices/rules/compositions.md +146 -0
  377. package/packages/pi/skills/remotion-best-practices/rules/display-captions.md +126 -0
  378. package/packages/pi/skills/remotion-best-practices/rules/extract-frames.md +229 -0
  379. package/packages/pi/skills/remotion-best-practices/rules/fonts.md +152 -0
  380. package/packages/pi/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
  381. package/packages/pi/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
  382. package/packages/pi/skills/remotion-best-practices/rules/get-video-duration.md +58 -0
  383. package/packages/pi/skills/remotion-best-practices/rules/gifs.md +138 -0
  384. package/packages/pi/skills/remotion-best-practices/rules/images.md +130 -0
  385. package/packages/pi/skills/remotion-best-practices/rules/import-srt-captions.md +67 -0
  386. package/packages/pi/skills/remotion-best-practices/rules/lottie.md +68 -0
  387. package/packages/pi/skills/remotion-best-practices/rules/measuring-dom-nodes.md +35 -0
  388. package/packages/pi/skills/remotion-best-practices/rules/measuring-text.md +143 -0
  389. package/packages/pi/skills/remotion-best-practices/rules/sequencing.md +106 -0
  390. package/packages/pi/skills/remotion-best-practices/rules/tailwind.md +11 -0
  391. package/packages/pi/skills/remotion-best-practices/rules/text-animations.md +20 -0
  392. package/packages/pi/skills/remotion-best-practices/rules/timing.md +179 -0
  393. package/packages/pi/skills/remotion-best-practices/rules/transcribe-captions.md +19 -0
  394. package/packages/pi/skills/remotion-best-practices/rules/transitions.md +122 -0
  395. package/packages/pi/skills/remotion-best-practices/rules/trimming.md +53 -0
  396. package/packages/pi/skills/remotion-best-practices/rules/videos.md +171 -0
  397. package/packages/pi/skills/search/SKILL.md +220 -0
  398. package/packages/pi/skills/spec/SKILL.md +377 -0
  399. package/packages/pi/skills/startup/SKILL.md +315 -0
  400. package/packages/pi/skills/web-architect/SKILL.md +309 -0
  401. package/packages/pi/skills/x-algorithm/SKILL.md +305 -0
  402. package/packages/pi/teams/dev-team.yaml +63 -0
  403. package/packages/pi/teams/gtm-team.yaml +79 -0
  404. package/packages/pi/themes/jfl.theme.json +76 -0
  405. package/packages/pi/tsconfig.json +21 -0
  406. package/scripts/collect-tuples.sh +124 -0
  407. package/scripts/destroy-fleet.sh +37 -0
  408. package/scripts/jfl-ide.sh +48 -0
  409. package/scripts/session/session-cleanup.sh +4 -11
  410. package/scripts/session/session-init.sh +6 -0
  411. package/scripts/session/session-sync.sh +25 -0
  412. package/scripts/setup-branch-protection.sh +106 -0
  413. package/scripts/spawn-fleet.sh +144 -0
  414. package/scripts/train-policy-head.py +434 -0
  415. package/scripts/vm-swarm/README.md +301 -0
  416. package/scripts/vm-swarm/collect-tuples.sh +331 -0
  417. package/scripts/vm-swarm/create-base-template.sh +339 -0
  418. package/scripts/vm-swarm/kill-fleet.sh +204 -0
  419. package/scripts/vm-swarm/monitor-fleet.sh +346 -0
  420. package/scripts/vm-swarm/spawn-fleet.sh +304 -0
  421. package/template/.github/workflows/jfl-eval.yml +105 -8
  422. package/template/.github/workflows/jfl-review.yml +4 -0
  423. package/template/scripts/session/session-end.sh +69 -6
  424. package/template/scripts/session/session-init.sh +55 -30
  425. package/template/scripts/session/session-lock.sh +464 -0
  426. package/template/templates/service-agent/workflows/jfl-eval.yml +19 -0
  427. package/dist/dashboard-static/assets/index-B6kRK9Rq.js +0 -116
  428. package/dist/dashboard-static/assets/index-BpdKJPLu.css +0 -1
@@ -0,0 +1,61 @@
1
+ /**
2
+ * CRM Tool Extension
3
+ *
4
+ * Registers jfl_crm tool with custom TUI rendering.
5
+ * Delegates to ./crm CLI (Google Sheets backed).
6
+ * Renders pipeline data with color-coded deal stages.
7
+ *
8
+ * @purpose jfl_crm tool — themed CRM display with deal status colors
9
+ */
10
+
11
+ import { execSync } from "child_process"
12
+ import type { PiContext } from "./types.js"
13
+ import { crmRenderCall, crmRenderResult } from "./tool-renderers.js"
14
+
15
+ let projectRoot = ""
16
+
17
+ export function setupCrmTool(ctx: PiContext): void {
18
+ projectRoot = ctx.session.projectRoot
19
+
20
+ ctx.registerTool({
21
+ name: "jfl_crm",
22
+ description: "Query or update the JFL CRM (contacts, deals, pipeline). Delegates to ./crm CLI backed by Google Sheets.",
23
+ promptSnippet: "Query CRM pipeline, contacts, and deals via Google Sheets",
24
+ inputSchema: {
25
+ type: "object",
26
+ properties: {
27
+ command: {
28
+ type: "string",
29
+ description: "CRM subcommand: list, prep, stale, priority, touch, update, add",
30
+ },
31
+ args: {
32
+ type: "string",
33
+ description: "Arguments for the subcommand (e.g., contact name, field, value)",
34
+ },
35
+ },
36
+ required: ["command"],
37
+ },
38
+ async handler(input) {
39
+ const { command, args } = input as { command: string; args?: string }
40
+ const fullCmd = args ? `./crm ${command} ${args}` : `./crm ${command}`
41
+
42
+ try {
43
+ const output = execSync(fullCmd, {
44
+ cwd: projectRoot,
45
+ timeout: 15000,
46
+ encoding: "utf-8",
47
+ })
48
+ return output.trim()
49
+ } catch (err: unknown) {
50
+ const error = err as { message?: string; stderr?: Buffer }
51
+ if (error.stderr) {
52
+ const stderr = error.stderr.toString().trim()
53
+ if (stderr) return `Error: ${stderr}`
54
+ }
55
+ return `Error running crm: ${error.message ?? String(err)}`
56
+ }
57
+ },
58
+ renderCall: crmRenderCall,
59
+ renderResult: crmRenderResult,
60
+ })
61
+ }
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Eval Tool
3
+ *
4
+ * Exposes eval scoring and history to the agent. The agent can check
5
+ * project quality, view eval trends, and trigger eval runs.
6
+ *
7
+ * @purpose Pi tool for eval system — check scores, view trends, trigger evals
8
+ */
9
+
10
+ import { existsSync, readFileSync, readdirSync } from "fs"
11
+ import { join } from "path"
12
+ import type { PiContext, JflConfig } from "./types.js"
13
+ import { emitCustomEvent } from "./map-bridge.js"
14
+
15
+ let projectRoot = ""
16
+
17
+ interface EvalEntry {
18
+ v: number
19
+ ts: string
20
+ agent: string
21
+ run_id: string
22
+ composite: number
23
+ metrics: Record<string, any>
24
+ model_version?: string
25
+ branch?: string
26
+ pr_number?: number
27
+ delta?: number
28
+ improved?: boolean
29
+ }
30
+
31
+ function readEvals(): EvalEntry[] {
32
+ const evalPath = join(projectRoot, ".jfl", "eval", "eval.jsonl")
33
+ if (!existsSync(evalPath)) return []
34
+ const entries: EvalEntry[] = []
35
+ for (const line of readFileSync(evalPath, "utf-8").split("\n")) {
36
+ if (!line.trim()) continue
37
+ try { entries.push(JSON.parse(line)) } catch {}
38
+ }
39
+ return entries.sort((a, b) => (a.ts || "").localeCompare(b.ts || ""))
40
+ }
41
+
42
+ function readServiceEvents(): Array<Record<string, any>> {
43
+ const eventsPath = join(projectRoot, ".jfl", "service-events.jsonl")
44
+ if (!existsSync(eventsPath)) return []
45
+ const entries: any[] = []
46
+ for (const line of readFileSync(eventsPath, "utf-8").split("\n")) {
47
+ if (!line.trim()) continue
48
+ try { entries.push(JSON.parse(line)) } catch {}
49
+ }
50
+ return entries
51
+ }
52
+
53
+ export async function setupEvalTool(ctx: PiContext, _config: JflConfig): Promise<void> {
54
+ projectRoot = ctx.session.projectRoot
55
+
56
+ ctx.registerTool({
57
+ name: "jfl_eval_status",
58
+ description: "Get current eval status — latest scores, trends, and quality metrics. Use to understand project health before making changes.",
59
+ inputSchema: {
60
+ type: "object",
61
+ properties: {
62
+ limit: {
63
+ type: "string",
64
+ description: "Number of recent eval entries to show (default: 5)",
65
+ },
66
+ },
67
+ },
68
+ async handler(input) {
69
+ const { limit: limitStr } = input as { limit?: string }
70
+ const limit = parseInt(limitStr || "5", 10)
71
+
72
+ const evals = readEvals()
73
+ if (evals.length === 0) {
74
+ return "No eval history found. Run `jfl eval` or push a PR to trigger CI eval."
75
+ }
76
+
77
+ const recent = evals.slice(-limit)
78
+ const latest = recent[recent.length - 1]
79
+
80
+ const lines = [
81
+ `Eval History: ${evals.length} total entries`,
82
+ "",
83
+ `Latest composite: ${latest.composite.toFixed(4)}`,
84
+ `Latest agent: ${latest.agent}`,
85
+ `Latest time: ${latest.ts}`,
86
+ ]
87
+
88
+ if (latest.metrics) {
89
+ lines.push("", "Dimensions:")
90
+ for (const [key, val] of Object.entries(latest.metrics)) {
91
+ if (typeof val === "number") {
92
+ lines.push(` ${key}: ${val.toFixed(4)}`)
93
+ }
94
+ }
95
+ }
96
+
97
+ if (recent.length > 1) {
98
+ lines.push("", "Trend (recent):")
99
+ for (const e of recent) {
100
+ const delta = e.delta ?? 0
101
+ const sign = delta >= 0 ? "+" : ""
102
+ const improved = e.improved ? "✓" : "✗"
103
+ lines.push(` ${e.ts.slice(0, 16)} ${e.composite.toFixed(4)} (${sign}${delta.toFixed(4)}) ${improved} [${e.agent}]`)
104
+ }
105
+ }
106
+
107
+ const deltas = evals.slice(-10).map(e => e.delta ?? 0)
108
+ const avgDelta = deltas.length > 0 ? deltas.reduce((a, b) => a + b, 0) / deltas.length : 0
109
+ const improving = deltas.filter(d => d > 0).length
110
+ lines.push(
111
+ "",
112
+ `10-run trend: avg delta ${avgDelta >= 0 ? "+" : ""}${avgDelta.toFixed(4)}, ${improving}/${deltas.length} improved`,
113
+ )
114
+
115
+ return lines.join("\n")
116
+ },
117
+ })
118
+
119
+ ctx.registerTool({
120
+ name: "jfl_eval_compare",
121
+ description: "Compare two eval snapshots by index (0 = oldest, -1 = latest). Useful for understanding what changed between versions.",
122
+ inputSchema: {
123
+ type: "object",
124
+ properties: {
125
+ a: {
126
+ type: "string",
127
+ description: "Index of first snapshot (default: -2, second-to-last)",
128
+ },
129
+ b: {
130
+ type: "string",
131
+ description: "Index of second snapshot (default: -1, latest)",
132
+ },
133
+ },
134
+ },
135
+ async handler(input) {
136
+ const { a: aStr, b: bStr } = input as { a?: string; b?: string }
137
+
138
+ const evals = readEvals()
139
+ if (evals.length < 2) {
140
+ return "Need at least 2 eval entries to compare."
141
+ }
142
+
143
+ const idxA = parseInt(aStr || "-2", 10)
144
+ const idxB = parseInt(bStr || "-1", 10)
145
+
146
+ const resolveIdx = (idx: number) => idx < 0 ? evals.length + idx : idx
147
+ const evalA = evals[resolveIdx(idxA)]
148
+ const evalB = evals[resolveIdx(idxB)]
149
+
150
+ if (!evalA || !evalB) {
151
+ return `Invalid indices. Have ${evals.length} entries (0 to ${evals.length - 1}).`
152
+ }
153
+
154
+ const lines = [
155
+ "Eval Comparison",
156
+ "",
157
+ `A: ${evalA.ts.slice(0, 16)} (composite: ${evalA.composite.toFixed(4)}) [${evalA.agent}]`,
158
+ `B: ${evalB.ts.slice(0, 16)} (composite: ${evalB.composite.toFixed(4)}) [${evalB.agent}]`,
159
+ "",
160
+ `Composite delta: ${(evalB.composite - evalA.composite) >= 0 ? "+" : ""}${(evalB.composite - evalA.composite).toFixed(4)}`,
161
+ "",
162
+ "Dimension changes:",
163
+ ]
164
+
165
+ const allKeys = new Set([
166
+ ...Object.keys(evalA.metrics || {}),
167
+ ...Object.keys(evalB.metrics || {}),
168
+ ])
169
+
170
+ for (const key of allKeys) {
171
+ const valA = (evalA.metrics?.[key] as number) ?? 0
172
+ const valB = (evalB.metrics?.[key] as number) ?? 0
173
+ if (typeof valA !== "number" || typeof valB !== "number") continue
174
+ const diff = valB - valA
175
+ const sign = diff >= 0 ? "+" : ""
176
+ const arrow = diff > 0.001 ? "↑" : diff < -0.001 ? "↓" : "="
177
+ lines.push(` ${arrow} ${key}: ${valA.toFixed(4)} → ${valB.toFixed(4)} (${sign}${diff.toFixed(4)})`)
178
+ }
179
+
180
+ return lines.join("\n")
181
+ },
182
+ })
183
+
184
+ ctx.registerCommand({
185
+ name: "eval",
186
+ description: "Show eval status and recent scores",
187
+ async handler(_args, ctx) {
188
+ const evals = readEvals()
189
+ if (evals.length === 0) {
190
+ ctx.ui.notify("No eval history. Push a PR or run jfl eval.", { level: "info" })
191
+ return
192
+ }
193
+
194
+ const latest = evals[evals.length - 1]
195
+ const recent = evals.slice(-5)
196
+
197
+ const lines = [
198
+ `Eval: ${evals.length} entries | Latest: ${latest.composite.toFixed(4)}`,
199
+ "",
200
+ ]
201
+
202
+ for (const e of recent) {
203
+ const delta = e.delta ?? 0
204
+ const sign = delta >= 0 ? "+" : ""
205
+ lines.push(` ${e.ts.slice(0, 10)} ${e.composite.toFixed(4)} (${sign}${delta.toFixed(4)}) [${e.agent}]`)
206
+ }
207
+
208
+ const serviceEvents = readServiceEvents()
209
+ const recentPRs = serviceEvents
210
+ .filter(e => e.type === "eval:scored")
211
+ .slice(-3)
212
+
213
+ if (recentPRs.length > 0) {
214
+ lines.push("", "Recent PR evals:")
215
+ for (const e of recentPRs) {
216
+ const d = e.data || {}
217
+ lines.push(` PR #${d.pr_number || "?"}: ${d.composite?.toFixed(4) || "?"} (delta: ${d.delta?.toFixed(4) || "?"})`)
218
+ }
219
+ }
220
+
221
+ ctx.ui.notify(lines.join("\n"), { level: "info" })
222
+ },
223
+ })
224
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Eval Extension
3
+ *
4
+ * Captures agent turn metrics and writes eval entries via eval-store.ts.
5
+ * Emits eval:submitted to MAP bus after each captured turn.
6
+ *
7
+ * @purpose Capture per-turn eval data and emit to MAP bus
8
+ */
9
+
10
+ import { randomUUID } from "crypto"
11
+ import type { PiContext, JflConfig, AgentEndEvent } from "./types.js"
12
+ import { emitCustomEvent } from "./map-bridge.js"
13
+
14
+ interface EvalEntry {
15
+ id: string
16
+ session_id: string
17
+ ts: string
18
+ model?: string
19
+ turn_count: number
20
+ tools_used?: string[]
21
+ files_changed?: string[]
22
+ duration_ms?: number
23
+ exit_reason?: string
24
+ project_root: string
25
+ }
26
+
27
+ let projectRoot = ""
28
+
29
+ export async function setupEval(ctx: PiContext, _config: JflConfig): Promise<void> {
30
+ projectRoot = ctx.session.projectRoot
31
+ }
32
+
33
+ export async function onAgentEnd(ctx: PiContext, event: AgentEndEvent): Promise<void> {
34
+ // Pi's AgentEndEvent has messages array; use length as turn count
35
+ const turnCount = event.messages?.length ?? event.turnCount ?? 0
36
+ if (turnCount < 1) return
37
+
38
+ const entry: EvalEntry = {
39
+ id: randomUUID(),
40
+ session_id: ctx.session.id,
41
+ ts: new Date().toISOString(),
42
+ model: event.model,
43
+ turn_count: turnCount,
44
+ tools_used: event.toolsUsed,
45
+ files_changed: event.filesChanged,
46
+ duration_ms: event.duration,
47
+ exit_reason: event.exitReason,
48
+ project_root: projectRoot,
49
+ }
50
+
51
+ try {
52
+ // @ts-ignore — resolved from jfl package at runtime
53
+ const { appendEval } = await import("../../src/lib/eval-store.js")
54
+ appendEval(entry as Parameters<typeof appendEval>[0], projectRoot)
55
+ } catch {
56
+ // eval-store may not be available in all contexts — non-fatal
57
+ }
58
+
59
+ await emitCustomEvent(ctx, "eval:submitted", entry)
60
+ }
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Custom Footer Extension
3
+ *
4
+ * Rich status footer showing project name, branch, turn count, session
5
+ * duration, model, pipeline deals, active agents, and eval score.
6
+ * Reactive to branch changes and turn updates.
7
+ *
8
+ * Layout: ◆ project [branch] │ T3 2m │ model │ 3 deals │ ✓ 0.89
9
+ *
10
+ * @purpose Custom footer with live project telemetry
11
+ */
12
+
13
+ import { existsSync, readFileSync, readdirSync } from "fs"
14
+ import { join } from "path"
15
+ import { execSync } from "child_process"
16
+ import type { PiContext, PiTheme, JflConfig } from "./types.js"
17
+
18
+ interface FooterState {
19
+ turnCount: () => number
20
+ sessionStart: () => number
21
+ model: () => string
22
+ }
23
+
24
+ let projectRoot = ""
25
+ let projectName = ""
26
+ let projectType = ""
27
+ let state: FooterState
28
+ let cachedPipelineCount: number | null = null
29
+ let pipelineCacheTime = 0
30
+ let cachedEvalScore: string | null = null
31
+ let evalCacheTime = 0
32
+
33
+ function formatDuration(ms: number): string {
34
+ const s = Math.floor(ms / 1000)
35
+ if (s < 60) return `${s}s`
36
+ const m = Math.floor(s / 60)
37
+ if (m < 60) return `${m}m`
38
+ const h = Math.floor(m / 60)
39
+ const rm = m % 60
40
+ return `${h}h${rm > 0 ? `${rm}m` : ""}`
41
+ }
42
+
43
+ function getPipelineCount(): number | null {
44
+ const now = Date.now()
45
+ if (cachedPipelineCount !== null && now - pipelineCacheTime < 60000) return cachedPipelineCount
46
+
47
+ try {
48
+ const output = execSync("./crm list --compact 2>/dev/null | wc -l", {
49
+ cwd: projectRoot,
50
+ timeout: 3000,
51
+ encoding: "utf-8",
52
+ }).trim()
53
+ cachedPipelineCount = parseInt(output, 10) || 0
54
+ pipelineCacheTime = now
55
+ return cachedPipelineCount
56
+ } catch {
57
+ return cachedPipelineCount
58
+ }
59
+ }
60
+
61
+ function getActiveAgentCount(): number {
62
+ try {
63
+ const journalDir = join(projectRoot, ".jfl", "journal")
64
+ if (!existsSync(journalDir)) return 0
65
+ const files = readdirSync(journalDir).filter(f => f.startsWith("session-") && f.endsWith(".jsonl"))
66
+ const now = Date.now()
67
+ let active = 0
68
+ for (const f of files) {
69
+ try {
70
+ const stat = require("fs").statSync(join(journalDir, f))
71
+ if (now - stat.mtimeMs < 300000) active++
72
+ } catch {}
73
+ }
74
+ return active
75
+ } catch {
76
+ return 0
77
+ }
78
+ }
79
+
80
+ function getEvalScore(): string | null {
81
+ const now = Date.now()
82
+ if (cachedEvalScore !== null && now - evalCacheTime < 30000) return cachedEvalScore
83
+
84
+ try {
85
+ const evalPath = join(projectRoot, ".jfl", "eval-store.jsonl")
86
+ if (!existsSync(evalPath)) return null
87
+ const lines = readFileSync(evalPath, "utf-8").trim().split("\n").filter(Boolean)
88
+ if (lines.length === 0) return null
89
+ const last = JSON.parse(lines[lines.length - 1])
90
+ if (last.composite !== undefined) {
91
+ cachedEvalScore = last.composite.toFixed(2)
92
+ evalCacheTime = now
93
+ return cachedEvalScore
94
+ }
95
+ return null
96
+ } catch {
97
+ return cachedEvalScore
98
+ }
99
+ }
100
+
101
+ function getJournalCount(): number {
102
+ try {
103
+ const journalDir = join(projectRoot, ".jfl", "journal")
104
+ if (!existsSync(journalDir)) return 0
105
+ let count = 0
106
+ for (const f of readdirSync(journalDir).filter(f => f.endsWith(".jsonl"))) {
107
+ const content = readFileSync(join(journalDir, f), "utf-8").trim()
108
+ count += content.split("\n").filter(Boolean).length
109
+ }
110
+ return count
111
+ } catch {
112
+ return 0
113
+ }
114
+ }
115
+
116
+ export function setupFooter(
117
+ ctx: PiContext,
118
+ config: JflConfig,
119
+ footerState: FooterState
120
+ ): void {
121
+ projectRoot = ctx.session.projectRoot
122
+ state = footerState
123
+
124
+ const configPath = join(projectRoot, ".jfl", "config.json")
125
+ if (existsSync(configPath)) {
126
+ try {
127
+ const cfg = JSON.parse(readFileSync(configPath, "utf-8"))
128
+ projectName = cfg.name ?? projectRoot.split("/").pop() ?? "JFL"
129
+ projectType = cfg.type ?? "gtm"
130
+ } catch {
131
+ projectName = projectRoot.split("/").pop() ?? "JFL"
132
+ projectType = "gtm"
133
+ }
134
+ } else {
135
+ projectName = projectRoot.split("/").pop() ?? "JFL"
136
+ projectType = "gtm"
137
+ }
138
+
139
+ if (config.pi?.disable_footer) return
140
+
141
+ ctx.ui.setFooter((tui: any, theme: PiTheme, footerData: any) => {
142
+ let disposed = false
143
+ const unsub = footerData?.onBranchChange?.(() => {
144
+ if (!disposed) tui.requestRender()
145
+ })
146
+
147
+ return {
148
+ dispose: () => {
149
+ disposed = true
150
+ if (typeof unsub === "function") unsub()
151
+ },
152
+
153
+ invalidate() {},
154
+
155
+ render(width: number): string[] {
156
+ const turns = state.turnCount()
157
+ const elapsed = formatDuration(Date.now() - state.sessionStart())
158
+ const model = state.model()
159
+ const branch = footerData?.getGitBranch?.() ?? ctx.session.branch
160
+
161
+ // ─── Left side: project identity + session info ───────────────
162
+ const bullet = theme.fg("accent", "◆")
163
+ const name = theme.fg("text", projectName)
164
+ const branchStr = theme.fg("muted", `[${branch}]`)
165
+ const turnStr = theme.fg("dim", `T${turns}`)
166
+ const timeStr = theme.fg("dim", elapsed)
167
+ const modelStr = model ? theme.fg("muted", model.split("/").pop() ?? model) : ""
168
+
169
+ const leftParts = [
170
+ `${bullet} ${name} ${branchStr}`,
171
+ `${turnStr} ${timeStr}`,
172
+ modelStr,
173
+ ].filter(Boolean)
174
+
175
+ const left = leftParts.join(theme.fg("dim", " │ "))
176
+
177
+ // ─── Right side: pipeline + agents + eval ─────────────────────
178
+ const rightParts: string[] = []
179
+
180
+ const pipeline = getPipelineCount()
181
+ if (pipeline !== null && pipeline > 0) {
182
+ rightParts.push(theme.fg("accent", `${pipeline} deal${pipeline !== 1 ? "s" : ""}`))
183
+ }
184
+
185
+ const agents = getActiveAgentCount()
186
+ if (agents > 0) {
187
+ rightParts.push(theme.fg("warning", `${agents} agent${agents !== 1 ? "s" : ""}`))
188
+ }
189
+
190
+ const journals = getJournalCount()
191
+ if (journals > 0) {
192
+ rightParts.push(theme.fg("dim", `${journals}j`))
193
+ }
194
+
195
+ const evalScore = getEvalScore()
196
+ if (evalScore) {
197
+ const score = parseFloat(evalScore)
198
+ const color = score >= 0.8 ? "success" : score >= 0.5 ? "warning" : "error"
199
+ rightParts.push(theme.fg(color, `✓${evalScore}`))
200
+ }
201
+
202
+ const right = rightParts.join(theme.fg("dim", " │ "))
203
+
204
+ // ─── Compose line ─────────────────────────────────────────────
205
+ const leftLen = stripAnsi(left).length
206
+ const rightLen = stripAnsi(right).length
207
+ const gap = Math.max(1, width - leftLen - rightLen)
208
+
209
+ const line = left + " ".repeat(gap) + right
210
+ return [truncateVisible(line, width)]
211
+ },
212
+ }
213
+ })
214
+
215
+ ctx.on("turn:start", () => {
216
+ // Footer auto-rerenders via requestRender when data changes
217
+ })
218
+ }
219
+
220
+ function stripAnsi(str: string): string {
221
+ return str.replace(/\x1b\[[0-9;]*m/g, "")
222
+ }
223
+
224
+ function truncateVisible(str: string, maxWidth: number): string {
225
+ const visible = stripAnsi(str)
226
+ if (visible.length <= maxWidth) return str
227
+ // Rough truncation — find where visible chars exceed width
228
+ let visCount = 0
229
+ let i = 0
230
+ while (i < str.length && visCount < maxWidth - 1) {
231
+ if (str[i] === "\x1b") {
232
+ const end = str.indexOf("m", i)
233
+ if (end !== -1) { i = end + 1; continue }
234
+ }
235
+ visCount++
236
+ i++
237
+ }
238
+ return str.slice(0, i) + "…"
239
+ }