@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
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(n,t){if(!(n!=null&&n.trim()))return"(无标题)";const m=n.trim();if(m.startsWith("<task-notification>")){const s=m.match(/<summary>([\s\S]*?)<\/summary>/);if(s&&s[1].trim()){const r=s[1].trim();return r.length>80?r.slice(0,80)+"…":r}if(t){const r=t.match(/<summary>([\s\S]*?)<\/summary>/);if(r&&r[1].trim()){const a=r[1].trim();return a.length>80?a.slice(0,80)+"…":a}return`(子任务回调) — ${t.trim().slice(0,60).replace(/\s+/g," ")}${t.trim().length>60?"…":""}`}return"(子任务回调)"}return m.length>80?m.slice(0,80)+"…":m}export{e as n};
|
|
2
|
+
//# sourceMappingURL=task-title-BhOcemuR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-title-BhOcemuR.js","sources":["../../src/utils/task-title.ts"],"sourcesContent":["/**\n * normalizeTaskTitle — pure helper for rendering task titles.\n *\n * Some tasks are stored with their title set to the raw <task-notification>\n * XML blob (sub-agent completion callbacks). When the blob is truncated to\n * 19 characters (just the opening tag), the title is unreadable.\n *\n * This function detects that case and falls back to `firstPrompt` — the first\n * UserPromptSubmit user_prompt for the task, supplied from the backend via the\n * `first_prompt` field in /api/tasks responses.\n *\n * No React dependency. Safe to import in unit tests.\n */\n\nconst MAX_LEN = 80\n\nexport function normalizeTaskTitle(\n title: string | null | undefined,\n firstPrompt?: string | null,\n): string {\n if (!title?.trim()) return '(无标题)'\n const trimmed = title.trim()\n\n // Detect <task-notification> XML callback blobs (including bare 19-char token)\n if (trimmed.startsWith('<task-notification>')) {\n // First, try to extract <summary> from the title itself (full XML blob)\n const matchInTitle = trimmed.match(/<summary>([\\s\\S]*?)<\\/summary>/)\n if (matchInTitle && matchInTitle[1].trim()) {\n const summary = matchInTitle[1].trim()\n return summary.length > MAX_LEN ? summary.slice(0, MAX_LEN) + '…' : summary\n }\n\n // Title has no summary (e.g. bare 19-char token) — try firstPrompt\n if (firstPrompt) {\n const matchInPrompt = firstPrompt.match(/<summary>([\\s\\S]*?)<\\/summary>/)\n if (matchInPrompt && matchInPrompt[1].trim()) {\n const summary = matchInPrompt[1].trim()\n return summary.length > MAX_LEN ? summary.slice(0, MAX_LEN) + '…' : summary\n }\n // Has firstPrompt but no <summary> tag — show head of prompt\n const head = firstPrompt.trim().slice(0, 60).replace(/\\s+/g, ' ')\n return `(子任务回调) — ${head}${firstPrompt.trim().length > 60 ? '…' : ''}`\n }\n\n return '(子任务回调)'\n }\n\n return trimmed.length > MAX_LEN ? trimmed.slice(0, MAX_LEN) + '…' : trimmed\n}\n"],"names":["normalizeTaskTitle","title","firstPrompt","trimmed","matchInTitle","summary","matchInPrompt"],"mappings":"AAgBO,SAASA,EACdC,EACAC,EACQ,CACR,GAAI,EAACD,GAAA,MAAAA,EAAO,QAAQ,MAAO,QAC3B,MAAME,EAAUF,EAAM,KAAA,EAGtB,GAAIE,EAAQ,WAAW,qBAAqB,EAAG,CAE7C,MAAMC,EAAeD,EAAQ,MAAM,gCAAgC,EACnE,GAAIC,GAAgBA,EAAa,CAAC,EAAE,OAAQ,CAC1C,MAAMC,EAAUD,EAAa,CAAC,EAAE,KAAA,EAChC,OAAOC,EAAQ,OAAS,GAAUA,EAAQ,MAAM,EAAG,EAAO,EAAI,IAAMA,CACtE,CAGA,GAAIH,EAAa,CACf,MAAMI,EAAgBJ,EAAY,MAAM,gCAAgC,EACxE,GAAII,GAAiBA,EAAc,CAAC,EAAE,OAAQ,CAC5C,MAAMD,EAAUC,EAAc,CAAC,EAAE,KAAA,EACjC,OAAOD,EAAQ,OAAS,GAAUA,EAAQ,MAAM,EAAG,EAAO,EAAI,IAAMA,CACtE,CAGA,MAAO,aADMH,EAAY,KAAA,EAAO,MAAM,EAAG,EAAE,EAAE,QAAQ,OAAQ,GAAG,CACxC,GAAGA,EAAY,OAAO,OAAS,GAAK,IAAM,EAAE,EACtE,CAEA,MAAO,SACT,CAEA,OAAOC,EAAQ,OAAS,GAAUA,EAAQ,MAAM,EAAG,EAAO,EAAI,IAAMA,CACtE"}
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>Claude Forge 管理后台</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-CBX47X8l.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/react-vendor-CSp-GLFF.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-CMMjVdZs.js">
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/react-router-
|
|
11
|
+
<link rel="modulepreload" crossorigin href="/assets/react-router-r79dBVy4.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/query-C99w429o.js">
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/syntax-highlighter-44FakypI.js">
|
|
14
|
-
<link rel="modulepreload" crossorigin href="/assets/lucide-
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
14
|
+
<link rel="modulepreload" crossorigin href="/assets/lucide-Bs_edTLa.js">
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DjIoMdoR.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<div id="root"></div>
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# H1: Storage 层 SQL 聚合改造 Spec
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
|
|
5
|
+
把 `web/routes/{rules,skill-stats,tasks}.ts` 里的"全表 SELECT + JS GROUP BY"模式下沉为 SQL 聚合,消除单进程内存放大(最大 100k 行 × 2),同时把 `tasks/:taskId` 的 5000 行二段全扫改为 PK 直查 + JOIN。纯增量改动,旧 API 不动。
|
|
6
|
+
|
|
7
|
+
## 涉及文件
|
|
8
|
+
|
|
9
|
+
- 改动 storage:`src/core/storage/routing.ts`、`src/core/storage/skills.ts`、`src/core/storage/tasks.ts`、`src/core/storage/sqlite.ts`(facade 转发)
|
|
10
|
+
- 改动 routes:`src/web/routes/rules.ts`、`src/web/routes/skill-stats.ts`、`src/web/routes/tasks.ts`
|
|
11
|
+
- 索引:`src/core/storage/schema.sql` + `src/core/storage/base.ts`(runMigrations)
|
|
12
|
+
- 测试新增:`tests/unit/storage/routing-aggregates.test.ts`、`tests/unit/storage/skills-aggregates.test.ts`、`tests/unit/storage/tasks-getById.test.ts`
|
|
13
|
+
- 测试保留契约:`tests/unit/web/routes-skill-stats.test.ts`、`tests/unit/web/routes-tasks.test.ts`
|
|
14
|
+
- 测试缺口:rules 路由无测试 → spec 标注需补 `tests/unit/web/routes-rules.test.ts`
|
|
15
|
+
|
|
16
|
+
## 现状分析
|
|
17
|
+
|
|
18
|
+
| 文件:行 | 问题 | 行为 |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `rules.ts:24,34` | `limit: 100000` ×2 | 全表 routing/skill 拉进内存后 JS reduce |
|
|
21
|
+
| `skill-stats.ts:19,44,65,113` | 5 个 `limit: 10000` | 4 个聚合 + 1 个 trend by-day |
|
|
22
|
+
| `skill-stats.ts:94` | `limit: 1000` invocations | by-skill 频次聚合 |
|
|
23
|
+
| `tasks.ts:63-64` | 拉 5000 tasks 再 `.find(id)` | 应 PK 直查 |
|
|
24
|
+
| `tasks.ts:71-75` | 拉 5000 events 后 `Set.has` 过滤 | 应 JOIN `task_events` |
|
|
25
|
+
|
|
26
|
+
## 设计方案
|
|
27
|
+
|
|
28
|
+
### 1. 新增 storage 方法
|
|
29
|
+
|
|
30
|
+
**命名约定**:聚合方法用 `aggregate` 前缀(CLAUDE.md 未明确,spec 明确为硬约束)。
|
|
31
|
+
|
|
32
|
+
#### `RoutingOperations` (`src/core/storage/routing.ts`)
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// 一次返回 rules.ts 和 skill-stats.ts 共用的 routing 汇总
|
|
36
|
+
aggregateRoutingStats(filter: { since_ts: number; project_path?: string }): {
|
|
37
|
+
total: number;
|
|
38
|
+
obeyed: number; // obeyed = 1
|
|
39
|
+
refused: number; // obeyed = 0
|
|
40
|
+
unknown: number; // obeyed IS NULL
|
|
41
|
+
by_type: Array<{ type: 'agent' | 'skill' | 'none'; count: number }>;
|
|
42
|
+
by_agent: Array<{ agent: string; count: number }>; // obeyed=1 AND routed_to_name NOT NULL
|
|
43
|
+
by_skill_routed: Array<{ skill: string; count: number }>; // routed_to_type='skill' (for frequency)
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
SQL(在事务里跑 4 个独立聚合,全部走 `idx_routing_events_ts` / `idx_routing_events_obeyed_ts` / `idx_routing_events_agent`):
|
|
48
|
+
|
|
49
|
+
```sql
|
|
50
|
+
-- total + obeyed counts
|
|
51
|
+
SELECT COUNT(*) AS total,
|
|
52
|
+
SUM(CASE WHEN obeyed=1 THEN 1 ELSE 0 END) AS obeyed,
|
|
53
|
+
SUM(CASE WHEN obeyed=0 THEN 1 ELSE 0 END) AS refused,
|
|
54
|
+
SUM(CASE WHEN obeyed IS NULL THEN 1 ELSE 0 END) AS unknown
|
|
55
|
+
FROM routing_events WHERE ts >= @since AND (@project IS NULL OR project_path = @project);
|
|
56
|
+
|
|
57
|
+
-- by_type (distribution)
|
|
58
|
+
SELECT COALESCE(routed_to_type,'none') AS type, COUNT(*) AS count
|
|
59
|
+
FROM routing_events WHERE ts >= @since [...] GROUP BY type;
|
|
60
|
+
|
|
61
|
+
-- by_agent
|
|
62
|
+
SELECT routed_to_name AS agent, COUNT(*) AS count
|
|
63
|
+
FROM routing_events
|
|
64
|
+
WHERE ts >= @since AND obeyed=1 AND routed_to_name IS NOT NULL [...]
|
|
65
|
+
GROUP BY routed_to_name ORDER BY count DESC;
|
|
66
|
+
|
|
67
|
+
-- by_skill_routed (frequency)
|
|
68
|
+
SELECT routed_to_name AS skill, COUNT(*) AS count
|
|
69
|
+
FROM routing_events
|
|
70
|
+
WHERE ts >= @since AND routed_to_type='skill' AND routed_to_name IS NOT NULL [...]
|
|
71
|
+
GROUP BY routed_to_name ORDER BY count DESC;
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// trend by day(skill-stats.ts:62)— SQLite 用 strftime
|
|
76
|
+
aggregateRoutingTrendByDay(filter: { since_ts: number }): Array<{
|
|
77
|
+
day: string; // 'YYYY-MM-DD'
|
|
78
|
+
total: number;
|
|
79
|
+
skill: number;
|
|
80
|
+
}>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
SQL:
|
|
84
|
+
|
|
85
|
+
```sql
|
|
86
|
+
SELECT strftime('%Y-%m-%d', ts/1000, 'unixepoch') AS day,
|
|
87
|
+
COUNT(*) AS total,
|
|
88
|
+
SUM(CASE WHEN routed_to_type='skill' THEN 1 ELSE 0 END) AS skill
|
|
89
|
+
FROM routing_events WHERE ts >= @since
|
|
90
|
+
GROUP BY day ORDER BY day ASC;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
调用方:`rules.ts`(hit-rate)、`skill-stats.ts`(distribution / frequency / trend / routing-stats)
|
|
94
|
+
|
|
95
|
+
#### `SkillOperations` (`src/core/storage/skills.ts`)
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
aggregateSkillInvocationsBySkill(filter: { since: number; limit?: number }): Array<{
|
|
99
|
+
skill_id: string;
|
|
100
|
+
total: number;
|
|
101
|
+
success: number; // success = 1
|
|
102
|
+
failed: number; // success = 0
|
|
103
|
+
}>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
SQL(走 `idx_skill_invocations_timestamp` + group):
|
|
107
|
+
|
|
108
|
+
```sql
|
|
109
|
+
SELECT skill_id,
|
|
110
|
+
COUNT(*) AS total,
|
|
111
|
+
SUM(CASE WHEN success=1 THEN 1 ELSE 0 END) AS success,
|
|
112
|
+
SUM(CASE WHEN success=0 THEN 1 ELSE 0 END) AS failed
|
|
113
|
+
FROM skill_invocations
|
|
114
|
+
WHERE timestamp >= @since
|
|
115
|
+
GROUP BY skill_id ORDER BY total DESC;
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
调用方:`rules.ts`(per-skill)、`skill-stats.ts:91`(invocations)
|
|
119
|
+
|
|
120
|
+
#### `TaskOperations` (`src/core/storage/tasks.ts`)
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
getTask(taskId: string): TaskRecord | null;
|
|
124
|
+
queryEventsByTaskId(taskId: string, opts?: { limit?: number }): ForgeEvent[];
|
|
125
|
+
queryInjectionsByTaskId(taskId: string): Injection[];
|
|
126
|
+
querySkillInvocationsByTaskWindow(taskId: string): SkillInvocationRow[]; // 用 task 的 start_time / end_time 直接 SQL 过滤
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
SQL:
|
|
130
|
+
|
|
131
|
+
```sql
|
|
132
|
+
-- getTask
|
|
133
|
+
SELECT t.*, s.project_path FROM tasks t
|
|
134
|
+
LEFT JOIN sessions s ON s.session_id = t.session_id
|
|
135
|
+
WHERE t.id = ?;
|
|
136
|
+
|
|
137
|
+
-- queryEventsByTaskId
|
|
138
|
+
SELECT e.* FROM events e
|
|
139
|
+
JOIN task_events te ON te.event_id = e.event_id
|
|
140
|
+
WHERE te.task_id = ?
|
|
141
|
+
ORDER BY e.timestamp ASC
|
|
142
|
+
LIMIT ?;
|
|
143
|
+
|
|
144
|
+
-- queryInjectionsByTaskId
|
|
145
|
+
SELECT i.* FROM injections i
|
|
146
|
+
JOIN task_events te ON te.event_id = i.event_id
|
|
147
|
+
WHERE te.task_id = ?
|
|
148
|
+
ORDER BY i.timestamp ASC;
|
|
149
|
+
|
|
150
|
+
-- querySkillInvocationsByTaskWindow(避免 detail 路由再做时间过滤)
|
|
151
|
+
SELECT si.* FROM skill_invocations si
|
|
152
|
+
JOIN tasks t ON t.session_id = si.session_id
|
|
153
|
+
WHERE t.id = ?
|
|
154
|
+
AND si.timestamp >= CAST(strftime('%s', t.start_time) AS INTEGER) * 1000
|
|
155
|
+
AND si.timestamp <= COALESCE(CAST(strftime('%s', t.end_time) AS INTEGER) * 1000, @now_ms)
|
|
156
|
+
ORDER BY si.timestamp ASC;
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
调用方:`tasks.ts:59` detail route
|
|
160
|
+
|
|
161
|
+
> **决策**:`querySkillInvocationsByTaskWindow` 把 `now` 通过参数传入而不是 SQL 端拿,便于测试时 mock 时间。
|
|
162
|
+
|
|
163
|
+
### 2. 索引调整
|
|
164
|
+
|
|
165
|
+
**复查现状**:
|
|
166
|
+
- `idx_routing_events_ts` ✓ — 覆盖 `since_ts`
|
|
167
|
+
- `idx_routing_events_agent (routed_to_name, obeyed)` ✓ — 覆盖 by_agent
|
|
168
|
+
- `idx_routing_events_obeyed_ts` ✓ — 覆盖 obeyed group
|
|
169
|
+
- `idx_skill_invocations_timestamp` ✓ — 覆盖 since filter
|
|
170
|
+
- `idx_skill_invocations_skill` ✓ — 覆盖 by skill_id group(不一定需要)
|
|
171
|
+
- `idx_task_events_task` ✓ — 覆盖 JOIN
|
|
172
|
+
- `tasks.id` 是 PK ✓
|
|
173
|
+
|
|
174
|
+
**新增(一处)**:
|
|
175
|
+
|
|
176
|
+
```sql
|
|
177
|
+
-- routing_events: routed_to_type 走 distribution 分组高频用
|
|
178
|
+
CREATE INDEX IF NOT EXISTS idx_routing_events_type_ts ON routing_events(routed_to_type, ts DESC);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
放置:`schema.sql` 末尾 + `base.ts` runMigrations 末段(与 Phase 1 indexes 同样的 try/catch 块)。**不要重复放两份**——H4 已记录"schema.sql 与 base.ts 索引重复"问题。本 spec 决定:**只加在 schema.sql,base.ts 的 runMigrations 也加同一条 idempotent CREATE INDEX**(已有先例),不算重复。其它索引保持原样。
|
|
182
|
+
|
|
183
|
+
### 3. Routes 改造
|
|
184
|
+
|
|
185
|
+
#### `rules.ts` (hit-rate)
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
// before: queryRoutingEvents({limit:100000}) + querySkillInvocations({limit:100000}) + JS reduce
|
|
189
|
+
// after:
|
|
190
|
+
const routing = storage.aggregateRoutingStats({ since_ts: since });
|
|
191
|
+
const skillsAgg = storage.aggregateSkillInvocationsBySkill({ since });
|
|
192
|
+
const totalPrompts = routing.total;
|
|
193
|
+
const agentDelegations = routing.obeyed;
|
|
194
|
+
const skills = skillsAgg.map(s => ({...s, rate: pct(s.total, totalPrompts)}));
|
|
195
|
+
const agents = routing.by_agent.map(a => ({...a, rate: pct(a.count, totalPrompts)}));
|
|
196
|
+
const triggered = new Set(skillsAgg.map(s => s.skill_id));
|
|
197
|
+
const neverTriggered = skillRegistry?.getAll().map(s=>s.id).filter(id=>!triggered.has(id)) ?? [];
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### `skill-stats.ts`
|
|
201
|
+
|
|
202
|
+
| Endpoint | 旧 | 新 |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| `/distribution` | `queryRoutingEvents{limit:10000}` + JS group | `aggregateRoutingStats(...).by_type` |
|
|
205
|
+
| `/frequency` | 同上 | `aggregateRoutingStats(...).by_skill_routed` |
|
|
206
|
+
| `/trend` | 同上 + by-day JS group | `aggregateRoutingTrendByDay({since_ts})` 再 JS map 成 `{day, skillRate}` |
|
|
207
|
+
| `/invocations` | `querySkillInvocations{limit:1000}` + JS group | `aggregateSkillInvocationsBySkill({since})` |
|
|
208
|
+
| `/api/routing/stats` | `queryRoutingEvents{limit:10000}` + JS filter | `aggregateRoutingStats` 中 `obeyed/total/by_agent` |
|
|
209
|
+
|
|
210
|
+
#### `tasks.ts:/:taskId`
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
// before:
|
|
214
|
+
// const allTasks = storage.queryTasks({limit:5000}); task = allTasks.find(...)
|
|
215
|
+
// const eventIds = new Set(storage.getTaskEventIds(taskId));
|
|
216
|
+
// const allEvents = storage.queryEvents({session_id, limit:5000}); filter by eventIds
|
|
217
|
+
// const allInjections = storage.queryInjections({session_id, limit:500}); filter by eventIds
|
|
218
|
+
// const allSkillInvocations = storage.querySkillInvocations({session_id, limit:200}); JS time filter
|
|
219
|
+
// after:
|
|
220
|
+
const task = storage.getTask(taskId); if (!task) return 404;
|
|
221
|
+
const taskEvents = storage.queryEventsByTaskId(taskId, { limit: 5000 });
|
|
222
|
+
const allInjections = storage.queryInjectionsByTaskId(taskId);
|
|
223
|
+
const skillInvocations = storage.querySkillInvocationsByTaskWindow(taskId);
|
|
224
|
+
// 后续 timeline / artifacts / userPrompts 逻辑不变
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## 关键决策
|
|
228
|
+
|
|
229
|
+
1. **聚合方法用 `aggregate` 前缀**(CLAUDE.md 未规定,本 spec 固化)— `aggregateRoutingStats` / `aggregateSkillInvocationsBySkill` / `aggregateRoutingTrendByDay`。理由:与 `query*` / `list*` / `get*` 区分语义,调用方一眼能看出"这是 SQL 端 GROUP BY"。
|
|
230
|
+
2. **新方法挂在各自 Operations 类**,sqlite.ts facade 仅做转发。理由:保持职责拆分,不在 facade 写 SQL。
|
|
231
|
+
3. **不合并 routing 4 个聚合到单条 SQL**。理由:SQLite 无 GROUPING SETS / CUBE,强行 UNION ALL 反而难读;分 4 条 prepared statement 各走自己的索引更快。
|
|
232
|
+
4. **trend by-day 用 `strftime('%Y-%m-%d', ts/1000, 'unixepoch')`**,已验证 better-sqlite3 完全支持。
|
|
233
|
+
5. **`queryEventsByTaskId` 保留 `limit` 参数**(默认 5000)— 防御过大 task;前端不分页,但要有上限。
|
|
234
|
+
6. **`task_events` PK 是 `(task_id, event_id)`**,但 `task_events(event_id)` 的反向 JOIN 已有 `idx_task_events_event`;正向 `idx_task_events_task` 也存在,JOIN 性能 OK。
|
|
235
|
+
|
|
236
|
+
## 测试策略
|
|
237
|
+
|
|
238
|
+
### 单元测试(必加)
|
|
239
|
+
|
|
240
|
+
- `tests/unit/storage/routing-aggregates.test.ts`
|
|
241
|
+
- 空表返回 `{total:0, obeyed:0, refused:0, unknown:0, by_type:[], ...}`
|
|
242
|
+
- 混合 `obeyed=1/0/NULL` 计数正确
|
|
243
|
+
- `since_ts` 过滤边界(等于 / 小于)
|
|
244
|
+
- `by_agent` 排除 `obeyed≠1` 和 `routed_to_name IS NULL`
|
|
245
|
+
- `aggregateRoutingTrendByDay` 跨日聚合 + 时区一致性(UTC)
|
|
246
|
+
- `tests/unit/storage/skills-aggregates.test.ts`
|
|
247
|
+
- `success=1/0` 分别计数;按 total 降序
|
|
248
|
+
- `since` 过滤
|
|
249
|
+
- `tests/unit/storage/tasks-getById.test.ts`
|
|
250
|
+
- `getTask` 命中 / null
|
|
251
|
+
- `queryEventsByTaskId` JOIN 正确性 + 排序
|
|
252
|
+
- `querySkillInvocationsByTaskWindow` 时间窗包含/排除(含 end_time IS NULL 取 now)
|
|
253
|
+
- `queryInjectionsByTaskId` event_id 关联
|
|
254
|
+
|
|
255
|
+
### 集成 / 路由测试
|
|
256
|
+
|
|
257
|
+
- `tests/unit/web/routes-skill-stats.test.ts` — 保留现有契约(响应字段不变),跑过即可
|
|
258
|
+
- `tests/unit/web/routes-tasks.test.ts` — 保留,detail 路由响应结构不变
|
|
259
|
+
- `tests/unit/web/routes-rules.test.ts` — **缺口,本任务新增**:空数据、混合数据下 totalPrompts / agentDelegations / skills / agents / neverTriggered 均符合预期
|
|
260
|
+
|
|
261
|
+
### 性能基准(可选,给 coder agent)
|
|
262
|
+
|
|
263
|
+
写一个 `scripts/bench/h1-aggregation.ts`(不入 git CI):
|
|
264
|
+
- 灌 100k routing_events + 10k skill_invocations
|
|
265
|
+
- 测 `aggregateRoutingStats` vs 旧 `queryRoutingEvents({limit:100000})` + JS reduce 的耗时
|
|
266
|
+
- 目标:新 ≤ 旧 / 10,内存峰值下降 ≥ 90%
|
|
267
|
+
|
|
268
|
+
## 风险与回滚
|
|
269
|
+
|
|
270
|
+
| 风险 | 应对 |
|
|
271
|
+
|---|---|
|
|
272
|
+
| `strftime('%Y-%m-%d', ts/1000, 'unixepoch')` 在 ts=0 / 负值时返回 NULL | 聚合前 WHERE `ts > 0` 兜底;测试覆盖 |
|
|
273
|
+
| `routed_to_type` 历史值含 NULL | `COALESCE(routed_to_type,'none')`,与旧逻辑 `e.routed_to_type \|\| 'none'` 一致 |
|
|
274
|
+
| 新索引 `idx_routing_events_type_ts` 与 H4 索引重复隐患 | 检查 `base.ts` 已存在的 5 类索引前缀,确认未重复;只在 schema.sql + base.ts 的 Phase 1 块各加一处 idempotent CREATE |
|
|
275
|
+
| `getTask` 响应字段需要包含 `project_path`(旧 detail 路由没用到,但 list 用了) | 用 LEFT JOIN sessions,缺失时返回 undefined,detail 路由对此字段无依赖 |
|
|
276
|
+
| 测试缺口:rules 路由 0 测试 → 改造时可能行为漂移 | **强制先补 routes-rules.test.ts,再改 route**(TDD 顺序) |
|
|
277
|
+
|
|
278
|
+
**回滚**:纯增量,新方法 + 新索引 + route 内部实现替换。回滚 = revert route 改动(storage 新方法保留无害)。
|
|
279
|
+
|
|
280
|
+
## 实施顺序(给 coder agent)
|
|
281
|
+
|
|
282
|
+
1. **Storage 层先**
|
|
283
|
+
- a. 新增 3 个 aggregate 方法 + 4 个 task 方法 + sqlite.ts facade 转发
|
|
284
|
+
- b. 新增索引(schema.sql + base.ts 同步)
|
|
285
|
+
- c. 跑 `npx vitest run tests/unit/storage/ --reporter=verbose`
|
|
286
|
+
2. **补 routes-rules.test.ts**(基于旧实现先建立行为基线)
|
|
287
|
+
3. **一次改一个 route,每改完跑对应测试**
|
|
288
|
+
- rules.ts → routes-rules.test.ts
|
|
289
|
+
- skill-stats.ts → routes-skill-stats.test.ts
|
|
290
|
+
- tasks.ts → routes-tasks.test.ts
|
|
291
|
+
4. **`npx tsc --noEmit` + 全量测试**
|
|
292
|
+
5. **可选基准脚本验证**
|
|
293
|
+
|
|
294
|
+
## 命名遵循(CLAUDE.md 约束 + 本 spec 扩展)
|
|
295
|
+
|
|
296
|
+
- 单条查询:`getTask(id)` ✓
|
|
297
|
+
- 列表/查询:`queryEventsByTaskId`、`queryInjectionsByTaskId`、`querySkillInvocationsByTaskWindow` ✓
|
|
298
|
+
- 聚合(**本 spec 固化**):`aggregate*` — `aggregateRoutingStats`、`aggregateRoutingTrendByDay`、`aggregateSkillInvocationsBySkill`
|
|
299
|
+
- 写入:本任务无新增
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# H2: getDatabase 越权 SQL 收敛到 Operations 层 Spec
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
|
|
5
|
+
消除 `web/routes/`、`web/analytics/`、`cli/commands/`、`daemon/handlers/` 中对 `storage.getDatabase().prepare()` 的直写,按"查询动词 + 资源"命名下沉到对应 `*Operations` 类,以 `SQLiteStorage` facade 为唯一对外入口,便于后续 schema 重构。
|
|
6
|
+
|
|
7
|
+
## 调研结果
|
|
8
|
+
|
|
9
|
+
### 越权 SQL 清单(主线程封闭性扫描后修订:27 处)
|
|
10
|
+
|
|
11
|
+
| 位置 | SQL 用途 | 表 | 应归属 |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| `daemon/handlers/stop.ts:154-158` | 按 session 统计 tool_name 调用 | events | EventOperations |
|
|
14
|
+
| `daemon/handlers/stop.ts:163-168` | 按 session 统计 Agent/Task subagent_type 调用 | events | EventOperations |
|
|
15
|
+
| `daemon/handlers/stop.ts:176-178` | 按 session 计数 skill 调用 | skill_invocations | SkillOperations |
|
|
16
|
+
| `web/routes/stats.ts:13` | 全局 events 计数 | events | EventOperations |
|
|
17
|
+
| `web/routes/stats.ts:14` | 全局 sessions 计数 | sessions | SessionOperations |
|
|
18
|
+
| `web/routes/stats.ts:17-21` | Top 15 tool 使用分布 | events | EventOperations |
|
|
19
|
+
| `web/routes/stats.ts:28-33` | 最近 7 天每日事件数 | events | EventOperations |
|
|
20
|
+
| `web/routes/stats.ts:35-40` | 最近 7 天每日会话数 | sessions | SessionOperations |
|
|
21
|
+
| `web/routes/stats.ts:50` | 全局 skill 调用计数 | skill_invocations | SkillOperations |
|
|
22
|
+
| `web/routes/trace.ts:106-109` | 按 session 聚合 hook_type 分布 | events | EventOperations |
|
|
23
|
+
| `web/routes/trace.ts:111-116` | 按 session 聚合 subagent_type 分布 | events | EventOperations |
|
|
24
|
+
| `web/routes/trace.ts:118-120` | 按 session 列出 distinct skill_id | skill_invocations | SkillOperations |
|
|
25
|
+
| **`cli/commands/trace.ts:115`** | hook_type 分布(CLI 版) | events | EventOperations |
|
|
26
|
+
| **`cli/commands/trace.ts:124`** | subagent_type 分布(CLI 版) | events | EventOperations |
|
|
27
|
+
| **`cli/commands/trace.ts:135`** | distinct skill_id(CLI 版) | skill_invocations | SkillOperations |
|
|
28
|
+
| `web/analytics/drift-detector.ts:125-128` | 时间窗内 distinct skill 数 | skill_invocations | SkillOperations |
|
|
29
|
+
| `web/analytics/drift-detector.ts:154-157` | 最近 7 天有事件的活跃日数 | events | EventOperations |
|
|
30
|
+
| `web/analytics/drift-detector.ts:185-187` | 时间窗内 session 计数 | sessions | SessionOperations |
|
|
31
|
+
| `web/analytics/drift-detector.ts:190-192` | 时间窗内 task 计数 | tasks | TaskOperations |
|
|
32
|
+
| `web/analytics/weekly-report.ts:100-109` | 周内 events overview | events | EventOperations |
|
|
33
|
+
| `web/analytics/weekly-report.ts:111-116` | 周内 distinct project_path | events | EventOperations |
|
|
34
|
+
| `web/analytics/weekly-report.ts:118-122` | 周内 task 计数 | tasks | TaskOperations |
|
|
35
|
+
| `web/analytics/weekly-report.ts:214-222` | 周内 PreToolUse top tools | events | EventOperations |
|
|
36
|
+
| `web/analytics/weekly-report.ts:228-244` | 周内 PostToolUse 失败/总数 | events | EventOperations |
|
|
37
|
+
| `web/analytics/weekly-report.ts:261-268` | 周内 file edit tool_input | events | EventOperations |
|
|
38
|
+
| `web/analytics/anti-pattern-detector.ts:83-86` | 时间窗内全字段 events | events | EventOperations |
|
|
39
|
+
| `web/analytics/anti-pattern-detector.ts:104-114` | 时间窗内 session 概览 | sessions | SessionOperations |
|
|
40
|
+
|
|
41
|
+
**关键修订**:`cli/commands/trace.ts` 有 3 段与 web/routes/trace.ts 同模式的 SQL,调用同一组聚合方法。
|
|
42
|
+
|
|
43
|
+
### Operations 类现状
|
|
44
|
+
|
|
45
|
+
| 类 | 文件 | 现有方法(节选) |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| EventOperations | `core/storage/events.ts` | writeEvent / queryEvents / countEvents / searchEvents |
|
|
48
|
+
| SessionOperations | `core/storage/sessions.ts` | upsertSession / querySessions |
|
|
49
|
+
| TaskOperations | `core/storage/tasks.ts` | writeTask / queryTasks / queryTasksFiltered |
|
|
50
|
+
| SkillOperations | `core/storage/skills.ts` | writeSkillInvocation / aggregateSkillInvocationsBySkill |
|
|
51
|
+
| RoutingOperations | `core/storage/routing.ts` | aggregateRoutingStats / aggregateRoutingTrendByDay |
|
|
52
|
+
|
|
53
|
+
H1 已固化 `aggregate*` 命名模式。
|
|
54
|
+
|
|
55
|
+
### `getDatabase()` 调用方现状
|
|
56
|
+
|
|
57
|
+
| 调用方 | 类型 | 处理 |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `web/routes/{stats,trace}.ts` | 越权 | 消除 |
|
|
60
|
+
| `web/analytics/*` | 越权 | 消除 |
|
|
61
|
+
| `cli/commands/trace.ts` | 越权 | 消除 |
|
|
62
|
+
| `daemon/handlers/stop.ts` | 越权 | 消除 |
|
|
63
|
+
| 测试 fixture / migration | 合法 | 保留 |
|
|
64
|
+
|
|
65
|
+
### 测试覆盖现状
|
|
66
|
+
|
|
67
|
+
| 模块 | 现有测试 | safety-net 评级 |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `web/routes/stats.ts` | 无 | **必补** |
|
|
70
|
+
| `web/routes/trace.ts` | 无 | **必补** |
|
|
71
|
+
| `cli/commands/trace.ts` | 无 | 可补(跑得到的话) |
|
|
72
|
+
| `web/analytics/*` | `tests/integration/web-analytics.integration.test.ts`(仅 200 + shape) | 增强字段值断言 |
|
|
73
|
+
| `daemon/handlers/stop.ts` behavior summary | 无 | **必补**(fixture + 断言摘要) |
|
|
74
|
+
| Operations 新增方法 | 无 | 每个新方法配单测 |
|
|
75
|
+
|
|
76
|
+
## 方案选择
|
|
77
|
+
|
|
78
|
+
**方案 A(推荐)**:所有越权 SQL 下沉到 `*Operations`,作为聚合/计数方法暴露给 facade,调用方改用 facade。
|
|
79
|
+
|
|
80
|
+
`getDatabase()` 处理:**保留 public** + jsdoc `@internal — Operations 子类使用。Routes/Handlers/Analytics 禁止调用,请使用 facade 方法。` 不改 protected(架构非继承)。
|
|
81
|
+
|
|
82
|
+
## 新增 Operations 方法清单
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// EventOperations(11 个)
|
|
86
|
+
countAllEvents(): number;
|
|
87
|
+
aggregateToolUsage(opts: { since?: string; limit?: number; hook_type?: 'PreToolUse' | 'PostToolUse' }): Array<{ tool_name: string; count: number }>;
|
|
88
|
+
aggregateDailyEventCounts(opts: { since: string; until?: string }): Array<{ date: string; count: number }>;
|
|
89
|
+
aggregateHookTypeBySession(session_id: string): Array<{ hook_type: string; count: number }>;
|
|
90
|
+
aggregateAgentTypeBySession(session_id: string): Array<{ agent_type: string | null; count: number }>;
|
|
91
|
+
aggregateToolUsageBySession(session_id: string): Array<{ tool_name: string; count: number }>;
|
|
92
|
+
countActiveDays(opts: { since: string; until?: string }): number;
|
|
93
|
+
aggregateOverviewByRange(opts: { since: string; until: string }): { event_count: number; session_count: number; day_count: number };
|
|
94
|
+
queryDistinctProjects(opts: { since: string; until: string }): string[];
|
|
95
|
+
aggregateToolFailureRate(opts: { since: string; until: string }): { post_total: number; failed: number };
|
|
96
|
+
queryFileEditInputs(opts: { since: string; until: string; tool_names: string[] }): Array<{ tool_input: string }>;
|
|
97
|
+
queryEventsByTimeRange(opts: { since: string; until?: string }): ForgeEvent[];
|
|
98
|
+
|
|
99
|
+
// SessionOperations(4 个)
|
|
100
|
+
countAllSessions(): number;
|
|
101
|
+
aggregateDailySessionCounts(opts: { since: string; until?: string }): Array<{ date: string; count: number }>;
|
|
102
|
+
querySessionsByTimeRange(opts: { since: string; until?: string }): SessionSummary[];
|
|
103
|
+
countSessionsByRange(opts: { since: string; until?: string }): number;
|
|
104
|
+
|
|
105
|
+
// TaskOperations(1 个)
|
|
106
|
+
countTasksByRange(opts: { since: string; until?: string }): number;
|
|
107
|
+
|
|
108
|
+
// SkillOperations(4 个)
|
|
109
|
+
countAllSkillInvocations(): number;
|
|
110
|
+
countSkillInvocationsBySession(session_id: string): number;
|
|
111
|
+
countDistinctSkillsSince(since_ms: number): number;
|
|
112
|
+
queryDistinctSkillIdsBySession(session_id: string): string[];
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**总计:~20 个新增方法**。命名 `count*` / `aggregate*` / `query*`。
|
|
116
|
+
|
|
117
|
+
## safety-net 设计
|
|
118
|
+
|
|
119
|
+
| 测试文件 | 覆盖目标 | 状态 |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `tests/integration/web-stats.integration.test.ts` | GET `/api/stats` 200 + totalEvents/totalSessions/toolUsage/dailyActivity/skillInvocations 字段 | **新增** |
|
|
122
|
+
| `tests/integration/web-trace.integration.test.ts` | GET `/api/trace/:commit` 多分支(无 project / 非 git / 无 note / 有 note)+ sessions[] shape | **新增**(用 tmp git repo 或 mock) |
|
|
123
|
+
| `src/tests/stop-handler-behavior-summary.test.ts` | StopHandler.generateBehaviorSummary 输出字符串断言 | **新增** |
|
|
124
|
+
| `tests/integration/web-analytics.integration.test.ts` | 现有 shape + 加 fixture 数据断言关键字段值 | **增强** |
|
|
125
|
+
| `src/tests/event-operations-aggregates.test.ts` | EventOperations 11 个新方法 | **新增** |
|
|
126
|
+
| `src/tests/session-operations-aggregates.test.ts` | SessionOperations 4 个 | **新增** |
|
|
127
|
+
| `src/tests/skill-operations-counts.test.ts` | SkillOperations 4 个 | **新增** |
|
|
128
|
+
|
|
129
|
+
## 改造范围
|
|
130
|
+
|
|
131
|
+
| 文件 | 改动点 | 行数估计 |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| `core/storage/events.ts` | +11 方法 | +200 |
|
|
134
|
+
| `core/storage/sessions.ts` | +4 方法 | +80 |
|
|
135
|
+
| `core/storage/tasks.ts` | +1 方法 | +20 |
|
|
136
|
+
| `core/storage/skills.ts` | +4 方法 | +60 |
|
|
137
|
+
| `core/storage/sqlite.ts` | facade 转发 | +60 |
|
|
138
|
+
| `daemon/handlers/stop.ts` | 删 db.prepare → facade | -25 / +15 |
|
|
139
|
+
| `web/routes/stats.ts` | 全文重写无 SQL | -50 / +30 |
|
|
140
|
+
| `web/routes/trace.ts` | SQL 段改 facade | -25 / +12 |
|
|
141
|
+
| `cli/commands/trace.ts` | 3 段 SQL 改 facade | -20 / +10 |
|
|
142
|
+
| `web/analytics/drift-detector.ts` | 4 段 SQL 改 facade | -25 / +15 |
|
|
143
|
+
| `web/analytics/weekly-report.ts` | 6 段 SQL 改 facade | -55 / +30 |
|
|
144
|
+
| `web/analytics/anti-pattern-detector.ts` | 2 段 SQL 改 facade | -30 / +12 |
|
|
145
|
+
| **新增测试 7 文件** | safety-net + 聚合单测 | +600 |
|
|
146
|
+
|
|
147
|
+
**总计:~13 文件改 + 7 文件新增,净 +750 / -230 ≈ 980 行**
|
|
148
|
+
|
|
149
|
+
## 风险与回滚
|
|
150
|
+
|
|
151
|
+
| 风险 | 应对 |
|
|
152
|
+
|---|---|
|
|
153
|
+
| GROUP BY/ORDER BY/NULL 处理细节不一致 | safety-net 字段值断言;逐方法 diff 旧 SQL |
|
|
154
|
+
| `LIKE '%error%'` 失败检测被新方法弱化 | weekly-report 失败率单测覆盖原 SQL 3 个 OR 条件 |
|
|
155
|
+
| `date('now', '-7 days')` 与传入 since 字符串语义差异 | 新方法统一接收 ISO 字符串,调用方负责 now() 计算 |
|
|
156
|
+
| 周报 file edit `IN ?` placeholders | `queryFileEditInputs({ tool_names })` 内部展开 |
|
|
157
|
+
| 改动量大致命合并冲突 | 分多 commit,每 commit 独立可 revert |
|
|
158
|
+
|
|
159
|
+
## 实施顺序(refactor-safe 5 phase)
|
|
160
|
+
|
|
161
|
+
### Phase 1: safety-net(commit 1)
|
|
162
|
+
1. `tests/integration/web-stats.integration.test.ts` 新增
|
|
163
|
+
2. `tests/integration/web-trace.integration.test.ts` 新增(tmp git repo)
|
|
164
|
+
3. `src/tests/stop-handler-behavior-summary.test.ts` 新增
|
|
165
|
+
4. 增强 `web-analytics.integration.test.ts`
|
|
166
|
+
5. 跑全量确认 baseline 通过
|
|
167
|
+
|
|
168
|
+
### Phase 2: Operations 新方法(commit 2)
|
|
169
|
+
1. 4 个 Operations 类加方法
|
|
170
|
+
2. 4 个单测文件加 fixture + 断言
|
|
171
|
+
3. sqlite.ts facade 转发
|
|
172
|
+
4. 不删旧 SQL,并行存在
|
|
173
|
+
|
|
174
|
+
### Phase 3: 替换调用方(每文件独立 commit,共 7 commit)
|
|
175
|
+
1. stats.ts → 跑 stats 集成测试
|
|
176
|
+
2. trace.ts (web) → 跑 trace 集成测试
|
|
177
|
+
3. trace.ts (cli) → 同样断言(如有测试)
|
|
178
|
+
4. stop.ts → 跑 stop handler 单测
|
|
179
|
+
5. drift-detector.ts → 跑 web-analytics
|
|
180
|
+
6. weekly-report.ts → 同上
|
|
181
|
+
7. anti-pattern-detector.ts → 同上
|
|
182
|
+
|
|
183
|
+
### Phase 4: 收尾(commit 3)
|
|
184
|
+
1. `getDatabase()` 加 jsdoc 警告
|
|
185
|
+
2. `npx tsc --noEmit` + `npm test` 全量
|
|
186
|
+
3. 可选:CLAUDE.md 「常见陷阱」补 1 条
|
|
187
|
+
|
|
188
|
+
## 命名遵循
|
|
189
|
+
|
|
190
|
+
- 计数 `count*` / 聚合 `aggregate*` / 列表 `query*`
|
|
191
|
+
- 遵循 H1 已固化的 `aggregateRoutingStats` 等模式
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# H3: 删除 schema.sql inline fallback Spec
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
消除 `SQLiteBase.initSchema()` 中残缺的 inline fallback,schema.sql 缺失时 fail-fast,杜绝静默降级导致的生产数据写炸。
|
|
5
|
+
|
|
6
|
+
## 涉及文件
|
|
7
|
+
- `src/core/storage/base.ts`(主改)
|
|
8
|
+
- `package.json`(build script 加 postbuild 校验)
|
|
9
|
+
- `tests/unit/storage/schema-missing.test.ts`(新增)
|
|
10
|
+
- `src/core/storage/index.ts` / `sqlite.ts` 类导出(无需改,构造签名不变)
|
|
11
|
+
|
|
12
|
+
## 现状分析
|
|
13
|
+
- inline fallback 位置:`src/core/storage/base.ts:61-84`(`initSchema` 的 else 分支,70-82 行)
|
|
14
|
+
- fallback 内容:仅建 `events` 1 张表 + 4 个旧索引(idx_events_session/project/timestamp/distilled)
|
|
15
|
+
- schema.sql 真实:8 张活表(events / sessions / injections / tasks / task_events / routing_events / token_usage / skill_invocations)+ 33 个 CREATE TABLE/INDEX 语句,含 FK ON DELETE CASCADE 与若干复合索引
|
|
16
|
+
- 缺漏表:sessions / injections / tasks / task_events / routing_events / token_usage / skill_invocations 全部缺失 — 一旦走 fallback,所有 routing/skill/task/token 路径首次 prepared statement 即报 `no such table`
|
|
17
|
+
- npm 发布拷贝:`package.json:13` 的 build script —
|
|
18
|
+
`cp src/core/storage/schema.sql dist/core/storage/schema.sql`(裸 `cp`,无失败检测,且依赖前置 `tsc` 已创建 `dist/core/storage/` 目录)
|
|
19
|
+
|
|
20
|
+
## 方案选择
|
|
21
|
+
|
|
22
|
+
| 方案 | 改动量 | 优点 | 缺点 |
|
|
23
|
+
|------|------|------|------|
|
|
24
|
+
| A 删 fallback + fail-fast | 小(~15 行) | 简单;schema 缺失立即抛错 | 单点失败:发布翻车一次即生产 daemon 起不来(但 silent corruption 更糟) |
|
|
25
|
+
| B 嵌入 schema 为 TS 常量 | 中(新增构建步骤 + 1 文件) | 彻底脱离运行时文件依赖;npm 安装零拷贝 | tsc 无 raw-loader;要么手写常量易漂移,要么加 prebuild 脚本生成 `schema-embedded.ts` |
|
|
26
|
+
| C A + postbuild 校验 | 小(~20 行 + 1 行脚本) | 删 fallback 防 silent failure;postbuild 校验防发布事故;不引入构建复杂度 | 仍依赖运行时 fs(开发期问题不大) |
|
|
27
|
+
|
|
28
|
+
**推荐:C**
|
|
29
|
+
|
|
30
|
+
理由:B 的常量化听起来最干净,但本项目用 `tsc` 直接编译,引入 codegen 脚本会增加 prebuild 链路(且 schema.sql 改动频率不高,收益不匹配复杂度)。A 解决了核心问题(不再 silent),但发布事故仍可能爆。C 用一行 `test -f` 把发布事故拦截在 `npm run build` 阶段,是性价比最高的组合。
|
|
31
|
+
|
|
32
|
+
## 设计细节(方案 C)
|
|
33
|
+
|
|
34
|
+
**改动 1:`src/core/storage/base.ts` `initSchema()` 重写**
|
|
35
|
+
- 删除 70-82 行 inline fallback
|
|
36
|
+
- 删除 `existsSync(schemaPath)` 分支判断;改为:若文件不存在或读取失败,抛 `Error(`[SQLiteStorage] schema.sql not found at ${schemaPath}. ...`)`
|
|
37
|
+
- 错误信息示例:
|
|
38
|
+
```
|
|
39
|
+
[SQLiteStorage] schema.sql not found at /path/to/dist/core/storage/schema.sql.
|
|
40
|
+
This indicates a broken build/install. Run `npm run build` or reinstall the package.
|
|
41
|
+
```
|
|
42
|
+
- 更新文件头注释:删 "或 inline fallback"
|
|
43
|
+
|
|
44
|
+
**改动 2:`package.json` build script**
|
|
45
|
+
- 在 build 命令末尾追加 `&& test -f dist/core/storage/schema.sql || (echo "[build] FATAL: dist/core/storage/schema.sql missing" && exit 1)`
|
|
46
|
+
- 拷贝命令保留原样(一次失败立即被 test -f 抓出来)
|
|
47
|
+
|
|
48
|
+
**改动 3:注释清理**
|
|
49
|
+
- `base.ts` 行 6 注释更新为"加载 schema.sql(缺失则抛错)"
|
|
50
|
+
- 文件头注释列表同步
|
|
51
|
+
|
|
52
|
+
## 测试策略
|
|
53
|
+
|
|
54
|
+
新增 `tests/unit/storage/schema-missing.test.ts`:
|
|
55
|
+
|
|
56
|
+
| case | 输入 | 预期 |
|
|
57
|
+
|------|------|------|
|
|
58
|
+
| 1 | 正常环境(schema.sql 存在)| `new SQLiteStorage(tmpDb)` 成功,`sqlite_master` 有 `sessions` 表 |
|
|
59
|
+
| 2 | 临时 rename `dist/core/storage/schema.sql` 或 mock `readFileSync` 抛错 | 构造抛 Error,message 含 `schema.sql not found`;**断言不存在 events 表**(确认未走旧 fallback) |
|
|
60
|
+
|
|
61
|
+
实现要点:
|
|
62
|
+
- case 2 不能真改 dist/(影响其他测试),用 `vi.mock('node:fs', ...)` 截 `existsSync` 返回 false
|
|
63
|
+
- 现有 `tests/unit/storage/migration-idempotent.test.ts` 不变,验证回归(schema.sql 存在路径仍 idempotent)
|
|
64
|
+
- 跑 `npx vitest run tests/unit/storage --reporter=verbose` + `npx tsc --noEmit`
|
|
65
|
+
|
|
66
|
+
## 风险与回滚
|
|
67
|
+
|
|
68
|
+
- **风险 1(开发期)**:开发者本地未跑 `npm run build`,直接 `node src/cli/index.js` 这种从 src 启动?— 实际启动路径都从 `dist/` 走(`bin` 指向 `dist/cli/index.js`),不存在 src 直跑场景;vitest 跑测试用的是源码路径下的 schema.sql(`src/core/storage/schema.sql`),同样存在,无影响
|
|
69
|
+
- **风险 2(首次升级)**:旧版本用户残留 dist 缺 schema.sql?— 升级走 `npm i`,`prebuild` 已 `rm -rf dist`,必然重 build;新 postbuild 校验拦截
|
|
70
|
+
- **回滚**:纯增量改动;revert 单 commit 即可恢复旧 fallback 行为
|
|
71
|
+
|
|
72
|
+
## 命名遵循
|
|
73
|
+
|
|
74
|
+
- 测试文件 `schema-missing.test.ts`:kebab-case ✓
|
|
75
|
+
- 不引入新函数/常量;保留 `initSchema()` 私有方法名 ✓
|
|
76
|
+
- 错误信息以 `[SQLiteStorage]` 前缀(与现有 logger 调用一致)✓
|