@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
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Application } from 'express';
|
|
2
2
|
import type { RouteContext } from './types.js';
|
|
3
|
+
import { truncateField } from './_helpers.js';
|
|
4
|
+
import { getCommand, getFilePath, getUserPrompt } from '../../core/event-fields.js';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* /api/sessions* — session lists, per-session detail, timeline.
|
|
@@ -78,22 +80,22 @@ export function registerSessionsRoutes(app: Application, ctx: RouteContext): voi
|
|
|
78
80
|
|
|
79
81
|
const buildTaskDetail = (events: typeof allEvents, injections: typeof allInjections) => {
|
|
80
82
|
const prompts = events
|
|
81
|
-
.filter(e => e.hook_type === 'UserPromptSubmit' && (e
|
|
82
|
-
.map(e => ({ timestamp: e.timestamp, content:
|
|
83
|
+
.filter(e => e.hook_type === 'UserPromptSubmit' && getUserPrompt(e))
|
|
84
|
+
.map(e => ({ timestamp: e.timestamp, content: getUserPrompt(e) }));
|
|
83
85
|
|
|
84
86
|
const toolUsage: Record<string, number> = {};
|
|
85
87
|
events.forEach(e => { if (e.tool_name) toolUsage[e.tool_name] = (toolUsage[e.tool_name] || 0) + 1; });
|
|
86
88
|
|
|
87
89
|
const filesChanged = [...new Set(
|
|
88
90
|
events
|
|
89
|
-
.filter(e => (e.tool_name === 'Edit' || e.tool_name === 'Write') && (e
|
|
90
|
-
.map(e => (e
|
|
91
|
+
.filter(e => (e.tool_name === 'Edit' || e.tool_name === 'Write') && getFilePath(e))
|
|
92
|
+
.map(e => getFilePath(e)!),
|
|
91
93
|
)];
|
|
92
94
|
|
|
93
95
|
const commits = events
|
|
94
|
-
.filter(e => e.tool_name === 'Bash' && (e
|
|
96
|
+
.filter(e => e.tool_name === 'Bash' && getCommand(e)?.includes('git commit'))
|
|
95
97
|
.map(e => {
|
|
96
|
-
const cmd = (e
|
|
98
|
+
const cmd = getCommand(e) || '';
|
|
97
99
|
const match = cmd.match(/git commit.*-m\s+["']([^"']+)["']/);
|
|
98
100
|
return { timestamp: e.timestamp, message: match ? match[1] : 'commit' };
|
|
99
101
|
});
|
|
@@ -155,12 +157,12 @@ export function registerSessionsRoutes(app: Application, ctx: RouteContext): voi
|
|
|
155
157
|
|
|
156
158
|
// User inputs
|
|
157
159
|
events
|
|
158
|
-
.filter(e => e.hook_type === 'UserPromptSubmit' && (e
|
|
160
|
+
.filter(e => e.hook_type === 'UserPromptSubmit' && getUserPrompt(e))
|
|
159
161
|
.forEach(e => {
|
|
160
162
|
timeline.push({
|
|
161
163
|
timestamp: e.timestamp,
|
|
162
164
|
type: 'user_input',
|
|
163
|
-
data: { content:
|
|
165
|
+
data: { content: getUserPrompt(e) },
|
|
164
166
|
});
|
|
165
167
|
});
|
|
166
168
|
|
|
@@ -183,30 +185,16 @@ export function registerSessionsRoutes(app: Application, ctx: RouteContext): voi
|
|
|
183
185
|
events
|
|
184
186
|
.filter(e => e.tool_name && e.hook_type === 'PreToolUse')
|
|
185
187
|
.forEach(e => {
|
|
186
|
-
const toolInput = e.tool_input
|
|
187
|
-
const toolOutput = e.tool_output
|
|
188
|
-
|
|
189
|
-
const truncateField = (obj: any, maxLen = 200): any => {
|
|
190
|
-
if (!obj) return obj;
|
|
191
|
-
if (typeof obj === 'string') return obj.length > maxLen ? obj.slice(0, maxLen) + '...' : obj;
|
|
192
|
-
if (Array.isArray(obj)) return obj.slice(0, 5);
|
|
193
|
-
if (typeof obj === 'object') {
|
|
194
|
-
const result: any = {};
|
|
195
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
196
|
-
result[k] = truncateField(v, maxLen);
|
|
197
|
-
}
|
|
198
|
-
return result;
|
|
199
|
-
}
|
|
200
|
-
return obj;
|
|
201
|
-
};
|
|
188
|
+
const toolInput = e.tool_input;
|
|
189
|
+
const toolOutput = e.tool_output;
|
|
202
190
|
|
|
203
191
|
timeline.push({
|
|
204
192
|
timestamp: e.timestamp,
|
|
205
193
|
type: 'tool_call',
|
|
206
194
|
data: {
|
|
207
195
|
tool: e.tool_name,
|
|
208
|
-
input: truncateField(toolInput),
|
|
209
|
-
output: truncateField(toolOutput),
|
|
196
|
+
input: truncateField(toolInput, 200, 5),
|
|
197
|
+
output: truncateField(toolOutput, 200, 5),
|
|
210
198
|
success: !toolOutput?.error,
|
|
211
199
|
},
|
|
212
200
|
});
|
|
@@ -233,17 +221,17 @@ export function registerSessionsRoutes(app: Application, ctx: RouteContext): voi
|
|
|
233
221
|
|
|
234
222
|
// Summary
|
|
235
223
|
const commits = events
|
|
236
|
-
.filter(e => e.tool_name === 'Bash' && (e
|
|
224
|
+
.filter(e => e.tool_name === 'Bash' && getCommand(e)?.includes('git commit'))
|
|
237
225
|
.map(e => {
|
|
238
|
-
const cmd = (e
|
|
226
|
+
const cmd = getCommand(e) || '';
|
|
239
227
|
const match = cmd.match(/git commit.*-m\s+["']([^"']+)["']/);
|
|
240
228
|
return { message: match ? match[1] : 'commit' };
|
|
241
229
|
});
|
|
242
230
|
|
|
243
231
|
const filesChanged = [...new Set(
|
|
244
232
|
events
|
|
245
|
-
.filter(e => (e.tool_name === 'Edit' || e.tool_name === 'Write') && (e
|
|
246
|
-
.map(e => (e
|
|
233
|
+
.filter(e => (e.tool_name === 'Edit' || e.tool_name === 'Write') && getFilePath(e))
|
|
234
|
+
.map(e => getFilePath(e)!),
|
|
247
235
|
)];
|
|
248
236
|
|
|
249
237
|
res.json({
|
|
@@ -8,6 +8,8 @@ import type { RouteContext } from './types.js';
|
|
|
8
8
|
* - /api/skill-stats/distribution — routing type distribution (agent/skill/none)
|
|
9
9
|
* - /api/skill-stats/frequency — skill usage frequency
|
|
10
10
|
* - /api/skill-stats/trend — skill usage rate trend over time
|
|
11
|
+
*
|
|
12
|
+
* H1: All endpoints now use storage-layer SQL aggregation (no JS GROUP BY).
|
|
11
13
|
*/
|
|
12
14
|
export function registerSkillStatsRoutes(app: Application, ctx: RouteContext): void {
|
|
13
15
|
const { storage } = ctx;
|
|
@@ -16,23 +18,14 @@ export function registerSkillStatsRoutes(app: Application, ctx: RouteContext): v
|
|
|
16
18
|
app.get('/api/skill-stats/distribution', (req, res) => {
|
|
17
19
|
const windowHours = parseInt((req.query.window as string) || '168'); // default 7d
|
|
18
20
|
const since = Date.now() - windowHours * 3600 * 1000;
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
const distribution: Record<string, number> = {
|
|
22
|
-
agent: 0,
|
|
23
|
-
skill: 0,
|
|
24
|
-
none: 0,
|
|
25
|
-
};
|
|
21
|
+
const agg = storage.aggregateRoutingStats({ since_ts: since });
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
// Ensure stable ordering and presence of all three buckets (agent/skill/none)
|
|
24
|
+
const counts: Record<string, number> = { agent: 0, skill: 0, none: 0 };
|
|
25
|
+
for (const row of agg.by_type) {
|
|
26
|
+
counts[row.type] = row.count;
|
|
30
27
|
}
|
|
31
|
-
|
|
32
|
-
const result = Object.entries(distribution).map(([type, count]) => ({
|
|
33
|
-
type,
|
|
34
|
-
count,
|
|
35
|
-
}));
|
|
28
|
+
const result = Object.entries(counts).map(([type, count]) => ({ type, count }));
|
|
36
29
|
|
|
37
30
|
res.json({ windowHours, distribution: result });
|
|
38
31
|
});
|
|
@@ -41,19 +34,9 @@ export function registerSkillStatsRoutes(app: Application, ctx: RouteContext): v
|
|
|
41
34
|
app.get('/api/skill-stats/frequency', (req, res) => {
|
|
42
35
|
const windowHours = parseInt((req.query.window as string) || '168');
|
|
43
36
|
const since = Date.now() - windowHours * 3600 * 1000;
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
const frequency = new Map<string, number>();
|
|
37
|
+
const agg = storage.aggregateRoutingStats({ since_ts: since });
|
|
47
38
|
|
|
48
|
-
|
|
49
|
-
if (e.routed_to_type === 'skill' && e.routed_to_name) {
|
|
50
|
-
frequency.set(e.routed_to_name, (frequency.get(e.routed_to_name) || 0) + 1);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const result = Array.from(frequency.entries())
|
|
55
|
-
.map(([name, count]) => ({ name, count }))
|
|
56
|
-
.sort((a, b) => b.count - a.count);
|
|
39
|
+
const result = agg.by_skill_routed.map(({ skill, count }) => ({ name: skill, count }));
|
|
57
40
|
|
|
58
41
|
res.json({ windowHours, frequency: result });
|
|
59
42
|
});
|
|
@@ -62,25 +45,12 @@ export function registerSkillStatsRoutes(app: Application, ctx: RouteContext): v
|
|
|
62
45
|
app.get('/api/skill-stats/trend', (req, res) => {
|
|
63
46
|
const windowHours = parseInt((req.query.window as string) || '168');
|
|
64
47
|
const since = Date.now() - windowHours * 3600 * 1000;
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
// Group by day
|
|
68
|
-
const dailyStats = new Map<string, { total: number; skill: number }>();
|
|
69
|
-
|
|
70
|
-
for (const e of events) {
|
|
71
|
-
const day = new Date(e.ts).toISOString().split('T')[0];
|
|
72
|
-
const stats = dailyStats.get(day) || { total: 0, skill: 0 };
|
|
73
|
-
stats.total++;
|
|
74
|
-
if (e.routed_to_type === 'skill') {
|
|
75
|
-
stats.skill++;
|
|
76
|
-
}
|
|
77
|
-
dailyStats.set(day, stats);
|
|
78
|
-
}
|
|
48
|
+
const rows = storage.aggregateRoutingTrendByDay({ since_ts: since });
|
|
79
49
|
|
|
80
|
-
const result =
|
|
81
|
-
.map(
|
|
82
|
-
day,
|
|
83
|
-
skillRate:
|
|
50
|
+
const result = rows
|
|
51
|
+
.map(r => ({
|
|
52
|
+
day: r.day,
|
|
53
|
+
skillRate: r.total > 0 ? Math.round((r.skill / r.total) * 100) : 0,
|
|
84
54
|
}))
|
|
85
55
|
.sort((a, b) => a.day.localeCompare(b.day));
|
|
86
56
|
|
|
@@ -91,41 +61,31 @@ export function registerSkillStatsRoutes(app: Application, ctx: RouteContext): v
|
|
|
91
61
|
app.get('/api/skill-stats/invocations', (req, res) => {
|
|
92
62
|
const days = parseInt((req.query.days as string) || '7');
|
|
93
63
|
const since = Date.now() - days * 24 * 3600 * 1000;
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
64
|
+
const rows = storage.aggregateSkillInvocationsBySkill({ since });
|
|
65
|
+
|
|
66
|
+
const total = rows.reduce((sum, r) => sum + r.total, 0);
|
|
67
|
+
const result = rows.map(r => ({
|
|
68
|
+
skill_id: r.skill_id,
|
|
69
|
+
total: r.total,
|
|
70
|
+
success: r.success,
|
|
71
|
+
failed: r.failed,
|
|
72
|
+
}));
|
|
104
73
|
|
|
105
|
-
|
|
106
|
-
res.json({ days, total: invocations.length, invocations: result });
|
|
74
|
+
res.json({ days, total, invocations: result });
|
|
107
75
|
});
|
|
108
76
|
|
|
109
77
|
// Routing/Agent call rate stats
|
|
110
78
|
app.get('/api/routing/stats', (req, res) => {
|
|
111
79
|
const days = parseInt((req.query.days as string) || '7');
|
|
112
80
|
const since = Date.now() - days * 24 * 3600 * 1000;
|
|
113
|
-
const
|
|
81
|
+
const agg = storage.aggregateRoutingStats({ since_ts: since });
|
|
114
82
|
|
|
115
|
-
const total =
|
|
116
|
-
const agentCalls =
|
|
83
|
+
const total = agg.total;
|
|
84
|
+
const agentCalls = agg.obeyed;
|
|
117
85
|
const noAgent = total - agentCalls;
|
|
118
86
|
const agentRate = total > 0 ? Math.round((agentCalls / total) * 1000) / 10 : 0;
|
|
119
87
|
|
|
120
|
-
const
|
|
121
|
-
for (const e of events) {
|
|
122
|
-
if (e.routed_to_name && e.obeyed === 1) {
|
|
123
|
-
byAgentMap.set(e.routed_to_name, (byAgentMap.get(e.routed_to_name) || 0) + 1);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
const byAgent = Array.from(byAgentMap.entries())
|
|
127
|
-
.map(([agent, count]) => ({ agent, count }))
|
|
128
|
-
.sort((a, b) => b.count - a.count);
|
|
88
|
+
const byAgent = agg.by_agent.map(({ agent, count }) => ({ agent, count }));
|
|
129
89
|
|
|
130
90
|
res.json({ days, total, agentCalls, noAgent, agentRate, byAgent });
|
|
131
91
|
});
|
package/src/web/routes/skills.ts
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
import { logger } from '../../core/utils/logger.js';
|
|
6
6
|
import type { RouteContext } from './types.js';
|
|
7
|
+
import { FORGE_PATHS } from '../../core/constants.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* /api/skills/* — list, read, update, version, rollback.
|
|
@@ -205,7 +206,7 @@ export function registerSkillsRoutes(app: Application, ctx: RouteContext): void
|
|
|
205
206
|
return;
|
|
206
207
|
}
|
|
207
208
|
|
|
208
|
-
const backupDir =
|
|
209
|
+
const backupDir = FORGE_PATHS.backups('skills');
|
|
209
210
|
fs.mkdirSync(backupDir, { recursive: true });
|
|
210
211
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
211
212
|
const backupPath = path.join(backupDir, `${name}-${timestamp}.md`);
|
|
@@ -232,7 +233,7 @@ export function registerSkillsRoutes(app: Application, ctx: RouteContext): void
|
|
|
232
233
|
return;
|
|
233
234
|
}
|
|
234
235
|
|
|
235
|
-
const backupDir =
|
|
236
|
+
const backupDir = FORGE_PATHS.backups('skills');
|
|
236
237
|
|
|
237
238
|
if (!fs.existsSync(backupDir)) {
|
|
238
239
|
res.json({ versions: [] });
|
|
@@ -276,7 +277,7 @@ export function registerSkillsRoutes(app: Application, ctx: RouteContext): void
|
|
|
276
277
|
return;
|
|
277
278
|
}
|
|
278
279
|
|
|
279
|
-
const backupDir =
|
|
280
|
+
const backupDir = FORGE_PATHS.backups('skills');
|
|
280
281
|
const filename = `${name}-${timestamp}.md`;
|
|
281
282
|
const filePath = path.join(backupDir, filename);
|
|
282
283
|
|
|
@@ -318,7 +319,7 @@ export function registerSkillsRoutes(app: Application, ctx: RouteContext): void
|
|
|
318
319
|
|
|
319
320
|
const skillsDir = path.join(homedir(), '.claude', 'skills');
|
|
320
321
|
const currentPath = path.join(skillsDir, `${name}.md`);
|
|
321
|
-
const backupDir =
|
|
322
|
+
const backupDir = FORGE_PATHS.backups('skills');
|
|
322
323
|
const versionPath = path.join(backupDir, `${name}-${timestamp}.md`);
|
|
323
324
|
|
|
324
325
|
if (!fs.existsSync(currentPath)) {
|
package/src/web/routes/stats.ts
CHANGED
|
@@ -3,51 +3,41 @@ import type { RouteContext } from './types.js';
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* /api/stats — System-wide statistics for the Dashboard.
|
|
6
|
+
*
|
|
7
|
+
* H2 Phase 3: 替换原 db.prepare 直写 SQL 为 SQLiteStorage facade 聚合方法。
|
|
8
|
+
* 响应 shape 保持不变(前端契约):totalSessions / totalEvents / toolUsage /
|
|
9
|
+
* dailyActivity / skillInvocations。
|
|
6
10
|
*/
|
|
7
11
|
export function registerStatsRoutes(app: Application, ctx: RouteContext): void {
|
|
8
12
|
const { storage } = ctx;
|
|
9
13
|
|
|
10
14
|
app.get('/api/stats', (_req, res) => {
|
|
11
|
-
const
|
|
15
|
+
const totalEvents = storage.countAllEvents();
|
|
16
|
+
const totalSessions = storage.countAllSessions();
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
// Tool usage distribution
|
|
17
|
-
const toolRows = db.prepare(
|
|
18
|
-
`SELECT tool_name, COUNT(*) as cnt FROM events
|
|
19
|
-
WHERE tool_name IS NOT NULL AND tool_name != ''
|
|
20
|
-
GROUP BY tool_name ORDER BY cnt DESC LIMIT 15`
|
|
21
|
-
).all() as Array<{ tool_name: string; cnt: number }>;
|
|
18
|
+
// Tool usage distribution (top 15)
|
|
19
|
+
const toolRows = storage.aggregateToolUsage({ limit: 15 });
|
|
22
20
|
const toolUsage: Record<string, number> = {};
|
|
23
21
|
for (const r of toolRows) {
|
|
24
|
-
toolUsage[r.tool_name] = r.
|
|
22
|
+
toolUsage[r.tool_name] = r.count;
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
// Daily activity (last 7 days)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
).all() as Array<{ d: string; eventCount: number }>;
|
|
34
|
-
|
|
35
|
-
const sessionDailyRows = db.prepare(
|
|
36
|
-
`SELECT date(start_time) as d, COUNT(*) as sessionCount
|
|
37
|
-
FROM sessions
|
|
38
|
-
WHERE start_time >= date('now', '-7 days')
|
|
39
|
-
GROUP BY d ORDER BY d`
|
|
40
|
-
).all() as Array<{ d: string; sessionCount: number }>;
|
|
26
|
+
// 旧 SQL: timestamp >= date('now', '-7 days')
|
|
27
|
+
// 新口径:调用方计算 ISO 字符串,传给 facade。
|
|
28
|
+
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
29
|
+
const dailyRows = storage.aggregateDailyEventCounts({ since: sevenDaysAgo });
|
|
30
|
+
const sessionDailyRows = storage.aggregateDailySessionCounts({ since: sevenDaysAgo });
|
|
41
31
|
|
|
42
|
-
const sessionMap = new Map(sessionDailyRows.map(r => [r.
|
|
32
|
+
const sessionMap = new Map(sessionDailyRows.map(r => [r.date, r.count]));
|
|
43
33
|
const dailyActivity = dailyRows.map(r => ({
|
|
44
|
-
date: r.
|
|
45
|
-
eventCount: r.
|
|
46
|
-
sessionCount: sessionMap.get(r.
|
|
34
|
+
date: r.date,
|
|
35
|
+
eventCount: r.count,
|
|
36
|
+
sessionCount: sessionMap.get(r.date) || 0,
|
|
47
37
|
}));
|
|
48
38
|
|
|
49
39
|
// Skill invocation count
|
|
50
|
-
const skillCount =
|
|
40
|
+
const skillCount = storage.countAllSkillInvocations();
|
|
51
41
|
|
|
52
42
|
res.json({
|
|
53
43
|
totalSessions,
|
package/src/web/routes/tasks.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { Application } from 'express';
|
|
3
3
|
import type { RouteContext } from './types.js';
|
|
4
|
+
import { truncateField } from './_helpers.js';
|
|
5
|
+
import { getFilePath, getUserPrompt } from '../../core/event-fields.js';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* /api/tasks — Task execution view.
|
|
@@ -59,28 +61,20 @@ export function registerTasksRoutes(app: Application, ctx: RouteContext): void {
|
|
|
59
61
|
app.get('/api/tasks/:taskId', (req, res) => {
|
|
60
62
|
const taskId = req.params.taskId;
|
|
61
63
|
|
|
62
|
-
//
|
|
63
|
-
const
|
|
64
|
-
const task = allTasks.find(t => t.id === taskId);
|
|
64
|
+
// H1: PK direct fetch (was: queryTasks({limit:5000}).find)
|
|
65
|
+
const task = storage.getTask(taskId);
|
|
65
66
|
if (!task) {
|
|
66
67
|
res.status(404).json({ error: 'Task not found' });
|
|
67
68
|
return;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
//
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
// Fetch all events for the session, then filter to task events
|
|
74
|
-
const allEvents = storage.queryEvents({ session_id: task.session_id, limit: 5000 });
|
|
75
|
-
const taskEvents = allEvents.filter(e => e.event_id && eventIds.has(e.event_id));
|
|
76
|
-
|
|
77
|
-
// Sort by timestamp ascending (chronological order)
|
|
78
|
-
taskEvents.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
71
|
+
// H1: JOIN task_events (was: queryEvents({limit:5000}) + Set.has filter)
|
|
72
|
+
const taskEvents = storage.queryEventsByTaskId(taskId, { limit: 5000 });
|
|
79
73
|
|
|
80
74
|
// ── Injections ──────────────────────────────────────────────────────
|
|
81
|
-
|
|
75
|
+
// H1: JOIN task_events (was: queryInjections({limit:500}) + Set.has filter)
|
|
76
|
+
const allInjections = storage.queryInjectionsByTaskId(taskId);
|
|
82
77
|
const injections = allInjections
|
|
83
|
-
.filter(inj => inj.event_id && eventIds.has(inj.event_id))
|
|
84
78
|
.map(inj => ({
|
|
85
79
|
id: inj.id,
|
|
86
80
|
timestamp: inj.timestamp,
|
|
@@ -92,20 +86,6 @@ export function registerTasksRoutes(app: Application, ctx: RouteContext): void {
|
|
|
92
86
|
}));
|
|
93
87
|
|
|
94
88
|
// ── Timeline ────────────────────────────────────────────────────────
|
|
95
|
-
const truncateField = (obj: unknown, maxLen = 300): unknown => {
|
|
96
|
-
if (!obj) return obj;
|
|
97
|
-
if (typeof obj === 'string') return obj.length > maxLen ? obj.slice(0, maxLen) + '...' : obj;
|
|
98
|
-
if (Array.isArray(obj)) return obj.slice(0, 10);
|
|
99
|
-
if (typeof obj === 'object') {
|
|
100
|
-
const result: Record<string, unknown> = {};
|
|
101
|
-
for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {
|
|
102
|
-
result[k] = truncateField(v, maxLen);
|
|
103
|
-
}
|
|
104
|
-
return result;
|
|
105
|
-
}
|
|
106
|
-
return obj;
|
|
107
|
-
};
|
|
108
|
-
|
|
109
89
|
const timeline = taskEvents
|
|
110
90
|
.filter(e => e.tool_name && (e.hook_type === 'PreToolUse' || e.hook_type === 'PostToolUse'))
|
|
111
91
|
.reduce<Array<{
|
|
@@ -129,7 +109,7 @@ export function registerTasksRoutes(app: Application, ctx: RouteContext): void {
|
|
|
129
109
|
return acc;
|
|
130
110
|
}
|
|
131
111
|
|
|
132
|
-
const toolInput = e.tool_input
|
|
112
|
+
const toolInput = e.tool_input ?? null;
|
|
133
113
|
const isAgentCall = e.tool_name === 'Agent' || e.tool_name === 'Task';
|
|
134
114
|
|
|
135
115
|
const entry: (typeof acc)[number] = {
|
|
@@ -143,8 +123,8 @@ export function registerTasksRoutes(app: Application, ctx: RouteContext): void {
|
|
|
143
123
|
|
|
144
124
|
if (isAgentCall && toolInput) {
|
|
145
125
|
entry.agent_detail = {
|
|
146
|
-
subagent_type: toolInput.subagent_type
|
|
147
|
-
name: toolInput.name
|
|
126
|
+
subagent_type: typeof toolInput.subagent_type === 'string' ? toolInput.subagent_type : undefined,
|
|
127
|
+
name: typeof toolInput.name === 'string' ? toolInput.name : undefined,
|
|
148
128
|
prompt: typeof toolInput.prompt === 'string'
|
|
149
129
|
? toolInput.prompt
|
|
150
130
|
: undefined,
|
|
@@ -156,15 +136,10 @@ export function registerTasksRoutes(app: Application, ctx: RouteContext): void {
|
|
|
156
136
|
}, []);
|
|
157
137
|
|
|
158
138
|
// ── Skill Invocations ───────────────────────────────────────────────
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const taskStart = task.start_time;
|
|
162
|
-
const taskEnd = task.end_time || new Date().toISOString();
|
|
163
|
-
const taskStartMs = new Date(taskStart).getTime();
|
|
164
|
-
const taskEndMs = new Date(taskEnd).getTime();
|
|
139
|
+
// H1: SQL window filter (was: querySkillInvocations({limit:200}) + JS time filter)
|
|
140
|
+
const allSkillInvocations = storage.querySkillInvocationsByTaskWindow(taskId);
|
|
165
141
|
|
|
166
142
|
const skillInvocations = allSkillInvocations
|
|
167
|
-
.filter(si => si.timestamp >= taskStartMs && si.timestamp <= taskEndMs)
|
|
168
143
|
.map(si => ({
|
|
169
144
|
id: si.id,
|
|
170
145
|
skill_id: si.skill_id,
|
|
@@ -184,17 +159,17 @@ export function registerTasksRoutes(app: Application, ctx: RouteContext): void {
|
|
|
184
159
|
taskEvents
|
|
185
160
|
.filter(e =>
|
|
186
161
|
(e.tool_name === 'Edit' || e.tool_name === 'Write') &&
|
|
187
|
-
(e
|
|
162
|
+
getFilePath(e),
|
|
188
163
|
)
|
|
189
|
-
.map(e => (e
|
|
164
|
+
.map(e => getFilePath(e)!),
|
|
190
165
|
)];
|
|
191
166
|
|
|
192
167
|
// ── User Prompts ────────────────────────────────────────────────────
|
|
193
168
|
const userPrompts = taskEvents
|
|
194
|
-
.filter(e => e.hook_type === 'UserPromptSubmit' && (e
|
|
169
|
+
.filter(e => e.hook_type === 'UserPromptSubmit' && getUserPrompt(e))
|
|
195
170
|
.map(e => ({
|
|
196
171
|
timestamp: e.timestamp,
|
|
197
|
-
content:
|
|
172
|
+
content: getUserPrompt(e),
|
|
198
173
|
}));
|
|
199
174
|
|
|
200
175
|
res.json({
|
package/src/web/routes/trace.ts
CHANGED
|
@@ -92,7 +92,7 @@ export function registerTraceRoutes(app: Application, ctx: RouteContext): void {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
// 4. Query session details
|
|
95
|
-
|
|
95
|
+
// H2 Phase 3: 改用 facade 聚合方法,消除 db.prepare 直写。
|
|
96
96
|
const allSessions = storage.querySessions({ limit: 5000 });
|
|
97
97
|
const sessionDetails = sessionIds.map(sid => {
|
|
98
98
|
const session = allSessions.find(s => s.session_id === sid || s.session_id.startsWith(sid));
|
|
@@ -103,21 +103,9 @@ export function registerTraceRoutes(app: Application, ctx: RouteContext): void {
|
|
|
103
103
|
const end = new Date(session.end_time);
|
|
104
104
|
const durationMin = Math.round((end.getTime() - start.getTime()) / 60000);
|
|
105
105
|
|
|
106
|
-
const eventBreakdown =
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
`).all(fullId) as Array<{ hook_type: string; cnt: number }>;
|
|
110
|
-
|
|
111
|
-
const agentRows = db.prepare(`
|
|
112
|
-
SELECT json_extract(tool_input, '$.subagent_type') as agent_type, COUNT(*) as cnt
|
|
113
|
-
FROM events
|
|
114
|
-
WHERE session_id = ? AND tool_name IN ('Agent', 'Task') AND tool_input IS NOT NULL
|
|
115
|
-
GROUP BY agent_type
|
|
116
|
-
`).all(fullId) as Array<{ agent_type: string | null; cnt: number }>;
|
|
117
|
-
|
|
118
|
-
const skillRows = db.prepare(`
|
|
119
|
-
SELECT DISTINCT skill_id FROM skill_invocations WHERE session_id = ?
|
|
120
|
-
`).all(fullId) as Array<{ skill_id: string }>;
|
|
106
|
+
const eventBreakdown = storage.aggregateHookTypeBySession(fullId);
|
|
107
|
+
const agentRows = storage.aggregateAgentTypeBySession(fullId);
|
|
108
|
+
const skillIds = storage.queryDistinctSkillIdsBySession(fullId);
|
|
121
109
|
|
|
122
110
|
return {
|
|
123
111
|
session_id: fullId,
|
|
@@ -127,9 +115,9 @@ export function registerTraceRoutes(app: Application, ctx: RouteContext): void {
|
|
|
127
115
|
end_time: session.end_time,
|
|
128
116
|
duration_min: durationMin,
|
|
129
117
|
event_count: session.event_count,
|
|
130
|
-
event_breakdown: Object.fromEntries(eventBreakdown.map(r => [r.hook_type, r.
|
|
131
|
-
agent_calls: agentRows.filter(r => r.agent_type).map(r => ({ type: r.agent_type, count: r.
|
|
132
|
-
skills:
|
|
118
|
+
event_breakdown: Object.fromEntries(eventBreakdown.map(r => [r.hook_type, r.count])),
|
|
119
|
+
agent_calls: agentRows.filter(r => r.agent_type).map(r => ({ type: r.agent_type, count: r.count })),
|
|
120
|
+
skills: skillIds,
|
|
133
121
|
};
|
|
134
122
|
});
|
|
135
123
|
|
package/src/web/routes/types.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'url';
|
|
|
8
8
|
import { homedir } from 'os';
|
|
9
9
|
import type { SQLiteStorage } from '../../core/storage/sqlite.js';
|
|
10
10
|
import type { SkillRegistry } from '../../skills/registry.js';
|
|
11
|
+
import { FORGE_PATHS } from '../../core/constants.js';
|
|
11
12
|
|
|
12
13
|
export interface RouteContext {
|
|
13
14
|
storage: SQLiteStorage;
|
|
@@ -29,13 +30,13 @@ export function resolvePatchTarget(
|
|
|
29
30
|
if (targetType === 'skill') {
|
|
30
31
|
return {
|
|
31
32
|
filePath: path.join(homedir(), '.claude', 'skills', `${targetName}.md`),
|
|
32
|
-
backupDir:
|
|
33
|
+
backupDir: FORGE_PATHS.backups('skills'),
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
if (targetType === 'routing_rule') {
|
|
36
37
|
return {
|
|
37
|
-
filePath:
|
|
38
|
-
backupDir:
|
|
38
|
+
filePath: FORGE_PATHS.routingYaml(),
|
|
39
|
+
backupDir: FORGE_PATHS.backups('routing'),
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
42
|
throw new Error(`Unsupported targetType: ${targetType}`);
|