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
|
@@ -0,0 +1,1042 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry Agent V2 - Real Platform Digest Consumer
|
|
3
|
+
*
|
|
4
|
+
* Fetches production telemetry from jfl-platform digest API,
|
|
5
|
+
* extracts real metrics, compares to previous runs, generates
|
|
6
|
+
* training tuples, and proposes scoped RL agents.
|
|
7
|
+
*
|
|
8
|
+
* @purpose Real telemetry agent consuming platform digest API for metrics-driven RL
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, appendFileSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
import { spawnSync } from 'child_process';
|
|
13
|
+
import { TrainingBuffer } from './training-buffer.js';
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Implementation
|
|
16
|
+
// ============================================================================
|
|
17
|
+
export class TelemetryAgentV2 {
|
|
18
|
+
projectRoot;
|
|
19
|
+
platformUrl;
|
|
20
|
+
installId;
|
|
21
|
+
stratusUrl;
|
|
22
|
+
stratusKey;
|
|
23
|
+
emitEvent;
|
|
24
|
+
statePath;
|
|
25
|
+
state;
|
|
26
|
+
constructor(opts) {
|
|
27
|
+
this.projectRoot = opts.projectRoot;
|
|
28
|
+
this.platformUrl = opts.platformUrl || process.env.JFL_PLATFORM_URL || this.loadPlatformUrl();
|
|
29
|
+
this.installId = opts.installId || this.loadInstallId();
|
|
30
|
+
this.stratusUrl = opts.stratusUrl || process.env.STRATUS_API_URL || 'https://api.stratus.run';
|
|
31
|
+
this.stratusKey = opts.stratusKey || process.env.STRATUS_API_KEY || '';
|
|
32
|
+
this.emitEvent = opts.emitEvent || (() => { });
|
|
33
|
+
this.statePath = join(this.projectRoot, '.jfl', 'telemetry-agent-v2-state.json');
|
|
34
|
+
this.state = this.loadState();
|
|
35
|
+
}
|
|
36
|
+
loadPlatformUrl() {
|
|
37
|
+
// Try project config first
|
|
38
|
+
const configPath = join(this.projectRoot, '.jfl', 'config.json');
|
|
39
|
+
if (existsSync(configPath)) {
|
|
40
|
+
try {
|
|
41
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
42
|
+
if (config.platformUrl)
|
|
43
|
+
return config.platformUrl;
|
|
44
|
+
}
|
|
45
|
+
catch { }
|
|
46
|
+
}
|
|
47
|
+
return 'https://jfl-platform.fly.dev';
|
|
48
|
+
}
|
|
49
|
+
loadInstallId() {
|
|
50
|
+
// Try to get from telemetry config
|
|
51
|
+
const telemetryConfigPath = join(this.projectRoot, '.jfl', 'telemetry-config.json');
|
|
52
|
+
if (existsSync(telemetryConfigPath)) {
|
|
53
|
+
try {
|
|
54
|
+
const config = JSON.parse(readFileSync(telemetryConfigPath, 'utf-8'));
|
|
55
|
+
if (config.installId)
|
|
56
|
+
return config.installId;
|
|
57
|
+
}
|
|
58
|
+
catch { }
|
|
59
|
+
}
|
|
60
|
+
// Generate new one
|
|
61
|
+
return `jfl_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
62
|
+
}
|
|
63
|
+
loadState() {
|
|
64
|
+
const defaults = {
|
|
65
|
+
lastRun: '',
|
|
66
|
+
runCount: 0,
|
|
67
|
+
recentTrainingTuples: 0,
|
|
68
|
+
totalTrainingTuples: 0,
|
|
69
|
+
proposedAgents: [],
|
|
70
|
+
lastStratusRun: '',
|
|
71
|
+
stratusFailures: 0,
|
|
72
|
+
healthTrajectory: [],
|
|
73
|
+
};
|
|
74
|
+
if (existsSync(this.statePath)) {
|
|
75
|
+
try {
|
|
76
|
+
return { ...defaults, ...JSON.parse(readFileSync(this.statePath, 'utf-8')) };
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
}
|
|
80
|
+
return defaults;
|
|
81
|
+
}
|
|
82
|
+
saveState() {
|
|
83
|
+
try {
|
|
84
|
+
const dir = join(this.projectRoot, '.jfl');
|
|
85
|
+
if (!existsSync(dir))
|
|
86
|
+
mkdirSync(dir, { recursive: true });
|
|
87
|
+
writeFileSync(this.statePath, JSON.stringify(this.state, null, 2));
|
|
88
|
+
}
|
|
89
|
+
catch { }
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Fetch digest from platform API
|
|
93
|
+
*/
|
|
94
|
+
async fetchDigest(hours = 24) {
|
|
95
|
+
try {
|
|
96
|
+
// Wake up Fly.io first (cold start can take 10+ seconds)
|
|
97
|
+
try {
|
|
98
|
+
await fetch(`${this.platformUrl}`, { method: 'HEAD', signal: AbortSignal.timeout(10000) }).catch(() => { });
|
|
99
|
+
}
|
|
100
|
+
catch { }
|
|
101
|
+
const controller = new AbortController();
|
|
102
|
+
const timer = setTimeout(() => controller.abort(), 60000); // 60s after warmup
|
|
103
|
+
const response = await fetch(`${this.platformUrl}/api/v1/telemetry/digest`, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: {
|
|
106
|
+
'Content-Type': 'application/json',
|
|
107
|
+
'x-install-id': this.installId,
|
|
108
|
+
},
|
|
109
|
+
body: JSON.stringify({ hours }),
|
|
110
|
+
signal: controller.signal,
|
|
111
|
+
});
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
if (response.status === 429) {
|
|
114
|
+
// Rate limited — wait and retry once
|
|
115
|
+
const retryAfter = parseInt(response.headers.get('retry-after') || '30', 10);
|
|
116
|
+
console.log(`Digest API rate limited, waiting ${retryAfter}s...`);
|
|
117
|
+
await new Promise(r => setTimeout(r, retryAfter * 1000));
|
|
118
|
+
const retry = await fetch(`${this.platformUrl}/api/v1/telemetry/digest`, {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
headers: { 'Content-Type': 'application/json', 'x-install-id': this.installId },
|
|
121
|
+
body: JSON.stringify({ hours }),
|
|
122
|
+
signal: AbortSignal.timeout(60000),
|
|
123
|
+
});
|
|
124
|
+
if (!retry.ok) {
|
|
125
|
+
console.error(`Digest API retry returned ${retry.status}`);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const raw = await retry.json();
|
|
129
|
+
return this.normalizeDigest(raw);
|
|
130
|
+
}
|
|
131
|
+
if (!response.ok) {
|
|
132
|
+
console.error(`Digest API returned ${response.status}`);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const raw = await response.json();
|
|
136
|
+
return this.normalizeDigest(raw);
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
console.error(`Failed to fetch digest: ${err.message}`);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
normalizeDigest(raw) {
|
|
144
|
+
// Normalize API field names to our PlatformDigest interface
|
|
145
|
+
// API returns: commandUsage, toolFrequency, flowActivity, hookUsage
|
|
146
|
+
// Our types expect: commands, toolStats, flowStats, hookStats
|
|
147
|
+
const normalized = {
|
|
148
|
+
periodHours: raw.periodHours,
|
|
149
|
+
generatedAt: raw.generatedAt,
|
|
150
|
+
activeInstalls: raw.activeInstalls || 0,
|
|
151
|
+
totalEvents: raw.totalEvents || 0,
|
|
152
|
+
totalSessions: raw.sessionCosts?.length || raw.totalSessions || 0,
|
|
153
|
+
// Commands: normalize commandUsage → commands
|
|
154
|
+
commands: (raw.commandUsage || raw.commands || []).map((c) => ({
|
|
155
|
+
command: c.command,
|
|
156
|
+
count: c.count,
|
|
157
|
+
avgDurationMs: c.avgDurationMs || 0,
|
|
158
|
+
p90DurationMs: c.p90DurationMs || 0,
|
|
159
|
+
p99DurationMs: c.p99DurationMs || 0,
|
|
160
|
+
successRate: c.successRate ?? 1,
|
|
161
|
+
errorCount: c.errorCount || 0,
|
|
162
|
+
})),
|
|
163
|
+
commandSuccessRate: raw.commandSuccessRate ?? 1,
|
|
164
|
+
worstP90Command: raw.worstP90Command,
|
|
165
|
+
// Errors
|
|
166
|
+
errorClusters: raw.errorClusters || [],
|
|
167
|
+
totalErrors: raw.totalErrors || 0,
|
|
168
|
+
// Session health
|
|
169
|
+
sessionHealth: raw.sessionHealth || { started: 0, ended: 0, crashed: 0, avgDurationS: 0, crashRate: 0 },
|
|
170
|
+
// Hub health
|
|
171
|
+
hubHealth: raw.hubHealth || { starts: 0, stops: 0, crashes: 0, mcpCalls: 0, avgMcpLatencyMs: 0, p90McpLatencyMs: 0, p99McpLatencyMs: 0 },
|
|
172
|
+
// Hooks: normalize hookUsage → hookStats
|
|
173
|
+
hookStats: raw.hookStats || {
|
|
174
|
+
totalReceived: (raw.hookUsage || []).reduce((s, h) => s + (h.count || 0), 0),
|
|
175
|
+
byEvent: Object.fromEntries((raw.hookUsage || []).map((h) => [h.hookEventName, h.count])),
|
|
176
|
+
byTool: {},
|
|
177
|
+
fileHotspots: (raw.fileHotspots || []).map((f) => ({ file: f.file || f.path, edits: f.edits || f.count || 0 })),
|
|
178
|
+
},
|
|
179
|
+
// Tools: normalize toolFrequency → toolStats
|
|
180
|
+
toolStats: (raw.toolFrequency || raw.toolStats || []).map((t) => ({
|
|
181
|
+
toolName: t.toolName,
|
|
182
|
+
callCount: t.callCount || t.count || 0,
|
|
183
|
+
avgLatencyMs: t.avgLatencyMs || t.avgDurationMs || 0,
|
|
184
|
+
p90LatencyMs: t.p90LatencyMs || 0,
|
|
185
|
+
errorRate: t.errorRate || 0,
|
|
186
|
+
})),
|
|
187
|
+
// Flows: normalize flowActivity → flowStats
|
|
188
|
+
flowStats: raw.flowStats || {
|
|
189
|
+
triggered: (raw.flowActivity || []).reduce((s, f) => s + (f.triggerCount || 0), 0),
|
|
190
|
+
completed: (raw.flowActivity || []).reduce((s, f) => s + (f.completedCount || 0), 0),
|
|
191
|
+
failed: (raw.flowActivity || []).reduce((s, f) => s + (f.failedActions || 0), 0),
|
|
192
|
+
byFlow: Object.fromEntries((raw.flowActivity || []).map((f) => [f.flowName, {
|
|
193
|
+
triggered: f.triggerCount || 0,
|
|
194
|
+
completed: f.completedCount || 0,
|
|
195
|
+
failed: f.failedActions || 0,
|
|
196
|
+
}])),
|
|
197
|
+
completionRate: (() => {
|
|
198
|
+
const t = (raw.flowActivity || []).reduce((s, f) => s + (f.triggerCount || 0), 0);
|
|
199
|
+
const c = (raw.flowActivity || []).reduce((s, f) => s + (f.completedCount || 0), 0);
|
|
200
|
+
return t > 0 ? c / t : 1;
|
|
201
|
+
})(),
|
|
202
|
+
},
|
|
203
|
+
// Latency
|
|
204
|
+
latencyPercentiles: raw.latencyPercentiles || [],
|
|
205
|
+
// Costs
|
|
206
|
+
modelCosts: raw.modelCosts || [],
|
|
207
|
+
sessionCosts: (raw.sessionCosts || []).map((s) => ({
|
|
208
|
+
sessionId: s.sessionId,
|
|
209
|
+
installId: s.installId,
|
|
210
|
+
totalTokens: s.totalTokens || 0,
|
|
211
|
+
estimatedCostUsd: s.estimatedCostUsd || 0,
|
|
212
|
+
durationS: s.durationS || 0,
|
|
213
|
+
modelBreakdown: s.modelBreakdown || [],
|
|
214
|
+
})),
|
|
215
|
+
totalCostUsd: raw.totalCostUsd || 0,
|
|
216
|
+
costPerSessionUsd: raw.costPerSessionUsd || 0,
|
|
217
|
+
};
|
|
218
|
+
return normalized;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Extract real metrics from platform digest
|
|
222
|
+
*/
|
|
223
|
+
extractMetrics(digest) {
|
|
224
|
+
// Command success rate: weighted average across all commands
|
|
225
|
+
let totalCommands = 0;
|
|
226
|
+
let successfulCommands = 0;
|
|
227
|
+
let worstP90 = 0;
|
|
228
|
+
for (const cmd of digest.commands) {
|
|
229
|
+
totalCommands += cmd.count;
|
|
230
|
+
successfulCommands += cmd.count * cmd.successRate;
|
|
231
|
+
if (cmd.p90DurationMs > worstP90)
|
|
232
|
+
worstP90 = cmd.p90DurationMs;
|
|
233
|
+
}
|
|
234
|
+
const commandSuccessRate = totalCommands > 0 ? successfulCommands / totalCommands : 1;
|
|
235
|
+
// Hub crash rate
|
|
236
|
+
const hubTotal = digest.hubHealth.starts + digest.hubHealth.stops + digest.hubHealth.crashes;
|
|
237
|
+
const hubCrashRate = hubTotal > 0 ? digest.hubHealth.crashes / hubTotal : 0;
|
|
238
|
+
// Session crash rate
|
|
239
|
+
const sessionCrashRate = digest.sessionHealth.started > 0
|
|
240
|
+
? digest.sessionHealth.crashed / digest.sessionHealth.started
|
|
241
|
+
: 0;
|
|
242
|
+
// Hook hit rate: total hooks received vs expected (approx)
|
|
243
|
+
// We use total received as a signal - more hits = better coverage
|
|
244
|
+
const hookHitRate = Math.min(1, digest.hookStats.totalReceived / Math.max(100, digest.totalSessions * 10));
|
|
245
|
+
// Flow completion rate
|
|
246
|
+
const flowCompletionRate = digest.flowStats.triggered > 0
|
|
247
|
+
? digest.flowStats.completed / digest.flowStats.triggered
|
|
248
|
+
: 1;
|
|
249
|
+
// Cost per session
|
|
250
|
+
const costPerSession = digest.totalSessions > 0
|
|
251
|
+
? digest.totalCostUsd / digest.totalSessions
|
|
252
|
+
: 0;
|
|
253
|
+
return {
|
|
254
|
+
command_success_rate: commandSuccessRate,
|
|
255
|
+
command_p90_latency_ms: worstP90,
|
|
256
|
+
hub_crash_rate: hubCrashRate,
|
|
257
|
+
session_crash_rate: sessionCrashRate,
|
|
258
|
+
hook_hit_rate: hookHitRate,
|
|
259
|
+
mcp_avg_latency_ms: digest.hubHealth.avgMcpLatencyMs,
|
|
260
|
+
error_cluster_count: digest.errorClusters.length,
|
|
261
|
+
flow_completion_rate: flowCompletionRate,
|
|
262
|
+
cost_per_session_usd: costPerSession,
|
|
263
|
+
active_installs: digest.activeInstalls,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Compare metrics to previous run
|
|
268
|
+
*/
|
|
269
|
+
compareMetrics(current) {
|
|
270
|
+
const comparisons = [];
|
|
271
|
+
const previous = this.state.previousDigest?.metrics || {};
|
|
272
|
+
// Define which direction is "better" for each metric
|
|
273
|
+
const metricDirections = {
|
|
274
|
+
command_success_rate: 'higher',
|
|
275
|
+
command_p90_latency_ms: 'lower',
|
|
276
|
+
hub_crash_rate: 'lower',
|
|
277
|
+
session_crash_rate: 'lower',
|
|
278
|
+
hook_hit_rate: 'higher',
|
|
279
|
+
mcp_avg_latency_ms: 'lower',
|
|
280
|
+
error_cluster_count: 'lower',
|
|
281
|
+
flow_completion_rate: 'higher',
|
|
282
|
+
cost_per_session_usd: 'lower',
|
|
283
|
+
active_installs: 'higher',
|
|
284
|
+
};
|
|
285
|
+
for (const [name, currentValue] of Object.entries(current)) {
|
|
286
|
+
const previousValue = previous[name] ?? currentValue;
|
|
287
|
+
const delta = currentValue - previousValue;
|
|
288
|
+
const percentChange = previousValue !== 0
|
|
289
|
+
? (delta / Math.abs(previousValue)) * 100
|
|
290
|
+
: (currentValue !== 0 ? 100 : 0);
|
|
291
|
+
const direction = metricDirections[name] || 'higher';
|
|
292
|
+
const isImproving = direction === 'higher' ? delta > 0 : delta < 0;
|
|
293
|
+
const isDeclining = direction === 'higher' ? delta < 0 : delta > 0;
|
|
294
|
+
// Threshold: >10% decline = alert, >20% improvement = win
|
|
295
|
+
const significantChange = Math.abs(percentChange) > 10;
|
|
296
|
+
const bigWin = isImproving && Math.abs(percentChange) > 20;
|
|
297
|
+
comparisons.push({
|
|
298
|
+
name,
|
|
299
|
+
current: currentValue,
|
|
300
|
+
previous: previousValue,
|
|
301
|
+
delta,
|
|
302
|
+
percentChange,
|
|
303
|
+
trend: isImproving ? 'improving' : (isDeclining && significantChange ? 'declining' : 'stable'),
|
|
304
|
+
isAlert: isDeclining && significantChange,
|
|
305
|
+
isWin: bigWin,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return comparisons;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Generate training tuple from digest comparison
|
|
312
|
+
*/
|
|
313
|
+
generateTrainingTuple(digest, metrics, comparisons) {
|
|
314
|
+
// Get recent commits as the "action"
|
|
315
|
+
const recentCommits = this.getRecentCommits();
|
|
316
|
+
if (!recentCommits || recentCommits.length === 0)
|
|
317
|
+
return;
|
|
318
|
+
// Build state
|
|
319
|
+
const state = {
|
|
320
|
+
composite_score: metrics.command_success_rate * (1 - metrics.session_crash_rate),
|
|
321
|
+
dimension_scores: {
|
|
322
|
+
command_success: metrics.command_success_rate,
|
|
323
|
+
hub_stability: 1 - metrics.hub_crash_rate,
|
|
324
|
+
session_stability: 1 - metrics.session_crash_rate,
|
|
325
|
+
flow_completion: metrics.flow_completion_rate,
|
|
326
|
+
cost_efficiency: 1 / (1 + metrics.cost_per_session_usd),
|
|
327
|
+
},
|
|
328
|
+
tests_passing: 0, // Not tracked here
|
|
329
|
+
tests_total: 0,
|
|
330
|
+
trajectory_length: this.state.runCount,
|
|
331
|
+
recent_deltas: this.state.healthTrajectory.slice(-5),
|
|
332
|
+
agent: 'telemetry-agent-v2',
|
|
333
|
+
};
|
|
334
|
+
// Build action from recent commits
|
|
335
|
+
const action = {
|
|
336
|
+
type: 'feature', // Could be 'fix' based on commit messages
|
|
337
|
+
description: recentCommits.slice(0, 3).join('; '),
|
|
338
|
+
files_affected: this.getRecentChangedFiles(),
|
|
339
|
+
scope: 'medium',
|
|
340
|
+
branch: this.getCurrentBranch(),
|
|
341
|
+
};
|
|
342
|
+
// Calculate composite reward from metric changes
|
|
343
|
+
const improvingMetrics = comparisons.filter(c => c.trend === 'improving');
|
|
344
|
+
const decliningMetrics = comparisons.filter(c => c.trend === 'declining');
|
|
345
|
+
const reward = {
|
|
346
|
+
composite_delta: improvingMetrics.length * 0.1 - decliningMetrics.length * 0.2,
|
|
347
|
+
dimension_deltas: Object.fromEntries(comparisons.map(c => [c.name, c.delta])),
|
|
348
|
+
tests_added: 0,
|
|
349
|
+
quality_score: metrics.command_success_rate,
|
|
350
|
+
improved: improvingMetrics.length > decliningMetrics.length,
|
|
351
|
+
};
|
|
352
|
+
// Write to training buffer
|
|
353
|
+
const buffer = new TrainingBuffer(this.projectRoot);
|
|
354
|
+
buffer.append({
|
|
355
|
+
agent: 'telemetry-agent-v2',
|
|
356
|
+
state,
|
|
357
|
+
action,
|
|
358
|
+
reward,
|
|
359
|
+
metadata: {
|
|
360
|
+
branch: this.getCurrentBranch(),
|
|
361
|
+
source: 'autoresearch',
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
this.state.recentTrainingTuples++;
|
|
365
|
+
this.state.totalTrainingTuples++;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Propose scoped agents based on metric analysis
|
|
369
|
+
*/
|
|
370
|
+
proposeAgents(metrics, comparisons) {
|
|
371
|
+
const proposed = [];
|
|
372
|
+
// CLI speed agent if p90 latency is high
|
|
373
|
+
if (metrics.command_p90_latency_ms > 1000) {
|
|
374
|
+
proposed.push({
|
|
375
|
+
name: 'cli-speed',
|
|
376
|
+
reason: `Command p90 latency is ${metrics.command_p90_latency_ms}ms (>1000ms threshold)`,
|
|
377
|
+
triggeredBy: 'command_p90_latency_ms',
|
|
378
|
+
priority: metrics.command_p90_latency_ms > 2000 ? 'high' : 'medium',
|
|
379
|
+
config: {
|
|
380
|
+
metric: 'avg_ms',
|
|
381
|
+
direction: 'minimize',
|
|
382
|
+
scope: 'performance',
|
|
383
|
+
filesInScope: ['src/commands/**', 'src/lib/**'],
|
|
384
|
+
timeBudgetSeconds: 300,
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
// Bug-fix agent if error clusters are growing
|
|
389
|
+
const errorComparison = comparisons.find(c => c.name === 'error_cluster_count');
|
|
390
|
+
if (errorComparison && errorComparison.trend === 'declining' && metrics.error_cluster_count > 3) {
|
|
391
|
+
proposed.push({
|
|
392
|
+
name: 'bug-fix',
|
|
393
|
+
reason: `Error clusters increased to ${metrics.error_cluster_count} (was ${errorComparison.previous})`,
|
|
394
|
+
triggeredBy: 'error_cluster_count',
|
|
395
|
+
priority: 'high',
|
|
396
|
+
config: {
|
|
397
|
+
metric: 'error_count_inverse',
|
|
398
|
+
direction: 'maximize',
|
|
399
|
+
scope: 'reliability',
|
|
400
|
+
filesInScope: ['src/**'],
|
|
401
|
+
timeBudgetSeconds: 300,
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
// Hook optimization agent if hit rate is low
|
|
406
|
+
if (metrics.hook_hit_rate < 0.3) {
|
|
407
|
+
proposed.push({
|
|
408
|
+
name: 'hook-optimization',
|
|
409
|
+
reason: `Hook hit rate is ${(metrics.hook_hit_rate * 100).toFixed(1)}% (<30% threshold)`,
|
|
410
|
+
triggeredBy: 'hook_hit_rate',
|
|
411
|
+
priority: 'low',
|
|
412
|
+
config: {
|
|
413
|
+
metric: 'hook_hit_rate',
|
|
414
|
+
direction: 'maximize',
|
|
415
|
+
scope: 'hooks',
|
|
416
|
+
filesInScope: ['src/lib/**/hooks*', 'src/commands/hooks.ts'],
|
|
417
|
+
timeBudgetSeconds: 300,
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
// Flow reliability agent if completion rate is dropping
|
|
422
|
+
const flowComparison = comparisons.find(c => c.name === 'flow_completion_rate');
|
|
423
|
+
if (flowComparison && flowComparison.trend === 'declining' && metrics.flow_completion_rate < 0.9) {
|
|
424
|
+
proposed.push({
|
|
425
|
+
name: 'flow-reliability',
|
|
426
|
+
reason: `Flow completion rate dropped to ${(metrics.flow_completion_rate * 100).toFixed(1)}%`,
|
|
427
|
+
triggeredBy: 'flow_completion_rate',
|
|
428
|
+
priority: 'medium',
|
|
429
|
+
config: {
|
|
430
|
+
metric: 'flow_completion_rate',
|
|
431
|
+
direction: 'maximize',
|
|
432
|
+
scope: 'flows',
|
|
433
|
+
filesInScope: ['src/lib/flows*', 'src/commands/flows.ts'],
|
|
434
|
+
timeBudgetSeconds: 300,
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
// Cost efficiency agent if cost per session is high
|
|
439
|
+
if (metrics.cost_per_session_usd > 0.50) {
|
|
440
|
+
proposed.push({
|
|
441
|
+
name: 'cost-efficiency',
|
|
442
|
+
reason: `Cost per session is $${metrics.cost_per_session_usd.toFixed(4)} (>$0.50 threshold)`,
|
|
443
|
+
triggeredBy: 'cost_per_session_usd',
|
|
444
|
+
priority: 'medium',
|
|
445
|
+
config: {
|
|
446
|
+
metric: 'cost_inverse',
|
|
447
|
+
direction: 'maximize',
|
|
448
|
+
scope: 'cost',
|
|
449
|
+
filesInScope: ['src/lib/**'],
|
|
450
|
+
timeBudgetSeconds: 300,
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
return proposed;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Write proposed agents to .jfl/agents/proposed/
|
|
458
|
+
*/
|
|
459
|
+
writeProposedAgents(agents) {
|
|
460
|
+
const proposedDir = join(this.projectRoot, '.jfl', 'agents', 'proposed');
|
|
461
|
+
if (!existsSync(proposedDir)) {
|
|
462
|
+
mkdirSync(proposedDir, { recursive: true });
|
|
463
|
+
}
|
|
464
|
+
for (const agent of agents) {
|
|
465
|
+
const toml = this.generateAgentToml(agent);
|
|
466
|
+
const path = join(proposedDir, `${agent.name}.toml`);
|
|
467
|
+
writeFileSync(path, toml);
|
|
468
|
+
this.state.proposedAgents.push({
|
|
469
|
+
timestamp: new Date().toISOString(),
|
|
470
|
+
agent: agent.name,
|
|
471
|
+
status: 'proposed',
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
generateAgentToml(agent) {
|
|
476
|
+
return `# ${agent.name} - Auto-proposed by telemetry-agent-v2
|
|
477
|
+
# Reason: ${agent.reason}
|
|
478
|
+
# Triggered by: ${agent.triggeredBy}
|
|
479
|
+
# Priority: ${agent.priority}
|
|
480
|
+
|
|
481
|
+
[agent]
|
|
482
|
+
name = "${agent.name}"
|
|
483
|
+
scope = "${agent.config.scope}"
|
|
484
|
+
metric = "${agent.config.metric}"
|
|
485
|
+
direction = "${agent.config.direction}"
|
|
486
|
+
time_budget_seconds = ${agent.config.timeBudgetSeconds}
|
|
487
|
+
|
|
488
|
+
[eval]
|
|
489
|
+
script = "eval/${agent.name}.sh"
|
|
490
|
+
data = "eval/fixtures/${agent.name}.jsonl"
|
|
491
|
+
|
|
492
|
+
[constraints]
|
|
493
|
+
files_in_scope = [${agent.config.filesInScope.map(f => `"${f}"`).join(', ')}]
|
|
494
|
+
files_readonly = ["eval/**", "node_modules/**"]
|
|
495
|
+
max_file_changes = 10
|
|
496
|
+
|
|
497
|
+
[policy]
|
|
498
|
+
embedding_model = "stratus-x1ac-base-claude-sonnet-4-6"
|
|
499
|
+
exploration_rate = 0.2
|
|
500
|
+
decay_per_round = 0.01
|
|
501
|
+
min_exploration = 0.05
|
|
502
|
+
|
|
503
|
+
[context_scope]
|
|
504
|
+
produces = ["${agent.config.scope}:improved"]
|
|
505
|
+
consumes = []
|
|
506
|
+
`;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Analyze ALL data sources — platform telemetry + local data — for product improvement insights
|
|
510
|
+
*/
|
|
511
|
+
async analyzeProduct(platformDigest) {
|
|
512
|
+
const insights = [];
|
|
513
|
+
const claudeMdUpdates = [];
|
|
514
|
+
const staleDocs = [];
|
|
515
|
+
const trainingGaps = [];
|
|
516
|
+
// ══════════════════════════════════════════════════════════════════
|
|
517
|
+
// PLATFORM TELEMETRY — the real shit from the database
|
|
518
|
+
// ══════════════════════════════════════════════════════════════════
|
|
519
|
+
// Fetch digests if not provided
|
|
520
|
+
const digest24h = platformDigest || await this.fetchDigest(24);
|
|
521
|
+
const digest7d = await this.fetchDigest(168);
|
|
522
|
+
let platformMetrics = null;
|
|
523
|
+
if (digest24h) {
|
|
524
|
+
platformMetrics = {};
|
|
525
|
+
// ── Command performance analysis ──
|
|
526
|
+
const slowCommands = digest24h.commands.filter(c => c.p90DurationMs > 1000);
|
|
527
|
+
if (slowCommands.length > 0) {
|
|
528
|
+
insights.push({
|
|
529
|
+
type: 'performance', severity: 'warning',
|
|
530
|
+
title: `${slowCommands.length} slow commands (p90 > 1s)`,
|
|
531
|
+
detail: slowCommands.map(c => `${c.command}: p90=${c.p90DurationMs}ms, avg=${c.avgDurationMs}ms (${c.count} calls)`).join('; ')
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
const failingCommands = digest24h.commands.filter(c => c.successRate < 0.9 && c.count >= 3);
|
|
535
|
+
if (failingCommands.length > 0) {
|
|
536
|
+
insights.push({
|
|
537
|
+
type: 'performance', severity: 'critical',
|
|
538
|
+
title: `${failingCommands.length} unreliable commands (<90% success)`,
|
|
539
|
+
detail: failingCommands.map(c => `${c.command}: ${(c.successRate * 100).toFixed(0)}% success, ${c.errorCount} errors out of ${c.count}`).join('; ')
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
// ── Which command is used most? That's what to optimize first ──
|
|
543
|
+
const sorted = [...digest24h.commands].sort((a, b) => b.count - a.count);
|
|
544
|
+
if (sorted.length > 0) {
|
|
545
|
+
platformMetrics.mostUsedCommand = sorted[0].command;
|
|
546
|
+
platformMetrics.mostUsedCount = sorted[0].count;
|
|
547
|
+
platformMetrics.leastUsedCommand = sorted[sorted.length - 1].command;
|
|
548
|
+
platformMetrics.leastUsedCount = sorted[sorted.length - 1].count;
|
|
549
|
+
}
|
|
550
|
+
// ── Error cluster analysis ──
|
|
551
|
+
if (digest24h.errorClusters.length > 0) {
|
|
552
|
+
const growingErrors = digest24h.errorClusters.filter(e => e.count >= 3);
|
|
553
|
+
if (growingErrors.length > 0) {
|
|
554
|
+
insights.push({
|
|
555
|
+
type: 'system', severity: 'critical',
|
|
556
|
+
title: `${growingErrors.length} recurring error clusters`,
|
|
557
|
+
detail: growingErrors.map(e => `[${e.errorType}] "${e.message}" — ${e.count}x across ${e.affectedInstalls} install(s), last seen ${e.lastSeen}`).join('\n')
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
platformMetrics.errorClusters = digest24h.errorClusters.length;
|
|
561
|
+
platformMetrics.totalErrors = digest24h.totalErrors;
|
|
562
|
+
}
|
|
563
|
+
// ── Cost analysis ──
|
|
564
|
+
if (digest24h.modelCosts.length > 0) {
|
|
565
|
+
const totalCost = digest24h.totalCostUsd;
|
|
566
|
+
const costPerSession = digest24h.costPerSessionUsd;
|
|
567
|
+
platformMetrics.totalCost24h = totalCost;
|
|
568
|
+
platformMetrics.costPerSession = costPerSession;
|
|
569
|
+
// Which model burns the most?
|
|
570
|
+
const sortedCosts = [...digest24h.modelCosts].sort((a, b) => b.estimatedCostUsd - a.estimatedCostUsd);
|
|
571
|
+
const topCostModel = sortedCosts[0];
|
|
572
|
+
if (topCostModel && topCostModel.estimatedCostUsd > 0.10) {
|
|
573
|
+
insights.push({
|
|
574
|
+
type: 'performance', severity: 'info',
|
|
575
|
+
title: `Top cost: ${topCostModel.model} ($${topCostModel.estimatedCostUsd.toFixed(4)})`,
|
|
576
|
+
detail: `${topCostModel.callCount} calls, ${topCostModel.totalTokens} tokens. Total 24h cost: $${totalCost.toFixed(4)} across ${digest24h.totalSessions} sessions ($${costPerSession.toFixed(4)}/session).`
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
// Cost trend: compare 24h vs 7d average
|
|
580
|
+
if (digest7d && digest7d.totalCostUsd > 0) {
|
|
581
|
+
const dailyAvg7d = digest7d.totalCostUsd / 7;
|
|
582
|
+
const costTrend = totalCost / dailyAvg7d;
|
|
583
|
+
if (costTrend > 1.5) {
|
|
584
|
+
insights.push({
|
|
585
|
+
type: 'performance', severity: 'warning',
|
|
586
|
+
title: `Cost spike: ${(costTrend * 100).toFixed(0)}% of 7-day daily average`,
|
|
587
|
+
detail: `24h cost: $${totalCost.toFixed(4)} vs 7d daily avg: $${dailyAvg7d.toFixed(4)}. Check for runaway sessions or expensive model upgrades.`
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
platformMetrics.costTrend = costTrend;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
// ── Session health ──
|
|
594
|
+
if (digest24h.sessionHealth.crashRate > 0.05) {
|
|
595
|
+
insights.push({
|
|
596
|
+
type: 'system', severity: 'critical',
|
|
597
|
+
title: `Session crash rate: ${(digest24h.sessionHealth.crashRate * 100).toFixed(1)}%`,
|
|
598
|
+
detail: `${digest24h.sessionHealth.crashed} crashed out of ${digest24h.sessionHealth.started} started. Avg session duration: ${digest24h.sessionHealth.avgDurationS.toFixed(0)}s.`
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
platformMetrics.sessionCrashRate = digest24h.sessionHealth.crashRate;
|
|
602
|
+
platformMetrics.avgSessionDuration = digest24h.sessionHealth.avgDurationS;
|
|
603
|
+
// ── Hub health ──
|
|
604
|
+
if (digest24h.hubHealth.crashes > 0) {
|
|
605
|
+
insights.push({
|
|
606
|
+
type: 'system', severity: 'warning',
|
|
607
|
+
title: `Hub crashed ${digest24h.hubHealth.crashes}x in 24h`,
|
|
608
|
+
detail: `${digest24h.hubHealth.starts} starts, ${digest24h.hubHealth.stops} stops, ${digest24h.hubHealth.crashes} crashes. MCP latency: avg=${digest24h.hubHealth.avgMcpLatencyMs}ms p90=${digest24h.hubHealth.p90McpLatencyMs}ms.`
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
if (digest24h.hubHealth.p90McpLatencyMs > 500) {
|
|
612
|
+
insights.push({
|
|
613
|
+
type: 'performance', severity: 'warning',
|
|
614
|
+
title: `MCP p90 latency: ${digest24h.hubHealth.p90McpLatencyMs}ms`,
|
|
615
|
+
detail: `Context hub MCP calls are slow. Avg: ${digest24h.hubHealth.avgMcpLatencyMs}ms, p90: ${digest24h.hubHealth.p90McpLatencyMs}ms, p99: ${digest24h.hubHealth.p99McpLatencyMs}ms.`
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
// ── Hook coverage ──
|
|
619
|
+
if (digest24h.hookStats.totalReceived > 0 && digest24h.hookStats.fileHotspots.length > 0) {
|
|
620
|
+
const hotFiles = digest24h.hookStats.fileHotspots.slice(0, 5);
|
|
621
|
+
platformMetrics.fileHotspots = hotFiles;
|
|
622
|
+
insights.push({
|
|
623
|
+
type: 'product', severity: 'info',
|
|
624
|
+
title: `File hotspots (most edited)`,
|
|
625
|
+
detail: hotFiles.map(f => `${f.file}: ${f.edits} edits`).join(', ')
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
// ── Tool usage patterns ──
|
|
629
|
+
if (digest24h.toolStats.length > 0) {
|
|
630
|
+
const unusedTools = digest24h.toolStats.filter(t => t.callCount === 0);
|
|
631
|
+
const errorProneTools = digest24h.toolStats.filter(t => t.errorRate > 0.1 && t.callCount >= 5);
|
|
632
|
+
if (errorProneTools.length > 0) {
|
|
633
|
+
insights.push({
|
|
634
|
+
type: 'system', severity: 'warning',
|
|
635
|
+
title: `${errorProneTools.length} MCP tools with >10% error rate`,
|
|
636
|
+
detail: errorProneTools.map(t => `${t.toolName}: ${(t.errorRate * 100).toFixed(0)}% errors, ${t.callCount} calls`).join('; ')
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
platformMetrics.totalToolCalls = digest24h.toolStats.reduce((sum, t) => sum + t.callCount, 0);
|
|
640
|
+
}
|
|
641
|
+
// ── Flow health ──
|
|
642
|
+
if (digest24h.flowStats.completionRate < 0.8 && digest24h.flowStats.triggered >= 5) {
|
|
643
|
+
insights.push({
|
|
644
|
+
type: 'system', severity: 'warning',
|
|
645
|
+
title: `Flow completion rate: ${(digest24h.flowStats.completionRate * 100).toFixed(0)}%`,
|
|
646
|
+
detail: `${digest24h.flowStats.completed}/${digest24h.flowStats.triggered} flows completed. ${digest24h.flowStats.failed} failed.`
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
// ── Session cost outliers ──
|
|
650
|
+
if (digest24h.sessionCosts.length > 0) {
|
|
651
|
+
const avgCost = digest24h.totalCostUsd / digest24h.sessionCosts.length;
|
|
652
|
+
const expensive = digest24h.sessionCosts.filter(s => s.estimatedCostUsd > avgCost * 3);
|
|
653
|
+
if (expensive.length > 0) {
|
|
654
|
+
insights.push({
|
|
655
|
+
type: 'performance', severity: 'info',
|
|
656
|
+
title: `${expensive.length} expensive sessions (>3x avg cost)`,
|
|
657
|
+
detail: expensive.map(s => `Session ${s.sessionId.slice(0, 8)}: $${s.estimatedCostUsd.toFixed(4)}, ${s.durationS}s, ${s.totalTokens} tokens`).join('; ')
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
platformMetrics.totalEvents24h = digest24h.totalEvents;
|
|
662
|
+
platformMetrics.totalSessions24h = digest24h.totalSessions;
|
|
663
|
+
platformMetrics.activeInstalls = digest24h.activeInstalls;
|
|
664
|
+
platformMetrics.commandSuccessRate = digest24h.commandSuccessRate;
|
|
665
|
+
}
|
|
666
|
+
// ══════════════════════════════════════════════════════════════════
|
|
667
|
+
// LOCAL DATA — journals, MAP events, training buffer, agent configs
|
|
668
|
+
// ══════════════════════════════════════════════════════════════════
|
|
669
|
+
// 1. Read MAP events
|
|
670
|
+
const eventsPath = join(this.projectRoot, '.jfl', 'map-events.jsonl');
|
|
671
|
+
let recentEvents = [];
|
|
672
|
+
if (existsSync(eventsPath)) {
|
|
673
|
+
const lines = readFileSync(eventsPath, 'utf-8').trim().split('\n').slice(-200);
|
|
674
|
+
for (const line of lines) {
|
|
675
|
+
try {
|
|
676
|
+
recentEvents.push(JSON.parse(line));
|
|
677
|
+
}
|
|
678
|
+
catch { }
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const eventTypes = new Map();
|
|
682
|
+
for (const e of recentEvents) {
|
|
683
|
+
eventTypes.set(e.type, (eventTypes.get(e.type) || 0) + 1);
|
|
684
|
+
}
|
|
685
|
+
// 2. Read journals
|
|
686
|
+
const journalDir = join(this.projectRoot, '.jfl', 'journal');
|
|
687
|
+
let recentJournalEntries = [];
|
|
688
|
+
if (existsSync(journalDir)) {
|
|
689
|
+
const files = readdirSync(journalDir).filter(f => f.endsWith('.jsonl')).sort().slice(-5);
|
|
690
|
+
for (const f of files) {
|
|
691
|
+
const lines = readFileSync(join(journalDir, f), 'utf-8').trim().split('\n');
|
|
692
|
+
for (const line of lines) {
|
|
693
|
+
try {
|
|
694
|
+
recentJournalEntries.push(JSON.parse(line));
|
|
695
|
+
}
|
|
696
|
+
catch { }
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
// 3. Training buffer analysis
|
|
701
|
+
const bufferPath = join(this.projectRoot, '.jfl', 'training-buffer.jsonl');
|
|
702
|
+
let tupleCount = 0;
|
|
703
|
+
let improvedCount = 0;
|
|
704
|
+
let tuplesWithDiffs = 0;
|
|
705
|
+
let agentDistribution = new Map();
|
|
706
|
+
if (existsSync(bufferPath)) {
|
|
707
|
+
const lines = readFileSync(bufferPath, 'utf-8').trim().split('\n');
|
|
708
|
+
tupleCount = lines.length;
|
|
709
|
+
for (const line of lines.slice(-100)) {
|
|
710
|
+
try {
|
|
711
|
+
const t = JSON.parse(line);
|
|
712
|
+
agentDistribution.set(t.agent || 'unknown', (agentDistribution.get(t.agent || 'unknown') || 0) + 1);
|
|
713
|
+
if (t.reward?.improved)
|
|
714
|
+
improvedCount++;
|
|
715
|
+
if (t.action?.code_diff)
|
|
716
|
+
tuplesWithDiffs++;
|
|
717
|
+
}
|
|
718
|
+
catch { }
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
// 4. Agent configs
|
|
722
|
+
const agentDir = join(this.projectRoot, '.jfl', 'agents');
|
|
723
|
+
const agentConfigs = [];
|
|
724
|
+
if (existsSync(agentDir)) {
|
|
725
|
+
for (const f of readdirSync(agentDir).filter(f => f.endsWith('.toml'))) {
|
|
726
|
+
agentConfigs.push(f.replace('.toml', ''));
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
// 5. Product context freshness
|
|
730
|
+
const contextPath = join(this.projectRoot, '.jfl', 'product-context.md');
|
|
731
|
+
let contextAge = 999;
|
|
732
|
+
if (existsSync(contextPath)) {
|
|
733
|
+
const stat = spawnSync('stat', ['-f', '%m', contextPath], { encoding: 'utf-8' });
|
|
734
|
+
if (stat.stdout) {
|
|
735
|
+
contextAge = Math.floor((Date.now() / 1000 - parseInt(stat.stdout.trim())) / 3600);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
// 6. CLAUDE.md drift detection
|
|
739
|
+
const claudeMdPath = join(this.projectRoot, 'CLAUDE.md');
|
|
740
|
+
let claudeMdContent = '';
|
|
741
|
+
if (existsSync(claudeMdPath)) {
|
|
742
|
+
claudeMdContent = readFileSync(claudeMdPath, 'utf-8');
|
|
743
|
+
}
|
|
744
|
+
// 7. Dashboard telemetry from local events
|
|
745
|
+
const dashboardEvents = recentEvents.filter(e => e.type.startsWith('dashboard:'));
|
|
746
|
+
const pageViews = new Map();
|
|
747
|
+
for (const e of dashboardEvents) {
|
|
748
|
+
if (e.type === 'dashboard:page-view' && e.data?.page) {
|
|
749
|
+
const page = String(e.data.page);
|
|
750
|
+
pageViews.set(page, (pageViews.get(page) || 0) + 1);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
// ══════════════════════════════════════════════════════════════════
|
|
754
|
+
// LOCAL INSIGHTS
|
|
755
|
+
// ══════════════════════════════════════════════════════════════════
|
|
756
|
+
// Training data health
|
|
757
|
+
if (tupleCount < 100) {
|
|
758
|
+
insights.push({ type: 'training', severity: 'warning', title: 'Low training data', detail: `Only ${tupleCount} tuples in buffer. Need 500+ for meaningful policy head training. Run: jfl eval mine --all` });
|
|
759
|
+
}
|
|
760
|
+
if (tupleCount > 100 && improvedCount === 0) {
|
|
761
|
+
insights.push({ type: 'training', severity: 'critical', title: 'No improvements in recent tuples', detail: `Last 100 tuples have 0 improvements. Agents may be stuck or metrics are wrong.` });
|
|
762
|
+
}
|
|
763
|
+
if (tupleCount > 0 && tuplesWithDiffs === 0) {
|
|
764
|
+
insights.push({ type: 'training', severity: 'info', title: 'No code diffs in training data', detail: `${tupleCount} tuples but none have code_diff attached. Run autoresearch to generate diff-enriched tuples for code-policy training.` });
|
|
765
|
+
}
|
|
766
|
+
trainingGaps.push(`Buffer: ${tupleCount} tuples, ${improvedCount}/100 recent improved, ${tuplesWithDiffs} with code diffs`);
|
|
767
|
+
trainingGaps.push(`Agent distribution: ${[...agentDistribution].map(([k, v]) => `${k}=${v}`).join(', ')}`);
|
|
768
|
+
if (contextAge > 24) {
|
|
769
|
+
insights.push({ type: 'product', severity: 'warning', title: 'Product context stale', detail: `Product context is ${contextAge}h old. Run: jfl peter synthesize` });
|
|
770
|
+
}
|
|
771
|
+
if (recentEvents.length < 10) {
|
|
772
|
+
insights.push({ type: 'system', severity: 'warning', title: 'Low event flow', detail: `Only ${recentEvents.length} recent events. Context hub may not be running.` });
|
|
773
|
+
}
|
|
774
|
+
const recentJournals = recentJournalEntries.filter(j => {
|
|
775
|
+
const age = Date.now() - new Date(j.ts).getTime();
|
|
776
|
+
return age < 24 * 60 * 60 * 1000;
|
|
777
|
+
});
|
|
778
|
+
if (recentJournals.length === 0) {
|
|
779
|
+
insights.push({ type: 'product', severity: 'info', title: 'No journal entries in 24h', detail: 'No sessions have produced journal entries recently. System may be idle.' });
|
|
780
|
+
}
|
|
781
|
+
const agentsWithTuples = new Set([...agentDistribution.keys()]);
|
|
782
|
+
const agentsWithoutData = agentConfigs.filter(a => !agentsWithTuples.has(a));
|
|
783
|
+
if (agentsWithoutData.length > 0) {
|
|
784
|
+
insights.push({ type: 'training', severity: 'info', title: 'Agents with no training data', detail: `${agentsWithoutData.join(', ')} have never produced tuples. Run autoresearch for them.` });
|
|
785
|
+
}
|
|
786
|
+
if (pageViews.size > 0) {
|
|
787
|
+
const sorted = [...pageViews].sort((a, b) => b[1] - a[1]);
|
|
788
|
+
insights.push({ type: 'product', severity: 'info', title: 'Dashboard usage', detail: `Most visited: ${sorted[0][0]} (${sorted[0][1]}x). Least visited: ${sorted[sorted.length - 1][0]} (${sorted[sorted.length - 1][1]}x).` });
|
|
789
|
+
}
|
|
790
|
+
// CLAUDE.md drift
|
|
791
|
+
const servicesPath = join(this.projectRoot, '.jfl', 'services.json');
|
|
792
|
+
if (existsSync(servicesPath)) {
|
|
793
|
+
try {
|
|
794
|
+
const services = JSON.parse(readFileSync(servicesPath, 'utf-8'));
|
|
795
|
+
const serviceNames = (services.services || services || []).map((s) => s.name || s).filter(Boolean);
|
|
796
|
+
for (const svc of serviceNames) {
|
|
797
|
+
if (!claudeMdContent.includes(svc)) {
|
|
798
|
+
claudeMdUpdates.push(`Service "${svc}" is registered but not mentioned in CLAUDE.md`);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
catch { }
|
|
803
|
+
}
|
|
804
|
+
for (const agent of agentConfigs) {
|
|
805
|
+
if (!claudeMdContent.includes(agent)) {
|
|
806
|
+
claudeMdUpdates.push(`Agent "${agent}" exists in .jfl/agents/ but not mentioned in CLAUDE.md`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
// Stale docs
|
|
810
|
+
const knowledgeDir = join(this.projectRoot, 'knowledge');
|
|
811
|
+
if (existsSync(knowledgeDir)) {
|
|
812
|
+
const checkDir = (dir, prefix) => {
|
|
813
|
+
if (!existsSync(dir))
|
|
814
|
+
return;
|
|
815
|
+
for (const f of readdirSync(dir)) {
|
|
816
|
+
const full = join(dir, f);
|
|
817
|
+
const stat2 = spawnSync('stat', ['-f', '%m', full], { encoding: 'utf-8' });
|
|
818
|
+
if (f.endsWith('.md') && stat2.stdout) {
|
|
819
|
+
const age = Math.floor((Date.now() / 1000 - parseInt(stat2.stdout.trim())) / (3600 * 24));
|
|
820
|
+
if (age > 14)
|
|
821
|
+
staleDocs.push(`${prefix}${f} (${age} days old)`);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
checkDir(knowledgeDir, '');
|
|
826
|
+
checkDir(join(knowledgeDir, 'research'), 'research/');
|
|
827
|
+
}
|
|
828
|
+
// Write insights to journal
|
|
829
|
+
if (insights.length > 0) {
|
|
830
|
+
const journalPath2 = join(this.projectRoot, '.jfl', 'journal');
|
|
831
|
+
if (existsSync(journalPath2)) {
|
|
832
|
+
const files = readdirSync(journalPath2).filter(f => f.endsWith('.jsonl')).sort();
|
|
833
|
+
const latest = files[files.length - 1];
|
|
834
|
+
if (latest) {
|
|
835
|
+
const entry = {
|
|
836
|
+
v: 2, ts: new Date().toISOString(), session: 'telemetry-agent',
|
|
837
|
+
type: 'discovery', status: 'complete',
|
|
838
|
+
title: `Product analysis: ${insights.length} insights (${insights.filter(i => i.severity === 'critical').length} critical)`,
|
|
839
|
+
summary: insights.map(i => `[${i.severity}] ${i.title}`).join('; '),
|
|
840
|
+
detail: JSON.stringify({ insights, claudeMdUpdates, staleDocs, trainingGaps, platformMetrics }),
|
|
841
|
+
};
|
|
842
|
+
appendFileSync(join(journalPath2, latest), '\n' + JSON.stringify(entry));
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
// Emit event
|
|
847
|
+
this.emitEvent('telemetry:product-analysis', {
|
|
848
|
+
insightCount: insights.length,
|
|
849
|
+
criticalCount: insights.filter(i => i.severity === 'critical').length,
|
|
850
|
+
warningCount: insights.filter(i => i.severity === 'warning').length,
|
|
851
|
+
claudeMdUpdates: claudeMdUpdates.length,
|
|
852
|
+
staleDocs: staleDocs.length,
|
|
853
|
+
trainingGaps: trainingGaps.length,
|
|
854
|
+
tupleCount,
|
|
855
|
+
tuplesWithDiffs,
|
|
856
|
+
improvedRate: tupleCount > 0 ? (improvedCount / Math.min(100, tupleCount)) : 0,
|
|
857
|
+
contextAgeH: contextAge,
|
|
858
|
+
hasPlatformData: !!digest24h,
|
|
859
|
+
}, 'telemetry-agent-v2');
|
|
860
|
+
return { insights, claudeMdUpdates, staleDocs, trainingGaps, platformMetrics };
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Run the telemetry agent
|
|
864
|
+
*/
|
|
865
|
+
async run() {
|
|
866
|
+
const now = new Date().toISOString();
|
|
867
|
+
// Fetch 24h and 7d digests
|
|
868
|
+
const [digest24h, digest7d] = await Promise.all([
|
|
869
|
+
this.fetchDigest(24),
|
|
870
|
+
this.fetchDigest(168), // 7 days
|
|
871
|
+
]);
|
|
872
|
+
if (!digest24h) {
|
|
873
|
+
// Even without platform digest, run local product analysis
|
|
874
|
+
let localAnalysis = null;
|
|
875
|
+
try {
|
|
876
|
+
localAnalysis = await this.analyzeProduct(null);
|
|
877
|
+
}
|
|
878
|
+
catch { }
|
|
879
|
+
return {
|
|
880
|
+
digest24h: null,
|
|
881
|
+
digest7d: null,
|
|
882
|
+
metrics: null,
|
|
883
|
+
comparisons: [],
|
|
884
|
+
proposedAgents: [],
|
|
885
|
+
alerts: [],
|
|
886
|
+
wins: [],
|
|
887
|
+
productAnalysis: localAnalysis,
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
// Extract metrics
|
|
891
|
+
const metrics = this.extractMetrics(digest24h);
|
|
892
|
+
// Compare to previous run
|
|
893
|
+
const comparisons = this.compareMetrics(metrics);
|
|
894
|
+
// Identify alerts and wins
|
|
895
|
+
const alerts = comparisons.filter(c => c.isAlert);
|
|
896
|
+
const wins = comparisons.filter(c => c.isWin);
|
|
897
|
+
// Generate training tuple
|
|
898
|
+
this.generateTrainingTuple(digest24h, metrics, comparisons);
|
|
899
|
+
// Propose agents based on metrics
|
|
900
|
+
const proposedAgents = this.proposeAgents(metrics, comparisons);
|
|
901
|
+
if (proposedAgents.length > 0) {
|
|
902
|
+
this.writeProposedAgents(proposedAgents);
|
|
903
|
+
}
|
|
904
|
+
// Update health trajectory
|
|
905
|
+
const healthScore = metrics.command_success_rate * (1 - metrics.session_crash_rate);
|
|
906
|
+
this.state.healthTrajectory.push(healthScore);
|
|
907
|
+
if (this.state.healthTrajectory.length > 50) {
|
|
908
|
+
this.state.healthTrajectory = this.state.healthTrajectory.slice(-50);
|
|
909
|
+
}
|
|
910
|
+
// Emit events
|
|
911
|
+
this.emitEvent('telemetry:digest-analyzed', {
|
|
912
|
+
timestamp: now,
|
|
913
|
+
metrics,
|
|
914
|
+
alerts: alerts.length,
|
|
915
|
+
wins: wins.length,
|
|
916
|
+
proposed_agents: proposedAgents.length,
|
|
917
|
+
}, 'telemetry-agent-v2');
|
|
918
|
+
for (const alert of alerts) {
|
|
919
|
+
this.emitEvent('telemetry:metric-alert', {
|
|
920
|
+
metric: alert.name,
|
|
921
|
+
current: alert.current,
|
|
922
|
+
previous: alert.previous,
|
|
923
|
+
delta: alert.delta,
|
|
924
|
+
percentChange: alert.percentChange,
|
|
925
|
+
}, 'telemetry-agent-v2');
|
|
926
|
+
}
|
|
927
|
+
for (const agent of proposedAgents) {
|
|
928
|
+
this.emitEvent('telemetry:agent-proposed', {
|
|
929
|
+
agent: agent.name,
|
|
930
|
+
reason: agent.reason,
|
|
931
|
+
triggeredBy: agent.triggeredBy,
|
|
932
|
+
priority: agent.priority,
|
|
933
|
+
}, 'telemetry-agent-v2');
|
|
934
|
+
}
|
|
935
|
+
// Run product analysis (platform telemetry + local data)
|
|
936
|
+
let productAnalysis = null;
|
|
937
|
+
try {
|
|
938
|
+
productAnalysis = await this.analyzeProduct(digest24h);
|
|
939
|
+
if (productAnalysis.insights.length > 0) {
|
|
940
|
+
console.log(` Product analysis: ${productAnalysis.insights.length} insights`);
|
|
941
|
+
for (const insight of productAnalysis.insights) {
|
|
942
|
+
console.log(` [${insight.severity}] ${insight.title}`);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
if (productAnalysis.claudeMdUpdates.length > 0) {
|
|
946
|
+
console.log(` CLAUDE.md drift: ${productAnalysis.claudeMdUpdates.length} items need updating`);
|
|
947
|
+
}
|
|
948
|
+
if (productAnalysis.staleDocs.length > 0) {
|
|
949
|
+
console.log(` Stale docs: ${productAnalysis.staleDocs.join(', ')}`);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
catch (err) {
|
|
953
|
+
console.error(` Product analysis failed: ${err.message}`);
|
|
954
|
+
}
|
|
955
|
+
// Save state
|
|
956
|
+
this.state.lastRun = now;
|
|
957
|
+
this.state.runCount++;
|
|
958
|
+
this.state.previousDigest = {
|
|
959
|
+
timestamp: now,
|
|
960
|
+
metrics: metrics,
|
|
961
|
+
};
|
|
962
|
+
this.saveState();
|
|
963
|
+
return {
|
|
964
|
+
digest24h,
|
|
965
|
+
digest7d,
|
|
966
|
+
metrics,
|
|
967
|
+
comparisons,
|
|
968
|
+
proposedAgents,
|
|
969
|
+
alerts,
|
|
970
|
+
wins,
|
|
971
|
+
productAnalysis,
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Get current metrics (cached from last run or fetch fresh)
|
|
976
|
+
*/
|
|
977
|
+
async getMetrics() {
|
|
978
|
+
const digest = await this.fetchDigest(24);
|
|
979
|
+
if (!digest)
|
|
980
|
+
return null;
|
|
981
|
+
return this.extractMetrics(digest);
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Helper to get recent commits
|
|
985
|
+
*/
|
|
986
|
+
getRecentCommits() {
|
|
987
|
+
try {
|
|
988
|
+
const result = spawnSync('git', ['log', '--oneline', '-10', '--pretty=format:%s'], {
|
|
989
|
+
cwd: this.projectRoot,
|
|
990
|
+
encoding: 'utf-8',
|
|
991
|
+
stdio: 'pipe',
|
|
992
|
+
});
|
|
993
|
+
if (result.status !== 0)
|
|
994
|
+
return [];
|
|
995
|
+
return (result.stdout || '').split('\n').filter(Boolean);
|
|
996
|
+
}
|
|
997
|
+
catch {
|
|
998
|
+
return [];
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Helper to get recent changed files
|
|
1003
|
+
*/
|
|
1004
|
+
getRecentChangedFiles() {
|
|
1005
|
+
try {
|
|
1006
|
+
const result = spawnSync('git', ['diff', '--name-only', 'HEAD~5..HEAD'], {
|
|
1007
|
+
cwd: this.projectRoot,
|
|
1008
|
+
encoding: 'utf-8',
|
|
1009
|
+
stdio: 'pipe',
|
|
1010
|
+
});
|
|
1011
|
+
if (result.status !== 0)
|
|
1012
|
+
return [];
|
|
1013
|
+
return (result.stdout || '').split('\n').filter(Boolean).slice(0, 20);
|
|
1014
|
+
}
|
|
1015
|
+
catch {
|
|
1016
|
+
return [];
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Helper to get current branch
|
|
1021
|
+
*/
|
|
1022
|
+
getCurrentBranch() {
|
|
1023
|
+
try {
|
|
1024
|
+
const result = spawnSync('git', ['branch', '--show-current'], {
|
|
1025
|
+
cwd: this.projectRoot,
|
|
1026
|
+
encoding: 'utf-8',
|
|
1027
|
+
stdio: 'pipe',
|
|
1028
|
+
});
|
|
1029
|
+
return (result.stdout || '').trim() || 'main';
|
|
1030
|
+
}
|
|
1031
|
+
catch {
|
|
1032
|
+
return 'main';
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Get agent status
|
|
1037
|
+
*/
|
|
1038
|
+
getStatus() {
|
|
1039
|
+
return this.state;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
//# sourceMappingURL=telemetry-agent-v2.js.map
|