@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,109 @@
|
|
|
1
|
+
# H4: DB 索引/列去重 Spec
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
消除 `schema.sql` 与 `base.ts::runMigrations` 之间的索引/列重复定义,让 schema.sql 成为新库权威源,runMigrations 只承担"存量库补齐"的职责,并保持 idempotent。
|
|
5
|
+
|
|
6
|
+
## 涉及文件
|
|
7
|
+
- `src/core/storage/schema.sql`(权威 schema,新库初始化)
|
|
8
|
+
- `src/core/storage/base.ts`(migration 入口,存量库补齐)
|
|
9
|
+
|
|
10
|
+
## 重复清单
|
|
11
|
+
|
|
12
|
+
| 名称 | schema.sql | base.ts | 类型 | 备注 |
|
|
13
|
+
|------|------------|---------|------|------|
|
|
14
|
+
| `first_prompt` | L55 | L99 | column | schema 已含,migration 仍 ALTER |
|
|
15
|
+
| `skill_confidence` | L139 | L101 | column | 同上 |
|
|
16
|
+
| `skill_source` | L140 | L102 | column | 同上 |
|
|
17
|
+
| `workflow` | L178 | L104 | column | 同上 |
|
|
18
|
+
| `phase` | L179 | L105 | column | 同上 |
|
|
19
|
+
| `feature_slug` | L180 | L106 | column | 同上 |
|
|
20
|
+
| `artifact_path` | L181 | L107 | column | 同上 |
|
|
21
|
+
| `idx_sessions_start_time` | L66 | L119 | index | 双写 |
|
|
22
|
+
| `idx_events_session_ts` | L198 | L127 | index | 双写 |
|
|
23
|
+
| `idx_routing_events_session_ts` | L199 | L128 | index | 双写 |
|
|
24
|
+
| `idx_skill_invocations_session_ts` | L200 | L129 | index | 双写 |
|
|
25
|
+
| `idx_routing_events_obeyed_ts` | L205 | L138 | index | 双写 |
|
|
26
|
+
| `idx_events_session_hook` | L211 | L139 | index | 双写 |
|
|
27
|
+
| `idx_injections_session_handler` | L214 | L140 | index | 双写 |
|
|
28
|
+
| `idx_routing_events_type_ts` | L217 | L141 | index | 双写(H1 新增)|
|
|
29
|
+
| `idx_skill_invocations_workflow` | 缺 | L111 | index | **仅 migration**,应补到 schema |
|
|
30
|
+
| `idx_skill_invocations_feature` | 缺 | L112 | index | 同上 |
|
|
31
|
+
|
|
32
|
+
## 设计方案
|
|
33
|
+
|
|
34
|
+
### 决策:方案 B(保留 idempotent 双写 + 显式 has 判断)
|
|
35
|
+
|
|
36
|
+
**理由**:
|
|
37
|
+
- 简单删 base.ts 的 `CREATE INDEX IF NOT EXISTS` 会导致存量库(旧版未跑过 H1 migration 的用户)永远缺索引;migration 必须保留补齐能力。
|
|
38
|
+
- 不能依赖"新装用户走 schema.sql、老用户走 migration"——同一份 base.ts 两端都跑。
|
|
39
|
+
- 索引层面 `CREATE INDEX IF NOT EXISTS` 本身已 idempotent,"双写"代价仅是一次 PRAGMA 查表,可接受。
|
|
40
|
+
- 列层面 ALTER TABLE 无 IF NOT EXISTS,必须靠 `hasColumn`(已存在)守护。
|
|
41
|
+
|
|
42
|
+
### 选定方案的详细做法
|
|
43
|
+
|
|
44
|
+
1. **schema.sql** 补两条缺失的索引:
|
|
45
|
+
- `idx_skill_invocations_workflow ON skill_invocations(workflow, phase)`
|
|
46
|
+
- `idx_skill_invocations_feature ON skill_invocations(feature_slug)`
|
|
47
|
+
这样新库一次成型,migration 块只是"防御性补齐"。
|
|
48
|
+
|
|
49
|
+
2. **base.ts::runMigrations** 全部改造为 has 判断守护:
|
|
50
|
+
- 列:维持 `addColumnIfMissing`(已有 hasColumn)。
|
|
51
|
+
- 索引:新增 `hasIndex(name)` 工具;每个 migration 索引创建前先判断 `if (!this.hasIndex(name)) exec(...)`,跳过则记一行 debug 日志。
|
|
52
|
+
- 目的不是性能(CREATE INDEX IF NOT EXISTS 已便宜),而是让"哪些索引由 migration 补齐"显式可读、便于日后裁剪。
|
|
53
|
+
|
|
54
|
+
3. **删除 inline fallback**(base.ts L57-72)—— 如果 schema.sql 缺失,应直接抛错而不是用残缺 schema 启动;保留只会埋坑。可在 spec 里建议但放在 H4 之外。**本次 H4 不动它**,仅注释 TODO。
|
|
55
|
+
|
|
56
|
+
### 新增工具函数(base.ts)
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
protected hasIndex(name: string): boolean {
|
|
60
|
+
try {
|
|
61
|
+
const row = this.db
|
|
62
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type='index' AND name = ?`)
|
|
63
|
+
.get(name);
|
|
64
|
+
return !!row;
|
|
65
|
+
} catch { return false; }
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
注:`pragma_index_list(table)` 需要知道表名,而 `sqlite_master` 直接按索引名查更简单。
|
|
70
|
+
|
|
71
|
+
## 测试策略
|
|
72
|
+
|
|
73
|
+
新增 `tests/unit/storage/migration-idempotent.test.ts`:
|
|
74
|
+
|
|
75
|
+
1. **baseline(旧库补齐)**:
|
|
76
|
+
- 建临时 db,先 exec 一份"H1 之前的旧 schema"(手写删掉 `idx_routing_events_type_ts` 等)+ 缺 `first_prompt` 列。
|
|
77
|
+
- 用 `new SQLiteStorage(path)` 启动,断言所有重复清单中的索引/列都已存在(`sqlite_master` + `PRAGMA table_info`)。
|
|
78
|
+
|
|
79
|
+
2. **新库无害**:
|
|
80
|
+
- 临时 db 走完整 `schema.sql` 初始化。
|
|
81
|
+
- 再实例化一次 SQLiteStorage,断言 migration 不抛错、不重复创建(spy `db.exec` 调用次数或检查 logger.warn 无输出)。
|
|
82
|
+
|
|
83
|
+
3. **idempotent(多次启动)**:
|
|
84
|
+
- 同一 db 连续 `new SQLiteStorage()` 3 次。
|
|
85
|
+
- 断言索引数量、列数量两次之间相等;断言 logger 无 error/warn。
|
|
86
|
+
|
|
87
|
+
4. **现有测试回归**:跑 `npx vitest run src/tests/storage*.test.ts` 与 `tests/unit/storage/`。
|
|
88
|
+
|
|
89
|
+
## 实施顺序
|
|
90
|
+
|
|
91
|
+
1. schema.sql 补 `idx_skill_invocations_workflow` / `idx_skill_invocations_feature`
|
|
92
|
+
2. base.ts 加 `hasIndex`
|
|
93
|
+
3. base.ts::runMigrations 所有 `CREATE INDEX IF NOT EXISTS` 用 `if (!this.hasIndex(...))` 守护
|
|
94
|
+
4. 新增 `tests/unit/storage/migration-idempotent.test.ts`
|
|
95
|
+
5. 跑 `npx vitest run tests/unit/storage` + `npx tsc --noEmit`
|
|
96
|
+
6. 跑全量 `npm test`
|
|
97
|
+
|
|
98
|
+
## 风险与回滚
|
|
99
|
+
|
|
100
|
+
- **风险**:存量数据库(线上用户)必须能补齐 → baseline 测试是硬门槛,CI 必跑。
|
|
101
|
+
- **风险**:`hasIndex` 名字查询不区分表(同名索引理论上不会跨表,SQLite 索引名全局唯一)—— 实际安全。
|
|
102
|
+
- **回滚**:所有改动局限在 schema.sql + base.ts + 1 个新测试,`git revert` 单 commit 即可。
|
|
103
|
+
- **不动 inline fallback**:H4 范围之外,避免一次改太多。
|
|
104
|
+
|
|
105
|
+
## 命名遵循
|
|
106
|
+
|
|
107
|
+
- `hasIndex` / `hasColumn`:has 前缀 → 符合 CLAUDE.md "布尔值用 is/has/should 前缀"。
|
|
108
|
+
- 测试文件名 `migration-idempotent.test.ts`:kebab-case + `.test.ts` → 符合命名规范。
|
|
109
|
+
- 工具函数 `hasIndex` 与已有 `hasColumn` 并列,对称命名。
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# H6: daemon/services analytics 类迁移 Spec
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
将 3 个纯查询/聚合类从 `src/daemon/services/` 迁出,纠正位置错配,使依赖图反映真实调用方向(仅 web 层使用)。
|
|
5
|
+
|
|
6
|
+
## 调研结果
|
|
7
|
+
|
|
8
|
+
### 调用链
|
|
9
|
+
| 类 | 文件 | daemon 调用 | web 调用 | cli 调用 |
|
|
10
|
+
|---|---|---|---|---|
|
|
11
|
+
| DriftDetector | `src/daemon/services/drift-detector.ts` | 0 | `web/routes/drift.ts:3,21` | 0 |
|
|
12
|
+
| WeeklyReportGenerator | `src/daemon/services/weekly-report.ts`(注意:非 `weekly-report-generator.ts`) | 0 | `web/routes/reports.ts:10,20` | 0 |
|
|
13
|
+
| AntiPatternDetector | `src/daemon/services/anti-pattern-detector.ts` | 0 | `web/routes/insights.ts:4,7,25` | 0 |
|
|
14
|
+
|
|
15
|
+
`src/daemon/{index,handlers,pipeline}` 全无引用。同目录的 `task-segmenter.ts` 是真 daemon 服务(`daemon/index.ts:28,105` + `handlers/stop.ts:14,27`),**留在原地**。
|
|
16
|
+
|
|
17
|
+
### 依赖关系
|
|
18
|
+
3 个文件 imports 仅有:
|
|
19
|
+
- `core/storage/sqlite` (SQLiteStorage 类型)
|
|
20
|
+
- `core/storage/sessions` (SessionSummary 类型,仅 anti-pattern)
|
|
21
|
+
- `core/types` (ForgeEvent,仅 anti-pattern)
|
|
22
|
+
- `node:crypto`(标准库)
|
|
23
|
+
|
|
24
|
+
**零 daemon 内部依赖** → 无反向依赖风险。注意 `weekly-report.ts:20` 构造时还接收 `skillRegistry`(来自 `web/routes/reports.ts` 的 ctx),但类型来源属 core 层,安全。
|
|
25
|
+
|
|
26
|
+
### 现有测试
|
|
27
|
+
- 单元测试:**无**(3 个文件 + 3 个调用方 routes 均无 `.test.ts`)
|
|
28
|
+
- 集成测试覆盖路由:**无**(`tests/` 下无 drift/report/insight/anti-pattern 测试)
|
|
29
|
+
- 现有 web 路由测试:sessions/stats/skill-stats/events/auth/tasks/rules(均不覆盖本次迁移路径)
|
|
30
|
+
|
|
31
|
+
## 目标目录决策
|
|
32
|
+
|
|
33
|
+
**推荐:`src/web/analytics/`**
|
|
34
|
+
|
|
35
|
+
理由:
|
|
36
|
+
1. 实际调用方 100% 是 `web/routes/`,符合"用在哪放在哪"
|
|
37
|
+
2. 当前无 cli/daemon 消费,提升到 core 是过度设计;未来若 cli 需要再上提成本低
|
|
38
|
+
3. 与同层模块 `web/routes/`、`web/static/` 平行,依赖方向天然向下(web → core)
|
|
39
|
+
|
|
40
|
+
## 改造范围
|
|
41
|
+
|
|
42
|
+
| 操作 | 来源 → 目标 / 修改点 |
|
|
43
|
+
|---|---|
|
|
44
|
+
| git mv | `src/daemon/services/drift-detector.ts` → `src/web/analytics/drift-detector.ts` |
|
|
45
|
+
| git mv | `src/daemon/services/weekly-report.ts` → `src/web/analytics/weekly-report.ts` |
|
|
46
|
+
| git mv | `src/daemon/services/anti-pattern-detector.ts` → `src/web/analytics/anti-pattern-detector.ts` |
|
|
47
|
+
| 改 import | `src/web/routes/drift.ts:3` → `'../analytics/drift-detector.js'` |
|
|
48
|
+
| 改 import | `src/web/routes/reports.ts:10` → `'../analytics/weekly-report.js'` |
|
|
49
|
+
| 改 import | `src/web/routes/insights.ts:7` → `'../analytics/anti-pattern-detector.js'` |
|
|
50
|
+
| 清理注释 | `weekly-report.ts:294`、`anti-pattern-detector.ts:80` 引用"daemon/services 既定做法"的文字 |
|
|
51
|
+
| barrel | **不建** `index.ts`(仅 3 个文件、3 个调用方,barrel 收益低) |
|
|
52
|
+
| 保留 | `src/daemon/services/task-segmenter.ts` 原地不动 |
|
|
53
|
+
|
|
54
|
+
## 测试策略
|
|
55
|
+
|
|
56
|
+
- **safety-net(必做)**:迁移前补一个最小集成测试 baseline
|
|
57
|
+
- `tests/integration/web-analytics.integration.test.ts`:分别 GET `/api/drift`、`/api/reports/weekly`、`/api/insights`,断言 200 + 关键字段存在(不校验业务正确性,只校验 wiring 没断)
|
|
58
|
+
- **回归**:`npx tsc --noEmit` + `npm test` 全量
|
|
59
|
+
- **手测**:`./scripts/dev-daemon.sh restart` 后 `curl` 3 个 endpoint
|
|
60
|
+
|
|
61
|
+
## 风险与回滚
|
|
62
|
+
|
|
63
|
+
- 风险 1:dist/ 残留旧路径 → `npm run build` 前清理 `dist/daemon/services/{drift,weekly,anti}*`
|
|
64
|
+
- 风险 2:第三方/外部脚本 import 旧路径 → 已 grep 确认仅 src/ 内部使用,无外部入口
|
|
65
|
+
- 风险 3:`weekly-report.ts:294` 注释提到 anti-pattern-detector 协作历史,迁移后描述失真(仅文档影响)
|
|
66
|
+
- 回滚:`git mv` 保留 history,单 commit 完成;失败 `git revert <hash>` 即可
|
|
67
|
+
|
|
68
|
+
## 实施顺序
|
|
69
|
+
|
|
70
|
+
1. 补 safety-net 集成测试(独立 commit,验证当前 main 通过)
|
|
71
|
+
2. `mkdir -p src/web/analytics`
|
|
72
|
+
3. `git mv` 3 个文件(一个 commit)
|
|
73
|
+
4. 改 3 处 import + 清理 2 处误导注释(同 commit 或下一 commit)
|
|
74
|
+
5. `npx tsc --noEmit` → `npm test` → daemon 重启 + curl 三 endpoint
|
|
75
|
+
6. 提交 PR / push
|
|
76
|
+
|
|
77
|
+
## 命名遵循
|
|
78
|
+
|
|
79
|
+
- 文件名 kebab-case ✓(保持原名)
|
|
80
|
+
- 类名 PascalCase ✓(无改动)
|
|
81
|
+
- 目录 `web/analytics/` 全小写 ✓
|
|
82
|
+
- 注意:源文件叫 `weekly-report.ts` 而非任务描述里写的 `weekly-report-generator.ts`,spec 以实际文件名为准
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# L1: SWARM_PROTOCOL 字面量抽到 .md 文件 Spec
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
|
|
5
|
+
把 `src/claudemd/claudemd-generator.ts` 中 223 行的 `SWARM_PROTOCOL` 字符串字面量抽到 `src/claudemd/templates/swarm-protocol.md`,build 时 cp 到 dist/,加载方式对齐 H3 `schema.sql` 的 fail-fast 模式。
|
|
6
|
+
|
|
7
|
+
## 调研结果
|
|
8
|
+
|
|
9
|
+
### 当前位置
|
|
10
|
+
|
|
11
|
+
- `src/claudemd/claudemd-generator.ts:555-777`(`const SWARM_PROTOCOL = `...`;`)
|
|
12
|
+
- 内容:223 行 markdown,含转义反引号
|
|
13
|
+
- 唯一使用点:第 59 行 `const full = SWARM_PROTOCOL + '\n\n' + projectContent;`(`generate()` 方法内)
|
|
14
|
+
- 上方有两段重复注释(547-553),可顺手清理
|
|
15
|
+
|
|
16
|
+
### 加载先例
|
|
17
|
+
|
|
18
|
+
1. **`src/core/storage/base.ts:61-73`(H3 schema.sql)**:`fileURLToPath(import.meta.url)` + `dirname` 解析同级目录,`existsSync` 校验,缺失抛 `[SQLiteStorage] schema.sql not found at ...`。ESM 兼容,dev/npm 一致。
|
|
19
|
+
2. **`src/skills/registry.ts:46-49` + `src/skills/official-skills.ts:52-55`(skills/official)**:同样 `fileURLToPath(import.meta.url)`。
|
|
20
|
+
|
|
21
|
+
### 现有测试
|
|
22
|
+
|
|
23
|
+
- `tests/integration/` 与 `src/tests/` 均无 `claudemd-generator` 测试 → **零覆盖**,必须先建 safety-net。
|
|
24
|
+
|
|
25
|
+
## 设计方案
|
|
26
|
+
|
|
27
|
+
### 文件位置
|
|
28
|
+
|
|
29
|
+
- 新建 `src/claudemd/templates/swarm-protocol.md`(kebab-case,内容为现 555-777 行的原文,去掉 JS 反引号转义)
|
|
30
|
+
|
|
31
|
+
### claudemd-generator.ts 改造
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
- 删除 const SWARM_PROTOCOL = `...`;(555-777)
|
|
35
|
+
- 删除上方 547-553 重复注释块
|
|
36
|
+
- 新增模块级函数 getSwarmProtocol(): string
|
|
37
|
+
· const thisDir = dirname(fileURLToPath(import.meta.url))
|
|
38
|
+
· const p = join(thisDir, 'templates', 'swarm-protocol.md')
|
|
39
|
+
· 若 !existsSync(p) 抛 `[ClaudeMdGenerator] swarm-protocol.md not found at ${p}. Run npm run build or reinstall.`
|
|
40
|
+
· readFileSync(p, 'utf-8')
|
|
41
|
+
- 一级缓存:模块级 let cached: string | null = null(避免每次 generate 都读盘)
|
|
42
|
+
- 第 59 行:const full = getSwarmProtocol() + '\n\n' + projectContent;
|
|
43
|
+
- 顶部 import 加 fileURLToPath(node:url)、dirname(已从 node:path 导入需补 dirname)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
命名 `get` 前缀(获取单一资源)。
|
|
47
|
+
|
|
48
|
+
### package.json build 改造
|
|
49
|
+
|
|
50
|
+
在现有 `build` 命令中追加:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
mkdir -p dist/claudemd/templates && cp src/claudemd/templates/*.md dist/claudemd/templates/
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
末尾 fail-fast 校验追加:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
&& (test -f dist/claudemd/templates/swarm-protocol.md || (echo '[build] FATAL: dist/claudemd/templates/swarm-protocol.md missing' && exit 1))
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
与现有 `dist/core/storage/schema.sql` 校验风格保持一致。
|
|
63
|
+
|
|
64
|
+
## 测试策略
|
|
65
|
+
|
|
66
|
+
### safety-net(必做,先行)
|
|
67
|
+
|
|
68
|
+
新增 `tests/integration/claudemd-generator.test.ts`:
|
|
69
|
+
|
|
70
|
+
1. **Baseline 测试(迁移前跑通)**:
|
|
71
|
+
- mock `ClaudeProvider.complete` 抛错(走 fallback 路径),调 `generator.generate(tmpDir)`
|
|
72
|
+
- 断言输出包含 SWARM_PROTOCOL 关键字段:`# claude-forge 工作区规范`、`## 自检`、`Two-Phase Workflow`、`harness-hotfix`、`refactor-safe`、`hybrid-feature-with-safety`
|
|
73
|
+
- 断言总行数 ≥ 200
|
|
74
|
+
2. **快照锚点**:保存 SWARM 段的 SHA256 或长度,迁移后断言不变
|
|
75
|
+
|
|
76
|
+
### 回归
|
|
77
|
+
|
|
78
|
+
- 迁移完成后重跑 safety-net,断言行为完全一致(同一 hash / 同一行数 / 同一关键字)
|
|
79
|
+
- `npx tsc --noEmit`
|
|
80
|
+
- `npm test`
|
|
81
|
+
- 手测:`npm run build` 后 `ls dist/claudemd/templates/swarm-protocol.md`
|
|
82
|
+
|
|
83
|
+
## 风险与回滚
|
|
84
|
+
|
|
85
|
+
| 风险 | 缓解 |
|
|
86
|
+
|---|---|
|
|
87
|
+
| dev 跑 `tsx src/` 时 dist 不存在 | `fileURLToPath(import.meta.url)` 解析到 `src/claudemd/`,同级 `templates/` 在 src 下存在,dev 路径天然兼容 |
|
|
88
|
+
| npm 安装包 cp 失败 | build 末尾 `test -f` 校验,CI 失败而非运行时炸 |
|
|
89
|
+
| .md 内反引号转义遗漏 | safety-net 哈希比对会发现 |
|
|
90
|
+
| 模块级缓存导致测试串扰 | 暴露 `__resetCache()` 仅 test 用,或不缓存(223 行读盘成本可忽略) |
|
|
91
|
+
|
|
92
|
+
**回滚**:单 commit revert 即可(generator.ts + package.json + 新 .md 文件)。
|
|
93
|
+
|
|
94
|
+
## 实施顺序
|
|
95
|
+
|
|
96
|
+
1. 补 `tests/integration/claudemd-generator.test.ts`(safety-net,**当前字面量实现下跑绿**)
|
|
97
|
+
2. 创建 `src/claudemd/templates/swarm-protocol.md`(复制 555-777 原文,去 JS 转义)
|
|
98
|
+
3. 改 `claudemd-generator.ts`:加 `getSwarmProtocol()`,删字面量与重复注释
|
|
99
|
+
4. 改 `package.json` build:cp + fail-fast 校验
|
|
100
|
+
5. 跑 `npm run build && npm test && npx tsc --noEmit`,对比 safety-net hash
|
|
101
|
+
|
|
102
|
+
## 命名遵循
|
|
103
|
+
|
|
104
|
+
- 文件 kebab-case:`swarm-protocol.md`、`claudemd-generator.test.ts`
|
|
105
|
+
- 加载函数 `get` 前缀:`getSwarmProtocol()`
|
|
106
|
+
- 错误前缀 `[ClaudeMdGenerator]`,与 `[SQLiteStorage]` 风格一致
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# M10: ~/.claude-forge 子路径硬编码收敛 Spec
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
把 `~/.claude-forge/<子路径>` 的硬编码(共 7 类、20+ 处)统一收敛到 `src/core/constants.ts` 的 `FORGE_PATHS`,消除 `path.join(homedir(), '.claude-forge', ...)` 重复并降低未来路径变更的成本。
|
|
5
|
+
|
|
6
|
+
## 调研结果
|
|
7
|
+
|
|
8
|
+
### 子路径分组(仅 `~/.claude-forge/` 下的真实文件路径)
|
|
9
|
+
|
|
10
|
+
| 子路径 | 引用文件:行 | 总次数 | 现有 FORGE_PATHS |
|
|
11
|
+
|---|---|---|---|
|
|
12
|
+
| `daemon.token` | lifecycle.ts:9; cli/mcp.ts:45; auth-middleware.ts:18 | 3 | 否 |
|
|
13
|
+
| `daemon.pid` | lifecycle.ts:10; cli/daemon.ts:45; cli/status.ts:8; cli/mcp.ts:86 | 4 | 否 |
|
|
14
|
+
| `daemon.sock` | lifecycle.ts:52 | 1(外加 error-handler.ts:239 的"forge.sock"字面量提示文本,**待统一**) | 否 |
|
|
15
|
+
| `daemon.log` / `daemon-stdout.log` / `daemon-stderr.log` | cli/daemon.ts:47,48,115 | 3 | 否 |
|
|
16
|
+
| `hooks/` | cli/init/hook-manager.ts:8 | 1 | 否 |
|
|
17
|
+
| `queue/` + `queue/dead/` | core/queue/index.ts:26-28 | 2 | 否 |
|
|
18
|
+
| `routing.yaml` | web/routes/types.ts:37 | 1 | 否 |
|
|
19
|
+
| `backups/skills` | web/routes/skills.ts:208,235,279,321; web/routes/types.ts:32 | 5 | 否 |
|
|
20
|
+
| `backups/routing` | web/routes/types.ts:38 | 1 | 否 |
|
|
21
|
+
| `data.db` (字面量) | cli/stats.ts:21 | 1 | **已有但未用** → 改为 `FORGE_PATHS.database()` |
|
|
22
|
+
|
|
23
|
+
### 不属于 FORGE_PATHS 的引用(保留硬编码)
|
|
24
|
+
|
|
25
|
+
- `process.cwd() + '.claude-forge/executions/by-route'`(cli/executions.ts:16,53,85)— **项目级目录**,不是 `~/.claude-forge`
|
|
26
|
+
- `project_path + '.claude-forge/history'`(daemon/handlers/history-exporter.ts:24)— 同上
|
|
27
|
+
- `templates/template-manager.ts:88`(在项目 `projectPath` 下创建 `.claude-forge`)— 项目级
|
|
28
|
+
- `cli/init/hook-manager.ts:34`(substring 匹配字符串,非路径拼接)
|
|
29
|
+
- `error-handler.ts` 内所有 `~/.claude-forge/...` 是**面向用户的提示文本**,建议保留字符串字面量(用户阅读),但 `forge.sock` 与代码中 `daemon.sock` 不一致 → 顺手修一下
|
|
30
|
+
- `claudemd-generator.ts:751` 与 `:211` 是模板内嵌字符串
|
|
31
|
+
|
|
32
|
+
## FORGE_PATHS 扩展设计
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
export const FORGE_PATHS = {
|
|
36
|
+
home: () => FORGE_HOME,
|
|
37
|
+
config: () => join(FORGE_HOME, 'config.yaml'),
|
|
38
|
+
database: () => join(FORGE_HOME, 'data.db'),
|
|
39
|
+
logs: () => join(FORGE_HOME, 'logs'),
|
|
40
|
+
|
|
41
|
+
// ── Daemon runtime files ────────────────────────────────
|
|
42
|
+
daemonSocket: () => join(FORGE_HOME, 'daemon.sock'),
|
|
43
|
+
daemonPid: () => join(FORGE_HOME, 'daemon.pid'),
|
|
44
|
+
daemonToken: () => join(FORGE_HOME, 'daemon.token'),
|
|
45
|
+
daemonLog: () => join(FORGE_HOME, 'daemon.log'),
|
|
46
|
+
daemonStdout: () => join(FORGE_HOME, 'daemon-stdout.log'),
|
|
47
|
+
daemonStderr: () => join(FORGE_HOME, 'daemon-stderr.log'),
|
|
48
|
+
|
|
49
|
+
// ── Subdirs ─────────────────────────────────────────────
|
|
50
|
+
hooks: () => join(FORGE_HOME, 'hooks'),
|
|
51
|
+
queue: () => join(FORGE_HOME, 'queue'),
|
|
52
|
+
queueDead: () => join(FORGE_HOME, 'queue', 'dead'),
|
|
53
|
+
|
|
54
|
+
// ── Patchable targets ───────────────────────────────────
|
|
55
|
+
routingYaml: () => join(FORGE_HOME, 'routing.yaml'),
|
|
56
|
+
backups: (kind: 'skills' | 'routing') => join(FORGE_HOME, 'backups', kind),
|
|
57
|
+
} as const;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 关键决策
|
|
61
|
+
|
|
62
|
+
1. **`backups(kind)` 参数化 vs 分开**:选**参数化**。理由:`backups` 的两个子目录有共同语义(patch 系统的备份根),且 `resolvePatchTarget` 内已经按 `targetType` 分支。`kind` 用字面量联合类型,越界编译期就报错。
|
|
63
|
+
2. **`daemonLog`/`daemonStdout`/`daemonStderr` 三个独立方法 vs 一个 `daemonLog(channel)`**:选**独立方法**。理由:三者在调用点完全分离(stdout/stderr 仅用于 launchd 重定向,daemon.log 是 logger 输出),不存在动态选择场景。
|
|
64
|
+
3. **动态参数(sessionId 等)不纳入**:`history-exporter` 是 `project_path` 拼接,**不属于** `~/.claude-forge`,本次范围外。
|
|
65
|
+
4. **`error-handler.ts` 的字符串字面量**:保留 `~/.claude-forge/...` 字符串(面向用户阅读),但把 `forge.sock` 改成 `daemon.sock` 与代码一致(顺手修,1 行)。
|
|
66
|
+
|
|
67
|
+
## 改造范围(按文件)
|
|
68
|
+
|
|
69
|
+
| 文件 | 改动点 | 影响行数 |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `src/core/constants.ts` | 扩展 FORGE_PATHS(11 个新方法)| +12 |
|
|
72
|
+
| `src/daemon/lifecycle.ts` | TOKEN_FILE/PID_FILE 改 FORGE_PATHS;getSocketPath 复用 daemonSocket | ~5 |
|
|
73
|
+
| `src/cli/commands/daemon.ts` | PID_FILE/STDOUT_LOG/STDERR_LOG + line 115 logFile | 4 |
|
|
74
|
+
| `src/cli/commands/status.ts` | PID_FILE(注意:与 lifecycle.ts 重复,可考虑后续从 lifecycle 复用,但本次保持就地替换)| 1 |
|
|
75
|
+
| `src/cli/commands/mcp.ts` | tokenPath:45, pid:86 | 2 |
|
|
76
|
+
| `src/cli/commands/stats.ts` | dbPath:21 改 `FORGE_PATHS.database()` | 1 |
|
|
77
|
+
| `src/cli/init/hook-manager.ts` | HOOKS_DIR 改 `FORGE_PATHS.hooks()` | 1 |
|
|
78
|
+
| `src/core/queue/index.ts` | FORGE_DIR/QUEUE_DIR/DEAD_DIR | 3 |
|
|
79
|
+
| `src/web/auth-middleware.ts` | TOKEN_FILE | 1 |
|
|
80
|
+
| `src/web/routes/types.ts` | resolvePatchTarget 4 处路径 | 4 |
|
|
81
|
+
| `src/web/routes/skills.ts` | backupDir × 4 处 | 4 |
|
|
82
|
+
| `src/core/utils/error-handler.ts` | `forge.sock` → `daemon.sock`(一致性修复)| 1 |
|
|
83
|
+
|
|
84
|
+
**合计:约 39 处替换、12 文件**。无字符串模板 `${homedir()}/.claude-forge/...` 形式(已用 grep 验证)。所有调用点都是 `path.join(homedir(), '.claude-forge', ...)`,机械替换安全。
|
|
85
|
+
|
|
86
|
+
## 测试策略
|
|
87
|
+
|
|
88
|
+
1. **新增** `tests/unit/core/forge-paths.test.ts`:
|
|
89
|
+
- 每个方法返回值以 `FORGE_HOME` 为前缀
|
|
90
|
+
- `backups('skills')` / `backups('routing')` 返回正确子路径
|
|
91
|
+
- 类型层面(编译期)拒绝 `backups('invalid')`
|
|
92
|
+
2. **回归**:
|
|
93
|
+
- `tests/unit/core/queue/*`(queue 模块已有测试)
|
|
94
|
+
- 单元测试全量 `npx vitest run`
|
|
95
|
+
- `npx tsc --noEmit`
|
|
96
|
+
3. **手测**:`./scripts/dev-daemon.sh restart` 验证 pid/token/socket 文件生成
|
|
97
|
+
|
|
98
|
+
## 风险与回滚
|
|
99
|
+
|
|
100
|
+
- **路径值不变**:FORGE_PATHS 只是函数封装,运行时返回值与原硬编码完全一致 → 已运行的 daemon、已存在的 token/pid/db 文件不受影响
|
|
101
|
+
- **回滚**:单次 PR、git revert 即可
|
|
102
|
+
- **测试覆盖**:daemon/handlers 部分零测试(CLAUDE.md 提示),本次不修改 handler 逻辑,仅替换路径常量,风险面小;但**因为涉及 `src/daemon/lifecycle.ts`(无单元测试)和 `src/web/routes/`(中等覆盖)**,按 CLAUDE.md「工作流升级判定」表,建议走 `refactor-safe`(safety-net 阶段先补 `forge-paths.test.ts` + 跑现有 queue/storage 测试作为安全网)
|
|
103
|
+
- **launchd plist**:未涉及 plist 模板路径(plist 是绝对路径生成时拼接,不在 FORGE_PATHS 范围)
|
|
104
|
+
|
|
105
|
+
## 实施顺序(给 coder agent)
|
|
106
|
+
|
|
107
|
+
1. **safety-net**:新增 `tests/unit/core/forge-paths.test.ts`(先写好测试,让其通过当前 FORGE_PATHS);跑一遍 `npx vitest run tests/unit/core` 与 `npx tsc --noEmit` 作为基线
|
|
108
|
+
2. **扩展 FORGE_PATHS**:编辑 `src/core/constants.ts` 添加 11 个方法;补全 forge-paths.test.ts 对新方法的断言
|
|
109
|
+
3. **分组替换**(每组完成后跑 `npx tsc --noEmit`):
|
|
110
|
+
- Group A:daemon 运行时文件(lifecycle / daemon / status / mcp / auth-middleware)
|
|
111
|
+
- Group B:queue(core/queue/index.ts)
|
|
112
|
+
- Group C:hooks(hook-manager.ts)
|
|
113
|
+
- Group D:web patch(types.ts + skills.ts × 4)
|
|
114
|
+
- Group E:stats.ts + error-handler.ts 文案修复
|
|
115
|
+
4. **全量验证**:`npm test` + `npm run build` + `./scripts/dev-daemon.sh restart` 验证 daemon 正常起停
|
|
116
|
+
|
|
117
|
+
## 命名遵循
|
|
118
|
+
|
|
119
|
+
- 方法名 camelCase:`daemonSocket`, `daemonPid`, `routingYaml`, `queueDead` ✓(与现有 `home/config/database/logs` 一致)
|
|
120
|
+
- 常量名 UPPER_SNAKE_CASE:`FORGE_PATHS`, `FORGE_HOME` ✓
|
|
121
|
+
- 测试文件位置:`tests/unit/core/forge-paths.test.ts`
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# M2 + M3: event tool_input 类型契约 Spec
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
为 `ForgeEvent.tool_input` 建立可选字段类型契约消除 `as any` 泛滥;同时让 `event_id` 在 parser 层尊重外部传入,为 hook 端端到端去重铺路。
|
|
5
|
+
|
|
6
|
+
## 调研结果
|
|
7
|
+
|
|
8
|
+
### M2 使用统计(30 处含 `as any` 与 `as Record<string,unknown>`)
|
|
9
|
+
|
|
10
|
+
| 工具 | 字段 | 使用点 |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| Bash | `command` | `web/routes/sessions.ts:95,97,223,225`; `anti-pattern-detector.ts:256,363`; `cli/commands/logs.ts:44`; `history-exporter.ts:103` |
|
|
13
|
+
| Edit/Write | `file_path` | `sessions.ts:90,91,232,233`; `tasks.ts:161,163`; `anti-pattern-detector.ts:140,356`; `resume-manager.ts:118`; `cli/commands/logs.ts:45`; `weekly-report.ts:274` |
|
|
14
|
+
| UserPromptSubmit fallback | `user_prompt` | `sessions.ts:82,83,159,164`; `tasks.ts:168,171`; `daemon/index.ts:161`; `storage/base.ts:253,257` |
|
|
15
|
+
| Agent/Task | `subagent_type`, `name`, `prompt` | `web/routes/tasks.ts:125–129`; `daemon/handlers/post-tool-use.ts:29` |
|
|
16
|
+
| Notebook | `notebook_path` | `weekly-report.ts:274` |
|
|
17
|
+
|
|
18
|
+
### 使用模式
|
|
19
|
+
**几乎全部是防御访问**:`(e.tool_input as any)?.command || ''`、`?.file_path`、`?? input?.command`。零处做 strict assert。
|
|
20
|
+
真正窄化的两处(`post-tool-use.ts:29`、`web/routes/tasks.ts:111-131`)也是 `?` 容忍。
|
|
21
|
+
|
|
22
|
+
### M3 event_id 流向
|
|
23
|
+
- **Hook 端**:`pre-tool-use.sh`、`post-tool-use.sh` 等当前**不产生** `event_id`;payload 里无该字段。
|
|
24
|
+
- **Queue 层**:`core/queue/index.ts:83` 已经写了 `event.event_id ?? randomUUID()`,文件名也依赖之。
|
|
25
|
+
- **Storage 层**:`core/storage/events.ts:21` schema 已 `event_id: z.string().uuid().optional()`,`writeEvent` 也是 `event.event_id || randomUUID()`。**唯一断点**:`daemon/event-parser.ts:23` 无条件 `randomUUID()`,先于 storage,外部传入永远被覆盖。
|
|
26
|
+
|
|
27
|
+
## 方案选择
|
|
28
|
+
|
|
29
|
+
### M2:方案 B(lenient bag)+ 辅助 getter
|
|
30
|
+
理由:
|
|
31
|
+
1. 30+ 处使用全是 undefined-friendly,方案 A 需要给每个站点加 type guard,改动大且收益低(hook event 名不等于 tool 名,`Edit`/`Write`/`Bash` 都来自 `event.tool_name`,不是 `hook_type` discriminator)。
|
|
32
|
+
2. Claude Code 的 tool 集合是开放的(未来还可能新增),union 难以穷举;bag + index signature 兼容前向。
|
|
33
|
+
3. 已有 `anti-pattern-detector.ts:354-365` 的 `extractFilePath`/`extractBashCommand` 范式,扩展即可。
|
|
34
|
+
|
|
35
|
+
### M3:保留 `event_id` 可选透传
|
|
36
|
+
`event-parser.ts` 改为 `event_id: validated.event_id ?? randomUUID()`,schema 加 `event_id: z.string().uuid().optional()`。**不强制 hook 端立即生成**。
|
|
37
|
+
|
|
38
|
+
## 类型设计
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// core/types.ts 追加
|
|
42
|
+
export interface ToolInputFields {
|
|
43
|
+
// Bash
|
|
44
|
+
command?: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
// Edit / Write / Read / NotebookEdit
|
|
47
|
+
file_path?: string;
|
|
48
|
+
notebook_path?: string;
|
|
49
|
+
old_string?: string;
|
|
50
|
+
new_string?: string;
|
|
51
|
+
content?: string;
|
|
52
|
+
// Agent / Task
|
|
53
|
+
subagent_type?: string;
|
|
54
|
+
name?: string;
|
|
55
|
+
prompt?: string;
|
|
56
|
+
// UserPromptSubmit fallback envelope
|
|
57
|
+
user_prompt?: string;
|
|
58
|
+
// 兼容未来未知字段
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ForgeEvent {
|
|
63
|
+
// ...
|
|
64
|
+
tool_input?: ToolInputFields;
|
|
65
|
+
tool_output?: Record<string, unknown>;
|
|
66
|
+
event_id?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// helpers(放 core/event-fields.ts)
|
|
70
|
+
export function getCommand(e: ForgeEvent): string | undefined {
|
|
71
|
+
const c = e.tool_input?.command;
|
|
72
|
+
return typeof c === 'string' ? c : undefined;
|
|
73
|
+
}
|
|
74
|
+
export function getFilePath(e: ForgeEvent): string | undefined {
|
|
75
|
+
const fp = e.tool_input?.file_path ?? e.tool_input?.notebook_path;
|
|
76
|
+
return typeof fp === 'string' && fp.length > 0 ? fp : undefined;
|
|
77
|
+
}
|
|
78
|
+
export function getUserPrompt(e: ForgeEvent): string | undefined {
|
|
79
|
+
return e.user_prompt ?? (typeof e.tool_input?.user_prompt === 'string' ? e.tool_input.user_prompt : undefined);
|
|
80
|
+
}
|
|
81
|
+
export function getSubagentType(e: ForgeEvent): string | undefined {
|
|
82
|
+
const s = e.tool_input?.subagent_type;
|
|
83
|
+
return typeof s === 'string' ? s : undefined;
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 改造范围
|
|
88
|
+
|
|
89
|
+
| 文件 | 改动点 | 行数 |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `src/core/types.ts` | 加 `ToolInputFields`;改 `ForgeEvent.tool_input` 类型 | +25 |
|
|
92
|
+
| `src/core/event-fields.ts`(新建)| 4 个 getter | +30 |
|
|
93
|
+
| `src/daemon/event-parser.ts` | schema 加 `event_id.optional()`;`event_id ?? randomUUID()`;`tool_input as ToolInputFields` | ~4 |
|
|
94
|
+
| `src/daemon/index.ts:161` | 改 `getUserPrompt(event)` | 1 |
|
|
95
|
+
| `src/web/routes/sessions.ts` | 8 处 `(tool_input as any)?.X` → getter | ~10 |
|
|
96
|
+
| `src/web/routes/tasks.ts` | 5 处 | ~6 |
|
|
97
|
+
| `src/cli/commands/logs.ts:42-46` | 直接用 `e.tool_input?.command` 等 | 4 |
|
|
98
|
+
| `src/web/analytics/anti-pattern-detector.ts` | `extractFilePath/Command` 内联或调 getter | 2 |
|
|
99
|
+
| `src/daemon/handlers/post-tool-use.ts:29` | `getSubagentType(event)` | 1 |
|
|
100
|
+
| `src/daemon/handlers/history-exporter.ts:102-103` | 直接 `.file_path / .command` | 2 |
|
|
101
|
+
| `src/claudemd/resume-manager.ts:118` | 已经接近规范,去 `as string` | 1 |
|
|
102
|
+
| `src/core/storage/events.ts` | `tool_input` 反序列化 cast 调整 | 1 |
|
|
103
|
+
| `src/core/storage/tasks.ts:233` | 同上 | 1 |
|
|
104
|
+
| `src/core/storage/base.ts:253,257` | SQL 不变(已用 json_extract);无代码改动 | 0 |
|
|
105
|
+
|
|
106
|
+
## 测试策略
|
|
107
|
+
- **新增** `src/tests/event-fields.test.ts`:4 个 getter × {undefined, wrong type, valid} 共约 12 case。
|
|
108
|
+
- **新增** `src/tests/event-parser.test.ts`:
|
|
109
|
+
- 无 `event_id` → 自动生成 UUID
|
|
110
|
+
- 传入 `event_id` (合法 UUID) → 透传
|
|
111
|
+
- `tool_input` 含 `command/file_path` → 落到 `ToolInputFields`
|
|
112
|
+
- **回归**:`src/tests/events.test.ts`(若存在)补一条 `writeEvent` + parser 端到端 event_id 透传 + 重放 UNIQUE 拒收。
|
|
113
|
+
- **集成**:现有 `tests/integration/*` 跑完无回归。
|
|
114
|
+
|
|
115
|
+
## 风险与回滚
|
|
116
|
+
- **风险 1**:`ToolInputFields` 加 `[key:string]: unknown` 等于半放弃严格性 — 接受,因为目标是消 `as any` 与统一访问入口,而非全量窄化。
|
|
117
|
+
- **风险 2**:M3 让外部 event_id 入库后,若 hook 端误用非 UUID 字符串,zod `.uuid()` 会抛 → 由 parser 抛错入 daemon error 路径,与现有 schema 失败同语义。回滚:parser 单行还原 `randomUUID()`。
|
|
118
|
+
- **回滚成本**:M2 是纯类型 + getter 替换,git revert 即可;M3 仅 2 行。
|
|
119
|
+
|
|
120
|
+
## 实施顺序
|
|
121
|
+
1. 新增 `core/types.ts` 类型 + `core/event-fields.ts` getter,先跑 `tsc --noEmit`(此时旧 `as any` 仍有效,无破坏)。
|
|
122
|
+
2. 改 `daemon/event-parser.ts`(M3 + tool_input cast);加 parser 测试。
|
|
123
|
+
3. 逐文件替换 `as any`:`daemon/index.ts` → `cli/logs.ts` → `daemon/handlers/*` → `web/routes/sessions.ts` → `web/routes/tasks.ts` → `web/analytics/*` → `claudemd/resume-manager.ts`。每改一个跑 `tsc --noEmit`。
|
|
124
|
+
4. 跑 `npm test`,确认 0 回归。
|
|
125
|
+
5. 抓 hook 端 event_id 预生成作为后续 follow-up(**本 spec 不实施**)。
|
|
126
|
+
|
|
127
|
+
## 命名遵循
|
|
128
|
+
- Getter 用 `get*` 前缀(符合「获取单个资源用 `get`」约定)。
|
|
129
|
+
- 类型用 PascalCase:`ToolInputFields`。
|
|
130
|
+
- 文件 kebab-case:`event-fields.ts`。
|
|
131
|
+
- 字段名沿用 hook 原始命名 (`file_path` / `command` / `subagent_type`),不改 snake_case。
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# M7: post-tool-use routing_event 关联修复 Spec
|
|
2
|
+
|
|
3
|
+
## 目标
|
|
4
|
+
|
|
5
|
+
修复 `PostToolUseHandler` 将 Agent 调用错误关联到已完成(`obeyed` 已填)的 routing_event 的时序错配 bug:把"最近一条"语义收紧为"最近一条 pending(`obeyed IS NULL`)"。
|
|
6
|
+
|
|
7
|
+
## 数据流分析
|
|
8
|
+
|
|
9
|
+
### routing_event 生命周期
|
|
10
|
+
|
|
11
|
+
| T | 触发 | 操作 | 状态 |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| T0 | user prompt P1 | `UserPromptHandler` 写 routing_event#1 | `obeyed=NULL` |
|
|
14
|
+
| T1 | Agent 调用 | `PostToolUseHandler` 取 most-recent → #1,update | `#1.obeyed=1` |
|
|
15
|
+
| T2 | 普通工具(Read/Bash)| 不写 routing_event | `#1.obeyed=1` |
|
|
16
|
+
| T3 | user prompt P2 | 写 routing_event#2 | `#2.obeyed=NULL`,#1 仍 obeyed=1 |
|
|
17
|
+
| T4 | Agent 调用 | 取 most-recent → #2 | 正常 |
|
|
18
|
+
|
|
19
|
+
### 现有读写点(routing_events)
|
|
20
|
+
|
|
21
|
+
| 调用方 | 操作 | 方法 |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `user-prompt.ts:120` | 每次 prompt insert,`obeyed=null` | `writeRoutingEvent` |
|
|
24
|
+
| `post-tool-use.ts:33` | 取最近一条 | `getRecentRoutingEvent(sessionId)` |
|
|
25
|
+
| `post-tool-use.ts:35` | 更新 obeyed/routed_to_* | `updateRoutingEvent(id, patch)` |
|
|
26
|
+
| daemon 恢复 | 拉 pending(项目级) | `queryPendingRoutingEvents(ageMs)` |
|
|
27
|
+
| 统计/聚合 | 只读 | `queryRoutingEvents`、`aggregate*` |
|
|
28
|
+
|
|
29
|
+
## Bug 复现路径
|
|
30
|
+
|
|
31
|
+
**真实错配场景**:同一 user prompt 内连续 2+ 个 Agent 调用。
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
T0 prompt P1 → write #1 (obeyed=NULL)
|
|
35
|
+
T1 Agent#a 完成 → PostToolUse → getRecent → #1 → update {obeyed=1, routed_to_name='a'}
|
|
36
|
+
T2 Agent#b 完成 → PostToolUse → getRecent → 仍是 #1 → update {obeyed=1, routed_to_name='b'}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
后果:#1 的 `routed_to_name`、`first_tool_ts` 被第二个 Agent **覆盖**;第二个 Agent 调用**没有自己的归属 routing_event**,统计层缺数。
|
|
40
|
+
|
|
41
|
+
**第二种错配**:跨 prompt 但中间 prompt 未走 UserPromptHandler(如 hook 失败 / daemon 重启后 buffer 丢失),下一个 Agent 仍会误关联到旧的 obeyed=1 的事件并把它再次覆盖。
|
|
42
|
+
|
|
43
|
+
> review 描述的"覆盖未来不相关"其实是"覆盖过去已完成"语义,效果一致——丢失上次填充结果且当前调用无新行。
|
|
44
|
+
|
|
45
|
+
## 方案对比
|
|
46
|
+
|
|
47
|
+
| 维度 | A: `obeyed IS NULL` 过滤 | B: sessionKey in-mem cache | C: A + B 组合 |
|
|
48
|
+
|---|---|---|---|
|
|
49
|
+
| 实现复杂度 | 低(1 个 SQL 改/加方法)| 中(cache + 失效)| 中高 |
|
|
50
|
+
| 跨进程/重启鲁棒 | 强(DB 真源)| 弱(cache 丢)| 强 |
|
|
51
|
+
| 同 prompt 多 Agent 行为 | 第 2 个 Agent 找不到 pending → 跳过更新(数据丢失)| 同上,cache 也指向同一 id | 同上 |
|
|
52
|
+
| 时序正确性 | 强 | 强(cache 命中时)| 强 |
|
|
53
|
+
| 风险 | 同 prompt 多 Agent 漏记需另案 | daemon crash 丢 cache | 复杂度高于必要 |
|
|
54
|
+
|
|
55
|
+
**推荐:方案 A**。理由:
|
|
56
|
+
1. DB 是单一真源,无 cache 一致性问题。
|
|
57
|
+
2. 同 prompt 多 Agent 场景下,方案 A 与 B 行为一致(都只命中一次),不构成 B 的优势。
|
|
58
|
+
3. "同 prompt 多 Agent 都要记录"是 M7 范围外的功能扩展(应该在 PostToolUse 里 **insert** 新 routing_event 而非 update),不在此次修复内。
|
|
59
|
+
|
|
60
|
+
## 改造点
|
|
61
|
+
|
|
62
|
+
| 文件 | 改动 |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `src/core/storage/routing.ts` | `getRecentRoutingEvent(sessionId)` SQL 加 `AND obeyed IS NULL`;或保留旧方法 + 新增 `getRecentPendingRoutingEvent(sessionId)`(推荐后者,向后兼容) |
|
|
65
|
+
| `src/daemon/handlers/post-tool-use.ts:33` | 调用新方法 `getRecentPendingRoutingEvent` |
|
|
66
|
+
| `src/core/storage/sqlite.ts` | facade 透传新方法 |
|
|
67
|
+
| `tests/unit/storage/routing.test.ts`(如缺则新建)| 单测:3 个时序 case |
|
|
68
|
+
| `tests/unit/daemon/post-tool-use.test.ts`(如缺则新建)| handler 行为测试 |
|
|
69
|
+
|
|
70
|
+
## 测试策略
|
|
71
|
+
|
|
72
|
+
**Storage 单测**(routing.test.ts):
|
|
73
|
+
- `getRecentPendingRoutingEvent` 返回最近 `obeyed IS NULL` 的行
|
|
74
|
+
- 已存在 obeyed=1 在前 + 新 pending 在后 → 返回新 pending
|
|
75
|
+
- 全部 obeyed 已填 → 返回 null(不误返)
|
|
76
|
+
- 不同 session 隔离
|
|
77
|
+
|
|
78
|
+
**Handler 单测**(post-tool-use.test.ts,新增):
|
|
79
|
+
- 时序 case 1(同 prompt 第 2 个 Agent):第 1 个 update 成功,第 2 个找不到 pending → silently skip(assert:原 routing_event 不被二次覆盖)
|
|
80
|
+
- 时序 case 2(跨 prompt):每个 prompt 的 Agent 关联到自己的 event
|
|
81
|
+
- 时序 case 3(无 routing_event):handler 不抛错
|
|
82
|
+
|
|
83
|
+
不需要集成测试(daemon 端到端已被 daemon 集成测试覆盖)。
|
|
84
|
+
|
|
85
|
+
## 风险与回滚
|
|
86
|
+
|
|
87
|
+
- **风险 1**:同 prompt 多 Agent 漏记 → 文档化为已知限制(M7 范围外);后续可改为 PostToolUse insert 新行。
|
|
88
|
+
- **风险 2**:旧库残留 obeyed=NULL 的 stale 行可能被新 prompt 的 Agent 错关 → 因为 ORDER BY ts DESC 限制 + session_id 过滤,最多影响"在该 session 内有非常老的孤儿 pending"的极端情况;可接受。
|
|
89
|
+
- **回滚**:单 commit revert;新方法可保留无害。
|
|
90
|
+
|
|
91
|
+
## 实施顺序
|
|
92
|
+
|
|
93
|
+
1. routing.ts 新增 `getRecentPendingRoutingEvent`(SQL 加 `AND obeyed IS NULL`)
|
|
94
|
+
2. sqlite.ts facade 透传
|
|
95
|
+
3. routing.test.ts 加单测
|
|
96
|
+
4. post-tool-use.ts 切换调用
|
|
97
|
+
5. post-tool-use.test.ts 加 handler 单测
|
|
98
|
+
6. `npx tsc --noEmit` + 相关 vitest
|
|
99
|
+
|
|
100
|
+
## 命名遵循
|
|
101
|
+
|
|
102
|
+
- `getRecentPendingRoutingEvent`(get 前缀,单个资源;与现有 `getRecentRoutingEvent`、`getExperimentAssignment` 对齐)
|
|
103
|
+
- 不用 `queryRecentPending*`(query 前缀留给返回行集合的方法)
|