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