@winspan/claude-forge 8.50.6 → 8.51.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/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/index.d.ts.map +1 -1
- package/dist/daemon/index.js +23 -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/index.ts +24 -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/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
|
@@ -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}`);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration test for ClaudeMdGenerator — safety-net for L1 SWARM_PROTOCOL extraction.
|
|
3
|
+
*
|
|
4
|
+
* Anchors the SWARM section content (open → projectContent boundary) via SHA256
|
|
5
|
+
* so that the .md file extraction must produce byte-for-byte identical output.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
9
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
import { ClaudeMdGenerator } from '../../src/claudemd/claudemd-generator.js';
|
|
14
|
+
|
|
15
|
+
// SWARM section SHA256, captured against the literal-string implementation
|
|
16
|
+
// before extracting to swarm-protocol.md. After extraction, this MUST stay equal.
|
|
17
|
+
const SWARM_SECTION_SHA256 =
|
|
18
|
+
'260dfe1e48fdecc324419a6330428eaa3597e3fd6e6262cf66cdcf301ca32e18';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Minimal stub for ClaudeProvider — only `complete` is exercised by generate().
|
|
22
|
+
*/
|
|
23
|
+
function makeFailingAi(): any {
|
|
24
|
+
return {
|
|
25
|
+
complete: async () => {
|
|
26
|
+
throw new Error('forced AI failure (safety-net)');
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('ClaudeMdGenerator (safety-net)', () => {
|
|
32
|
+
let tmpDir: string;
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
tmpDir = mkdtempSync(join(tmpdir(), 'claudemd-gen-'));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('generates fallback CLAUDE.md containing all SWARM_PROTOCOL anchors', async () => {
|
|
43
|
+
const gen = new ClaudeMdGenerator(makeFailingAi());
|
|
44
|
+
const output = await gen.generate(tmpDir);
|
|
45
|
+
|
|
46
|
+
// ── Key field anchors (SWARM section) ──────────────────────────────
|
|
47
|
+
expect(output).toContain('# claude-forge 工作区规范');
|
|
48
|
+
expect(output).toContain('## 自检');
|
|
49
|
+
expect(output).toContain('Two-Phase Workflow');
|
|
50
|
+
expect(output).toContain('harness-hotfix');
|
|
51
|
+
expect(output).toContain('refactor-safe');
|
|
52
|
+
expect(output).toContain('hybrid-feature-with-safety');
|
|
53
|
+
|
|
54
|
+
// ── Total line count ≥ 200 (SWARM ~223 lines + fallback) ───────────
|
|
55
|
+
const lineCount = output.split('\n').length;
|
|
56
|
+
expect(lineCount).toBeGreaterThanOrEqual(200);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('SWARM section hash is stable across literal/file-loaded implementations', async () => {
|
|
60
|
+
const gen = new ClaudeMdGenerator(makeFailingAi());
|
|
61
|
+
const output = await gen.generate(tmpDir);
|
|
62
|
+
|
|
63
|
+
// Split SWARM from projectContent: fallback always starts with sectionOverview ("## 项目概述").
|
|
64
|
+
// SWARM_PROTOCOL ends with "\n" + "\n\n" separator, so the boundary is the first
|
|
65
|
+
// "\n\n## 项目概述" occurrence — everything before that is the SWARM section.
|
|
66
|
+
const boundary = output.indexOf('\n\n## 项目概述');
|
|
67
|
+
expect(boundary).toBeGreaterThan(0);
|
|
68
|
+
|
|
69
|
+
// Include the trailing newline of SWARM_PROTOCOL (it ends with `\n`), exclude the `\n\n` separator.
|
|
70
|
+
const swarmSection = output.slice(0, boundary + 1);
|
|
71
|
+
|
|
72
|
+
const hash = createHash('sha256').update(swarmSection, 'utf-8').digest('hex');
|
|
73
|
+
|
|
74
|
+
// Print for first-run baseline capture (visible with --reporter=verbose).
|
|
75
|
+
// eslint-disable-next-line no-console
|
|
76
|
+
console.log('[safety-net] SWARM_SECTION_SHA256 =', hash);
|
|
77
|
+
// eslint-disable-next-line no-console
|
|
78
|
+
console.log('[safety-net] SWARM section length =', swarmSection.length, 'bytes');
|
|
79
|
+
|
|
80
|
+
// Sanity: SWARM section must be substantial.
|
|
81
|
+
expect(swarmSection.length).toBeGreaterThan(4000);
|
|
82
|
+
expect(swarmSection.startsWith('# claude-forge 工作区规范')).toBe(true);
|
|
83
|
+
expect(swarmSection.endsWith('(由 daemon 自动更新,记录最近任务上下文)\n\n')).toBe(true);
|
|
84
|
+
|
|
85
|
+
// If a known hash is set (non-placeholder), assert equality.
|
|
86
|
+
// The placeholder below is the all-zero-ish string used on first run; replaced
|
|
87
|
+
// automatically by the orchestration script once baseline is captured.
|
|
88
|
+
expect(hash).toBe(SWARM_SECTION_SHA256);
|
|
89
|
+
});
|
|
90
|
+
});
|