@winspan/claude-forge 8.53.2 → 8.54.3

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.
Files changed (390) hide show
  1. package/DEVELOPMENT.md +290 -221
  2. package/README.md +50 -8
  3. package/dist/cli/commands/skills.d.ts.map +1 -1
  4. package/dist/cli/commands/skills.js +7 -3
  5. package/dist/cli/commands/skills.js.map +1 -1
  6. package/dist/cli/init/hook-manager.d.ts +1 -1
  7. package/dist/cli/init/hook-manager.d.ts.map +1 -1
  8. package/dist/cli/init/hook-manager.js +1 -0
  9. package/dist/cli/init/hook-manager.js.map +1 -1
  10. package/dist/core/storage/events.d.ts.map +1 -1
  11. package/dist/core/storage/events.js +0 -1
  12. package/dist/core/storage/events.js.map +1 -1
  13. package/dist/core/storage/maintenance.d.ts +25 -3
  14. package/dist/core/storage/maintenance.d.ts.map +1 -1
  15. package/dist/core/storage/maintenance.js +33 -4
  16. package/dist/core/storage/maintenance.js.map +1 -1
  17. package/dist/core/storage/routing.d.ts +4 -0
  18. package/dist/core/storage/routing.d.ts.map +1 -1
  19. package/dist/core/storage/routing.js +10 -4
  20. package/dist/core/storage/routing.js.map +1 -1
  21. package/dist/core/storage/sessions.d.ts +17 -0
  22. package/dist/core/storage/sessions.d.ts.map +1 -1
  23. package/dist/core/storage/sessions.js +64 -0
  24. package/dist/core/storage/sessions.js.map +1 -1
  25. package/dist/core/storage/skills.d.ts +4 -0
  26. package/dist/core/storage/skills.d.ts.map +1 -1
  27. package/dist/core/storage/skills.js +10 -2
  28. package/dist/core/storage/skills.js.map +1 -1
  29. package/dist/core/storage/sqlite.d.ts +5 -0
  30. package/dist/core/storage/sqlite.d.ts.map +1 -1
  31. package/dist/core/storage/sqlite.js +6 -0
  32. package/dist/core/storage/sqlite.js.map +1 -1
  33. package/dist/core/storage/tasks.d.ts.map +1 -1
  34. package/dist/core/storage/tasks.js +2 -0
  35. package/dist/core/storage/tasks.js.map +1 -1
  36. package/dist/core/types.d.ts +7 -0
  37. package/dist/core/types.d.ts.map +1 -1
  38. package/dist/daemon/index.d.ts.map +1 -1
  39. package/dist/daemon/index.js +19 -4
  40. package/dist/daemon/index.js.map +1 -1
  41. package/dist/skills/registry.d.ts.map +1 -1
  42. package/dist/skills/registry.js +13 -2
  43. package/dist/skills/registry.js.map +1 -1
  44. package/dist/skills/semantic-matcher.d.ts +2 -2
  45. package/dist/skills/semantic-matcher.d.ts.map +1 -1
  46. package/dist/skills/semantic-matcher.js +14 -19
  47. package/dist/skills/semantic-matcher.js.map +1 -1
  48. package/dist/skills/upgrade-engine.d.ts +3 -1
  49. package/dist/skills/upgrade-engine.d.ts.map +1 -1
  50. package/dist/skills/upgrade-engine.js +25 -14
  51. package/dist/skills/upgrade-engine.js.map +1 -1
  52. package/dist/web/analytics/weekly-report.d.ts.map +1 -1
  53. package/dist/web/analytics/weekly-report.js +21 -29
  54. package/dist/web/analytics/weekly-report.js.map +1 -1
  55. package/dist/web/routes/patch.d.ts.map +1 -1
  56. package/dist/web/routes/patch.js +32 -2
  57. package/dist/web/routes/patch.js.map +1 -1
  58. package/dist/web/routes/sessions.d.ts.map +1 -1
  59. package/dist/web/routes/sessions.js +9 -7
  60. package/dist/web/routes/sessions.js.map +1 -1
  61. package/dist/web/routes/trace.d.ts.map +1 -1
  62. package/dist/web/routes/trace.js +2 -3
  63. package/dist/web/routes/trace.js.map +1 -1
  64. package/dist/web/server.d.ts.map +1 -1
  65. package/dist/web/server.js +3 -2
  66. package/dist/web/server.js.map +1 -1
  67. package/package.json +12 -2
  68. package/scripts/postinstall.cjs +21 -0
  69. package/.claude/CLAUDE.md +0 -17
  70. package/.eslintrc.js +0 -23
  71. package/.prettierrc +0 -8
  72. package/ARCHITECTURE_ISSUES.md +0 -249
  73. package/CLAUDE.md +0 -265
  74. package/CLAUDE.md.backup +0 -488
  75. package/docs/concurrent-agents.md +0 -129
  76. package/docs/design/architecture-review-20260516.md +0 -232
  77. package/docs/design/fix-skills-data-and-set-leak-spec-20260516-1300.md +0 -219
  78. package/docs/design/h1-storage-aggregation-spec-20260518-1121.md +0 -299
  79. package/docs/design/h2-getdatabase-encapsulation-spec-20260518-1450.md +0 -191
  80. package/docs/design/h3-fallback-removal-spec-20260518-1245.md +0 -76
  81. package/docs/design/h4-index-dedup-spec-20260518-1230.md +0 -109
  82. package/docs/design/h6-services-migration-spec-20260518-1355.md +0 -82
  83. package/docs/design/hook-failure-queue-spec-20260516-1530.md +0 -204
  84. package/docs/design/l1-swarm-protocol-extract-spec-20260518-1605.md +0 -106
  85. package/docs/design/m10-forge-paths-spec-20260518-1320.md +0 -121
  86. package/docs/design/m2-m3-tool-input-spec-20260518-1425.md +0 -131
  87. package/docs/design/m7-routing-event-association-spec-20260518-1545.md +0 -103
  88. package/docs/design/project-path-gitroot-spec-20260518-1715.md +0 -134
  89. package/docs/design/refactor-phase1-spec-20260515-1600.md +0 -543
  90. package/docs/design/refactor-phase2-spec-20260515-1700.md +0 -424
  91. package/docs/design/skill-ai-upgrade-spec-20260518-1930.md +0 -297
  92. package/docs/design/task-active-gc-spec-20260518-1745.md +0 -146
  93. package/docs/design/tasks-list-filter-pagination-spec-20260518-0930.md +0 -208
  94. package/docs/implementation/daemon-skill-sync-changelog-20260518-2000.md +0 -22
  95. package/docs/implementation/fix-skills-data-and-set-leak-changelog-20260516-1300.md +0 -104
  96. package/docs/implementation/h1-storage-aggregation-changelog-20260518-1121.md +0 -82
  97. package/docs/implementation/h2-final-changelog-20260518-1530.md +0 -61
  98. package/docs/implementation/h2-phase1-safety-net-changelog-20260518-1450.md +0 -70
  99. package/docs/implementation/h2-phase2-operations-changelog-20260518-1450.md +0 -120
  100. package/docs/implementation/h2-phase3-callsites-changelog-20260518-1450.md +0 -71
  101. package/docs/implementation/h3-fallback-removal-changelog-20260518-1245.md +0 -71
  102. package/docs/implementation/h4-index-dedup-changelog-20260518-1230.md +0 -60
  103. package/docs/implementation/h6-services-migration-changelog-20260518-1355.md +0 -46
  104. package/docs/implementation/h7-m9-defaults-changelog-20260518-1300.md +0 -46
  105. package/docs/implementation/hook-failure-queue-changelog-20260516-1530.md +0 -196
  106. package/docs/implementation/hotfix-daemon-event-reject-20260516-1430.md +0 -56
  107. package/docs/implementation/l1-swarm-protocol-extract-changelog-20260518-1605.md +0 -45
  108. package/docs/implementation/l3-l4-daemon-perf-changelog-20260518-1410.md +0 -63
  109. package/docs/implementation/l6-l8-final-cleanup-changelog-20260518-1640.md +0 -38
  110. package/docs/implementation/m1-m4-m5-l7-cleanup-changelog-20260518-1310.md +0 -58
  111. package/docs/implementation/m10-forge-paths-changelog-20260518-1320.md +0 -60
  112. package/docs/implementation/m2-m3-tool-input-changelog-20260518-1425.md +0 -43
  113. package/docs/implementation/m6-m8-naming-shutdown-changelog-20260518-1340.md +0 -56
  114. package/docs/implementation/m7-routing-association-changelog-20260518-1545.md +0 -69
  115. package/docs/implementation/project-path-gitroot-changelog-20260518-1715.md +0 -63
  116. package/docs/implementation/refactor-phase1-changelog-20260515-1630.md +0 -354
  117. package/docs/implementation/refactor-phase2-changelog-20260515-1705.md +0 -421
  118. package/docs/implementation/skill-ai-upgrade-changelog-20260518-1930.md +0 -49
  119. package/docs/implementation/task-active-gc-changelog-20260518-1745.md +0 -35
  120. package/docs/implementation/task-title-summary-changelog-20260518-1130.md +0 -39
  121. package/docs/implementation/tasks-detail-back-loses-filters-changelog-20260518-1100.md +0 -22
  122. package/docs/implementation/tasks-list-filter-pagination-changelog-20260518-0930.md +0 -72
  123. package/docs/implementation/tasks-page-white-screen-hotfix-changelog-20260518-1015.md +0 -56
  124. package/docs/reviews/claudemd-template-sync.md +0 -54
  125. package/docs/reviews/task-title-summary.md +0 -92
  126. package/docs/reviews/tasks-detail-back-loses-filters.md +0 -58
  127. package/docs/reviews/tasks-filter-pagination.md +0 -80
  128. package/docs/reviews/tasks-page-white-screen-hotfix.md +0 -126
  129. package/docs/ruflo-learning-strategy.md +0 -322
  130. package/docs/skills-deduplication-analysis.md +0 -83
  131. package/docs/skills-multiformat-support.md +0 -177
  132. package/docs/skills-third-party.md +0 -183
  133. package/docs/testing/tasks-filter-pagination-test-report.md +0 -86
  134. package/forge +0 -321
  135. package/playwright.config.ts +0 -40
  136. package/scripts/demo-v2.ts +0 -91
  137. package/scripts/dev-daemon.sh +0 -232
  138. package/scripts/dev-web.ts +0 -109
  139. package/scripts/e2e-mcp-link.ts +0 -423
  140. package/scripts/e2e-methodology-quality.ts +0 -253
  141. package/scripts/e2e-routing.ts +0 -456
  142. package/scripts/e2e-user-methodology.ts +0 -326
  143. package/scripts/e2e-web-workflows.ts +0 -299
  144. package/scripts/migrate-legacy-to-dynamic.sql +0 -108
  145. package/scripts/regenerate-execution-docs.ts +0 -116
  146. package/scripts/sync-agent-skills.ts +0 -193
  147. package/scripts/test-hook.sh +0 -71
  148. package/scripts/verify-skill-loading.ts +0 -62
  149. package/src/claudemd/claudemd-generator.ts +0 -568
  150. package/src/claudemd/convention-extractor.ts +0 -69
  151. package/src/claudemd/index.ts +0 -35
  152. package/src/claudemd/persona-manager.ts +0 -88
  153. package/src/claudemd/resume-manager.ts +0 -236
  154. package/src/claudemd/tech-detector.ts +0 -220
  155. package/src/claudemd/templates/swarm-protocol.md +0 -222
  156. package/src/cli/commands/claudemd.ts +0 -84
  157. package/src/cli/commands/config.ts +0 -46
  158. package/src/cli/commands/daemon.ts +0 -310
  159. package/src/cli/commands/executions.ts +0 -115
  160. package/src/cli/commands/init.ts +0 -204
  161. package/src/cli/commands/logs.ts +0 -181
  162. package/src/cli/commands/mcp.ts +0 -242
  163. package/src/cli/commands/menu.ts +0 -357
  164. package/src/cli/commands/skills.ts +0 -328
  165. package/src/cli/commands/stats.ts +0 -73
  166. package/src/cli/commands/status.ts +0 -69
  167. package/src/cli/commands/template.ts +0 -77
  168. package/src/cli/commands/trace.ts +0 -148
  169. package/src/cli/index.ts +0 -42
  170. package/src/cli/init/hook-manager.ts +0 -132
  171. package/src/core/ai/provider.ts +0 -308
  172. package/src/core/ai/types.ts +0 -51
  173. package/src/core/config.ts +0 -124
  174. package/src/core/constants.ts +0 -67
  175. package/src/core/event-fields.ts +0 -32
  176. package/src/core/queue/index.ts +0 -192
  177. package/src/core/storage/base.ts +0 -302
  178. package/src/core/storage/events.ts +0 -434
  179. package/src/core/storage/injections.ts +0 -78
  180. package/src/core/storage/maintenance.ts +0 -59
  181. package/src/core/storage/migrations/002_add_skill_tracking.sql +0 -6
  182. package/src/core/storage/migrations/003_add_skill_invocations.sql +0 -23
  183. package/src/core/storage/performance-indexes.sql +0 -23
  184. package/src/core/storage/routing.ts +0 -322
  185. package/src/core/storage/rows.ts +0 -112
  186. package/src/core/storage/schema.sql +0 -224
  187. package/src/core/storage/sessions.ts +0 -168
  188. package/src/core/storage/skills.ts +0 -233
  189. package/src/core/storage/sqlite.ts +0 -293
  190. package/src/core/storage/tasks.ts +0 -318
  191. package/src/core/storage/token-usage.ts +0 -93
  192. package/src/core/types.ts +0 -181
  193. package/src/core/utils/error-handler.ts +0 -257
  194. package/src/core/utils/forge-resume-block.ts +0 -74
  195. package/src/core/utils/format.ts +0 -69
  196. package/src/core/utils/git.ts +0 -23
  197. package/src/core/utils/logger.ts +0 -134
  198. package/src/core/utils/lru-cache.ts +0 -54
  199. package/src/core/utils/path.ts +0 -19
  200. package/src/core/utils/session.ts +0 -26
  201. package/src/core/utils/time.ts +0 -37
  202. package/src/core/utils/token-tracker.ts +0 -97
  203. package/src/daemon/event-parser.ts +0 -36
  204. package/src/daemon/handlers/history-exporter.ts +0 -117
  205. package/src/daemon/handlers/post-tool-use.ts +0 -54
  206. package/src/daemon/handlers/stop.ts +0 -208
  207. package/src/daemon/handlers/user-prompt.ts +0 -178
  208. package/src/daemon/hook-sync.ts +0 -91
  209. package/src/daemon/index.ts +0 -312
  210. package/src/daemon/launchd/com.claude-forge.daemon.plist.template +0 -47
  211. package/src/daemon/launchd-installer.ts +0 -260
  212. package/src/daemon/lifecycle.ts +0 -128
  213. package/src/daemon/router.ts +0 -40
  214. package/src/daemon/server.ts +0 -196
  215. package/src/daemon/services/task-segmenter.ts +0 -112
  216. package/src/daemon/skill-sync.ts +0 -88
  217. package/src/hooks/hook-lib.sh +0 -118
  218. package/src/hooks/notification.sh +0 -35
  219. package/src/hooks/post-tool-use.sh +0 -61
  220. package/src/hooks/pre-tool-use.sh +0 -63
  221. package/src/hooks/stop.sh +0 -43
  222. package/src/hooks/user-prompt-submit.sh +0 -69
  223. package/src/mcp/server.ts +0 -322
  224. package/src/skills/index.ts +0 -2
  225. package/src/skills/invocation-guard.ts +0 -177
  226. package/src/skills/matcher.ts +0 -148
  227. package/src/skills/official/code-simplifier.md +0 -52
  228. package/src/skills/official/find-skills.md +0 -142
  229. package/src/skills/official/official-api-design.md +0 -30
  230. package/src/skills/official/official-architecture-decision.md +0 -41
  231. package/src/skills/official/official-bmad.md +0 -118
  232. package/src/skills/official/official-db-schema-design.md +0 -34
  233. package/src/skills/official/official-debug.md +0 -25
  234. package/src/skills/official/official-doc-driven.md +0 -31
  235. package/src/skills/official/official-harness-engineering.md +0 -108
  236. package/src/skills/official/official-performance-optimization.md +0 -30
  237. package/src/skills/official/official-pr-review.md +0 -35
  238. package/src/skills/official/official-release-checklist.md +0 -30
  239. package/src/skills/official/official-security-hardening.md +0 -32
  240. package/src/skills/official/official-spec-driven-design.md +0 -31
  241. package/src/skills/official/planning-with-files.md +0 -241
  242. package/src/skills/official/ui-ux-pro-max.md +0 -105
  243. package/src/skills/official/webapp-testing.md +0 -96
  244. package/src/skills/official-skills.ts +0 -89
  245. package/src/skills/registry.ts +0 -355
  246. package/src/skills/semantic-matcher.ts +0 -234
  247. package/src/skills/tools/pipeline-suggest.ts +0 -226
  248. package/src/skills/tools/skill-invoke.ts +0 -168
  249. package/src/skills/tools/skill-list.ts +0 -59
  250. package/src/skills/upgrade-engine.ts +0 -541
  251. package/src/skills/upgrade-prompt.ts +0 -84
  252. package/src/templates/go.yaml +0 -53
  253. package/src/templates/python.yaml +0 -59
  254. package/src/templates/react.yaml +0 -55
  255. package/src/templates/template-manager.ts +0 -170
  256. package/src/web/analytics/anti-pattern-detector.ts +0 -367
  257. package/src/web/analytics/drift-detector.ts +0 -219
  258. package/src/web/analytics/weekly-report.ts +0 -431
  259. package/src/web/auth-middleware.ts +0 -54
  260. package/src/web/routes/_helpers.ts +0 -34
  261. package/src/web/routes/ai.ts +0 -204
  262. package/src/web/routes/auth.ts +0 -22
  263. package/src/web/routes/drift.ts +0 -25
  264. package/src/web/routes/error-handler.ts +0 -120
  265. package/src/web/routes/events.ts +0 -47
  266. package/src/web/routes/insights.ts +0 -43
  267. package/src/web/routes/patch.ts +0 -117
  268. package/src/web/routes/reports.ts +0 -34
  269. package/src/web/routes/rules.ts +0 -76
  270. package/src/web/routes/sessions.ts +0 -250
  271. package/src/web/routes/skill-stats.ts +0 -92
  272. package/src/web/routes/skills.ts +0 -350
  273. package/src/web/routes/static.ts +0 -67
  274. package/src/web/routes/stats.ts +0 -50
  275. package/src/web/routes/status.ts +0 -30
  276. package/src/web/routes/tasks.ts +0 -193
  277. package/src/web/routes/token-usage.ts +0 -20
  278. package/src/web/routes/trace.ts +0 -126
  279. package/src/web/routes/types.ts +0 -57
  280. package/src/web/server.ts +0 -134
  281. package/src/web/ssrf-guard.ts +0 -112
  282. package/src/web/static/index.html +0 -3251
  283. package/src/web/static/vendor/chart.umd.min.js +0 -20
  284. package/tests/e2e/dashboard.spec.ts +0 -205
  285. package/tests/e2e/routing-skill-e2e.test.ts +0 -39
  286. package/tests/helpers/mock-ai.ts +0 -92
  287. package/tests/helpers/mock-storage.ts +0 -159
  288. package/tests/integration/claudemd-generator.test.ts +0 -90
  289. package/tests/integration/queue-replay.integration.test.ts +0 -193
  290. package/tests/integration/tasks-filter.integration.test.ts +0 -154
  291. package/tests/integration/web-analytics.integration.test.ts +0 -133
  292. package/tests/integration/web-stats.integration.test.ts +0 -135
  293. package/tests/integration/web-trace.integration.test.ts +0 -175
  294. package/tests/performance/database.benchmark.ts +0 -161
  295. package/tests/semantic-matcher.test.ts +0 -99
  296. package/tests/skill-matcher.test.ts +0 -110
  297. package/tests/unit/ai-provider-retry.test.ts +0 -194
  298. package/tests/unit/ai-provider-vision.test.ts +0 -224
  299. package/tests/unit/claudemd-generator.test.ts +0 -68
  300. package/tests/unit/cli-mcp.test.ts +0 -141
  301. package/tests/unit/core/forge-paths.test.ts +0 -99
  302. package/tests/unit/daemon/hook-sync.test.ts +0 -71
  303. package/tests/unit/daemon/post-tool-use.test.ts +0 -121
  304. package/tests/unit/daemon/skill-sync.test.ts +0 -75
  305. package/tests/unit/daemon/stop-handler-behavior-summary.test.ts +0 -202
  306. package/tests/unit/daemon/task-segmenter-recover.test.ts +0 -84
  307. package/tests/unit/event-fields.test.ts +0 -88
  308. package/tests/unit/event-parser.test.ts +0 -55
  309. package/tests/unit/handlers.test.ts +0 -171
  310. package/tests/unit/hooks/resolve-project-path.test.ts +0 -122
  311. package/tests/unit/invocation-guard.test.ts +0 -125
  312. package/tests/unit/queue.test.ts +0 -272
  313. package/tests/unit/router.test.ts +0 -138
  314. package/tests/unit/security.test.ts +0 -128
  315. package/tests/unit/skill-invocations-workflow.test.ts +0 -495
  316. package/tests/unit/skill-registry.test.ts +0 -94
  317. package/tests/unit/skills/invocation-guard-ttl.test.ts +0 -211
  318. package/tests/unit/skills/official-skills-loader.test.ts +0 -126
  319. package/tests/unit/skills/registry-multiformat.test.ts +0 -92
  320. package/tests/unit/skills/upgrade-engine-parse.test.ts +0 -138
  321. package/tests/unit/skills/upgrade-engine.test.ts +0 -401
  322. package/tests/unit/skills/upgrade-prompt.test.ts +0 -89
  323. package/tests/unit/socket-server.test.ts +0 -183
  324. package/tests/unit/storage/event-operations-aggregates.test.ts +0 -342
  325. package/tests/unit/storage/migration-idempotent.test.ts +0 -304
  326. package/tests/unit/storage/routing-aggregates.test.ts +0 -276
  327. package/tests/unit/storage/routing.test.ts +0 -117
  328. package/tests/unit/storage/schema-missing.test.ts +0 -81
  329. package/tests/unit/storage/session-operations-aggregates.test.ts +0 -120
  330. package/tests/unit/storage/sessions-aggregate.test.ts +0 -435
  331. package/tests/unit/storage/skill-operations-counts.test.ts +0 -106
  332. package/tests/unit/storage/skills-aggregates.test.ts +0 -104
  333. package/tests/unit/storage/sqlite-refactor-harness.test.ts +0 -314
  334. package/tests/unit/storage/task-operations-counts.test.ts +0 -46
  335. package/tests/unit/storage/tasks-getById.test.ts +0 -343
  336. package/tests/unit/storage/tasks-stale-gc.test.ts +0 -86
  337. package/tests/unit/storage.test.ts +0 -172
  338. package/tests/unit/token-usage.test.ts +0 -144
  339. package/tests/unit/type-guards.test.ts +0 -201
  340. package/tests/unit/utils/format.test.ts +0 -189
  341. package/tests/unit/utils/session.test.ts +0 -89
  342. package/tests/unit/utils/time.test.ts +0 -112
  343. package/tests/unit/web/navigation-back-contract.test.ts +0 -134
  344. package/tests/unit/web/routes-auth.test.ts +0 -93
  345. package/tests/unit/web/routes-events.test.ts +0 -101
  346. package/tests/unit/web/routes-rules.test.ts +0 -182
  347. package/tests/unit/web/routes-sessions.test.ts +0 -181
  348. package/tests/unit/web/routes-skill-stats.test.ts +0 -179
  349. package/tests/unit/web/routes-stats.test.ts +0 -92
  350. package/tests/unit/web/routes-tasks.test.ts +0 -385
  351. package/tests/unit/web/task-title-contract.test.ts +0 -210
  352. package/tests/unit/web/tasks-component-contract.test.ts +0 -179
  353. package/tsconfig.json +0 -22
  354. package/vitest.config.ts +0 -21
  355. package/vitest.integration.config.ts +0 -16
  356. package/web/CLAUDE.md +0 -20
  357. package/web/index.html +0 -13
  358. package/web/package-lock.json +0 -4854
  359. package/web/package.json +0 -35
  360. package/web/postcss.config.js +0 -6
  361. package/web/src/App.tsx +0 -110
  362. package/web/src/components/CodeBlock.tsx +0 -31
  363. package/web/src/components/Confirm.tsx +0 -96
  364. package/web/src/components/Drawer.tsx +0 -60
  365. package/web/src/components/Layout.tsx +0 -145
  366. package/web/src/components/MarkdownRenderer.tsx +0 -77
  367. package/web/src/components/SearchInput.tsx +0 -31
  368. package/web/src/components/SessionDetailContent.tsx +0 -157
  369. package/web/src/components/Toast.tsx +0 -92
  370. package/web/src/index.css +0 -19
  371. package/web/src/main.tsx +0 -31
  372. package/web/src/pages/AIConfig.tsx +0 -233
  373. package/web/src/pages/Dashboard.tsx +0 -572
  374. package/web/src/pages/Events.tsx +0 -271
  375. package/web/src/pages/Reports.tsx +0 -428
  376. package/web/src/pages/SessionDetail.tsx +0 -162
  377. package/web/src/pages/Sessions.tsx +0 -205
  378. package/web/src/pages/Skills.tsx +0 -180
  379. package/web/src/pages/TaskDetail.tsx +0 -515
  380. package/web/src/pages/Tasks.tsx +0 -415
  381. package/web/src/utils/auth.ts +0 -59
  382. package/web/src/utils/export.ts +0 -54
  383. package/web/src/utils/navigation.ts +0 -25
  384. package/web/src/utils/task-title.ts +0 -49
  385. package/web/src/utils/time.ts +0 -13
  386. package/web/tailwind.config.js +0 -11
  387. package/web/tsconfig.json +0 -21
  388. package/web/tsconfig.node.json +0 -10
  389. package/web/vite.config.ts +0 -76
  390. package/winspan-claude-forge-8.43.0.tgz +0 -0
