@winspan/claude-forge 8.50.6 → 8.51.1
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/CLAUDE.md +7 -7
- package/dist/claudemd/claudemd-generator.d.ts.map +1 -1
- package/dist/claudemd/claudemd-generator.js +27 -237
- package/dist/claudemd/claudemd-generator.js.map +1 -1
- package/dist/claudemd/resume-manager.js +1 -1
- package/dist/claudemd/resume-manager.js.map +1 -1
- package/dist/claudemd/templates/swarm-protocol.md +222 -0
- package/dist/cli/commands/daemon.js +6 -6
- package/dist/cli/commands/daemon.js.map +1 -1
- package/dist/cli/commands/executions.d.ts.map +1 -1
- package/dist/cli/commands/executions.js +4 -3
- package/dist/cli/commands/executions.js.map +1 -1
- package/dist/cli/commands/init.js +2 -2
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/logs.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +3 -5
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/menu.d.ts.map +1 -1
- package/dist/cli/commands/menu.js +4 -3
- package/dist/cli/commands/menu.js.map +1 -1
- package/dist/cli/commands/stats.d.ts.map +1 -1
- package/dist/cli/commands/stats.js +2 -3
- package/dist/cli/commands/stats.js.map +1 -1
- package/dist/cli/commands/status.js +2 -2
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/trace.d.ts.map +1 -1
- package/dist/cli/commands/trace.js +11 -23
- package/dist/cli/commands/trace.js.map +1 -1
- package/dist/cli/init/hook-manager.d.ts.map +1 -1
- package/dist/cli/init/hook-manager.js +2 -2
- package/dist/cli/init/hook-manager.js.map +1 -1
- package/dist/core/ai/provider.js +2 -2
- package/dist/core/ai/provider.js.map +1 -1
- package/dist/core/constants.d.ts +12 -1
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/constants.js +15 -1
- package/dist/core/constants.js.map +1 -1
- package/dist/core/event-fields.d.ts +16 -0
- package/dist/core/event-fields.d.ts.map +1 -0
- package/dist/core/event-fields.js +19 -0
- package/dist/core/event-fields.js.map +1 -0
- package/dist/core/queue/index.d.ts.map +1 -1
- package/dist/core/queue/index.js +3 -4
- package/dist/core/queue/index.js.map +1 -1
- package/dist/core/storage/base.d.ts +36 -3
- package/dist/core/storage/base.d.ts.map +1 -1
- package/dist/core/storage/base.js +101 -58
- package/dist/core/storage/base.js.map +1 -1
- package/dist/core/storage/events.d.ts +92 -3
- package/dist/core/storage/events.d.ts.map +1 -1
- package/dist/core/storage/events.js +147 -0
- package/dist/core/storage/events.js.map +1 -1
- package/dist/core/storage/routing.d.ts +54 -1
- package/dist/core/storage/routing.d.ts.map +1 -1
- package/dist/core/storage/routing.js +99 -1
- package/dist/core/storage/routing.js.map +1 -1
- package/dist/core/storage/schema.sql +12 -2
- package/dist/core/storage/sessions.d.ts +20 -0
- package/dist/core/storage/sessions.d.ts.map +1 -1
- package/dist/core/storage/sessions.js +59 -0
- package/dist/core/storage/sessions.js.map +1 -1
- package/dist/core/storage/skills.d.ts +23 -0
- package/dist/core/storage/skills.d.ts.map +1 -1
- package/dist/core/storage/skills.js +47 -0
- package/dist/core/storage/skills.js.map +1 -1
- package/dist/core/storage/sqlite.d.ts +35 -2
- package/dist/core/storage/sqlite.d.ts.map +1 -1
- package/dist/core/storage/sqlite.js +93 -4
- package/dist/core/storage/sqlite.js.map +1 -1
- package/dist/core/storage/tasks.d.ts +49 -0
- package/dist/core/storage/tasks.d.ts.map +1 -1
- package/dist/core/storage/tasks.js +143 -1
- package/dist/core/storage/tasks.js.map +1 -1
- package/dist/core/storage/token-usage.d.ts +1 -1
- package/dist/core/storage/token-usage.d.ts.map +1 -1
- package/dist/core/storage/token-usage.js +1 -1
- package/dist/core/storage/token-usage.js.map +1 -1
- package/dist/core/types.d.ts +24 -3
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/utils/error-handler.d.ts.map +1 -1
- package/dist/core/utils/error-handler.js +3 -2
- package/dist/core/utils/error-handler.js.map +1 -1
- package/dist/core/utils/git.d.ts +10 -0
- package/dist/core/utils/git.d.ts.map +1 -0
- package/dist/core/utils/git.js +24 -0
- package/dist/core/utils/git.js.map +1 -0
- package/dist/core/utils/logger.d.ts.map +1 -1
- package/dist/core/utils/logger.js +15 -1
- package/dist/core/utils/logger.js.map +1 -1
- package/dist/core/utils/lru-cache.d.ts +1 -0
- package/dist/core/utils/lru-cache.d.ts.map +1 -1
- package/dist/core/utils/lru-cache.js +3 -0
- package/dist/core/utils/lru-cache.js.map +1 -1
- package/dist/core/utils/token-tracker.js +1 -1
- package/dist/core/utils/token-tracker.js.map +1 -1
- package/dist/daemon/event-parser.d.ts.map +1 -1
- package/dist/daemon/event-parser.js +2 -1
- package/dist/daemon/event-parser.js.map +1 -1
- package/dist/daemon/handlers/history-exporter.js.map +1 -1
- package/dist/daemon/handlers/post-tool-use.d.ts.map +1 -1
- package/dist/daemon/handlers/post-tool-use.js +7 -3
- package/dist/daemon/handlers/post-tool-use.js.map +1 -1
- package/dist/daemon/handlers/stop.d.ts +4 -0
- package/dist/daemon/handlers/stop.d.ts.map +1 -1
- package/dist/daemon/handlers/stop.js +23 -35
- package/dist/daemon/handlers/stop.js.map +1 -1
- package/dist/daemon/handlers/user-prompt.d.ts +3 -3
- package/dist/daemon/handlers/user-prompt.d.ts.map +1 -1
- package/dist/daemon/handlers/user-prompt.js +12 -22
- package/dist/daemon/handlers/user-prompt.js.map +1 -1
- package/dist/daemon/hook-sync.d.ts +17 -0
- package/dist/daemon/hook-sync.d.ts.map +1 -0
- package/dist/daemon/hook-sync.js +74 -0
- package/dist/daemon/hook-sync.js.map +1 -0
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +33 -9
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/lifecycle.js +3 -4
- package/dist/daemon/lifecycle.js.map +1 -1
- package/dist/daemon/server.d.ts +6 -4
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +76 -85
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/services/task-segmenter.js +1 -1
- package/dist/daemon/services/task-segmenter.js.map +1 -1
- package/dist/hooks/hook-lib.sh +37 -0
- package/dist/hooks/notification.sh +2 -2
- package/dist/hooks/post-tool-use.sh +2 -2
- package/dist/hooks/pre-tool-use.sh +2 -2
- package/dist/hooks/stop.sh +9 -6
- package/dist/hooks/user-prompt-submit.sh +2 -2
- package/dist/{daemon/services → web/analytics}/anti-pattern-detector.d.ts +3 -4
- package/dist/web/analytics/anti-pattern-detector.d.ts.map +1 -0
- package/dist/{daemon/services → web/analytics}/anti-pattern-detector.js +7 -46
- package/dist/{daemon/services → web/analytics}/anti-pattern-detector.js.map +1 -1
- package/dist/web/analytics/drift-detector.d.ts.map +1 -0
- package/dist/{daemon/services → web/analytics}/drift-detector.js +10 -13
- package/dist/web/analytics/drift-detector.js.map +1 -0
- package/dist/web/analytics/weekly-report.d.ts.map +1 -0
- package/dist/{daemon/services → web/analytics}/weekly-report.js +51 -50
- package/dist/web/analytics/weekly-report.js.map +1 -0
- package/dist/web/auth-middleware.d.ts.map +1 -1
- package/dist/web/auth-middleware.js +1 -2
- package/dist/web/auth-middleware.js.map +1 -1
- package/dist/web/routes/_helpers.d.ts +16 -0
- package/dist/web/routes/_helpers.d.ts.map +1 -0
- package/dist/web/routes/_helpers.js +32 -0
- package/dist/web/routes/_helpers.js.map +1 -0
- package/dist/web/routes/drift.js +1 -1
- package/dist/web/routes/drift.js.map +1 -1
- package/dist/web/routes/insights.js +1 -1
- package/dist/web/routes/insights.js.map +1 -1
- package/dist/web/routes/reports.js +1 -1
- package/dist/web/routes/reports.js.map +1 -1
- package/dist/web/routes/rules.d.ts +3 -0
- package/dist/web/routes/rules.d.ts.map +1 -1
- package/dist/web/routes/rules.js +28 -52
- package/dist/web/routes/rules.js.map +1 -1
- package/dist/web/routes/sessions.d.ts.map +1 -1
- package/dist/web/routes/sessions.js +16 -30
- package/dist/web/routes/sessions.js.map +1 -1
- package/dist/web/routes/skill-stats.d.ts +2 -0
- package/dist/web/routes/skill-stats.d.ts.map +1 -1
- package/dist/web/routes/skill-stats.js +28 -64
- package/dist/web/routes/skill-stats.js.map +1 -1
- package/dist/web/routes/skills.d.ts.map +1 -1
- package/dist/web/routes/skills.js +5 -4
- package/dist/web/routes/skills.js.map +1 -1
- package/dist/web/routes/stats.d.ts +4 -0
- package/dist/web/routes/stats.d.ts.map +1 -1
- package/dist/web/routes/stats.js +19 -21
- package/dist/web/routes/stats.js.map +1 -1
- package/dist/web/routes/tasks.d.ts.map +1 -1
- package/dist/web/routes/tasks.js +17 -42
- package/dist/web/routes/tasks.js.map +1 -1
- package/dist/web/routes/trace.d.ts.map +1 -1
- package/dist/web/routes/trace.js +7 -17
- package/dist/web/routes/trace.js.map +1 -1
- package/dist/web/routes/types.d.ts.map +1 -1
- package/dist/web/routes/types.js +4 -3
- package/dist/web/routes/types.js.map +1 -1
- package/dist/web/static/assets/{AIConfig-BQCAQE9D.js → AIConfig-CdDWzJyO.js} +2 -2
- package/dist/web/static/assets/{AIConfig-BQCAQE9D.js.map → AIConfig-CdDWzJyO.js.map} +1 -1
- package/dist/web/static/assets/{Dashboard-D7Bo6Kan.js → Dashboard-CoEmmIDt.js} +2 -2
- package/dist/web/static/assets/{Dashboard-D7Bo6Kan.js.map → Dashboard-CoEmmIDt.js.map} +1 -1
- package/dist/web/static/assets/{Drawer-BeHRQxUS.js → Drawer-DdRTzlLB.js} +2 -2
- package/dist/web/static/assets/{Drawer-BeHRQxUS.js.map → Drawer-DdRTzlLB.js.map} +1 -1
- package/dist/web/static/assets/{Events-K_tCY2ti.js → Events-DrIq1SUS.js} +2 -2
- package/dist/web/static/assets/{Events-K_tCY2ti.js.map → Events-DrIq1SUS.js.map} +1 -1
- package/dist/web/static/assets/{Reports-BJCmBnc_.js → Reports-DFBM3MDK.js} +2 -2
- package/dist/web/static/assets/{Reports-BJCmBnc_.js.map → Reports-DFBM3MDK.js.map} +1 -1
- package/dist/web/static/assets/{SearchInput-BX2KhMkw.js → SearchInput-qCj_jAcf.js} +2 -2
- package/dist/web/static/assets/{SearchInput-BX2KhMkw.js.map → SearchInput-qCj_jAcf.js.map} +1 -1
- package/dist/web/static/assets/{SessionDetail-Bkr-kC7V.js → SessionDetail-CCzwdoT7.js} +2 -2
- package/dist/web/static/assets/{SessionDetail-Bkr-kC7V.js.map → SessionDetail-CCzwdoT7.js.map} +1 -1
- package/dist/web/static/assets/{Sessions-Chx9OCLH.js → Sessions-FfLYkAw9.js} +2 -2
- package/dist/web/static/assets/{Sessions-Chx9OCLH.js.map → Sessions-FfLYkAw9.js.map} +1 -1
- package/dist/web/static/assets/{Skills-O0GT1i7m.js → Skills-C8Gvs3Qa.js} +2 -2
- package/dist/web/static/assets/{Skills-O0GT1i7m.js.map → Skills-C8Gvs3Qa.js.map} +1 -1
- package/dist/web/static/assets/TaskDetail-BS8pYhaR.js +2 -0
- package/dist/web/static/assets/TaskDetail-BS8pYhaR.js.map +1 -0
- package/dist/web/static/assets/Tasks-CyuhizG8.js +2 -0
- package/dist/web/static/assets/Tasks-CyuhizG8.js.map +1 -0
- package/dist/web/static/assets/index-CBX47X8l.js +3 -0
- package/dist/web/static/assets/{index-DxIbmNmr.js.map → index-CBX47X8l.js.map} +1 -1
- package/dist/web/static/assets/index-DjIoMdoR.css +1 -0
- package/dist/web/static/assets/{lucide-fJlPI3H7.js → lucide-Bs_edTLa.js} +44 -39
- package/dist/web/static/assets/lucide-Bs_edTLa.js.map +1 -0
- package/dist/web/static/assets/react-router-r79dBVy4.js +20 -0
- package/dist/web/static/assets/{react-router-I-HqunH7.js.map → react-router-r79dBVy4.js.map} +1 -1
- package/dist/web/static/assets/task-title-BhOcemuR.js +2 -0
- package/dist/web/static/assets/task-title-BhOcemuR.js.map +1 -0
- package/dist/web/static/index.html +4 -4
- package/docs/design/h1-storage-aggregation-spec-20260518-1121.md +299 -0
- package/docs/design/h2-getdatabase-encapsulation-spec-20260518-1450.md +191 -0
- package/docs/design/h3-fallback-removal-spec-20260518-1245.md +76 -0
- package/docs/design/h4-index-dedup-spec-20260518-1230.md +109 -0
- package/docs/design/h6-services-migration-spec-20260518-1355.md +82 -0
- package/docs/design/l1-swarm-protocol-extract-spec-20260518-1605.md +106 -0
- package/docs/design/m10-forge-paths-spec-20260518-1320.md +121 -0
- package/docs/design/m2-m3-tool-input-spec-20260518-1425.md +131 -0
- package/docs/design/m7-routing-event-association-spec-20260518-1545.md +103 -0
- package/docs/design/project-path-gitroot-spec-20260518-1715.md +134 -0
- package/docs/design/task-active-gc-spec-20260518-1745.md +146 -0
- package/docs/implementation/h1-storage-aggregation-changelog-20260518-1121.md +82 -0
- package/docs/implementation/h2-final-changelog-20260518-1530.md +61 -0
- package/docs/implementation/h2-phase1-safety-net-changelog-20260518-1450.md +70 -0
- package/docs/implementation/h2-phase2-operations-changelog-20260518-1450.md +120 -0
- package/docs/implementation/h2-phase3-callsites-changelog-20260518-1450.md +71 -0
- package/docs/implementation/h3-fallback-removal-changelog-20260518-1245.md +71 -0
- package/docs/implementation/h4-index-dedup-changelog-20260518-1230.md +60 -0
- package/docs/implementation/h6-services-migration-changelog-20260518-1355.md +46 -0
- package/docs/implementation/h7-m9-defaults-changelog-20260518-1300.md +46 -0
- package/docs/implementation/l1-swarm-protocol-extract-changelog-20260518-1605.md +45 -0
- package/docs/implementation/l3-l4-daemon-perf-changelog-20260518-1410.md +63 -0
- package/docs/implementation/l6-l8-final-cleanup-changelog-20260518-1640.md +38 -0
- package/docs/implementation/m1-m4-m5-l7-cleanup-changelog-20260518-1310.md +58 -0
- package/docs/implementation/m10-forge-paths-changelog-20260518-1320.md +60 -0
- package/docs/implementation/m2-m3-tool-input-changelog-20260518-1425.md +43 -0
- package/docs/implementation/m6-m8-naming-shutdown-changelog-20260518-1340.md +56 -0
- package/docs/implementation/m7-routing-association-changelog-20260518-1545.md +69 -0
- package/docs/implementation/project-path-gitroot-changelog-20260518-1715.md +63 -0
- package/docs/implementation/task-active-gc-changelog-20260518-1745.md +35 -0
- package/docs/implementation/task-title-summary-changelog-20260518-1130.md +39 -0
- package/docs/implementation/tasks-detail-back-loses-filters-changelog-20260518-1100.md +22 -0
- package/docs/implementation/tasks-page-white-screen-hotfix-changelog-20260518-1015.md +56 -0
- package/docs/reviews/task-title-summary.md +92 -0
- package/docs/reviews/tasks-detail-back-loses-filters.md +58 -0
- package/docs/reviews/tasks-page-white-screen-hotfix.md +126 -0
- package/package.json +2 -2
- package/src/claudemd/claudemd-generator.ts +29 -238
- package/src/claudemd/resume-manager.ts +1 -1
- package/src/claudemd/templates/swarm-protocol.md +222 -0
- package/src/cli/commands/daemon.ts +6 -6
- package/src/cli/commands/executions.ts +4 -3
- package/src/cli/commands/init.ts +2 -2
- package/src/cli/commands/logs.ts +1 -1
- package/src/cli/commands/mcp.ts +3 -5
- package/src/cli/commands/menu.ts +4 -3
- package/src/cli/commands/stats.ts +2 -3
- package/src/cli/commands/status.ts +2 -2
- package/src/cli/commands/trace.ts +10 -26
- package/src/cli/init/hook-manager.ts +2 -2
- package/src/core/ai/provider.ts +2 -2
- package/src/core/constants.ts +18 -1
- package/src/core/event-fields.ts +32 -0
- package/src/core/queue/index.ts +3 -4
- package/src/core/storage/base.ts +132 -56
- package/src/core/storage/events.ts +183 -4
- package/src/core/storage/routing.ts +129 -1
- package/src/core/storage/schema.sql +12 -2
- package/src/core/storage/sessions.ts +64 -0
- package/src/core/storage/skills.ts +69 -0
- package/src/core/storage/sqlite.ts +103 -4
- package/src/core/storage/tasks.ts +149 -1
- package/src/core/storage/token-usage.ts +1 -1
- package/src/core/types.ts +30 -3
- package/src/core/utils/error-handler.ts +3 -2
- package/src/core/utils/git.ts +23 -0
- package/src/core/utils/logger.ts +16 -1
- package/src/core/utils/lru-cache.ts +4 -0
- package/src/core/utils/token-tracker.ts +1 -1
- package/src/daemon/event-parser.ts +4 -3
- package/src/daemon/handlers/history-exporter.ts +1 -1
- package/src/daemon/handlers/post-tool-use.ts +7 -3
- package/src/daemon/handlers/stop.ts +32 -39
- package/src/daemon/handlers/user-prompt.ts +12 -22
- package/src/daemon/hook-sync.ts +91 -0
- package/src/daemon/index.ts +34 -10
- package/src/daemon/lifecycle.ts +3 -3
- package/src/daemon/server.ts +76 -89
- package/src/daemon/services/task-segmenter.ts +1 -1
- package/src/hooks/hook-lib.sh +37 -0
- package/src/hooks/notification.sh +2 -2
- package/src/hooks/post-tool-use.sh +2 -2
- package/src/hooks/pre-tool-use.sh +2 -2
- package/src/hooks/stop.sh +9 -6
- package/src/hooks/user-prompt-submit.sh +2 -2
- package/src/{daemon/services → web/analytics}/anti-pattern-detector.ts +9 -54
- package/src/{daemon/services → web/analytics}/drift-detector.ts +10 -23
- package/src/{daemon/services → web/analytics}/weekly-report.ts +52 -75
- package/src/web/auth-middleware.ts +1 -2
- package/src/web/routes/_helpers.ts +34 -0
- package/src/web/routes/drift.ts +1 -1
- package/src/web/routes/insights.ts +1 -1
- package/src/web/routes/reports.ts +1 -1
- package/src/web/routes/rules.ts +31 -56
- package/src/web/routes/sessions.ts +18 -30
- package/src/web/routes/skill-stats.ts +29 -69
- package/src/web/routes/skills.ts +5 -4
- package/src/web/routes/stats.ts +19 -29
- package/src/web/routes/tasks.ts +17 -42
- package/src/web/routes/trace.ts +7 -19
- package/src/web/routes/types.ts +4 -3
- package/tests/integration/claudemd-generator.test.ts +90 -0
- package/tests/integration/web-analytics.integration.test.ts +133 -0
- package/tests/integration/web-stats.integration.test.ts +135 -0
- package/tests/integration/web-trace.integration.test.ts +175 -0
- package/tests/unit/core/forge-paths.test.ts +99 -0
- package/tests/unit/daemon/hook-sync.test.ts +71 -0
- package/tests/unit/daemon/post-tool-use.test.ts +121 -0
- package/tests/unit/daemon/stop-handler-behavior-summary.test.ts +202 -0
- package/tests/unit/daemon/task-segmenter-recover.test.ts +84 -0
- package/tests/unit/event-fields.test.ts +88 -0
- package/tests/unit/event-parser.test.ts +55 -0
- package/tests/unit/hooks/resolve-project-path.test.ts +122 -0
- package/tests/unit/socket-server.test.ts +183 -0
- package/tests/unit/storage/event-operations-aggregates.test.ts +342 -0
- package/tests/unit/storage/migration-idempotent.test.ts +304 -0
- package/tests/unit/storage/routing-aggregates.test.ts +276 -0
- package/tests/unit/storage/routing.test.ts +117 -0
- package/tests/unit/storage/schema-missing.test.ts +81 -0
- package/tests/unit/storage/session-operations-aggregates.test.ts +120 -0
- package/tests/unit/storage/skill-operations-counts.test.ts +106 -0
- package/tests/unit/storage/skills-aggregates.test.ts +104 -0
- package/tests/unit/storage/sqlite-refactor-harness.test.ts +3 -3
- package/tests/unit/storage/task-operations-counts.test.ts +46 -0
- package/tests/unit/storage/tasks-getById.test.ts +343 -0
- package/tests/unit/storage/tasks-stale-gc.test.ts +86 -0
- package/tests/unit/token-usage.test.ts +6 -6
- package/tests/unit/web/navigation-back-contract.test.ts +134 -0
- package/tests/unit/web/routes-rules.test.ts +182 -0
- package/tests/unit/web/routes-tasks.test.ts +34 -0
- package/tests/unit/web/task-title-contract.test.ts +210 -0
- package/tests/unit/web/tasks-component-contract.test.ts +179 -0
- package/vitest.config.ts +1 -1
- package/web/src/pages/TaskDetail.tsx +9 -5
- package/web/src/pages/Tasks.tsx +315 -50
- package/web/src/utils/navigation.ts +25 -0
- package/web/src/utils/task-title.ts +49 -0
- package/dist/daemon/services/anti-pattern-detector.d.ts.map +0 -1
- package/dist/daemon/services/drift-detector.d.ts.map +0 -1
- package/dist/daemon/services/drift-detector.js.map +0 -1
- package/dist/daemon/services/weekly-report.d.ts.map +0 -1
- package/dist/daemon/services/weekly-report.js.map +0 -1
- package/dist/web/static/assets/TaskDetail-5SR8zGzv.js +0 -2
- package/dist/web/static/assets/TaskDetail-5SR8zGzv.js.map +0 -1
- package/dist/web/static/assets/Tasks-DCgDqvOZ.js +0 -2
- package/dist/web/static/assets/Tasks-DCgDqvOZ.js.map +0 -1
- package/dist/web/static/assets/index-D8AKj26b.css +0 -1
- package/dist/web/static/assets/index-DxIbmNmr.js +0 -3
- package/dist/web/static/assets/lucide-fJlPI3H7.js.map +0 -1
- package/dist/web/static/assets/react-router-I-HqunH7.js +0 -20
- /package/dist/{daemon/services → web/analytics}/drift-detector.d.ts +0 -0
- /package/dist/{daemon/services → web/analytics}/weekly-report.d.ts +0 -0
|
@@ -12,8 +12,8 @@ INPUT=$(cat)
|
|
|
12
12
|
# 提取字段(单次 jq 调用,替代 3 个 python3 进程)
|
|
13
13
|
TOOL_NAME="${CLAUDE_TOOL_NAME:-$(echo "$INPUT" | jq -r '.tool_name // ""')}"
|
|
14
14
|
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
|
|
15
|
-
|
|
16
|
-
PROJECT_PATH
|
|
15
|
+
RAW_CWD=$(echo "$INPUT" | jq -r '.cwd // ""')
|
|
16
|
+
PROJECT_PATH=$(resolve_project_path "${RAW_CWD:-$PWD}")
|
|
17
17
|
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // .sessionId // ""')
|
|
18
18
|
SESSION_ID="${SESSION_ID:-${CLAUDE_CODE_SESSION_ID:-cli}}"
|
|
19
19
|
|
package/src/hooks/stop.sh
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Claude Forge - Stop Hook
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
# shellcheck source=./hook-lib.sh
|
|
5
|
+
source "$(dirname "$0")/hook-lib.sh"
|
|
6
|
+
|
|
5
7
|
AUTH_TOKEN=$(cat "$HOME/.claude-forge/daemon.token" 2>/dev/null || echo '')
|
|
6
8
|
|
|
7
9
|
# 读取 stdin
|
|
8
10
|
INPUT=$(cat 2>/dev/null || echo '{}')
|
|
9
11
|
|
|
10
|
-
# 提取 cwd
|
|
11
|
-
|
|
12
|
-
PROJECT_PATH
|
|
12
|
+
# 提取 cwd,上溯 git root
|
|
13
|
+
RAW_CWD=$(echo "$INPUT" | jq -r '.cwd // ""')
|
|
14
|
+
PROJECT_PATH=$(resolve_project_path "${RAW_CWD:-$PWD}")
|
|
13
15
|
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // .sessionId // ""')
|
|
14
16
|
SESSION_ID="${SESSION_ID:-${CLAUDE_CODE_SESSION_ID:-cli}}"
|
|
15
17
|
|
|
@@ -24,8 +26,9 @@ EVENT=$(jq -n \
|
|
|
24
26
|
'{hook_type: $hook_type, timestamp: $timestamp, session_id: $session_id,
|
|
25
27
|
project_path: $project_path, tool_name: "stop", tool_input: {}, _auth: $auth}')
|
|
26
28
|
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
+
# 发送事件并读取响应(失败时入队)
|
|
30
|
+
send_event_or_enqueue "$EVENT" 5
|
|
31
|
+
RESPONSE="$HOOK_RESPONSE"
|
|
29
32
|
|
|
30
33
|
# 解析响应:allow=false 时输出 reason 并以 exit 2 阻断
|
|
31
34
|
if [ -n "$RESPONSE" ]; then
|
|
@@ -12,8 +12,8 @@ INPUT=$(cat)
|
|
|
12
12
|
|
|
13
13
|
# 提取字段(jq 替代 python3)
|
|
14
14
|
USER_PROMPT=$(echo "$INPUT" | jq -r '.prompt // ""')
|
|
15
|
-
|
|
16
|
-
PROJECT_PATH
|
|
15
|
+
RAW_CWD=$(echo "$INPUT" | jq -r '.cwd // ""')
|
|
16
|
+
PROJECT_PATH=$(resolve_project_path "${RAW_CWD:-$PWD}")
|
|
17
17
|
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // .sessionId // ""')
|
|
18
18
|
SESSION_ID="${SESSION_ID:-${CLAUDE_CODE_SESSION_ID:-cli}}"
|
|
19
19
|
|
|
@@ -55,8 +55,8 @@ export class AntiPatternDetector {
|
|
|
55
55
|
const detectedAt = new Date(now).toISOString();
|
|
56
56
|
|
|
57
57
|
// 一次性拉取窗口内事件 + 会话,4 条规则共用
|
|
58
|
-
const events = this.
|
|
59
|
-
const sessions = this.
|
|
58
|
+
const events = this.queryEventsSince(sinceISO);
|
|
59
|
+
const sessions = this.querySessionsSince(sinceISO);
|
|
60
60
|
|
|
61
61
|
const patterns: AntiPattern[] = [
|
|
62
62
|
...this.detectEditLoop(events, detectedAt),
|
|
@@ -76,50 +76,14 @@ export class AntiPatternDetector {
|
|
|
76
76
|
/**
|
|
77
77
|
* 拉取窗口内全部事件。
|
|
78
78
|
*
|
|
79
|
-
*
|
|
80
|
-
* 这是 daemon/services 层的既定做法(见 DriftDetector)。
|
|
79
|
+
* H2 Phase 3: 改用 facade 聚合方法。
|
|
81
80
|
*/
|
|
82
|
-
private
|
|
83
|
-
|
|
84
|
-
const rows = db.prepare(
|
|
85
|
-
`SELECT * FROM events WHERE timestamp >= ? ORDER BY timestamp ASC`
|
|
86
|
-
).all(sinceISO) as Array<Record<string, unknown>>;
|
|
87
|
-
|
|
88
|
-
return rows.map((row) => ({
|
|
89
|
-
event_id: row.event_id as string,
|
|
90
|
-
session_id: row.session_id as string,
|
|
91
|
-
project_path: row.project_path as string,
|
|
92
|
-
timestamp: row.timestamp as string,
|
|
93
|
-
hook_type: row.hook_type as ForgeEvent['hook_type'],
|
|
94
|
-
tool_name: (row.tool_name as string) || undefined,
|
|
95
|
-
tool_input: row.tool_input ? safeParse(row.tool_input as string) : undefined,
|
|
96
|
-
tool_output: row.tool_output ? safeParse(row.tool_output as string) : undefined,
|
|
97
|
-
user_prompt: (row.user_prompt as string) || undefined,
|
|
98
|
-
ai_response: (row.ai_response as string) || undefined,
|
|
99
|
-
}));
|
|
81
|
+
private queryEventsSince(sinceISO: string): ForgeEvent[] {
|
|
82
|
+
return this.storage.queryEventsByTimeRange({ since: sinceISO });
|
|
100
83
|
}
|
|
101
84
|
|
|
102
|
-
private
|
|
103
|
-
|
|
104
|
-
const rows = db.prepare(
|
|
105
|
-
`SELECT
|
|
106
|
-
session_id,
|
|
107
|
-
first_prompt,
|
|
108
|
-
event_count,
|
|
109
|
-
start_time,
|
|
110
|
-
COALESCE(end_time, last_event_time, start_time) AS end_time
|
|
111
|
-
FROM sessions
|
|
112
|
-
WHERE start_time >= ?
|
|
113
|
-
ORDER BY start_time DESC`
|
|
114
|
-
).all(sinceISO) as Array<Record<string, unknown>>;
|
|
115
|
-
|
|
116
|
-
return rows.map((r) => ({
|
|
117
|
-
session_id: r.session_id as string,
|
|
118
|
-
first_prompt: (r.first_prompt as string) || '',
|
|
119
|
-
event_count: (r.event_count as number) ?? 0,
|
|
120
|
-
start_time: r.start_time as string,
|
|
121
|
-
end_time: r.end_time as string,
|
|
122
|
-
}));
|
|
85
|
+
private querySessionsSince(sinceISO: string): SessionSummary[] {
|
|
86
|
+
return this.storage.querySessionsByTimeRange({ since: sinceISO });
|
|
123
87
|
}
|
|
124
88
|
|
|
125
89
|
// ── Rule 1: edit_loop ─────────────────────────────────────────────────
|
|
@@ -342,23 +306,14 @@ export class AntiPatternDetector {
|
|
|
342
306
|
|
|
343
307
|
// ── helpers ─────────────────────────────────────────────────────────────
|
|
344
308
|
|
|
345
|
-
function
|
|
346
|
-
try {
|
|
347
|
-
const v = JSON.parse(json);
|
|
348
|
-
return v && typeof v === 'object' ? (v as Record<string, unknown>) : undefined;
|
|
349
|
-
} catch {
|
|
350
|
-
return undefined;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function extractFilePath(input: Record<string, unknown> | undefined): string | undefined {
|
|
309
|
+
function extractFilePath(input: ForgeEvent['tool_input']): string | undefined {
|
|
355
310
|
if (!input) return undefined;
|
|
356
311
|
const fp = input.file_path;
|
|
357
312
|
if (typeof fp === 'string' && fp.length > 0) return fp;
|
|
358
313
|
return undefined;
|
|
359
314
|
}
|
|
360
315
|
|
|
361
|
-
function extractBashCommand(input:
|
|
316
|
+
function extractBashCommand(input: ForgeEvent['tool_input']): string | undefined {
|
|
362
317
|
if (!input) return undefined;
|
|
363
318
|
const cmd = input.command;
|
|
364
319
|
return typeof cmd === 'string' ? cmd : undefined;
|
|
@@ -121,12 +121,8 @@ export class DriftDetector {
|
|
|
121
121
|
* 预期: 至少 3 个不同 Skill 被调用
|
|
122
122
|
*/
|
|
123
123
|
private checkActiveSkillCoverage(since: number): DriftCheck {
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
`SELECT COUNT(DISTINCT skill_id) as cnt FROM skill_invocations
|
|
127
|
-
WHERE timestamp >= ?`
|
|
128
|
-
).get(since) as { cnt: number } | undefined;
|
|
129
|
-
const distinctCount = row?.cnt ?? 0;
|
|
124
|
+
// H2 Phase 3: 改用 facade。countDistinctSkillsSince 接收 unix ms。
|
|
125
|
+
const distinctCount = this.storage.countDistinctSkillsSince(since);
|
|
130
126
|
|
|
131
127
|
let status: DriftCheck['status'];
|
|
132
128
|
if (distinctCount >= 3) {
|
|
@@ -150,12 +146,11 @@ export class DriftDetector {
|
|
|
150
146
|
* 预期: 最近 7 天中至少 5 天有事件(工作日)
|
|
151
147
|
*/
|
|
152
148
|
private checkEventContinuity(): DriftCheck {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const activeDays = row?.cnt ?? 0;
|
|
149
|
+
// H2 Phase 3: 改用 facade。
|
|
150
|
+
// 旧 SQL: timestamp >= date('now', '-7 days')
|
|
151
|
+
// 新口径:调用方计算 ISO 字符串。
|
|
152
|
+
const sevenDaysAgoIso = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
153
|
+
const activeDays = this.storage.countActiveDays({ since: sevenDaysAgoIso });
|
|
159
154
|
|
|
160
155
|
let status: DriftCheck['status'];
|
|
161
156
|
if (activeDays >= 5) {
|
|
@@ -179,18 +174,10 @@ export class DriftDetector {
|
|
|
179
174
|
* 预期: 每个 session 至少有 1 个 task (tasks/sessions >= 0.8)
|
|
180
175
|
*/
|
|
181
176
|
private checkTaskSegmentation(since: number): DriftCheck {
|
|
182
|
-
|
|
177
|
+
// H2 Phase 3: 改用 facade。
|
|
183
178
|
const sinceISO = new Date(since).toISOString();
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
`SELECT COUNT(*) as cnt FROM sessions WHERE start_time >= ?`
|
|
187
|
-
).get(sinceISO) as { cnt: number } | undefined;
|
|
188
|
-
const sessionCount = sessionRow?.cnt ?? 0;
|
|
189
|
-
|
|
190
|
-
const taskRow = db.prepare(
|
|
191
|
-
`SELECT COUNT(*) as cnt FROM tasks WHERE start_time >= ?`
|
|
192
|
-
).get(sinceISO) as { cnt: number } | undefined;
|
|
193
|
-
const taskCount = taskRow?.cnt ?? 0;
|
|
179
|
+
const sessionCount = this.storage.countSessionsByRange({ since: sinceISO });
|
|
180
|
+
const taskCount = this.storage.countTasksByRange({ since: sinceISO });
|
|
194
181
|
|
|
195
182
|
const ratio = sessionCount > 0
|
|
196
183
|
? Math.round((taskCount / sessionCount) * 100) / 100
|
|
@@ -95,38 +95,26 @@ export class WeeklyReportGenerator {
|
|
|
95
95
|
// ── Aggregation methods ──────────────────────────────────────────────
|
|
96
96
|
|
|
97
97
|
private aggregateOverview(week: WeekRange): WeeklyReport['overview'] {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const projectRows = db.prepare(
|
|
112
|
-
`SELECT DISTINCT project_path
|
|
113
|
-
FROM events
|
|
114
|
-
WHERE timestamp >= ? AND timestamp < ?
|
|
115
|
-
AND project_path IS NOT NULL AND project_path != ''`,
|
|
116
|
-
).all(week.startISO, week.endISO) as Array<{ project_path: string }>;
|
|
117
|
-
|
|
118
|
-
const tasksRow = db.prepare(
|
|
119
|
-
`SELECT COUNT(*) as cnt
|
|
120
|
-
FROM tasks
|
|
121
|
-
WHERE start_time >= ? AND start_time < ?`,
|
|
122
|
-
).get(week.startISO, week.endISO) as { cnt: number };
|
|
98
|
+
// H2 Phase 3: 改用 facade 聚合方法。
|
|
99
|
+
const overview = this.storage.aggregateOverviewByRange({
|
|
100
|
+
since: week.startISO,
|
|
101
|
+
until: week.endISO,
|
|
102
|
+
});
|
|
103
|
+
const activeProjects = this.storage.queryDistinctProjects({
|
|
104
|
+
since: week.startISO,
|
|
105
|
+
until: week.endISO,
|
|
106
|
+
});
|
|
107
|
+
const totalTasks = this.storage.countTasksByRange({
|
|
108
|
+
since: week.startISO,
|
|
109
|
+
until: week.endISO,
|
|
110
|
+
});
|
|
123
111
|
|
|
124
112
|
return {
|
|
125
|
-
totalSessions:
|
|
126
|
-
totalEvents:
|
|
127
|
-
totalTasks:
|
|
128
|
-
activeProjects
|
|
129
|
-
activeDays:
|
|
113
|
+
totalSessions: overview.session_count || 0,
|
|
114
|
+
totalEvents: overview.event_count || 0,
|
|
115
|
+
totalTasks: totalTasks || 0,
|
|
116
|
+
activeProjects,
|
|
117
|
+
activeDays: overview.day_count || 0,
|
|
130
118
|
};
|
|
131
119
|
}
|
|
132
120
|
|
|
@@ -209,42 +197,36 @@ export class WeeklyReportGenerator {
|
|
|
209
197
|
}
|
|
210
198
|
|
|
211
199
|
private aggregateTools(week: WeekRange): WeeklyReport['tools'] {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
200
|
+
// H2 Phase 3: 改用 facade。
|
|
201
|
+
// Phase 2 偏差:spec 列出的 aggregateToolUsage({ since, until, ... }) 实际只实现了
|
|
202
|
+
// since/limit/hook_type,缺 until。这里用 queryEventsByTimeRange + 内存聚合保留
|
|
203
|
+
// 旧 SQL 的 [since, until) 半开区间语义(changelog 已记)。
|
|
204
|
+
const preEvents = this.storage.queryEventsByTimeRange({
|
|
205
|
+
since: week.startISO,
|
|
206
|
+
until: week.endISO,
|
|
207
|
+
}).filter(e => e.hook_type === 'PreToolUse' && e.tool_name);
|
|
208
|
+
|
|
209
|
+
const toolCount = new Map<string, number>();
|
|
210
|
+
for (const ev of preEvents) {
|
|
211
|
+
const name = ev.tool_name as string;
|
|
212
|
+
if (!name) continue;
|
|
213
|
+
toolCount.set(name, (toolCount.get(name) || 0) + 1);
|
|
214
|
+
}
|
|
215
|
+
const toolRows = Array.from(toolCount.entries())
|
|
216
|
+
.map(([tool_name, cnt]) => ({ tool_name, cnt }))
|
|
217
|
+
.sort((a, b) => b.cnt - a.cnt);
|
|
223
218
|
|
|
224
219
|
const totalCalls = toolRows.reduce((sum, r) => sum + r.cnt, 0);
|
|
225
220
|
const topTools = toolRows.slice(0, 5).map(r => ({ name: r.tool_name, count: r.cnt }));
|
|
226
221
|
|
|
227
|
-
// Failure
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
OR tool_output LIKE '%"is_error":true%'
|
|
236
|
-
OR tool_output LIKE '%"isError":true%')`,
|
|
237
|
-
).get(week.startISO, week.endISO) as { failed: number };
|
|
238
|
-
|
|
239
|
-
const postCallRow = db.prepare(
|
|
240
|
-
`SELECT COUNT(*) as total
|
|
241
|
-
FROM events
|
|
242
|
-
WHERE timestamp >= ? AND timestamp < ?
|
|
243
|
-
AND hook_type = 'PostToolUse'`,
|
|
244
|
-
).get(week.startISO, week.endISO) as { total: number };
|
|
245
|
-
|
|
246
|
-
const failureRate = postCallRow.total > 0
|
|
247
|
-
? `${((failureRow.failed / postCallRow.total) * 100).toFixed(1)}%`
|
|
222
|
+
// Failure rate: facade 提供 [since, until) 半开区间精确计算。
|
|
223
|
+
const failure = this.storage.aggregateToolFailureRate({
|
|
224
|
+
since: week.startISO,
|
|
225
|
+
until: week.endISO,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const failureRate = failure.post_total > 0
|
|
229
|
+
? `${((failure.failed / failure.post_total) * 100).toFixed(1)}%`
|
|
248
230
|
: '0.0%';
|
|
249
231
|
|
|
250
232
|
return {
|
|
@@ -255,17 +237,12 @@ export class WeeklyReportGenerator {
|
|
|
255
237
|
}
|
|
256
238
|
|
|
257
239
|
private aggregateFiles(week: WeekRange): WeeklyReport['files'] {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
WHERE timestamp >= ? AND timestamp < ?
|
|
265
|
-
AND hook_type = 'PreToolUse'
|
|
266
|
-
AND tool_name IN (${placeholders})
|
|
267
|
-
AND tool_input IS NOT NULL`,
|
|
268
|
-
).all(week.startISO, week.endISO, ...Array.from(FILE_EDIT_TOOLS)) as Array<{ tool_input: string }>;
|
|
240
|
+
// H2 Phase 3: 改用 facade 聚合方法。
|
|
241
|
+
const rows = this.storage.queryFileEditInputs({
|
|
242
|
+
since: week.startISO,
|
|
243
|
+
until: week.endISO,
|
|
244
|
+
tool_names: Array.from(FILE_EDIT_TOOLS),
|
|
245
|
+
});
|
|
269
246
|
|
|
270
247
|
const fileMap = new Map<string, number>();
|
|
271
248
|
let totalEdits = 0;
|
|
@@ -291,8 +268,8 @@ export class WeeklyReportGenerator {
|
|
|
291
268
|
}
|
|
292
269
|
|
|
293
270
|
private aggregateAnomalies(_week: WeekRange): WeeklyReport['anomalies'] {
|
|
294
|
-
//
|
|
295
|
-
//
|
|
271
|
+
// Anomaly aggregation is delegated to AntiPatternDetector
|
|
272
|
+
// (exposed separately via /api/insights). Keep empty here.
|
|
296
273
|
return [];
|
|
297
274
|
}
|
|
298
275
|
|
|
@@ -11,11 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import fs from 'fs';
|
|
14
|
-
import path from 'path';
|
|
15
14
|
import type { Request, Response, NextFunction } from 'express';
|
|
16
15
|
import { FORGE_PATHS } from '../core/constants.js';
|
|
17
16
|
|
|
18
|
-
const TOKEN_FILE =
|
|
17
|
+
const TOKEN_FILE = FORGE_PATHS.daemonToken();
|
|
19
18
|
|
|
20
19
|
/** 读取当前 daemon auth token,不存在或读取失败返回 null */
|
|
21
20
|
export function readAuthToken(): string | null {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for web routes.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Recursively truncate string/array fields inside an arbitrary value so the
|
|
7
|
+
* serialised JSON payload stays bounded. Used to keep tool input/output
|
|
8
|
+
* previews small in /api/tasks and /api/sessions.
|
|
9
|
+
*
|
|
10
|
+
* - strings longer than `maxLen` are sliced and suffixed with "..."
|
|
11
|
+
* - arrays are sliced to the first `arrayLimit` elements (NOT recursed into,
|
|
12
|
+
* matching the original call sites' behaviour)
|
|
13
|
+
* - objects are walked recursively
|
|
14
|
+
* - everything else is returned as-is (including null/undefined)
|
|
15
|
+
*/
|
|
16
|
+
export function truncateField(
|
|
17
|
+
obj: unknown,
|
|
18
|
+
maxLen = 300,
|
|
19
|
+
arrayLimit = 10,
|
|
20
|
+
): unknown {
|
|
21
|
+
if (!obj) return obj;
|
|
22
|
+
if (typeof obj === 'string') {
|
|
23
|
+
return obj.length > maxLen ? obj.slice(0, maxLen) + '...' : obj;
|
|
24
|
+
}
|
|
25
|
+
if (Array.isArray(obj)) return obj.slice(0, arrayLimit);
|
|
26
|
+
if (typeof obj === 'object') {
|
|
27
|
+
const result: Record<string, unknown> = {};
|
|
28
|
+
for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {
|
|
29
|
+
result[k] = truncateField(v, maxLen, arrayLimit);
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
return obj;
|
|
34
|
+
}
|
package/src/web/routes/drift.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Application } from 'express';
|
|
2
2
|
import type { RouteContext } from './types.js';
|
|
3
|
-
import { DriftDetector } from '
|
|
3
|
+
import { DriftDetector } from '../analytics/drift-detector.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* /api/drift/* — CLAUDE.md 漂移检测 API
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { Application } from 'express';
|
|
9
9
|
import type { RouteContext } from './types.js';
|
|
10
|
-
import { WeeklyReportGenerator } from '
|
|
10
|
+
import { WeeklyReportGenerator } from '../analytics/weekly-report.js';
|
|
11
11
|
|
|
12
12
|
export function registerReportsRoutes(app: Application, ctx: RouteContext): void {
|
|
13
13
|
const { storage, skillRegistry } = ctx;
|
package/src/web/routes/rules.ts
CHANGED
|
@@ -7,11 +7,19 @@
|
|
|
7
7
|
* - Per-skill breakdown
|
|
8
8
|
* - Per-agent breakdown
|
|
9
9
|
* - Never-triggered skills
|
|
10
|
+
*
|
|
11
|
+
* H1 refactor: pushed GROUP BY into SQL via storage.aggregateRoutingStats /
|
|
12
|
+
* aggregateSkillInvocationsBySkill — no more 100k-row JS scans.
|
|
10
13
|
*/
|
|
11
14
|
|
|
12
15
|
import type { Application } from 'express';
|
|
13
16
|
import type { RouteContext } from './types.js';
|
|
14
17
|
|
|
18
|
+
function pct(num: number, denom: number): string {
|
|
19
|
+
if (denom <= 0) return '0%';
|
|
20
|
+
return `${(num / denom * 100).toFixed(1)}%`;
|
|
21
|
+
}
|
|
22
|
+
|
|
15
23
|
export function registerRulesRoutes(app: Application, ctx: RouteContext): void {
|
|
16
24
|
const { storage, skillRegistry } = ctx;
|
|
17
25
|
|
|
@@ -20,68 +28,35 @@ export function registerRulesRoutes(app: Application, ctx: RouteContext): void {
|
|
|
20
28
|
const since = Date.now() - days * 24 * 3600 * 1000;
|
|
21
29
|
const sinceISO = new Date(since).toISOString();
|
|
22
30
|
|
|
23
|
-
//
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
// Agent delegations: obeyed=1 means the routing was followed
|
|
28
|
-
const agentDelegations = routingEvents.filter(e => e.obeyed === 1).length;
|
|
29
|
-
const agentRate = totalPrompts > 0
|
|
30
|
-
? `${(agentDelegations / totalPrompts * 100).toFixed(1)}%`
|
|
31
|
-
: '0%';
|
|
32
|
-
|
|
33
|
-
// Skill invocations
|
|
34
|
-
const skillInvocations = storage.querySkillInvocations({ since, limit: 100000 });
|
|
35
|
-
const skillTotal = skillInvocations.length;
|
|
36
|
-
const skillRate = totalPrompts > 0
|
|
37
|
-
? `${(skillTotal / totalPrompts * 100).toFixed(1)}%`
|
|
38
|
-
: '0%';
|
|
39
|
-
|
|
40
|
-
// Per-skill aggregation
|
|
41
|
-
const skillMap = new Map<string, { total: number; success: number; failed: number }>();
|
|
42
|
-
for (const inv of skillInvocations) {
|
|
43
|
-
const cur = skillMap.get(inv.skill_id) || { total: 0, success: 0, failed: 0 };
|
|
44
|
-
cur.total++;
|
|
45
|
-
if (inv.success === 1) cur.success++;
|
|
46
|
-
else cur.failed++;
|
|
47
|
-
skillMap.set(inv.skill_id, cur);
|
|
48
|
-
}
|
|
31
|
+
// SQL-side aggregates (replaces 2 × 100k-row scans + JS reduce)
|
|
32
|
+
const routing = storage.aggregateRoutingStats({ since_ts: since });
|
|
33
|
+
const skillsAgg = storage.aggregateSkillInvocationsBySkill({ since });
|
|
49
34
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
skill_id,
|
|
53
|
-
total: s.total,
|
|
54
|
-
success: s.success,
|
|
55
|
-
failed: s.failed,
|
|
56
|
-
rate: totalPrompts > 0
|
|
57
|
-
? `${(s.total / totalPrompts * 100).toFixed(1)}%`
|
|
58
|
-
: '0%',
|
|
59
|
-
}))
|
|
60
|
-
.sort((a, b) => b.total - a.total);
|
|
35
|
+
const totalPrompts = routing.total;
|
|
36
|
+
const agentDelegations = routing.obeyed;
|
|
61
37
|
|
|
62
|
-
//
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
38
|
+
// Skills frequency (sorted by total desc — SQL ORDER BY total DESC)
|
|
39
|
+
const skillTotal = skillsAgg.reduce((acc, s) => acc + s.total, 0);
|
|
40
|
+
const skills = skillsAgg.map(s => ({
|
|
41
|
+
skill_id: s.skill_id,
|
|
42
|
+
total: s.total,
|
|
43
|
+
success: s.success,
|
|
44
|
+
failed: s.failed,
|
|
45
|
+
rate: pct(s.total, totalPrompts),
|
|
46
|
+
}));
|
|
69
47
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
: '0%',
|
|
77
|
-
}))
|
|
78
|
-
.sort((a, b) => b.count - a.count);
|
|
48
|
+
// Agents breakdown (already obeyed=1, sorted by count desc in SQL)
|
|
49
|
+
const agents = routing.by_agent.map(a => ({
|
|
50
|
+
agent: a.agent,
|
|
51
|
+
count: a.count,
|
|
52
|
+
rate: pct(a.count, totalPrompts),
|
|
53
|
+
}));
|
|
79
54
|
|
|
80
55
|
// Never-triggered skills
|
|
56
|
+
const triggeredSkillIds = new Set(skillsAgg.map(s => s.skill_id));
|
|
81
57
|
const allSkillIds = skillRegistry
|
|
82
58
|
? skillRegistry.getAll().map(s => s.id)
|
|
83
59
|
: [];
|
|
84
|
-
const triggeredSkillIds = new Set(skillMap.keys());
|
|
85
60
|
const neverTriggered = allSkillIds.filter(id => !triggeredSkillIds.has(id));
|
|
86
61
|
|
|
87
62
|
res.json({
|
|
@@ -89,9 +64,9 @@ export function registerRulesRoutes(app: Application, ctx: RouteContext): void {
|
|
|
89
64
|
summary: {
|
|
90
65
|
totalPrompts,
|
|
91
66
|
agentDelegations,
|
|
92
|
-
agentRate,
|
|
67
|
+
agentRate: pct(agentDelegations, totalPrompts),
|
|
93
68
|
skillInvocations: skillTotal,
|
|
94
|
-
skillRate,
|
|
69
|
+
skillRate: pct(skillTotal, totalPrompts),
|
|
95
70
|
},
|
|
96
71
|
skills,
|
|
97
72
|
agents,
|