@winspan/claude-forge 8.53.2 → 8.54.4

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 (394) 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/official/official-openspec.md +89 -0
  42. package/dist/skills/official-skills.d.ts.map +1 -1
  43. package/dist/skills/official-skills.js +1 -0
  44. package/dist/skills/official-skills.js.map +1 -1
  45. package/dist/skills/registry.d.ts.map +1 -1
  46. package/dist/skills/registry.js +13 -2
  47. package/dist/skills/registry.js.map +1 -1
  48. package/dist/skills/semantic-matcher.d.ts +2 -2
  49. package/dist/skills/semantic-matcher.d.ts.map +1 -1
  50. package/dist/skills/semantic-matcher.js +14 -19
  51. package/dist/skills/semantic-matcher.js.map +1 -1
  52. package/dist/skills/upgrade-engine.d.ts +3 -1
  53. package/dist/skills/upgrade-engine.d.ts.map +1 -1
  54. package/dist/skills/upgrade-engine.js +25 -14
  55. package/dist/skills/upgrade-engine.js.map +1 -1
  56. package/dist/web/analytics/weekly-report.d.ts.map +1 -1
  57. package/dist/web/analytics/weekly-report.js +21 -29
  58. package/dist/web/analytics/weekly-report.js.map +1 -1
  59. package/dist/web/routes/patch.d.ts.map +1 -1
  60. package/dist/web/routes/patch.js +32 -2
  61. package/dist/web/routes/patch.js.map +1 -1
  62. package/dist/web/routes/sessions.d.ts.map +1 -1
  63. package/dist/web/routes/sessions.js +9 -7
  64. package/dist/web/routes/sessions.js.map +1 -1
  65. package/dist/web/routes/trace.d.ts.map +1 -1
  66. package/dist/web/routes/trace.js +2 -3
  67. package/dist/web/routes/trace.js.map +1 -1
  68. package/dist/web/server.d.ts.map +1 -1
  69. package/dist/web/server.js +3 -2
  70. package/dist/web/server.js.map +1 -1
  71. package/package.json +12 -2
  72. package/scripts/postinstall.cjs +21 -0
  73. package/.claude/CLAUDE.md +0 -17
  74. package/.eslintrc.js +0 -23
  75. package/.prettierrc +0 -8
  76. package/ARCHITECTURE_ISSUES.md +0 -249
  77. package/CLAUDE.md +0 -265
  78. package/CLAUDE.md.backup +0 -488
  79. package/docs/concurrent-agents.md +0 -129
  80. package/docs/design/architecture-review-20260516.md +0 -232
  81. package/docs/design/fix-skills-data-and-set-leak-spec-20260516-1300.md +0 -219
  82. package/docs/design/h1-storage-aggregation-spec-20260518-1121.md +0 -299
  83. package/docs/design/h2-getdatabase-encapsulation-spec-20260518-1450.md +0 -191
  84. package/docs/design/h3-fallback-removal-spec-20260518-1245.md +0 -76
  85. package/docs/design/h4-index-dedup-spec-20260518-1230.md +0 -109
  86. package/docs/design/h6-services-migration-spec-20260518-1355.md +0 -82
  87. package/docs/design/hook-failure-queue-spec-20260516-1530.md +0 -204
  88. package/docs/design/l1-swarm-protocol-extract-spec-20260518-1605.md +0 -106
  89. package/docs/design/m10-forge-paths-spec-20260518-1320.md +0 -121
  90. package/docs/design/m2-m3-tool-input-spec-20260518-1425.md +0 -131
  91. package/docs/design/m7-routing-event-association-spec-20260518-1545.md +0 -103
  92. package/docs/design/project-path-gitroot-spec-20260518-1715.md +0 -134
  93. package/docs/design/refactor-phase1-spec-20260515-1600.md +0 -543
  94. package/docs/design/refactor-phase2-spec-20260515-1700.md +0 -424
  95. package/docs/design/skill-ai-upgrade-spec-20260518-1930.md +0 -297
  96. package/docs/design/task-active-gc-spec-20260518-1745.md +0 -146
  97. package/docs/design/tasks-list-filter-pagination-spec-20260518-0930.md +0 -208
  98. package/docs/implementation/daemon-skill-sync-changelog-20260518-2000.md +0 -22
  99. package/docs/implementation/fix-skills-data-and-set-leak-changelog-20260516-1300.md +0 -104
  100. package/docs/implementation/h1-storage-aggregation-changelog-20260518-1121.md +0 -82
  101. package/docs/implementation/h2-final-changelog-20260518-1530.md +0 -61
  102. package/docs/implementation/h2-phase1-safety-net-changelog-20260518-1450.md +0 -70
  103. package/docs/implementation/h2-phase2-operations-changelog-20260518-1450.md +0 -120
  104. package/docs/implementation/h2-phase3-callsites-changelog-20260518-1450.md +0 -71
  105. package/docs/implementation/h3-fallback-removal-changelog-20260518-1245.md +0 -71
  106. package/docs/implementation/h4-index-dedup-changelog-20260518-1230.md +0 -60
  107. package/docs/implementation/h6-services-migration-changelog-20260518-1355.md +0 -46
  108. package/docs/implementation/h7-m9-defaults-changelog-20260518-1300.md +0 -46
  109. package/docs/implementation/hook-failure-queue-changelog-20260516-1530.md +0 -196
  110. package/docs/implementation/hotfix-daemon-event-reject-20260516-1430.md +0 -56
  111. package/docs/implementation/l1-swarm-protocol-extract-changelog-20260518-1605.md +0 -45
  112. package/docs/implementation/l3-l4-daemon-perf-changelog-20260518-1410.md +0 -63
  113. package/docs/implementation/l6-l8-final-cleanup-changelog-20260518-1640.md +0 -38
  114. package/docs/implementation/m1-m4-m5-l7-cleanup-changelog-20260518-1310.md +0 -58
  115. package/docs/implementation/m10-forge-paths-changelog-20260518-1320.md +0 -60
  116. package/docs/implementation/m2-m3-tool-input-changelog-20260518-1425.md +0 -43
  117. package/docs/implementation/m6-m8-naming-shutdown-changelog-20260518-1340.md +0 -56
  118. package/docs/implementation/m7-routing-association-changelog-20260518-1545.md +0 -69
  119. package/docs/implementation/project-path-gitroot-changelog-20260518-1715.md +0 -63
  120. package/docs/implementation/refactor-phase1-changelog-20260515-1630.md +0 -354
  121. package/docs/implementation/refactor-phase2-changelog-20260515-1705.md +0 -421
  122. package/docs/implementation/skill-ai-upgrade-changelog-20260518-1930.md +0 -49
  123. package/docs/implementation/task-active-gc-changelog-20260518-1745.md +0 -35
  124. package/docs/implementation/task-title-summary-changelog-20260518-1130.md +0 -39
  125. package/docs/implementation/tasks-detail-back-loses-filters-changelog-20260518-1100.md +0 -22
  126. package/docs/implementation/tasks-list-filter-pagination-changelog-20260518-0930.md +0 -72
  127. package/docs/implementation/tasks-page-white-screen-hotfix-changelog-20260518-1015.md +0 -56
  128. package/docs/reviews/claudemd-template-sync.md +0 -54
  129. package/docs/reviews/task-title-summary.md +0 -92
  130. package/docs/reviews/tasks-detail-back-loses-filters.md +0 -58
  131. package/docs/reviews/tasks-filter-pagination.md +0 -80
  132. package/docs/reviews/tasks-page-white-screen-hotfix.md +0 -126
  133. package/docs/ruflo-learning-strategy.md +0 -322
  134. package/docs/skills-deduplication-analysis.md +0 -83
  135. package/docs/skills-multiformat-support.md +0 -177
  136. package/docs/skills-third-party.md +0 -183
  137. package/docs/testing/tasks-filter-pagination-test-report.md +0 -86
  138. package/forge +0 -321
  139. package/playwright.config.ts +0 -40
  140. package/scripts/demo-v2.ts +0 -91
  141. package/scripts/dev-daemon.sh +0 -232
  142. package/scripts/dev-web.ts +0 -109
  143. package/scripts/e2e-mcp-link.ts +0 -423
  144. package/scripts/e2e-methodology-quality.ts +0 -253
  145. package/scripts/e2e-routing.ts +0 -456
  146. package/scripts/e2e-user-methodology.ts +0 -326
  147. package/scripts/e2e-web-workflows.ts +0 -299
  148. package/scripts/migrate-legacy-to-dynamic.sql +0 -108
  149. package/scripts/regenerate-execution-docs.ts +0 -116
  150. package/scripts/sync-agent-skills.ts +0 -193
  151. package/scripts/test-hook.sh +0 -71
  152. package/scripts/verify-skill-loading.ts +0 -62
  153. package/src/claudemd/claudemd-generator.ts +0 -568
  154. package/src/claudemd/convention-extractor.ts +0 -69
  155. package/src/claudemd/index.ts +0 -35
  156. package/src/claudemd/persona-manager.ts +0 -88
  157. package/src/claudemd/resume-manager.ts +0 -236
  158. package/src/claudemd/tech-detector.ts +0 -220
  159. package/src/claudemd/templates/swarm-protocol.md +0 -222
  160. package/src/cli/commands/claudemd.ts +0 -84
  161. package/src/cli/commands/config.ts +0 -46
  162. package/src/cli/commands/daemon.ts +0 -310
  163. package/src/cli/commands/executions.ts +0 -115
  164. package/src/cli/commands/init.ts +0 -204
  165. package/src/cli/commands/logs.ts +0 -181
  166. package/src/cli/commands/mcp.ts +0 -242
  167. package/src/cli/commands/menu.ts +0 -357
  168. package/src/cli/commands/skills.ts +0 -328
  169. package/src/cli/commands/stats.ts +0 -73
  170. package/src/cli/commands/status.ts +0 -69
  171. package/src/cli/commands/template.ts +0 -77
  172. package/src/cli/commands/trace.ts +0 -148
  173. package/src/cli/index.ts +0 -42
  174. package/src/cli/init/hook-manager.ts +0 -132
  175. package/src/core/ai/provider.ts +0 -308
  176. package/src/core/ai/types.ts +0 -51
  177. package/src/core/config.ts +0 -124
  178. package/src/core/constants.ts +0 -67
  179. package/src/core/event-fields.ts +0 -32
  180. package/src/core/queue/index.ts +0 -192
  181. package/src/core/storage/base.ts +0 -302
  182. package/src/core/storage/events.ts +0 -434
  183. package/src/core/storage/injections.ts +0 -78
  184. package/src/core/storage/maintenance.ts +0 -59
  185. package/src/core/storage/migrations/002_add_skill_tracking.sql +0 -6
  186. package/src/core/storage/migrations/003_add_skill_invocations.sql +0 -23
  187. package/src/core/storage/performance-indexes.sql +0 -23
  188. package/src/core/storage/routing.ts +0 -322
  189. package/src/core/storage/rows.ts +0 -112
  190. package/src/core/storage/schema.sql +0 -224
  191. package/src/core/storage/sessions.ts +0 -168
  192. package/src/core/storage/skills.ts +0 -233
  193. package/src/core/storage/sqlite.ts +0 -293
  194. package/src/core/storage/tasks.ts +0 -318
  195. package/src/core/storage/token-usage.ts +0 -93
  196. package/src/core/types.ts +0 -181
  197. package/src/core/utils/error-handler.ts +0 -257
  198. package/src/core/utils/forge-resume-block.ts +0 -74
  199. package/src/core/utils/format.ts +0 -69
  200. package/src/core/utils/git.ts +0 -23
  201. package/src/core/utils/logger.ts +0 -134
  202. package/src/core/utils/lru-cache.ts +0 -54
  203. package/src/core/utils/path.ts +0 -19
  204. package/src/core/utils/session.ts +0 -26
  205. package/src/core/utils/time.ts +0 -37
  206. package/src/core/utils/token-tracker.ts +0 -97
  207. package/src/daemon/event-parser.ts +0 -36
  208. package/src/daemon/handlers/history-exporter.ts +0 -117
  209. package/src/daemon/handlers/post-tool-use.ts +0 -54
  210. package/src/daemon/handlers/stop.ts +0 -208
  211. package/src/daemon/handlers/user-prompt.ts +0 -178
  212. package/src/daemon/hook-sync.ts +0 -91
  213. package/src/daemon/index.ts +0 -312
  214. package/src/daemon/launchd/com.claude-forge.daemon.plist.template +0 -47
  215. package/src/daemon/launchd-installer.ts +0 -260
  216. package/src/daemon/lifecycle.ts +0 -128
  217. package/src/daemon/router.ts +0 -40
  218. package/src/daemon/server.ts +0 -196
  219. package/src/daemon/services/task-segmenter.ts +0 -112
  220. package/src/daemon/skill-sync.ts +0 -88
  221. package/src/hooks/hook-lib.sh +0 -118
  222. package/src/hooks/notification.sh +0 -35
  223. package/src/hooks/post-tool-use.sh +0 -61
  224. package/src/hooks/pre-tool-use.sh +0 -63
  225. package/src/hooks/stop.sh +0 -43
  226. package/src/hooks/user-prompt-submit.sh +0 -69
  227. package/src/mcp/server.ts +0 -322
  228. package/src/skills/index.ts +0 -2
  229. package/src/skills/invocation-guard.ts +0 -177
  230. package/src/skills/matcher.ts +0 -148
  231. package/src/skills/official/code-simplifier.md +0 -52
  232. package/src/skills/official/find-skills.md +0 -142
  233. package/src/skills/official/official-api-design.md +0 -30
  234. package/src/skills/official/official-architecture-decision.md +0 -41
  235. package/src/skills/official/official-bmad.md +0 -118
  236. package/src/skills/official/official-db-schema-design.md +0 -34
  237. package/src/skills/official/official-debug.md +0 -25
  238. package/src/skills/official/official-doc-driven.md +0 -31
  239. package/src/skills/official/official-harness-engineering.md +0 -108
  240. package/src/skills/official/official-performance-optimization.md +0 -30
  241. package/src/skills/official/official-pr-review.md +0 -35
  242. package/src/skills/official/official-release-checklist.md +0 -30
  243. package/src/skills/official/official-security-hardening.md +0 -32
  244. package/src/skills/official/official-spec-driven-design.md +0 -31
  245. package/src/skills/official/planning-with-files.md +0 -241
  246. package/src/skills/official/ui-ux-pro-max.md +0 -105
  247. package/src/skills/official/webapp-testing.md +0 -96
  248. package/src/skills/official-skills.ts +0 -89
  249. package/src/skills/registry.ts +0 -355
  250. package/src/skills/semantic-matcher.ts +0 -234
  251. package/src/skills/tools/pipeline-suggest.ts +0 -226
  252. package/src/skills/tools/skill-invoke.ts +0 -168
  253. package/src/skills/tools/skill-list.ts +0 -59
  254. package/src/skills/upgrade-engine.ts +0 -541
  255. package/src/skills/upgrade-prompt.ts +0 -84
  256. package/src/templates/go.yaml +0 -53
  257. package/src/templates/python.yaml +0 -59
  258. package/src/templates/react.yaml +0 -55
  259. package/src/templates/template-manager.ts +0 -170
  260. package/src/web/analytics/anti-pattern-detector.ts +0 -367
  261. package/src/web/analytics/drift-detector.ts +0 -219
  262. package/src/web/analytics/weekly-report.ts +0 -431
  263. package/src/web/auth-middleware.ts +0 -54
  264. package/src/web/routes/_helpers.ts +0 -34
  265. package/src/web/routes/ai.ts +0 -204
  266. package/src/web/routes/auth.ts +0 -22
  267. package/src/web/routes/drift.ts +0 -25
  268. package/src/web/routes/error-handler.ts +0 -120
  269. package/src/web/routes/events.ts +0 -47
  270. package/src/web/routes/insights.ts +0 -43
  271. package/src/web/routes/patch.ts +0 -117
  272. package/src/web/routes/reports.ts +0 -34
  273. package/src/web/routes/rules.ts +0 -76
  274. package/src/web/routes/sessions.ts +0 -250
  275. package/src/web/routes/skill-stats.ts +0 -92
  276. package/src/web/routes/skills.ts +0 -350
  277. package/src/web/routes/static.ts +0 -67
  278. package/src/web/routes/stats.ts +0 -50
  279. package/src/web/routes/status.ts +0 -30
  280. package/src/web/routes/tasks.ts +0 -193
  281. package/src/web/routes/token-usage.ts +0 -20
  282. package/src/web/routes/trace.ts +0 -126
  283. package/src/web/routes/types.ts +0 -57
  284. package/src/web/server.ts +0 -134
  285. package/src/web/ssrf-guard.ts +0 -112
  286. package/src/web/static/index.html +0 -3251
  287. package/src/web/static/vendor/chart.umd.min.js +0 -20
  288. package/tests/e2e/dashboard.spec.ts +0 -205
  289. package/tests/e2e/routing-skill-e2e.test.ts +0 -39
  290. package/tests/helpers/mock-ai.ts +0 -92
  291. package/tests/helpers/mock-storage.ts +0 -159
  292. package/tests/integration/claudemd-generator.test.ts +0 -90
  293. package/tests/integration/queue-replay.integration.test.ts +0 -193
  294. package/tests/integration/tasks-filter.integration.test.ts +0 -154
  295. package/tests/integration/web-analytics.integration.test.ts +0 -133
  296. package/tests/integration/web-stats.integration.test.ts +0 -135
  297. package/tests/integration/web-trace.integration.test.ts +0 -175
  298. package/tests/performance/database.benchmark.ts +0 -161
  299. package/tests/semantic-matcher.test.ts +0 -99
  300. package/tests/skill-matcher.test.ts +0 -110
  301. package/tests/unit/ai-provider-retry.test.ts +0 -194
  302. package/tests/unit/ai-provider-vision.test.ts +0 -224
  303. package/tests/unit/claudemd-generator.test.ts +0 -68
  304. package/tests/unit/cli-mcp.test.ts +0 -141
  305. package/tests/unit/core/forge-paths.test.ts +0 -99
  306. package/tests/unit/daemon/hook-sync.test.ts +0 -71
  307. package/tests/unit/daemon/post-tool-use.test.ts +0 -121
  308. package/tests/unit/daemon/skill-sync.test.ts +0 -75
  309. package/tests/unit/daemon/stop-handler-behavior-summary.test.ts +0 -202
  310. package/tests/unit/daemon/task-segmenter-recover.test.ts +0 -84
  311. package/tests/unit/event-fields.test.ts +0 -88
  312. package/tests/unit/event-parser.test.ts +0 -55
  313. package/tests/unit/handlers.test.ts +0 -171
  314. package/tests/unit/hooks/resolve-project-path.test.ts +0 -122
  315. package/tests/unit/invocation-guard.test.ts +0 -125
  316. package/tests/unit/queue.test.ts +0 -272
  317. package/tests/unit/router.test.ts +0 -138
  318. package/tests/unit/security.test.ts +0 -128
  319. package/tests/unit/skill-invocations-workflow.test.ts +0 -495
  320. package/tests/unit/skill-registry.test.ts +0 -94
  321. package/tests/unit/skills/invocation-guard-ttl.test.ts +0 -211
  322. package/tests/unit/skills/official-skills-loader.test.ts +0 -126
  323. package/tests/unit/skills/registry-multiformat.test.ts +0 -92
  324. package/tests/unit/skills/upgrade-engine-parse.test.ts +0 -138
  325. package/tests/unit/skills/upgrade-engine.test.ts +0 -401
  326. package/tests/unit/skills/upgrade-prompt.test.ts +0 -89
  327. package/tests/unit/socket-server.test.ts +0 -183
  328. package/tests/unit/storage/event-operations-aggregates.test.ts +0 -342
  329. package/tests/unit/storage/migration-idempotent.test.ts +0 -304
  330. package/tests/unit/storage/routing-aggregates.test.ts +0 -276
  331. package/tests/unit/storage/routing.test.ts +0 -117
  332. package/tests/unit/storage/schema-missing.test.ts +0 -81
  333. package/tests/unit/storage/session-operations-aggregates.test.ts +0 -120
  334. package/tests/unit/storage/sessions-aggregate.test.ts +0 -435
  335. package/tests/unit/storage/skill-operations-counts.test.ts +0 -106
  336. package/tests/unit/storage/skills-aggregates.test.ts +0 -104
  337. package/tests/unit/storage/sqlite-refactor-harness.test.ts +0 -314
  338. package/tests/unit/storage/task-operations-counts.test.ts +0 -46
  339. package/tests/unit/storage/tasks-getById.test.ts +0 -343
  340. package/tests/unit/storage/tasks-stale-gc.test.ts +0 -86
  341. package/tests/unit/storage.test.ts +0 -172
  342. package/tests/unit/token-usage.test.ts +0 -144
  343. package/tests/unit/type-guards.test.ts +0 -201
  344. package/tests/unit/utils/format.test.ts +0 -189
  345. package/tests/unit/utils/session.test.ts +0 -89
  346. package/tests/unit/utils/time.test.ts +0 -112
  347. package/tests/unit/web/navigation-back-contract.test.ts +0 -134
  348. package/tests/unit/web/routes-auth.test.ts +0 -93
  349. package/tests/unit/web/routes-events.test.ts +0 -101
  350. package/tests/unit/web/routes-rules.test.ts +0 -182
  351. package/tests/unit/web/routes-sessions.test.ts +0 -181
  352. package/tests/unit/web/routes-skill-stats.test.ts +0 -179
  353. package/tests/unit/web/routes-stats.test.ts +0 -92
  354. package/tests/unit/web/routes-tasks.test.ts +0 -385
  355. package/tests/unit/web/task-title-contract.test.ts +0 -210
  356. package/tests/unit/web/tasks-component-contract.test.ts +0 -179
  357. package/tsconfig.json +0 -22
  358. package/vitest.config.ts +0 -21
  359. package/vitest.integration.config.ts +0 -16
  360. package/web/CLAUDE.md +0 -20
  361. package/web/index.html +0 -13
  362. package/web/package-lock.json +0 -4854
  363. package/web/package.json +0 -35
  364. package/web/postcss.config.js +0 -6
  365. package/web/src/App.tsx +0 -110
  366. package/web/src/components/CodeBlock.tsx +0 -31
  367. package/web/src/components/Confirm.tsx +0 -96
  368. package/web/src/components/Drawer.tsx +0 -60
  369. package/web/src/components/Layout.tsx +0 -145
  370. package/web/src/components/MarkdownRenderer.tsx +0 -77
  371. package/web/src/components/SearchInput.tsx +0 -31
  372. package/web/src/components/SessionDetailContent.tsx +0 -157
  373. package/web/src/components/Toast.tsx +0 -92
  374. package/web/src/index.css +0 -19
  375. package/web/src/main.tsx +0 -31
  376. package/web/src/pages/AIConfig.tsx +0 -233
  377. package/web/src/pages/Dashboard.tsx +0 -572
  378. package/web/src/pages/Events.tsx +0 -271
  379. package/web/src/pages/Reports.tsx +0 -428
  380. package/web/src/pages/SessionDetail.tsx +0 -162
  381. package/web/src/pages/Sessions.tsx +0 -205
  382. package/web/src/pages/Skills.tsx +0 -180
  383. package/web/src/pages/TaskDetail.tsx +0 -515
  384. package/web/src/pages/Tasks.tsx +0 -415
  385. package/web/src/utils/auth.ts +0 -59
  386. package/web/src/utils/export.ts +0 -54
  387. package/web/src/utils/navigation.ts +0 -25
  388. package/web/src/utils/task-title.ts +0 -49
  389. package/web/src/utils/time.ts +0 -13
  390. package/web/tailwind.config.js +0 -11
  391. package/web/tsconfig.json +0 -21
  392. package/web/tsconfig.node.json +0 -10
  393. package/web/vite.config.ts +0 -76
  394. 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
- });