@@ -1,342 +0,0 @@
1
- /**
2
- * H2: EventOperations 新增 aggregate / count / query 方法测试
3
- *
4
- * 覆盖 11 个方法:
5
- * countAllEvents
6
- * aggregateToolUsage(含 hook_type 过滤)
7
- * aggregateDailyEventCounts
8
- * aggregateHookTypeBySession
9
- * aggregateAgentTypeBySession
10
- * aggregateToolUsageBySession
11
- * countActiveDays
12
- * aggregateOverviewByRange
13
- * queryDistinctProjects
14
- * aggregateToolFailureRate
15
- * queryFileEditInputs
16
- * queryEventsByTimeRange
17
- */
18
-
19
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
20
- import { mkdtempSync, rmSync } from 'node:fs';
21
- import { tmpdir } from 'node:os';
22
- import { join } from 'node:path';
23
- import { SQLiteStorage } from '../../../src/core/storage/sqlite.js';
24
- import type { ForgeEvent } from '../../../src/core/types.js';
25
-
26
- describe('EventOperations H2 aggregates', () => {
27
- let tmp: string;
28
- let storage: SQLiteStorage;
29
-
30
- beforeEach(() => {
31
- tmp = mkdtempSync(join(tmpdir(), 'forge-h2-events-'));
32
- storage = new SQLiteStorage(join(tmp, 'data.db'));
33
- });
34
-
35
- afterEach(() => {
36
- try { storage.close(); } catch { /* ignore */ }
37
- rmSync(tmp, { recursive: true, force: true });
38
- });
39
-
40
- function makeEvent(overrides: Partial<ForgeEvent>): ForgeEvent {
41
- return {
42
- session_id: 's1',
43
- project_path: '/tmp/proj',
44
- timestamp: '2026-05-10T10:00:00.000Z',
45
- hook_type: 'PreToolUse',
46
- ...overrides,
47
- };
48
- }
49
-
50
- // ── countAllEvents ─────────────────────────────────────────────────
51
- describe('countAllEvents', () => {
52
- it('空表返回 0', () => {
53
- expect(storage.countAllEvents()).toBe(0);
54
- });
55
- it('多条事件返回总数', () => {
56
- storage.writeEvent(makeEvent({ session_id: 'a' }));
57
- storage.writeEvent(makeEvent({ session_id: 'b' }));
58
- storage.writeEvent(makeEvent({ session_id: 'c' }));
59
- expect(storage.countAllEvents()).toBe(3);
60
- });
61
- });
62
-
63
- // ── aggregateToolUsage ─────────────────────────────────────────────
64
- describe('aggregateToolUsage', () => {
65
- it('空表返回空数组', () => {
66
- expect(storage.aggregateToolUsage()).toEqual([]);
67
- });
68
- it('按 count 降序,跳过空 tool_name', () => {
69
- storage.writeEvent(makeEvent({ tool_name: 'Bash' }));
70
- storage.writeEvent(makeEvent({ tool_name: 'Bash' }));
71
- storage.writeEvent(makeEvent({ tool_name: 'Read' }));
72
- storage.writeEvent(makeEvent({ hook_type: 'UserPromptSubmit' })); // null tool_name
73
- const r = storage.aggregateToolUsage();
74
- expect(r).toEqual([
75
- { tool_name: 'Bash', count: 2 },
76
- { tool_name: 'Read', count: 1 },
77
- ]);
78
- });
79
- it('hook_type 过滤只统计指定类型', () => {
80
- storage.writeEvent(makeEvent({ tool_name: 'Bash', hook_type: 'PreToolUse' }));
81
- storage.writeEvent(makeEvent({ tool_name: 'Bash', hook_type: 'PostToolUse' }));
82
- storage.writeEvent(makeEvent({ tool_name: 'Read', hook_type: 'PreToolUse' }));
83
- const r = storage.aggregateToolUsage({ hook_type: 'PreToolUse' });
84
- expect(r.find(x => x.tool_name === 'Bash')?.count).toBe(1);
85
- expect(r.find(x => x.tool_name === 'Read')?.count).toBe(1);
86
- });
87
- it('since 过滤排除早期事件', () => {
88
- storage.writeEvent(makeEvent({ tool_name: 'Bash', timestamp: '2026-01-01T00:00:00.000Z' }));
89
- storage.writeEvent(makeEvent({ tool_name: 'Bash', timestamp: '2026-05-10T00:00:00.000Z', session_id: 's2' }));
90
- const r = storage.aggregateToolUsage({ since: '2026-03-01T00:00:00.000Z' });
91
- expect(r).toEqual([{ tool_name: 'Bash', count: 1 }]);
92
- });
93
- });
94
-
95
- // ── aggregateDailyEventCounts ──────────────────────────────────────
96
- describe('aggregateDailyEventCounts', () => {
97
- it('按 date 分组并升序', () => {
98
- storage.writeEvent(makeEvent({ timestamp: '2026-05-10T10:00:00.000Z' }));
99
- storage.writeEvent(makeEvent({ timestamp: '2026-05-10T15:00:00.000Z', session_id: 's2' }));
100
- storage.writeEvent(makeEvent({ timestamp: '2026-05-11T10:00:00.000Z', session_id: 's3' }));
101
- const r = storage.aggregateDailyEventCounts({ since: '2026-05-01T00:00:00.000Z' });
102
- expect(r).toEqual([
103
- { date: '2026-05-10', count: 2 },
104
- { date: '2026-05-11', count: 1 },
105
- ]);
106
- });
107
- it('until 过滤排除上界', () => {
108
- storage.writeEvent(makeEvent({ timestamp: '2026-05-10T10:00:00.000Z' }));
109
- storage.writeEvent(makeEvent({ timestamp: '2026-05-15T10:00:00.000Z', session_id: 's2' }));
110
- const r = storage.aggregateDailyEventCounts({
111
- since: '2026-05-01T00:00:00.000Z',
112
- until: '2026-05-12T00:00:00.000Z',
113
- });
114
- expect(r).toEqual([{ date: '2026-05-10', count: 1 }]);
115
- });
116
- });
117
-
118
- // ── aggregateHookTypeBySession ─────────────────────────────────────
119
- describe('aggregateHookTypeBySession', () => {
120
- it('空表返回空', () => {
121
- expect(storage.aggregateHookTypeBySession('nope')).toEqual([]);
122
- });
123
- it('只统计指定 session 的 hook_type', () => {
124
- storage.writeEvent(makeEvent({ session_id: 'a', hook_type: 'PreToolUse' }));
125
- storage.writeEvent(makeEvent({ session_id: 'a', hook_type: 'PostToolUse' }));
126
- storage.writeEvent(makeEvent({ session_id: 'a', hook_type: 'PreToolUse' }));
127
- storage.writeEvent(makeEvent({ session_id: 'b', hook_type: 'PreToolUse' }));
128
- const r = storage.aggregateHookTypeBySession('a');
129
- const map = Object.fromEntries(r.map(x => [x.hook_type, x.count]));
130
- expect(map).toEqual({ PreToolUse: 2, PostToolUse: 1 });
131
- });
132
- });
133
-
134
- // ── aggregateAgentTypeBySession ────────────────────────────────────
135
- describe('aggregateAgentTypeBySession', () => {
136
- it('json_extract subagent_type 并分组', () => {
137
- storage.writeEvent(makeEvent({
138
- session_id: 'a',
139
- tool_name: 'Task',
140
- tool_input: { subagent_type: 'researcher' },
141
- }));
142
- storage.writeEvent(makeEvent({
143
- session_id: 'a',
144
- tool_name: 'Task',
145
- tool_input: { subagent_type: 'researcher' },
146
- }));
147
- storage.writeEvent(makeEvent({
148
- session_id: 'a',
149
- tool_name: 'Agent',
150
- tool_input: { subagent_type: 'coder' },
151
- }));
152
- // 非 Agent/Task 不应被纳入
153
- storage.writeEvent(makeEvent({
154
- session_id: 'a',
155
- tool_name: 'Bash',
156
- tool_input: { foo: 'bar' },
157
- }));
158
- const r = storage.aggregateAgentTypeBySession('a');
159
- const map = Object.fromEntries(r.map(x => [x.agent_type, x.count]));
160
- expect(map.researcher).toBe(2);
161
- expect(map.coder).toBe(1);
162
- });
163
- });
164
-
165
- // ── aggregateToolUsageBySession ────────────────────────────────────
166
- describe('aggregateToolUsageBySession', () => {
167
- it('按 session_id 统计 tool_name', () => {
168
- storage.writeEvent(makeEvent({ session_id: 'a', tool_name: 'Bash' }));
169
- storage.writeEvent(makeEvent({ session_id: 'a', tool_name: 'Bash' }));
170
- storage.writeEvent(makeEvent({ session_id: 'a', tool_name: 'Read' }));
171
- storage.writeEvent(makeEvent({ session_id: 'b', tool_name: 'Bash' }));
172
- const r = storage.aggregateToolUsageBySession('a');
173
- expect(r).toEqual([
174
- { tool_name: 'Bash', count: 2 },
175
- { tool_name: 'Read', count: 1 },
176
- ]);
177
- });
178
- });
179
-
180
- // ── countActiveDays ────────────────────────────────────────────────
181
- describe('countActiveDays', () => {
182
- it('distinct date 计数', () => {
183
- storage.writeEvent(makeEvent({ timestamp: '2026-05-10T10:00:00.000Z' }));
184
- storage.writeEvent(makeEvent({ timestamp: '2026-05-10T11:00:00.000Z', session_id: 's2' }));
185
- storage.writeEvent(makeEvent({ timestamp: '2026-05-11T10:00:00.000Z', session_id: 's3' }));
186
- storage.writeEvent(makeEvent({ timestamp: '2026-05-12T10:00:00.000Z', session_id: 's4' }));
187
- expect(storage.countActiveDays({ since: '2026-05-01T00:00:00.000Z' })).toBe(3);
188
- });
189
- it('空范围返回 0', () => {
190
- expect(storage.countActiveDays({ since: '2026-05-01T00:00:00.000Z' })).toBe(0);
191
- });
192
- });
193
-
194
- // ── aggregateOverviewByRange ───────────────────────────────────────
195
- describe('aggregateOverviewByRange', () => {
196
- it('返回 event/session/day 复合计数', () => {
197
- storage.writeEvent(makeEvent({ session_id: 'a', timestamp: '2026-05-10T10:00:00.000Z' }));
198
- storage.writeEvent(makeEvent({ session_id: 'a', timestamp: '2026-05-10T11:00:00.000Z' }));
199
- storage.writeEvent(makeEvent({ session_id: 'b', timestamp: '2026-05-11T10:00:00.000Z' }));
200
- const r = storage.aggregateOverviewByRange({
201
- since: '2026-05-01T00:00:00.000Z',
202
- until: '2026-06-01T00:00:00.000Z',
203
- });
204
- expect(r.event_count).toBe(3);
205
- expect(r.session_count).toBe(2);
206
- expect(r.day_count).toBe(2);
207
- });
208
- it('空表返回全 0', () => {
209
- const r = storage.aggregateOverviewByRange({
210
- since: '2026-05-01T00:00:00.000Z',
211
- until: '2026-06-01T00:00:00.000Z',
212
- });
213
- expect(r).toEqual({ event_count: 0, session_count: 0, day_count: 0 });
214
- });
215
- });
216
-
217
- // ── queryDistinctProjects ──────────────────────────────────────────
218
- describe('queryDistinctProjects', () => {
219
- it('返回 distinct project_path', () => {
220
- storage.writeEvent(makeEvent({ project_path: '/a' }));
221
- storage.writeEvent(makeEvent({ project_path: '/a', session_id: 's2' }));
222
- storage.writeEvent(makeEvent({ project_path: '/b', session_id: 's3' }));
223
- const r = storage.queryDistinctProjects({
224
- since: '2026-05-01T00:00:00.000Z',
225
- until: '2026-06-01T00:00:00.000Z',
226
- });
227
- expect(r.sort()).toEqual(['/a', '/b']);
228
- });
229
- });
230
-
231
- // ── aggregateToolFailureRate ───────────────────────────────────────
232
- describe('aggregateToolFailureRate', () => {
233
- it('LIKE %error% 三个分支正确识别失败', () => {
234
- // PostToolUse with error
235
- storage.writeEvent(makeEvent({
236
- hook_type: 'PostToolUse',
237
- tool_name: 'Bash',
238
- tool_output: { error: 'oops' }, // becomes '"error":"oops"' in JSON
239
- }));
240
- storage.writeEvent(makeEvent({
241
- hook_type: 'PostToolUse',
242
- tool_name: 'Bash',
243
- tool_output: { is_error: true },
244
- session_id: 's2',
245
- }));
246
- storage.writeEvent(makeEvent({
247
- hook_type: 'PostToolUse',
248
- tool_name: 'Bash',
249
- tool_output: { isError: true },
250
- session_id: 's3',
251
- }));
252
- // PostToolUse OK
253
- storage.writeEvent(makeEvent({
254
- hook_type: 'PostToolUse',
255
- tool_name: 'Bash',
256
- tool_output: { result: 'ok' },
257
- session_id: 's4',
258
- }));
259
- // PreToolUse should not count
260
- storage.writeEvent(makeEvent({
261
- hook_type: 'PreToolUse',
262
- tool_name: 'Bash',
263
- tool_output: { error: 'should not count' },
264
- session_id: 's5',
265
- }));
266
-
267
- const r = storage.aggregateToolFailureRate({
268
- since: '2026-05-01T00:00:00.000Z',
269
- until: '2026-06-01T00:00:00.000Z',
270
- });
271
- expect(r.post_total).toBe(4);
272
- expect(r.failed).toBe(3);
273
- });
274
- it('空范围返回 0/0', () => {
275
- const r = storage.aggregateToolFailureRate({
276
- since: '2026-05-01T00:00:00.000Z',
277
- until: '2026-06-01T00:00:00.000Z',
278
- });
279
- expect(r).toEqual({ post_total: 0, failed: 0 });
280
- });
281
- });
282
-
283
- // ── queryFileEditInputs ────────────────────────────────────────────
284
- describe('queryFileEditInputs', () => {
285
- it('IN 展开多个 tool_name', () => {
286
- storage.writeEvent(makeEvent({
287
- hook_type: 'PreToolUse',
288
- tool_name: 'Edit',
289
- tool_input: { file_path: '/a.ts' },
290
- }));
291
- storage.writeEvent(makeEvent({
292
- hook_type: 'PreToolUse',
293
- tool_name: 'Write',
294
- tool_input: { file_path: '/b.ts' },
295
- session_id: 's2',
296
- }));
297
- storage.writeEvent(makeEvent({
298
- hook_type: 'PreToolUse',
299
- tool_name: 'Bash', // not file edit
300
- tool_input: { command: 'ls' },
301
- session_id: 's3',
302
- }));
303
- const r = storage.queryFileEditInputs({
304
- since: '2026-05-01T00:00:00.000Z',
305
- until: '2026-06-01T00:00:00.000Z',
306
- tool_names: ['Edit', 'Write', 'MultiEdit'],
307
- });
308
- expect(r.length).toBe(2);
309
- expect(r.map(x => JSON.parse(x.tool_input).file_path).sort()).toEqual(['/a.ts', '/b.ts']);
310
- });
311
- it('空 tool_names 返回空', () => {
312
- storage.writeEvent(makeEvent({ tool_name: 'Edit', tool_input: { file_path: '/a.ts' } }));
313
- expect(storage.queryFileEditInputs({
314
- since: '2026-05-01T00:00:00.000Z',
315
- until: '2026-06-01T00:00:00.000Z',
316
- tool_names: [],
317
- })).toEqual([]);
318
- });
319
- });
320
-
321
- // ── queryEventsByTimeRange ─────────────────────────────────────────
322
- describe('queryEventsByTimeRange', () => {
323
- it('返回 [since, until) 区间事件,升序', () => {
324
- storage.writeEvent(makeEvent({ timestamp: '2026-05-10T10:00:00.000Z', tool_name: 'A' }));
325
- storage.writeEvent(makeEvent({ timestamp: '2026-05-11T10:00:00.000Z', tool_name: 'B', session_id: 's2' }));
326
- storage.writeEvent(makeEvent({ timestamp: '2026-05-12T10:00:00.000Z', tool_name: 'C', session_id: 's3' }));
327
- const r = storage.queryEventsByTimeRange({
328
- since: '2026-05-10T00:00:00.000Z',
329
- until: '2026-05-12T00:00:00.000Z',
330
- });
331
- expect(r.length).toBe(2);
332
- expect(r[0].tool_name).toBe('A');
333
- expect(r[1].tool_name).toBe('B');
334
- });
335
- it('未指定 until 返回 since 起始的全部', () => {
336
- storage.writeEvent(makeEvent({ timestamp: '2026-05-10T10:00:00.000Z' }));
337
- storage.writeEvent(makeEvent({ timestamp: '2026-05-20T10:00:00.000Z', session_id: 's2' }));
338
- const r = storage.queryEventsByTimeRange({ since: '2026-05-15T00:00:00.000Z' });
339
- expect(r.length).toBe(1);
340
- });
341
- });
342
- });
@@ -1,304 +0,0 @@
1
- /**
2
- * H4: migration idempotent
3
- *
4
- * 覆盖 schema.sql 与 base.ts::runMigrations 的去重边界:
5
- * 1. baseline — 老库缺索引/列,SQLiteStorage 启动后必须补齐
6
- * 2. 新库无害 — 走完整 schema.sql 后,再 new SQLiteStorage 不抛错,不重复创建
7
- * 3. idempotent — 连续 new 多次,索引/列数量不变
8
- */
9
-
10
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
11
- import Database from 'better-sqlite3';
12
- import { mkdtempSync, rmSync, readFileSync, existsSync } from 'node:fs';
13
- import { tmpdir } from 'node:os';
14
- import { dirname, join } from 'node:path';
15
- import { fileURLToPath } from 'node:url';
16
- import { SQLiteStorage } from '../../../src/core/storage/sqlite.js';
17
- import { logger } from '../../../src/core/utils/logger.js';
18
-
19
- // 与 base.ts::initSchema 解析方式一致
20
- const THIS_DIR = dirname(fileURLToPath(import.meta.url));
21
- const SCHEMA_PATH = join(THIS_DIR, '../../../src/core/storage/schema.sql');
22
-
23
- function listIndexes(db: Database.Database): Set<string> {
24
- const rows = db
25
- .prepare(`SELECT name FROM sqlite_master WHERE type='index' AND name NOT LIKE 'sqlite_%'`)
26
- .all() as Array<{ name: string }>;
27
- return new Set(rows.map(r => r.name));
28
- }
29
-
30
- function listColumns(db: Database.Database, table: string): Set<string> {
31
- const rows = db.prepare(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
32
- return new Set(rows.map(r => r.name));
33
- }
34
-
35
- /**
36
- * 写入一份"老库"——故意删掉若干索引,模拟存量库。
37
- * 列定义保持与最新 schema.sql 等价(不缺列),因为 schema.sql 的
38
- * CREATE TABLE IF NOT EXISTS 在表已存在时不会补列;同时 schema.sql 中
39
- * 多个索引(如 idx_skill_invocations_workflow)依赖 workflow 列存在。
40
- * 故"老库缺列"测试见单独 case,与"老库缺索引"分开覆盖,避免初始化期失败。
41
- */
42
- function execLegacySchemaMissingIndexes(db: Database.Database): void {
43
- db.exec(`
44
- CREATE TABLE events (
45
- event_id TEXT PRIMARY KEY,
46
- session_id TEXT NOT NULL,
47
- project_path TEXT NOT NULL,
48
- timestamp TEXT NOT NULL,
49
- hook_type TEXT NOT NULL,
50
- tool_name TEXT,
51
- tool_input TEXT,
52
- tool_output TEXT,
53
- user_prompt TEXT,
54
- ai_response TEXT,
55
- distilled INTEGER DEFAULT 0,
56
- created_at TEXT DEFAULT (datetime('now'))
57
- );
58
-
59
- CREATE TABLE sessions (
60
- session_id TEXT PRIMARY KEY,
61
- project_path TEXT NOT NULL,
62
- status TEXT NOT NULL DEFAULT 'active',
63
- first_prompt TEXT,
64
- start_time TEXT NOT NULL,
65
- end_time TEXT,
66
- last_event_time TEXT,
67
- event_count INTEGER DEFAULT 0,
68
- created_at TEXT DEFAULT (datetime('now')),
69
- updated_at TEXT DEFAULT (datetime('now'))
70
- );
71
-
72
- CREATE TABLE injections (
73
- id TEXT PRIMARY KEY,
74
- event_id TEXT,
75
- session_id TEXT NOT NULL,
76
- timestamp TEXT NOT NULL,
77
- source_handler TEXT NOT NULL,
78
- injection_type TEXT NOT NULL,
79
- content TEXT NOT NULL,
80
- created_at TEXT DEFAULT (datetime('now'))
81
- );
82
-
83
- CREATE TABLE tasks (
84
- id TEXT PRIMARY KEY,
85
- session_id TEXT NOT NULL,
86
- title TEXT NOT NULL,
87
- description TEXT,
88
- start_time TEXT NOT NULL,
89
- end_time TEXT,
90
- status TEXT DEFAULT 'active',
91
- event_count INTEGER DEFAULT 0,
92
- created_at TEXT DEFAULT (datetime('now'))
93
- );
94
-
95
- CREATE TABLE task_events (
96
- task_id TEXT NOT NULL,
97
- event_id TEXT NOT NULL,
98
- PRIMARY KEY (task_id, event_id)
99
- );
100
-
101
- CREATE TABLE routing_events (
102
- id INTEGER PRIMARY KEY AUTOINCREMENT,
103
- session_id TEXT NOT NULL,
104
- route_request_id TEXT,
105
- project_path TEXT NOT NULL,
106
- ts INTEGER NOT NULL,
107
- prompt TEXT NOT NULL,
108
- intent_json TEXT NOT NULL,
109
- routed_to_type TEXT,
110
- routed_to_name TEXT,
111
- is_forced INTEGER DEFAULT 0,
112
- obeyed INTEGER,
113
- classification_ms INTEGER,
114
- fallback_used INTEGER DEFAULT 0,
115
- refusal_reason TEXT,
116
- first_tool_name TEXT,
117
- first_tool_ts INTEGER,
118
- completed_ts INTEGER,
119
- total_execution_ms INTEGER,
120
- completion_reason TEXT,
121
- downstream_task_chain TEXT,
122
- injection_version TEXT,
123
- experiment_id TEXT,
124
- experiment_group TEXT,
125
- skill_confidence REAL,
126
- skill_source TEXT,
127
- created_at TEXT DEFAULT (datetime('now'))
128
- );
129
-
130
- CREATE TABLE token_usage (
131
- id INTEGER PRIMARY KEY AUTOINCREMENT,
132
- session_id TEXT NOT NULL,
133
- timestamp INTEGER NOT NULL,
134
- input_tokens INTEGER NOT NULL,
135
- output_tokens INTEGER NOT NULL,
136
- total_tokens INTEGER NOT NULL,
137
- model TEXT,
138
- tool_name TEXT,
139
- created_at TEXT DEFAULT (datetime('now'))
140
- );
141
-
142
- CREATE TABLE skill_invocations (
143
- id TEXT PRIMARY KEY,
144
- route_request_id TEXT,
145
- session_id TEXT NOT NULL,
146
- agent_id TEXT,
147
- skill_id TEXT NOT NULL,
148
- invocation_type TEXT NOT NULL,
149
- reason TEXT,
150
- workflow TEXT,
151
- phase TEXT,
152
- feature_slug TEXT,
153
- artifact_path TEXT,
154
- depth INTEGER DEFAULT 0,
155
- success INTEGER DEFAULT 1,
156
- error TEXT,
157
- timestamp INTEGER NOT NULL,
158
- created_at TEXT DEFAULT (datetime('now'))
159
- );
160
- -- 故意不创建以下任何索引(migration 必须补齐)
161
- `);
162
- }
163
-
164
- /**
165
- * 老库——只缺 sessions.first_prompt 列(其他列齐全 + 完整索引)。
166
- * 单独 case,验证 addColumnIfMissing 补齐。
167
- */
168
- function execLegacySchemaMissingFirstPrompt(db: Database.Database): void {
169
- db.exec(`
170
- CREATE TABLE sessions (
171
- session_id TEXT PRIMARY KEY,
172
- project_path TEXT NOT NULL,
173
- status TEXT NOT NULL DEFAULT 'active',
174
- start_time TEXT NOT NULL,
175
- end_time TEXT,
176
- last_event_time TEXT,
177
- event_count INTEGER DEFAULT 0,
178
- created_at TEXT DEFAULT (datetime('now')),
179
- updated_at TEXT DEFAULT (datetime('now'))
180
- );
181
- `);
182
- }
183
-
184
- describe('H4: storage migration idempotent', () => {
185
- let tmp: string;
186
- let dbPath: string;
187
-
188
- beforeEach(() => {
189
- tmp = mkdtempSync(join(tmpdir(), 'forge-h4-migration-'));
190
- dbPath = join(tmp, 'data.db');
191
- });
192
-
193
- afterEach(() => {
194
- rmSync(tmp, { recursive: true, force: true });
195
- vi.restoreAllMocks();
196
- });
197
-
198
- it('baseline (缺索引): 老库索引齐缺,SQLiteStorage 启动后补齐全部 migration 索引', () => {
199
- // 1) 手工写入只缺索引的旧 schema
200
- const raw = new Database(dbPath);
201
- execLegacySchemaMissingIndexes(raw);
202
- const beforeIdx = listIndexes(raw);
203
- expect(beforeIdx.has('idx_routing_events_type_ts')).toBe(false);
204
- expect(beforeIdx.has('idx_skill_invocations_workflow')).toBe(false);
205
- expect(beforeIdx.has('idx_skill_invocations_feature')).toBe(false);
206
- expect(beforeIdx.has('idx_sessions_start_time')).toBe(false);
207
- expect(beforeIdx.has('idx_events_session_ts')).toBe(false);
208
- raw.close();
209
-
210
- // 2) 用 SQLiteStorage 打开 → 触发 schema.sql + runMigrations
211
- const storage = new SQLiteStorage(dbPath);
212
- const db = storage.getDatabase();
213
-
214
- // 3) 关键索引全部补齐(涵盖 spec 重复清单中所有 migration-only / 双写索引)
215
- const idx = listIndexes(db);
216
- const expected = [
217
- 'idx_sessions_start_time',
218
- 'idx_events_session_ts',
219
- 'idx_routing_events_session_ts',
220
- 'idx_skill_invocations_session_ts',
221
- 'idx_routing_events_obeyed_ts',
222
- 'idx_events_session_hook',
223
- 'idx_injections_session_handler',
224
- 'idx_routing_events_type_ts',
225
- 'idx_skill_invocations_workflow',
226
- 'idx_skill_invocations_feature',
227
- ];
228
- for (const name of expected) {
229
- expect(idx.has(name), `expected index ${name} to exist after migration`).toBe(true);
230
- }
231
-
232
- storage.close();
233
- });
234
-
235
- it('baseline (缺列): 老库缺 sessions.first_prompt,migration ALTER 补齐', () => {
236
- const raw = new Database(dbPath);
237
- execLegacySchemaMissingFirstPrompt(raw);
238
- expect(listColumns(raw, 'sessions').has('first_prompt')).toBe(false);
239
- raw.close();
240
-
241
- const storage = new SQLiteStorage(dbPath);
242
- const db = storage.getDatabase();
243
-
244
- // first_prompt 列由 addColumnIfMissing 补齐
245
- expect(listColumns(db, 'sessions').has('first_prompt')).toBe(true);
246
-
247
- storage.close();
248
- });
249
-
250
- it('新库(完整 schema.sql 初始化)再启动 SQLiteStorage:migration 不会重复创建索引', () => {
251
- // 1) 用完整 schema.sql 初始化(模拟 base.initSchema 已跑完,sqlite-base 同样这么干)
252
- expect(existsSync(SCHEMA_PATH)).toBe(true);
253
- const schemaSql = readFileSync(SCHEMA_PATH, 'utf-8');
254
- const raw = new Database(dbPath);
255
- raw.exec(schemaSql);
256
- const indexesAfterSchema = listIndexes(raw);
257
- raw.close();
258
-
259
- // 2) 监听 logger.debug,统计"migration created idx_xxx"次数
260
- const debugSpy = vi.spyOn(logger, 'debug').mockImplementation(() => {});
261
- const warnSpy = vi.spyOn(logger, 'warn').mockImplementation(() => {});
262
- const errorSpy = vi.spyOn(logger, 'error').mockImplementation(() => {});
263
-
264
- // 3) new SQLiteStorage → runMigrations 应当跳过所有 createIndexIfMissing
265
- const storage = new SQLiteStorage(dbPath);
266
-
267
- const createdCalls = debugSpy.mock.calls.filter(
268
- args => typeof args[0] === 'string' && args[0].includes('migration created'),
269
- );
270
- expect(createdCalls.length).toBe(0);
271
-
272
- // 不应有 warn/error
273
- expect(warnSpy).not.toHaveBeenCalled();
274
- expect(errorSpy).not.toHaveBeenCalled();
275
-
276
- // 索引集合相比 schema.sql 初始化后无变化
277
- const indexesAfterMigration = listIndexes(storage.getDatabase());
278
- expect(indexesAfterMigration).toEqual(indexesAfterSchema);
279
-
280
- storage.close();
281
- });
282
-
283
- it('idempotent: 连续多次 new SQLiteStorage 不改变索引/列数量', () => {
284
- const snapshots: Array<{ idx: Set<string>; cols: Set<string> }> = [];
285
-
286
- for (let i = 0; i < 3; i++) {
287
- const storage = new SQLiteStorage(dbPath);
288
- const db = storage.getDatabase();
289
- snapshots.push({
290
- idx: listIndexes(db),
291
- cols: listColumns(db, 'skill_invocations'),
292
- });
293
- storage.close();
294
- }
295
-
296
- // 索引集合在三次之间完全一致
297
- expect(snapshots[0].idx).toEqual(snapshots[1].idx);
298
- expect(snapshots[1].idx).toEqual(snapshots[2].idx);
299
-
300
- // skill_invocations 列集合在三次之间一致
301
- expect(snapshots[0].cols).toEqual(snapshots[1].cols);
302
- expect(snapshots[1].cols).toEqual(snapshots[2].cols);
303
- });
304
- });