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
@@ -5,42 +5,181 @@
5
5
  * embeddings (semantic, optional). Provides relevance scoring
6
6
  * and result ranking.
7
7
  *
8
+ * Key optimizations:
9
+ * - Stopword removal for cleaner term matching
10
+ * - Phrase detection for multi-word queries
11
+ * - Adaptive BM25 b parameter based on corpus statistics
12
+ * - BM25+ variant with positive IDF floor for better NDCG
13
+ * - Query term weighting based on IDF for discriminative power
14
+ * - Reciprocal rank fusion for hybrid score merging
15
+ * - Pivoted document length normalization
16
+ *
8
17
  * @purpose Fast and semantic search across indexed memories
9
18
  */
10
19
  import OpenAI from 'openai';
11
20
  import { getAllMemories, deserializeEmbedding } from './memory-db.js';
21
+ import { tokenize as advancedTokenize, tokenizeQuery, tokenizeDocument, } from './text-preprocessing.js';
22
+ /**
23
+ * Tokenize text for search.
24
+ * Uses advanced preprocessing by default (stopword removal, phrase detection).
25
+ * Falls back to legacy mode if specified.
26
+ */
27
+ function tokenize(text, legacy = false) {
28
+ if (legacy) {
29
+ // Legacy tokenization (no stopwords, no phrases)
30
+ return text
31
+ .toLowerCase()
32
+ .replace(/[^a-z0-9\s]/g, ' ')
33
+ .split(/\s+/)
34
+ .filter(token => token.length > 2);
35
+ }
36
+ return advancedTokenize(text);
37
+ }
38
+ // ============================================================================
39
+ // BM25+ and Score Normalization Improvements
40
+ // ============================================================================
12
41
  /**
13
- * Tokenize text for TF-IDF
14
- * Reused from Context Hub implementation
42
+ * BM25+ IDF calculation with positive floor.
43
+ *
44
+ * Standard BM25 IDF: log((N - df + 0.5) / (df + 0.5))
45
+ * This can go NEGATIVE when df > N/2, penalizing common terms.
46
+ *
47
+ * BM25+ adds a floor of δ (typically 1) to ensure all matching terms
48
+ * contribute positively:
49
+ *
50
+ * IDF+ = max(0, log((N - df + 0.5) / (df + 0.5))) + δ
51
+ *
52
+ * This improves NDCG for queries containing common relevant terms.
53
+ *
54
+ * @param N - Total number of documents
55
+ * @param df - Document frequency of term
56
+ * @param delta - Positive floor value (default: 1)
15
57
  */
