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.
- package/dist/commands/context-hub.d.ts.map +1 -1
- package/dist/commands/context-hub.js +818 -39
- package/dist/commands/context-hub.js.map +1 -1
- package/dist/commands/eval.d.ts +1 -1
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +192 -1
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/findings.d.ts +6 -0
- package/dist/commands/findings.d.ts.map +1 -0
- package/dist/commands/findings.js +203 -0
- package/dist/commands/findings.js.map +1 -0
- package/dist/commands/hud.d.ts.map +1 -1
- package/dist/commands/hud.js +47 -9
- package/dist/commands/hud.js.map +1 -1
- package/dist/commands/ide.d.ts +27 -0
- package/dist/commands/ide.d.ts.map +1 -0
- package/dist/commands/ide.js +546 -0
- package/dist/commands/ide.js.map +1 -0
- package/dist/commands/onboard.d.ts.map +1 -1
- package/dist/commands/onboard.js +212 -2
- package/dist/commands/onboard.js.map +1 -1
- package/dist/commands/openclaw.d.ts +3 -0
- package/dist/commands/openclaw.d.ts.map +1 -1
- package/dist/commands/openclaw.js +76 -2
- package/dist/commands/openclaw.js.map +1 -1
- package/dist/commands/peter.d.ts +1 -0
- package/dist/commands/peter.d.ts.map +1 -1
- package/dist/commands/peter.js +935 -15
- package/dist/commands/peter.js.map +1 -1
- package/dist/commands/pi-fleet.d.ts +18 -0
- package/dist/commands/pi-fleet.d.ts.map +1 -0
- package/dist/commands/pi-fleet.js +382 -0
- package/dist/commands/pi-fleet.js.map +1 -0
- package/dist/commands/pi.d.ts.map +1 -1
- package/dist/commands/pi.js +18 -3
- package/dist/commands/pi.js.map +1 -1
- package/dist/commands/scope.d.ts.map +1 -1
- package/dist/commands/scope.js +90 -1
- package/dist/commands/scope.js.map +1 -1
- package/dist/commands/services.d.ts.map +1 -1
- package/dist/commands/services.js +18 -0
- package/dist/commands/services.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +22 -4
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/viz.d.ts.map +1 -1
- package/dist/commands/viz.js +417 -0
- package/dist/commands/viz.js.map +1 -1
- package/dist/dashboard-static/assets/index-B6b867Pv.js +121 -0
- package/dist/dashboard-static/assets/index-Y4BrqxV-.css +1 -0
- package/dist/dashboard-static/index.html +2 -2
- package/dist/index.js +225 -61
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-config.d.ts +52 -0
- package/dist/lib/agent-config.d.ts.map +1 -0
- package/dist/lib/agent-config.js +231 -0
- package/dist/lib/agent-config.js.map +1 -0
- package/dist/lib/agent-generator.d.ts +10 -0
- package/dist/lib/agent-generator.d.ts.map +1 -1
- package/dist/lib/agent-generator.js +64 -10
- package/dist/lib/agent-generator.js.map +1 -1
- package/dist/lib/agent-session.d.ts +104 -0
- package/dist/lib/agent-session.d.ts.map +1 -0
- package/dist/lib/agent-session.js +627 -0
- package/dist/lib/agent-session.js.map +1 -0
- package/dist/lib/eval-snapshot.d.ts +47 -0
- package/dist/lib/eval-snapshot.d.ts.map +1 -0
- package/dist/lib/eval-snapshot.js +315 -0
- package/dist/lib/eval-snapshot.js.map +1 -0
- package/dist/lib/eval-store.d.ts +5 -0
- package/dist/lib/eval-store.d.ts.map +1 -1
- package/dist/lib/eval-store.js +33 -3
- package/dist/lib/eval-store.js.map +1 -1
- package/dist/lib/findings-engine.d.ts +51 -0
- package/dist/lib/findings-engine.d.ts.map +1 -0
- package/dist/lib/findings-engine.js +338 -0
- package/dist/lib/findings-engine.js.map +1 -0
- package/dist/lib/flow-engine.d.ts +8 -0
- package/dist/lib/flow-engine.d.ts.map +1 -1
- package/dist/lib/flow-engine.js +84 -2
- package/dist/lib/flow-engine.js.map +1 -1
- package/dist/lib/hub-client.d.ts +1 -0
- package/dist/lib/hub-client.d.ts.map +1 -1
- package/dist/lib/hub-client.js +33 -6
- package/dist/lib/hub-client.js.map +1 -1
- package/dist/lib/ide-panes.d.ts +58 -0
- package/dist/lib/ide-panes.d.ts.map +1 -0
- package/dist/lib/ide-panes.js +508 -0
- package/dist/lib/ide-panes.js.map +1 -0
- package/dist/lib/memory-db.js +4 -4
- package/dist/lib/memory-db.js.map +1 -1
- package/dist/lib/memory-indexer.d.ts.map +1 -1
- package/dist/lib/memory-indexer.js +3 -0
- package/dist/lib/memory-indexer.js.map +1 -1
- package/dist/lib/memory-search.d.ts +148 -4
- package/dist/lib/memory-search.d.ts.map +1 -1
- package/dist/lib/memory-search.js +496 -58
- package/dist/lib/memory-search.js.map +1 -1
- package/dist/lib/meta-orchestrator.d.ts +104 -0
- package/dist/lib/meta-orchestrator.d.ts.map +1 -0
- package/dist/lib/meta-orchestrator.js +373 -0
- package/dist/lib/meta-orchestrator.js.map +1 -0
- package/dist/lib/peer-agent-generator.d.ts.map +1 -1
- package/dist/lib/peer-agent-generator.js +43 -19
- package/dist/lib/peer-agent-generator.js.map +1 -1
- package/dist/lib/policy-head.d.ts +25 -0
- package/dist/lib/policy-head.d.ts.map +1 -0
- package/dist/lib/policy-head.js +136 -0
- package/dist/lib/policy-head.js.map +1 -0
- package/dist/lib/replay-buffer.d.ts +93 -0
- package/dist/lib/replay-buffer.d.ts.map +1 -0
- package/dist/lib/replay-buffer.js +302 -0
- package/dist/lib/replay-buffer.js.map +1 -0
- package/dist/lib/sentinel-rl.d.ts +97 -0
- package/dist/lib/sentinel-rl.d.ts.map +1 -0
- package/dist/lib/sentinel-rl.js +430 -0
- package/dist/lib/sentinel-rl.js.map +1 -0
- package/dist/lib/session-lock.d.ts +61 -0
- package/dist/lib/session-lock.d.ts.map +1 -0
- package/dist/lib/session-lock.js +438 -0
- package/dist/lib/session-lock.js.map +1 -0
- package/dist/lib/stratus-client.d.ts +1 -0
- package/dist/lib/stratus-client.d.ts.map +1 -1
- package/dist/lib/stratus-client.js +24 -2
- package/dist/lib/stratus-client.js.map +1 -1
- package/dist/lib/telemetry-agent-v2.d.ts +128 -0
- package/dist/lib/telemetry-agent-v2.d.ts.map +1 -0
- package/dist/lib/telemetry-agent-v2.js +1042 -0
- package/dist/lib/telemetry-agent-v2.js.map +1 -0
- package/dist/lib/telemetry-agent.d.ts.map +1 -1
- package/dist/lib/telemetry-agent.js +27 -6
- package/dist/lib/telemetry-agent.js.map +1 -1
- package/dist/lib/telemetry-digest.d.ts.map +1 -1
- package/dist/lib/telemetry-digest.js +27 -5
- package/dist/lib/telemetry-digest.js.map +1 -1
- package/dist/lib/telemetry.d.ts.map +1 -1
- package/dist/lib/telemetry.js +29 -4
- package/dist/lib/telemetry.js.map +1 -1
- package/dist/lib/text-preprocessing.d.ts +83 -0
- package/dist/lib/text-preprocessing.d.ts.map +1 -0
- package/dist/lib/text-preprocessing.js +261 -0
- package/dist/lib/text-preprocessing.js.map +1 -0
- package/dist/lib/training-buffer.d.ts +86 -0
- package/dist/lib/training-buffer.d.ts.map +1 -0
- package/dist/lib/training-buffer.js +139 -0
- package/dist/lib/training-buffer.js.map +1 -0
- package/dist/lib/tuple-miner.d.ts +30 -0
- package/dist/lib/tuple-miner.d.ts.map +1 -0
- package/dist/lib/tuple-miner.js +427 -0
- package/dist/lib/tuple-miner.js.map +1 -0
- package/dist/lib/vm-backend.d.ts +72 -0
- package/dist/lib/vm-backend.d.ts.map +1 -0
- package/dist/lib/vm-backend.js +175 -0
- package/dist/lib/vm-backend.js.map +1 -0
- package/dist/lib/workspace/backend.d.ts +53 -0
- package/dist/lib/workspace/backend.d.ts.map +1 -0
- package/dist/lib/workspace/backend.js +37 -0
- package/dist/lib/workspace/backend.js.map +1 -0
- package/dist/lib/workspace/cmux-adapter.d.ts +46 -0
- package/dist/lib/workspace/cmux-adapter.d.ts.map +1 -0
- package/dist/lib/workspace/cmux-adapter.js +261 -0
- package/dist/lib/workspace/cmux-adapter.js.map +1 -0
- package/dist/lib/workspace/data-pipeline.d.ts +35 -0
- package/dist/lib/workspace/data-pipeline.d.ts.map +1 -0
- package/dist/lib/workspace/data-pipeline.js +463 -0
- package/dist/lib/workspace/data-pipeline.js.map +1 -0
- package/dist/lib/workspace/engine.d.ts +64 -0
- package/dist/lib/workspace/engine.d.ts.map +1 -0
- package/dist/lib/workspace/engine.js +397 -0
- package/dist/lib/workspace/engine.js.map +1 -0
- package/dist/lib/workspace/notifications.d.ts +14 -0
- package/dist/lib/workspace/notifications.d.ts.map +1 -0
- package/dist/lib/workspace/notifications.js +41 -0
- package/dist/lib/workspace/notifications.js.map +1 -0
- package/dist/lib/workspace/surface-registry.d.ts +49 -0
- package/dist/lib/workspace/surface-registry.d.ts.map +1 -0
- package/dist/lib/workspace/surface-registry.js +217 -0
- package/dist/lib/workspace/surface-registry.js.map +1 -0
- package/dist/lib/workspace/surface-type.d.ts +153 -0
- package/dist/lib/workspace/surface-type.d.ts.map +1 -0
- package/dist/lib/workspace/surface-type.js +9 -0
- package/dist/lib/workspace/surface-type.js.map +1 -0
- package/dist/lib/workspace/surfaces/agent-overview.d.ts +16 -0
- package/dist/lib/workspace/surfaces/agent-overview.d.ts.map +1 -0
- package/dist/lib/workspace/surfaces/agent-overview.js +116 -0
- package/dist/lib/workspace/surfaces/agent-overview.js.map +1 -0
- package/dist/lib/workspace/surfaces/agent.d.ts +16 -0
- package/dist/lib/workspace/surfaces/agent.d.ts.map +1 -0
- package/dist/lib/workspace/surfaces/agent.js +112 -0
- package/dist/lib/workspace/surfaces/agent.js.map +1 -0
- package/dist/lib/workspace/surfaces/claude.d.ts +15 -0
- package/dist/lib/workspace/surfaces/claude.d.ts.map +1 -0
- package/dist/lib/workspace/surfaces/claude.js +23 -0
- package/dist/lib/workspace/surfaces/claude.js.map +1 -0
- package/dist/lib/workspace/surfaces/dashboard.d.ts +21 -0
- package/dist/lib/workspace/surfaces/dashboard.d.ts.map +1 -0
- package/dist/lib/workspace/surfaces/dashboard.js +32 -0
- package/dist/lib/workspace/surfaces/dashboard.js.map +1 -0
- package/dist/lib/workspace/surfaces/eval.d.ts +15 -0
- package/dist/lib/workspace/surfaces/eval.d.ts.map +1 -0
- package/dist/lib/workspace/surfaces/eval.js +42 -0
- package/dist/lib/workspace/surfaces/eval.js.map +1 -0
- package/dist/lib/workspace/surfaces/event-stream.d.ts +16 -0
- package/dist/lib/workspace/surfaces/event-stream.d.ts.map +1 -0
- package/dist/lib/workspace/surfaces/event-stream.js +40 -0
- package/dist/lib/workspace/surfaces/event-stream.js.map +1 -0
- package/dist/lib/workspace/surfaces/flow.d.ts +16 -0
- package/dist/lib/workspace/surfaces/flow.d.ts.map +1 -0
- package/dist/lib/workspace/surfaces/flow.js +49 -0
- package/dist/lib/workspace/surfaces/flow.js.map +1 -0
- package/dist/lib/workspace/surfaces/index.d.ts +16 -0
- package/dist/lib/workspace/surfaces/index.d.ts.map +1 -0
- package/dist/lib/workspace/surfaces/index.js +16 -0
- package/dist/lib/workspace/surfaces/index.js.map +1 -0
- package/dist/lib/workspace/surfaces/portfolio.d.ts +16 -0
- package/dist/lib/workspace/surfaces/portfolio.d.ts.map +1 -0
- package/dist/lib/workspace/surfaces/portfolio.js +102 -0
- package/dist/lib/workspace/surfaces/portfolio.js.map +1 -0
- package/dist/lib/workspace/surfaces/service.d.ts +16 -0
- package/dist/lib/workspace/surfaces/service.d.ts.map +1 -0
- package/dist/lib/workspace/surfaces/service.js +45 -0
- package/dist/lib/workspace/surfaces/service.js.map +1 -0
- package/dist/lib/workspace/surfaces/shell.d.ts +15 -0
- package/dist/lib/workspace/surfaces/shell.d.ts.map +1 -0
- package/dist/lib/workspace/surfaces/shell.js +19 -0
- package/dist/lib/workspace/surfaces/shell.js.map +1 -0
- package/dist/lib/workspace/surfaces/telemetry.d.ts +16 -0
- package/dist/lib/workspace/surfaces/telemetry.d.ts.map +1 -0
- package/dist/lib/workspace/surfaces/telemetry.js +48 -0
- package/dist/lib/workspace/surfaces/telemetry.js.map +1 -0
- package/dist/lib/workspace/surfaces/topology.d.ts +15 -0
- package/dist/lib/workspace/surfaces/topology.d.ts.map +1 -0
- package/dist/lib/workspace/surfaces/topology.js +19 -0
- package/dist/lib/workspace/surfaces/topology.js.map +1 -0
- package/dist/lib/workspace/surfaces/training.d.ts +16 -0
- package/dist/lib/workspace/surfaces/training.d.ts.map +1 -0
- package/dist/lib/workspace/surfaces/training.js +22 -0
- package/dist/lib/workspace/surfaces/training.js.map +1 -0
- package/dist/lib/workspace/tmux-adapter.d.ts +27 -0
- package/dist/lib/workspace/tmux-adapter.d.ts.map +1 -0
- package/dist/lib/workspace/tmux-adapter.js +106 -0
- package/dist/lib/workspace/tmux-adapter.js.map +1 -0
- package/dist/mcp/context-hub-mcp.js +7 -24
- package/dist/mcp/context-hub-mcp.js.map +1 -1
- package/dist/types/flows.d.ts +2 -0
- package/dist/types/flows.d.ts.map +1 -1
- package/dist/types/ide.d.ts +49 -0
- package/dist/types/ide.d.ts.map +1 -0
- package/dist/types/ide.js +5 -0
- package/dist/types/ide.js.map +1 -0
- package/dist/types/platform-digest.d.ts +228 -0
- package/dist/types/platform-digest.d.ts.map +1 -0
- package/dist/types/platform-digest.js +5 -0
- package/dist/types/platform-digest.js.map +1 -0
- package/dist/types/telemetry-digest.d.ts +2 -0
- package/dist/types/telemetry-digest.d.ts.map +1 -1
- package/dist/utils/ensure-project.d.ts +1 -0
- package/dist/utils/ensure-project.d.ts.map +1 -1
- package/dist/utils/ensure-project.js +19 -7
- package/dist/utils/ensure-project.js.map +1 -1
- package/dist/utils/jfl-config.d.ts +1 -0
- package/dist/utils/jfl-config.d.ts.map +1 -1
- package/dist/utils/jfl-config.js +19 -1
- package/dist/utils/jfl-config.js.map +1 -1
- package/dist/utils/jfl-paths.d.ts +5 -0
- package/dist/utils/jfl-paths.d.ts.map +1 -1
- package/dist/utils/jfl-paths.js +25 -3
- package/dist/utils/jfl-paths.js.map +1 -1
- package/package.json +3 -2
- package/packages/pi/AGENTS.md +112 -0
- package/packages/pi/extensions/agent-grid.ts +191 -0
- package/packages/pi/extensions/agent-names.ts +178 -0
- package/packages/pi/extensions/autoresearch.ts +427 -0
- package/packages/pi/extensions/bookmarks.ts +85 -0
- package/packages/pi/extensions/context.ts +151 -0
- package/packages/pi/extensions/crm-tool.ts +61 -0
- package/packages/pi/extensions/eval-tool.ts +224 -0
- package/packages/pi/extensions/eval.ts +60 -0
- package/packages/pi/extensions/footer.ts +239 -0
- package/packages/pi/extensions/hud-tool.ts +145 -0
- package/packages/pi/extensions/index.ts +392 -0
- package/packages/pi/extensions/journal.ts +224 -0
- package/packages/pi/extensions/map-bridge.ts +178 -0
- package/packages/pi/extensions/memory-tool.ts +68 -0
- package/packages/pi/extensions/notifications.ts +73 -0
- package/packages/pi/extensions/peter-parker.ts +202 -0
- package/packages/pi/extensions/policy-head-tool.ts +276 -0
- package/packages/pi/extensions/portfolio-bridge.ts +90 -0
- package/packages/pi/extensions/session.ts +90 -0
- package/packages/pi/extensions/shortcuts.ts +259 -0
- package/packages/pi/extensions/stratus-bridge.ts +115 -0
- package/packages/pi/extensions/synopsis-tool.ts +83 -0
- package/packages/pi/extensions/tool-renderers.ts +352 -0
- package/packages/pi/extensions/training-buffer-tool.ts +368 -0
- package/packages/pi/extensions/types.ts +163 -0
- package/packages/pi/package-lock.json +346 -0
- package/packages/pi/package.json +44 -0
- package/packages/pi/skills/agent-browser/SKILL.md +116 -0
- package/packages/pi/skills/brand-architect/SKILL.md +240 -0
- package/packages/pi/skills/brand-architect/config.yaml +137 -0
- package/packages/pi/skills/campaign-hud/config.yaml +112 -0
- package/packages/pi/skills/content-creator/SKILL.md +294 -0
- package/packages/pi/skills/context/SKILL.md +65 -0
- package/packages/pi/skills/debug/MULTI_AGENT.md +360 -0
- package/packages/pi/skills/debug/SKILL.md +554 -0
- package/packages/pi/skills/end/SKILL.md +1782 -0
- package/packages/pi/skills/eval/SKILL.md +75 -0
- package/packages/pi/skills/fly-deploy/SKILL.md +676 -0
- package/packages/pi/skills/founder-video/SKILL.md +467 -0
- package/packages/pi/skills/hud/SKILL.md +160 -0
- package/packages/pi/skills/orchestrate/SKILL.md +74 -0
- package/packages/pi/skills/pi-agents/SKILL.md +78 -0
- package/packages/pi/skills/react-best-practices/AGENTS.md +2249 -0
- package/packages/pi/skills/react-best-practices/README.md +123 -0
- package/packages/pi/skills/react-best-practices/SKILL.md +125 -0
- package/packages/pi/skills/react-best-practices/metadata.json +15 -0
- package/packages/pi/skills/react-best-practices/rules/_sections.md +46 -0
- package/packages/pi/skills/react-best-practices/rules/_template.md +28 -0
- package/packages/pi/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/packages/pi/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/packages/pi/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/packages/pi/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/packages/pi/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/packages/pi/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/packages/pi/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/packages/pi/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/packages/pi/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/packages/pi/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/packages/pi/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/packages/pi/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/packages/pi/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/packages/pi/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/packages/pi/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/packages/pi/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/packages/pi/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/packages/pi/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/packages/pi/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/packages/pi/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/packages/pi/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/packages/pi/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/packages/pi/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/packages/pi/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/packages/pi/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/packages/pi/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/packages/pi/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/packages/pi/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/packages/pi/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/packages/pi/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/packages/pi/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/packages/pi/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/packages/pi/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/packages/pi/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/packages/pi/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/packages/pi/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/packages/pi/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/packages/pi/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/packages/pi/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/packages/pi/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/packages/pi/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/packages/pi/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/packages/pi/skills/react-best-practices/rules/server-cache-react.md +26 -0
- package/packages/pi/skills/react-best-practices/rules/server-parallel-fetching.md +79 -0
- package/packages/pi/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/packages/pi/skills/remotion-best-practices/SKILL.md +43 -0
- package/packages/pi/skills/remotion-best-practices/rules/3d.md +86 -0
- package/packages/pi/skills/remotion-best-practices/rules/animations.md +29 -0
- package/packages/pi/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
- package/packages/pi/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
- package/packages/pi/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +108 -0
- package/packages/pi/skills/remotion-best-practices/rules/assets.md +78 -0
- package/packages/pi/skills/remotion-best-practices/rules/audio.md +172 -0
- package/packages/pi/skills/remotion-best-practices/rules/calculate-metadata.md +104 -0
- package/packages/pi/skills/remotion-best-practices/rules/can-decode.md +75 -0
- package/packages/pi/skills/remotion-best-practices/rules/charts.md +58 -0
- package/packages/pi/skills/remotion-best-practices/rules/compositions.md +146 -0
- package/packages/pi/skills/remotion-best-practices/rules/display-captions.md +126 -0
- package/packages/pi/skills/remotion-best-practices/rules/extract-frames.md +229 -0
- package/packages/pi/skills/remotion-best-practices/rules/fonts.md +152 -0
- package/packages/pi/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
- package/packages/pi/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
- package/packages/pi/skills/remotion-best-practices/rules/get-video-duration.md +58 -0
- package/packages/pi/skills/remotion-best-practices/rules/gifs.md +138 -0
- package/packages/pi/skills/remotion-best-practices/rules/images.md +130 -0
- package/packages/pi/skills/remotion-best-practices/rules/import-srt-captions.md +67 -0
- package/packages/pi/skills/remotion-best-practices/rules/lottie.md +68 -0
- package/packages/pi/skills/remotion-best-practices/rules/measuring-dom-nodes.md +35 -0
- package/packages/pi/skills/remotion-best-practices/rules/measuring-text.md +143 -0
- package/packages/pi/skills/remotion-best-practices/rules/sequencing.md +106 -0
- package/packages/pi/skills/remotion-best-practices/rules/tailwind.md +11 -0
- package/packages/pi/skills/remotion-best-practices/rules/text-animations.md +20 -0
- package/packages/pi/skills/remotion-best-practices/rules/timing.md +179 -0
- package/packages/pi/skills/remotion-best-practices/rules/transcribe-captions.md +19 -0
- package/packages/pi/skills/remotion-best-practices/rules/transitions.md +122 -0
- package/packages/pi/skills/remotion-best-practices/rules/trimming.md +53 -0
- package/packages/pi/skills/remotion-best-practices/rules/videos.md +171 -0
- package/packages/pi/skills/search/SKILL.md +220 -0
- package/packages/pi/skills/spec/SKILL.md +377 -0
- package/packages/pi/skills/startup/SKILL.md +315 -0
- package/packages/pi/skills/web-architect/SKILL.md +309 -0
- package/packages/pi/skills/x-algorithm/SKILL.md +305 -0
- package/packages/pi/teams/dev-team.yaml +63 -0
- package/packages/pi/teams/gtm-team.yaml +79 -0
- package/packages/pi/themes/jfl.theme.json +76 -0
- package/packages/pi/tsconfig.json +21 -0
- package/scripts/collect-tuples.sh +124 -0
- package/scripts/destroy-fleet.sh +37 -0
- package/scripts/jfl-ide.sh +48 -0
- package/scripts/session/session-cleanup.sh +4 -11
- package/scripts/session/session-init.sh +6 -0
- package/scripts/session/session-sync.sh +25 -0
- package/scripts/setup-branch-protection.sh +106 -0
- package/scripts/spawn-fleet.sh +144 -0
- package/scripts/train-policy-head.py +434 -0
- package/scripts/vm-swarm/README.md +301 -0
- package/scripts/vm-swarm/collect-tuples.sh +331 -0
- package/scripts/vm-swarm/create-base-template.sh +339 -0
- package/scripts/vm-swarm/kill-fleet.sh +204 -0
- package/scripts/vm-swarm/monitor-fleet.sh +346 -0
- package/scripts/vm-swarm/spawn-fleet.sh +304 -0
- package/template/.github/workflows/jfl-eval.yml +6 -1
- package/template/.github/workflows/jfl-review.yml +4 -0
- package/template/scripts/session/session-end.sh +69 -6
- package/template/scripts/session/session-init.sh +55 -30
- package/template/scripts/session/session-lock.sh +464 -0
- package/template/templates/service-agent/workflows/jfl-eval.yml +19 -0
- package/dist/dashboard-static/assets/index-B6kRK9Rq.js +0 -116
- 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
|
-
*
|
|
14
|
-
*
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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
|
-
|
|
27
|
-
const
|
|
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 =
|
|
43
|
-
|
|
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
|
|
93
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
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
|
|
177
|
-
const [
|
|
178
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
224
|
-
|
|
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
|
-
|
|
640
|
+
results = await searchMemoriesEmbedding(query, memories, maxItems * 2);
|
|
228
641
|
}
|
|
229
642
|
else {
|
|
230
|
-
|
|
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
|