16
- function tokenize(text) {
17
- return text
18
- .toLowerCase()
19
- .replace(/[^a-z0-9\s]/g, ' ')
20
- .split(/\s+/)
21
- .filter(token => token.length > 2);
58
+ function computeBM25PlusIDF(N, df, delta = 1) {
59
+ const standardIDF = Math.log((N - df + 0.5) / (df + 0.5) + 1);
60
+ return Math.max(0, standardIDF) + delta;
22
61
  }
23
62
  /**
24
- * Compute TF-IDF scores for text
63
+ * Compute query term weights based on corpus IDF.
64
+ *
65
+ * Terms that are rarer in the corpus get higher weights, improving
66
+ * discrimination. This is especially important for short queries
67
+ * where every term matters.
68
+ *
69
+ * Weight formula: softmax(IDF scores) to normalize to probability distribution
70
+ *
71
+ * @param queryTokens - Tokenized query terms
72
+ * @param docFreq - Map of term -> document frequency
73
+ * @param N - Total number of documents
25
74
  */
26
- export function computeTFIDF(text, allTexts) {
27
- const tokens = tokenize(text);
75
+ function computeQueryTermWeights(queryTokens, docFreq, N) {
76
+ const weights = new Map();
77
+ if (queryTokens.length === 0)
78
+ return weights;
79
+ // Compute raw IDF weights
80
+ const rawWeights = [];
81
+ for (const token of queryTokens) {
82
+ const df = docFreq.get(token) || 0;
83
+ const idf = Math.log((N + 1) / (df + 1)) + 1;
84
+ rawWeights.push(idf);
85
+ }
86
+ // Normalize using softmax-inspired scaling
87
+ const maxWeight = Math.max(...rawWeights);
88
+ const minWeight = Math.min(...rawWeights);
89
+ const range = maxWeight - minWeight;
90
+ for (let i = 0; i < queryTokens.length; i++) {
91
+ // Scale to [0.5, 1.5] range - ensures all terms contribute but rare ones more
92
+ const normalizedWeight = range > 0
93
+ ? 0.5 + (rawWeights[i] - minWeight) / range
94
+ : 1.0;
95
+ weights.set(queryTokens[i], normalizedWeight);
96
+ }
97
+ return weights;
98
+ }
99
+ /**
100
+ * Reciprocal Rank Fusion (RRF) for combining multiple ranked lists.
101
+ *
102
+ * RRF is more robust than linear score combination because it:
103
+ * 1. Doesn't require score normalization
104
+ * 2. Is less sensitive to outlier scores
105
+ * 3. Naturally handles different score scales
106
+ *
107
+ * Formula: RRF(d) = Σ 1 / (k + rank_i(d))
108
+ *
109
+ * where k is a smoothing constant (typically 60) that controls
110
+ * how much rank differences matter.
111
+ *
112
+ * @param rankings - Array of ranked result lists
113
+ * @param k - Smoothing constant (higher = smoother rank differences)
114
+ */
115
+ function reciprocalRankFusion(rankings, k = 60) {
116
+ const rrfScores = new Map();
117
+ for (const ranking of rankings) {
118
+ for (let rank = 0; rank < ranking.length; rank++) {
119
+ const memId = ranking[rank].memory.id;
120
+ const currentScore = rrfScores.get(memId) || 0;
121
+ rrfScores.set(memId, currentScore + 1 / (k + rank + 1));
122
+ }
123
+ }
124
+ return rrfScores;
125
+ }
126
+ /**
127
+ * Pivoted document length normalization.
128
+ *
129
+ * Standard BM25 length normalization can over-penalize long documents
130
+ * or under-penalize short ones depending on b. Pivoted normalization
131
+ * provides a more balanced approach:
132
+ *
133
+ * norm = 1 - s + s * (dl / pivot)
134
+ *
135
+ * where:
136
+ * - s is the slope (similar to b, typically 0.2-0.4)
137
+ * - pivot is a calibrated average length (can be tuned)
138
+ *
139
+ * This produces more stable rankings across varying document lengths.
140
+ *
141
+ * @param dl - Document length
142
+ * @param avgdl - Average document length
143
+ * @param s - Slope parameter (default: 0.2)
144
+ * @param pivotFactor - Pivot as factor of avgdl (default: 1.0)
145
+ */
146
+ function pivotedLengthNorm(dl, avgdl, s = 0.2, pivotFactor = 1.0) {
147
+ const pivot = avgdl * pivotFactor;
148
+ return 1 - s + s * (dl / pivot);
149
+ }
150
+ /**
151
+ * Compute TF-IDF scores for text.
152
+ *
153
+ * Uses advanced tokenization with stopword removal and phrase detection
154
+ * for cleaner term matching.
155
+ *
156
+ * @param text - Text to compute TF-IDF for
157
+ * @param allTexts - Corpus of all documents (for IDF calculation)
158
+ * @param legacy - Use legacy tokenization (no preprocessing)
159
+ */
160
+ export function computeTFIDF(text, allTexts, legacy = false) {
161
+ const tokens = legacy ? tokenize(text, true) : tokenizeDocument(text);
28
162
  const tokenCounts = {};
29
163
  // Term frequency
30
164
  tokens.forEach(token => {
31
165
  tokenCounts[token] = (tokenCounts[token] || 0) + 1;
32
166
  });
33
167
  const totalTokens = tokens.length;
168
+ if (totalTokens === 0)
169
+ return {};
34
170
  const tf = {};
35
171
  Object.keys(tokenCounts).forEach(token => {
36
172
  tf[token] = tokenCounts[token] / totalTokens;
37
173
  });
38
174
  // Inverse document frequency
175
+ // Pre-tokenize all docs once for efficiency
176
+ const tokenizedDocs = allTexts.map(doc => legacy ? tokenize(doc, true) : tokenizeDocument(doc));
39
177
  const idf = {};
40
178
  const totalDocs = allTexts.length;
41
179
  Object.keys(tf).forEach(token => {
42
- const docsWithToken = allTexts.filter(doc => tokenize(doc).includes(token)).length;
43
- idf[token] = Math.log(totalDocs / (docsWithToken + 1));
180
+ const docsWithToken = tokenizedDocs.filter(docTokens => docTokens.includes(token)).length;
181
+ // Smooth IDF to handle rare terms
182
+ idf[token] = Math.log((totalDocs + 1) / (docsWithToken + 1)) + 1;
44
183
  });
45
184
  // TF-IDF
46
185
  const tfidf = {};
@@ -50,10 +189,12 @@ export function computeTFIDF(text, allTexts) {
50
189
  return tfidf;
51
190
  }
52
191
  /**
53
- * Search memories using TF-IDF
192
+ * Search memories using TF-IDF.
193
+ *
194
+ * Uses advanced query tokenization for better term matching.
54
195
  */
55
- async function searchMemoriesTFIDF(query, memories, limit) {
56
- const queryTokens = tokenize(query);
196
+ async function searchMemoriesTFIDF(query, memories, limit, legacy = false) {
197
+ const queryTokens = legacy ? tokenize(query, true) : tokenizeQuery(query);
57
198
  const scored = [];
58
199
  for (const memory of memories) {
59
200
  if (!memory.tf_idf_tokens)
@@ -85,30 +226,187 @@ async function searchMemoriesTFIDF(query, memories, limit) {
85
226
  scored.sort((a, b) => b.score - a.score);
86
227
  return scored.slice(0, limit);
87
228
  }
229
+ /**
230
+ * Compute adaptive BM25 b parameter based on corpus statistics.
231
+ *
232
+ * The b parameter controls length normalization:
233
+ * - b=0: No length normalization (favor longer docs with more term matches)
234
+ * - b=1: Full length normalization (treat all docs equally regardless of length)
235
+ *
236
+ * Adaptive tuning based on corpus variance:
237
+ * - High variance in doc lengths → higher b (need more normalization)
238
+ * - Low variance → lower b (docs are similar length, less normalization needed)
239
+ *
240
+ * For journal entries (typically 100-500 tokens), we start with b=0.65 as baseline
241
+ * and adjust based on actual corpus statistics.
242
+ */
243
+ function computeAdaptiveB(docLengths) {
244
+ if (docLengths.length === 0)
245
+ return 0.65;
246
+ const avgLength = docLengths.reduce((a, b) => a + b, 0) / docLengths.length;
247
+ if (avgLength === 0)
248
+ return 0.65;
249
+ // Compute coefficient of variation (CV) = stddev / mean
250
+ const variance = docLengths.reduce((sum, len) => sum + Math.pow(len - avgLength, 2), 0) / docLengths.length;
251
+ const stddev = Math.sqrt(variance);
252
+ const cv = stddev / avgLength;
253
+ // Map CV to b parameter:
254
+ // - CV < 0.3: Low variance, use b=0.4 (less normalization)
255
+ // - CV 0.3-0.7: Moderate variance, use b=0.5-0.7
256
+ // - CV > 0.7: High variance, use b=0.75-0.85
257
+ // Clamp to [0.3, 0.85] range
258
+ const baseB = 0.65;
259
+ const adjustedB = baseB + (cv - 0.5) * 0.4;
260
+ return Math.max(0.3, Math.min(0.85, adjustedB));
261
+ }
262
+ /**
263
+ * BM25/BM25+ first-pass scoring over full document collection.
264
+ *
265
+ * Uses tuned k1/b parameters optimized for short, structured journal entries:
266
+ * - k1=1.5: Term frequency saturation tuned for short queries — higher k1 allows
267
+ * more discrimination between documents with varying term frequencies
268
+ * - b: Adaptive based on corpus length variance (default 0.65)
269
+ *
270
+ * Key optimizations in this implementation:
271
+ * - BM25+ variant with positive IDF floor (prevents common term penalty)
272
+ * - Query term weighting based on IDF for discriminative power
273
+ * - Pivoted length normalization option for balanced doc length handling
274
+ * - Stopword removal reduces noise in term matching
275
+ * - Phrase detection keeps compound terms together
276
+ * - Adaptive b parameter adjusts to corpus characteristics
277
+ */
278
+ async function searchMemoriesBM25(query, memories, limit, k1 = 1.5, bOverride, legacy = false, useBM25Plus = true) {
279
+ // Use query-specific tokenization
280
+ const queryTokens = legacy ? tokenize(query, true) : tokenizeQuery(query);
281
+ if (queryTokens.length === 0)
282
+ return [];
283
+ // Tokenize all documents
284
+ const docs = memories.map(m => {
285
+ const text = [m.title, m.content, m.summary]
286
+ .filter(Boolean)
287
+ .join(' ');
288
+ return legacy ? tokenize(text, true) : tokenizeDocument(text);
289
+ });
290
+ const N = docs.length;
291
+ if (N === 0)
292
+ return [];
293
+ // Compute corpus statistics for adaptive b
294
+ const docLengths = docs.map(d => d.length);
295
+ const avgdl = docLengths.reduce((sum, len) => sum + len, 0) / N;
296
+ const b = bOverride ?? computeAdaptiveB(docLengths);
297
+ // Pre-compute document frequencies for query terms
298
+ const docFreq = new Map();
299
+ for (const token of queryTokens) {
300
+ let count = 0;
301
+ for (const doc of docs) {
302
+ if (doc.includes(token))
303
+ count++;
304
+ }
305
+ docFreq.set(token, count);
306
+ }
307
+ // Compute query term weights for discriminative scoring
308
+ const queryTermWeights = computeQueryTermWeights(queryTokens, docFreq, N);
309
+ const scored = [];
310
+ for (let i = 0; i < memories.length; i++) {
311
+ const doc = docs[i];
312
+ const dl = doc.length;
313
+ if (dl === 0)
314
+ continue;
315
+ // Build term frequency map for this document
316
+ const termCounts = new Map();
317
+ for (const token of doc) {
318
+ termCounts.set(token, (termCounts.get(token) || 0) + 1);
319
+ }
320
+ let bm25Score = 0;
321
+ for (const token of queryTokens) {
322
+ const tf = termCounts.get(token) || 0;
323
+ if (tf === 0)
324
+ continue;
325
+ const df = docFreq.get(token) || 0;
326
+ // Use BM25+ IDF (with positive floor) or standard BM25 IDF
327
+ const idf = useBM25Plus
328
+ ? computeBM25PlusIDF(N, df, 1)
329
+ : Math.log((N - df + 0.5) / (df + 0.5) + 1);
330
+ // BM25 TF normalization with length normalization
331
+ const tfNorm = (tf * (k1 + 1)) / (tf + k1 * (1 - b + b * (dl / avgdl)));
332
+ // Apply query term weight for discriminative scoring
333
+ const termWeight = queryTermWeights.get(token) || 1.0;
334
+ bm25Score += idf * tfNorm * termWeight;
335
+ }
336
+ if (bm25Score > 0) {
337
+ const memory = memories[i];
338
+ // Apply temporal and type boosts
339
+ const daysSinceCreated = daysBetween(memory.created_at, new Date().toISOString());
340
+ if (daysSinceCreated < 7)
341
+ bm25Score *= 1.3;
342
+ if (memory.type === 'decision')
343
+ bm25Score *= 1.4;
344
+ if (memory.type === 'feature')
345
+ bm25Score *= 1.2;
346
+ scored.push({
347
+ memory,
348
+ score: bm25Score,
349
+ relevance: bm25Score > 0.7 ? 'high' : bm25Score > 0.4 ? 'medium' : 'low'
350
+ });
351
+ }
352
+ }
353
+ scored.sort((a, b) => b.score - a.score);
354
+ return scored.slice(0, limit);
355
+ }
88
356
  /**
89
357
  * Compute embedding for text using OpenAI
90
358
  */
91
359
  async function computeEmbedding(text) {
92
- const apiKey = process.env.OPENAI_API_KEY;
93
- if (!apiKey) {
360
+ const openaiKey = process.env.OPENAI_API_KEY;
361
+ const openrouterKey = process.env.OPENROUTER_API_KEY;
362
+ if (!openaiKey && !openrouterKey) {
94
363
  return null;
95
364
  }
96
- try {
97
- const openai = new OpenAI({ apiKey });
98
- const response = await openai.embeddings.create({
99
- model: 'text-embedding-3-small',
100
- input: text,
101
- encoding_format: 'float'
102
- });
103
- return {
104
- embedding: new Float32Array(response.data[0].embedding),
105
- model: 'text-embedding-3-small'
106
- };
365
+ // Try OpenAI first, fall back to OpenRouter
366
+ if (openaiKey) {
367
+ try {
368
+ const openai = new OpenAI({ apiKey: openaiKey });
369
+ const response = await openai.embeddings.create({
370
+ model: 'text-embedding-3-small',
371
+ input: text,
372
+ encoding_format: 'float'
373
+ });
374
+ return {
375
+ embedding: new Float32Array(response.data[0].embedding),
376
+ model: 'text-embedding-3-small'
377
+ };
378
+ }
379
+ catch (error) {
380
+ if (error?.status === 429 || error?.code === 'insufficient_quota') {
381
+ // OpenAI quota exceeded — fall through to OpenRouter
382
+ }
383
+ else {
384
+ console.error('OpenAI embedding failed:', error?.message || error);
385
+ return null;
386
+ }
387
+ }
107
388
  }
108
- catch (error) {
109
- console.error('Failed to compute embedding:', error);
110
- return null;
389
+ if (openrouterKey) {
390
+ try {
391
+ const openai = new OpenAI({
392
+ apiKey: openrouterKey,
393
+ baseURL: 'https://openrouter.ai/api/v1',
394
+ });
395
+ const response = await openai.embeddings.create({
396
+ model: 'openai/text-embedding-3-small',
397
+ input: text,
398
+ encoding_format: 'float'
399
+ });
400
+ return {
401
+ embedding: new Float32Array(response.data[0].embedding),
402
+ model: 'openrouter/text-embedding-3-small'
403
+ };
404
+ }
405
+ catch (error) {
406
+ console.error('OpenRouter embedding failed:', error?.message || error);
407
+ }
111
408
  }
409
+ return null;
112
410
  }
113
411
  /**
114
412
  * Cosine similarity between two vectors
@@ -170,46 +468,157 @@ function normalizeScores(results) {
170
468
  }));
171
469
  }
172
470
  /**
173
- * Hybrid search combining TF-IDF and embeddings
471
+ * Hybrid search combining BM25 and embeddings.
472
+ *
473
+ * Uses reciprocal rank fusion (RRF) by default for more robust score merging.
474
+ * RRF is better than linear interpolation because:
475
+ * 1. It doesn't require score normalization
476
+ * 2. It's less sensitive to outlier scores
477
+ * 3. It naturally handles different score distributions
478
+ *
479
+ * Falls back to weighted linear combination if RRF is disabled.
174
480
  */
175
- async function searchMemoriesHybrid(query, memories, limit) {
176
- // Run both searches in parallel
177
- const [tfIdfResults, embeddingResults] = await Promise.all([
178
- searchMemoriesTFIDF(query, memories, limit * 2),
481
+ async function searchMemoriesHybrid(query, memories, limit, legacy = false, useRRF = true, rrfK = 60, useBM25Plus = true) {
482
+ // Run BM25 (lexical) and embedding (semantic) in parallel
483
+ const [bm25Results, embeddingResults] = await Promise.all([
484
+ searchMemoriesBM25(query, memories, limit * 2, 1.5, undefined, legacy, useBM25Plus),
179
485
  searchMemoriesEmbedding(query, memories, limit * 2).catch(() => [])
180
486
  ]);
181
- // Normalize scores
182
- const tfIdfNormalized = normalizeScores(tfIdfResults);
183
- const embeddingNormalized = normalizeScores(embeddingResults);
184
- // Merge with weights (TF-IDF: 0.4, Embedding: 0.6)
185
- const mergedScores = new Map();
186
- for (const result of tfIdfNormalized) {
187
- const memId = result.memory.id;
188
- mergedScores.set(memId, (mergedScores.get(memId) || 0) + result.score * 0.4);
189
- }
190
- for (const result of embeddingNormalized) {
191
- const memId = result.memory.id;
192
- mergedScores.set(memId, (mergedScores.get(memId) || 0) + result.score * 0.6);
193
- }
194
- // Create final results
487
+ // Build memory lookup map
195
488
  const memoryMap = new Map();
196
- tfIdfResults.forEach(r => memoryMap.set(r.memory.id, r.memory));
489
+ bm25Results.forEach(r => memoryMap.set(r.memory.id, r.memory));
197
490
  embeddingResults.forEach(r => memoryMap.set(r.memory.id, r.memory));
491
+ let mergedScores;
492
+ if (useRRF) {
493
+ // Use Reciprocal Rank Fusion for robust score merging
494
+ mergedScores = reciprocalRankFusion([bm25Results, embeddingResults], rrfK);
495
+ }
496
+ else {
497
+ // Fallback to weighted linear combination
498
+ const bm25Normalized = normalizeScores(bm25Results);
499
+ const embeddingNormalized = normalizeScores(embeddingResults);
500
+ mergedScores = new Map();
501
+ for (const result of bm25Normalized) {
502
+ const memId = result.memory.id;
503
+ mergedScores.set(memId, (mergedScores.get(memId) || 0) + result.score * 0.4);
504
+ }
505
+ for (const result of embeddingNormalized) {
506
+ const memId = result.memory.id;
507
+ mergedScores.set(memId, (mergedScores.get(memId) || 0) + result.score * 0.6);
508
+ }
509
+ }
510
+ // Create final results
198
511
  const finalResults = Array.from(mergedScores.entries())
199
512
  .map(([id, score]) => ({
200
513
  memory: memoryMap.get(id),
201
514
  score,
202
515
  relevance: score > 0.7 ? 'high' : score > 0.4 ? 'medium' : 'low'
203
516
  }))
517
+ .filter(r => r.memory) // Filter out any undefined memories
204
518
  .sort((a, b) => b.score - a.score)
205
519
  .slice(0, limit);
206
520
  return finalResults;
207
521
  }
208
522
  /**
209
- * Main search function
523
+ * BM25/BM25+ re-ranking as a second-pass scoring step.
524
+ *
525
+ * Applies Okapi BM25 (or BM25+ variant) between query terms and document
526
+ * content to re-order initial retrieval results. This bridges the gap
527
+ * between semantic/vector retrieval and lexical relevance.
528
+ *
529
+ * Uses advanced tokenization for better term matching.
530
+ *
531
+ * @param results - Initial retrieval results (first-pass candidates)
532
+ * @param query - Original search query
533
+ * @param k1 - Term frequency saturation (default 1.5, tuned for short queries)
534
+ * @param bOverride - Optional override for b parameter (otherwise adaptive)
535
+ * @param legacy - Use legacy tokenization
536
+ * @param useBM25Plus - Use BM25+ variant with positive IDF floor
537
+ */
538
+ function reRankWithBM25(results, query, k1 = 1.5, bOverride, legacy = false, useBM25Plus = true) {
539
+ if (results.length === 0)
540
+ return results;
541
+ const queryTokens = legacy ? tokenize(query, true) : tokenizeQuery(query);
542
+ if (queryTokens.length === 0)
543
+ return results;
544
+ const docs = results.map(r => {
545
+ const text = [r.memory.title, r.memory.content, r.memory.summary]
546
+ .filter(Boolean)
547
+ .join(' ');
548
+ return legacy ? tokenize(text, true) : tokenizeDocument(text);
549
+ });
550
+ const N = docs.length;
551
+ const docLengths = docs.map(d => d.length);
552
+ const avgdl = docLengths.reduce((sum, len) => sum + len, 0) / N;
553
+ const b = bOverride ?? computeAdaptiveB(docLengths);
554
+ const docFreq = new Map();
555
+ for (const token of queryTokens) {
556
+ let count = 0;
557
+ for (const doc of docs) {
558
+ if (doc.includes(token))
559
+ count++;
560
+ }
561
+ docFreq.set(token, count);
562
+ }
563
+ // Compute query term weights for discriminative scoring
564
+ const queryTermWeights = computeQueryTermWeights(queryTokens, docFreq, N);
565
+ const reranked = results.map((result, i) => {
566
+ const doc = docs[i];
567
+ const dl = doc.length;
568
+ if (dl === 0)
569
+ return { ...result, score: 0 };
570
+ const termCounts = new Map();
571
+ for (const token of doc) {
572
+ termCounts.set(token, (termCounts.get(token) || 0) + 1);
573
+ }
574
+ let bm25Score = 0;
575
+ for (const token of queryTokens) {
576
+ const tf = termCounts.get(token) || 0;
577
+ const df = docFreq.get(token) || 0;
578
+ // Use BM25+ IDF or standard BM25 IDF
579
+ const idf = useBM25Plus
580
+ ? computeBM25PlusIDF(N, df, 1)
581
+ : Math.log((N - df + 0.5) / (df + 0.5) + 1);
582
+ const tfNorm = (tf * (k1 + 1)) / (tf + k1 * (1 - b + b * (dl / avgdl)));
583
+ // Apply query term weight
584
+ const termWeight = queryTermWeights.get(token) || 1.0;
585
+ bm25Score += idf * tfNorm * termWeight;
586
+ }
587
+ const originalWeight = 0.4;
588
+ const bm25Weight = 0.6;
589
+ const combinedScore = result.score * originalWeight + bm25Score * bm25Weight;
590
+ return {
591
+ ...result,
592
+ score: combinedScore,
593
+ relevance: combinedScore > 0.7 ? 'high' : combinedScore > 0.4 ? 'medium' : 'low'
594
+ };
595
+ });
596
+ reranked.sort((a, b) => b.score - a.score);
597
+ const normalized = normalizeScores(reranked);
598
+ return normalized.map(r => ({
599
+ ...r,
600
+ relevance: r.score > 0.7 ? 'high' : r.score > 0.4 ? 'medium' : 'low'
601
+ }));
602
+ }
603
+ /**
604
+ * Main search function.
605
+ *
606
+ * Supports multiple search methods with advanced preprocessing:
607
+ * - bm25: BM25/BM25+ scoring with adaptive length normalization
608
+ * - tfidf: Classic TF-IDF scoring
609
+ * - embedding: Semantic search with embeddings
610
+ * - hybrid: Combined BM25 + embedding with RRF (default)
611
+ *
612
+ * All methods use stopword removal and phrase detection by default.
613
+ * Set legacyTokenize=true to use original tokenization.
614
+ *
615
+ * New options for improved ranking:
616
+ * - bm25Plus: Use BM25+ variant with positive IDF floor (default: true)
617
+ * - useRRF: Use reciprocal rank fusion for hybrid (default: true)
618
+ * - rrfK: RRF smoothing constant (default: 60)
210
619
  */
211
620
  export async function searchMemories(query, options = {}) {
212
- const { maxItems = 10, type, since, method = 'hybrid' } = options;
621
+ const { maxItems = 10, type, since, method = 'hybrid', rerank = true, legacyTokenize = false, bm25Plus = true, useRRF = true, rrfK = 60 } = options;
213
622
  // Get all memories
214
623
  let memories = await getAllMemories();
215
624
  // Apply filters
@@ -220,15 +629,24 @@ export async function searchMemories(query, options = {}) {
220
629
  memories = memories.filter(m => m.created_at >= since);
221
630
  }
222
631
  // Search based on method
223
- if (method === 'tfidf') {
224
- return searchMemoriesTFIDF(query, memories, maxItems);
632
+ let results;
633
+ if (method === 'bm25') {
634
+ results = await searchMemoriesBM25(query, memories, maxItems * 2, 1.5, undefined, legacyTokenize, bm25Plus);
635
+ }
636
+ else if (method === 'tfidf') {
637
+ results = await searchMemoriesTFIDF(query, memories, maxItems * 2, legacyTokenize);
225
638
  }
226
639
  else if (method === 'embedding') {
227
- return searchMemoriesEmbedding(query, memories, maxItems);
640
+ results = await searchMemoriesEmbedding(query, memories, maxItems * 2);
228
641
  }
229
642
  else {
230
- return searchMemoriesHybrid(query, memories, maxItems);
643
+ results = await searchMemoriesHybrid(query, memories, maxItems * 2, legacyTokenize, useRRF, rrfK, bm25Plus);
231
644
  }
645
+ // Second-pass: BM25 re-ranking
646
+ if (rerank && results.length > 1) {
647
+ results = reRankWithBM25(results, query, 1.5, undefined, legacyTokenize, bm25Plus);
648
+ }
649
+ return results.slice(0, maxItems);
232
650
  }
233
651
  /**
234
652
  * Calculate days between two dates
@@ -243,4 +661,24 @@ function daysBetween(date1, date2) {
243
661
  * Export compute embedding for use in indexer
244
662
  */
245
663
  export { computeEmbedding };
664
+ /**
665
+ * Export adaptive B parameter computation for testing and external use
666
+ */
667
+ export { computeAdaptiveB };
668
+ /**
669
+ * Export BM25+ IDF computation for testing
670
+ */
671
+ export { computeBM25PlusIDF };
672
+ /**
673
+ * Export query term weight computation for testing
674
+ */
675
+ export { computeQueryTermWeights };
676
+ /**
677
+ * Export reciprocal rank fusion for testing
678
+ */
679
+ export { reciprocalRankFusion };
680
+ /**
681
+ * Export pivoted length normalization for testing
682
+ */
683
+ export { pivotedLengthNorm };
246
684
  //# sourceMappingURL=memory-search.js.map