@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,194 +0,0 @@
1
- /**
2
- * Tests for ClaudeProvider retry + timeout logic.
3
- *
4
- * The SDK client is replaced with a mock object exposing only
5
- * `messages.create`; the provider never hits the network.
6
- */
7
-
8
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
9
- import { ClaudeProvider, timing } from '../../src/core/ai/provider.js';
10
-
11
- /** Shape of what `messages.create` must return for the provider to succeed. */
12
- function makeOkResponse(text = 'ok') {
13
- return {
14
- content: [{ type: 'text', text }],
15
- usage: { input_tokens: 3, output_tokens: 5 },
16
- model: 'claude-sonnet-4-20250514',
17
- };
18
- }
19
-
20
- /** Build an error object that mimics an Anthropic APIError. */
21
- function apiError(status: number, message = `HTTP ${status}`): Error & { status: number } {
22
- const err = new Error(message) as Error & { status: number };
23
- err.status = status;
24
- return err;
25
- }
26
-
27
- function abortError(message = 'Request aborted'): Error {
28
- const err = new Error(message);
29
- err.name = 'AbortError';
30
- return err;
31
- }
32
-
33
- function installMockClient(provider: ClaudeProvider, create: ReturnType<typeof vi.fn>) {
34
- // @ts-expect-error — overriding internal client for test isolation
35
- provider.client = { messages: { create } };
36
- }
37
-
38
- describe('ClaudeProvider retry + timeout', () => {
39
- let provider: ClaudeProvider;
40
- let delaySpy: ReturnType<typeof vi.spyOn>;
41
-
42
- beforeEach(() => {
43
- provider = new ClaudeProvider('test-key', 'claude-sonnet-4-20250514');
44
- vi.useRealTimers();
45
- // Skip the actual backoff wait so tests finish quickly while still being
46
- // able to assert the backoff durations via spy call args.
47
- delaySpy = vi.spyOn(timing, 'delay').mockResolvedValue();
48
- });
49
-
50
- afterEach(() => {
51
- vi.restoreAllMocks();
52
- vi.useRealTimers();
53
- });
54
-
55
- it('retries on 429 rate limit then succeeds', async () => {
56
- const create = vi
57
- .fn()
58
- .mockRejectedValueOnce(apiError(429, 'rate limited'))
59
- .mockRejectedValueOnce(apiError(429, 'rate limited'))
60
- .mockResolvedValueOnce(makeOkResponse('done'));
61
- installMockClient(provider, create);
62
-
63
- const res = await provider.complete('hi', { maxRetries: 2, timeoutMs: 1000 });
64
-
65
- expect(res).toBe('done');
66
- expect(create).toHaveBeenCalledTimes(3);
67
- });
68
-
69
- it('retries on 5xx errors then succeeds', async () => {
70
- const create = vi
71
- .fn()
72
- .mockRejectedValueOnce(apiError(503, 'unavailable'))
73
- .mockRejectedValueOnce(apiError(500, 'boom'))
74
- .mockResolvedValueOnce(makeOkResponse('recovered'));
75
- installMockClient(provider, create);
76
-
77
- const res = await provider.complete('hi', { maxRetries: 2, timeoutMs: 1000 });
78
-
79
- expect(res).toBe('recovered');
80
- expect(create).toHaveBeenCalledTimes(3);
81
- });
82
-
83
- it('gives up after maxRetries when errors keep coming', async () => {
84
- const create = vi.fn().mockRejectedValue(apiError(500, 'persistent'));
85
- installMockClient(provider, create);
86
-
87
- await expect(
88
- provider.complete('hi', { maxRetries: 2, timeoutMs: 1000 }),
89
- ).rejects.toThrow('persistent');
90
-
91
- // attempt 0 + 2 retries = 3 calls
92
- expect(create).toHaveBeenCalledTimes(3);
93
- });
94
-
95
- it('does not retry on 4xx other than 429', async () => {
96
- const create = vi.fn().mockRejectedValue(apiError(400, 'bad request'));
97
- installMockClient(provider, create);
98
-
99
- await expect(
100
- provider.complete('hi', { maxRetries: 3, timeoutMs: 1000 }),
101
- ).rejects.toThrow('bad request');
102
-
103
- expect(create).toHaveBeenCalledTimes(1);
104
- });
105
-
106
- it('does not retry on 401 auth errors', async () => {
107
- const create = vi.fn().mockRejectedValue(apiError(401, 'unauthorized'));
108
- installMockClient(provider, create);
109
-
110
- await expect(
111
- provider.complete('hi', { maxRetries: 3, timeoutMs: 1000 }),
112
- ).rejects.toThrow('unauthorized');
113
-
114
- expect(create).toHaveBeenCalledTimes(1);
115
- });
116
-
117
- it('timeout triggers retry and eventually surfaces', async () => {
118
- // Each call hangs longer than timeoutMs; we reject with AbortError to mirror
119
- // what the SDK throws when its signal fires, so we don't have to wire a
120
- // real long-running pending promise into the test.
121
- const create = vi.fn().mockImplementation(
122
- () => new Promise((_resolve, reject) => {
123
- setTimeout(() => reject(abortError()), 20);
124
- }),
125
- );
126
- installMockClient(provider, create);
127
-
128
- await expect(
129
- provider.complete('hi', { maxRetries: 2, timeoutMs: 10 }),
130
- ).rejects.toMatchObject({ name: 'AbortError' });
131
-
132
- expect(create).toHaveBeenCalledTimes(3);
133
- });
134
-
135
- it('uses exponential backoff between retries (1000ms, 2000ms, ...)', async () => {
136
- const create = vi
137
- .fn()
138
- .mockRejectedValueOnce(apiError(500))
139
- .mockRejectedValueOnce(apiError(500))
140
- .mockResolvedValueOnce(makeOkResponse());
141
- installMockClient(provider, create);
142
-
143
- const res = await provider.complete('hi', { maxRetries: 2, timeoutMs: 5000 });
144
-
145
- expect(res).toBe('ok');
146
- expect(create).toHaveBeenCalledTimes(3);
147
- // delay() is called once per retry with exponential durations.
148
- const delayCalls = delaySpy.mock.calls.map((args) => args[0]);
149
- expect(delayCalls).toEqual([1000, 2000]);
150
- });
151
-
152
- it('propagates external signal aborts without retry', async () => {
153
- const create = vi.fn().mockImplementation(() => {
154
- return new Promise((_resolve, reject) => {
155
- // Simulate SDK surfacing AbortError once external signal fires.
156
- setTimeout(() => reject(abortError('user abort')), 5);
157
- });
158
- });
159
- installMockClient(provider, create);
160
-
161
- const controller = new AbortController();
162
- setTimeout(() => controller.abort(), 1);
163
-
164
- await expect(
165
- provider.complete('hi', { maxRetries: 3, timeoutMs: 1000, signal: controller.signal }),
166
- ).rejects.toThrow('user abort');
167
-
168
- // Only one attempt: external aborts short-circuit retry.
169
- expect(create).toHaveBeenCalledTimes(1);
170
- });
171
-
172
- it('completeWithUsage returns usage and model on success', async () => {
173
- const create = vi.fn().mockResolvedValue(makeOkResponse('hello'));
174
- installMockClient(provider, create);
175
-
176
- const res = await provider.completeWithUsage('prompt', { timeoutMs: 500 });
177
- expect(res.text).toBe('hello');
178
- expect(res.usage).toEqual({ input_tokens: 3, output_tokens: 5 });
179
- expect(res.model).toBe('claude-sonnet-4-20250514');
180
- });
181
-
182
- it('passes signal and maxRetries:0 to the SDK', async () => {
183
- const create = vi.fn().mockResolvedValue(makeOkResponse());
184
- installMockClient(provider, create);
185
-
186
- await provider.complete('hi', { timeoutMs: 1234, maxRetries: 1 });
187
-
188
- expect(create).toHaveBeenCalledTimes(1);
189
- const [, requestOptions] = create.mock.calls[0];
190
- expect(requestOptions.maxRetries).toBe(0);
191
- expect(requestOptions.timeout).toBe(1234);
192
- expect(requestOptions.signal).toBeDefined();
193
- });
194
- });
@@ -1,224 +0,0 @@
1
- /**
2
- * Tests for ClaudeProvider.completeWithImage (Vision support).
3
- *
4
- * The SDK client is replaced with a mock that captures the request body so we
5
- * can assert content-block shape, base64 encoding, and that the existing
6
- * retry/timeout pipeline is reused. The provider never hits the network.
7
- */
8
-
9
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
10
- import { promises as fs } from 'node:fs';
11
- import * as os from 'node:os';
12
- import * as path from 'node:path';
13
- import { ClaudeProvider, timing } from '../../src/core/ai/provider.js';
14
- import type { ImageInput } from '../../src/core/ai/types.js';
15
-
16
- /** Minimal valid response shape the SDK returns. */
17
- function makeOkResponse(text = 'image described'): unknown {
18
- return {
19
- content: [{ type: 'text', text }],
20
- usage: { input_tokens: 100, output_tokens: 20 },
21
- model: 'claude-sonnet-4-20250514',
22
- };
23
- }
24
-
25
- /** Build an Anthropic-style API error. */
26
- function apiError(status: number, message = `HTTP ${status}`): Error & { status: number } {
27
- const err = new Error(message) as Error & { status: number };
28
- err.status = status;
29
- return err;
30
- }
31
-
32
- function installMockClient(provider: ClaudeProvider, create: ReturnType<typeof vi.fn>): void {
33
- // @ts-expect-error — overriding internal client for test isolation
34
- provider.client = { messages: { create } };
35
- }
36
-
37
- /** Create a tmp file containing the given bytes; returns the absolute path. */
38
- async function writeTmpImage(bytes: Buffer, suffix = '.png'): Promise<string> {
39
- const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'forge-vision-'));
40
- const file = path.join(dir, `img${suffix}`);
41
- await fs.writeFile(file, bytes);
42
- return file;
43
- }
44
-
45
- describe('ClaudeProvider.completeWithImage', () => {
46
- let provider: ClaudeProvider;
47
- // The retry path uses timing.delay; stub it to keep retry tests fast.
48
- let delaySpy: ReturnType<typeof vi.spyOn>;
49
-
50
- beforeEach(() => {
51
- provider = new ClaudeProvider('test-key', 'claude-sonnet-4-20250514');
52
- delaySpy = vi.spyOn(timing, 'delay').mockResolvedValue();
53
- });
54
-
55
- afterEach(() => {
56
- vi.restoreAllMocks();
57
- });
58
-
59
- it('encodes image as base64 and packs an image+text user message', async () => {
60
- const bytes = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); // PNG magic
61
- const imgPath = await writeTmpImage(bytes, '.png');
62
-
63
- const create = vi.fn().mockResolvedValue(makeOkResponse('a tiny png'));
64
- installMockClient(provider, create);
65
-
66
- const text = await provider.completeWithImage('What is this?', {
67
- images: [{ path: imgPath, mediaType: 'image/png' }],
68
- timeoutMs: 1000,
69
- });
70
-
71
- expect(text).toBe('a tiny png');
72
- expect(create).toHaveBeenCalledTimes(1);
73
-
74
- const [requestBody] = create.mock.calls[0];
75
- expect(requestBody.messages).toHaveLength(1);
76
-
77
- const msg = requestBody.messages[0];
78
- expect(msg.role).toBe('user');
79
- expect(Array.isArray(msg.content)).toBe(true);
80
- expect(msg.content).toHaveLength(2);
81
-
82
- // Image block first, text block second.
83
- expect(msg.content[0]).toEqual({
84
- type: 'image',
85
- source: {
86
- type: 'base64',
87
- media_type: 'image/png',
88
- data: bytes.toString('base64'),
89
- },
90
- });
91
- expect(msg.content[1]).toEqual({ type: 'text', text: 'What is this?' });
92
- });
93
-
94
- it('supports multiple images in stable order before the prompt', async () => {
95
- const a = await writeTmpImage(Buffer.from('aaaa'), '.jpg');
96
- const b = await writeTmpImage(Buffer.from('bbbb'), '.webp');
97
-
98
- const create = vi.fn().mockResolvedValue(makeOkResponse());
99
- installMockClient(provider, create);
100
-
101
- const images: ImageInput[] = [
102
- { path: a, mediaType: 'image/jpeg' },
103
- { path: b, mediaType: 'image/webp' },
104
- ];
105
-
106
- await provider.completeWithImage('compare these', { images, timeoutMs: 1000 });
107
-
108
- const [requestBody] = create.mock.calls[0];
109
- const content = requestBody.messages[0].content as Array<{ type: string }>;
110
- expect(content.map((c) => c.type)).toEqual(['image', 'image', 'text']);
111
-
112
- const block0 = content[0] as { source: { media_type: string; data: string } };
113
- const block1 = content[1] as { source: { media_type: string; data: string } };
114
- expect(block0.source.media_type).toBe('image/jpeg');
115
- expect(block0.source.data).toBe(Buffer.from('aaaa').toString('base64'));
116
- expect(block1.source.media_type).toBe('image/webp');
117
- expect(block1.source.data).toBe(Buffer.from('bbbb').toString('base64'));
118
- });
119
-
120
- it('forwards system / model / maxTokens through to messages.create', async () => {
121
- const imgPath = await writeTmpImage(Buffer.from('xy'), '.gif');
122
-
123
- const create = vi.fn().mockResolvedValue(makeOkResponse());
124
- installMockClient(provider, create);
125
-
126
- await provider.completeWithImage('describe', {
127
- images: [{ path: imgPath, mediaType: 'image/gif' }],
128
- system: 'you are a vision expert',
129
- model: 'claude-opus-4-20250514',
130
- maxTokens: 2048,
131
- timeoutMs: 1234,
132
- });
133
-
134
- const [requestBody, requestOptions] = create.mock.calls[0];
135
- expect(requestBody.system).toBe('you are a vision expert');
136
- expect(requestBody.model).toBe('claude-opus-4-20250514');
137
- expect(requestBody.max_tokens).toBe(2048);
138
- // Confirm we still strip retries (own retry pipeline) and pass timeout.
139
- expect(requestOptions.maxRetries).toBe(0);
140
- expect(requestOptions.timeout).toBe(1234);
141
- });
142
-
143
- it('throws a clear error when the file does not exist', async () => {
144
- const create = vi.fn().mockResolvedValue(makeOkResponse());
145
- installMockClient(provider, create);
146
-
147
- const missing = path.join(os.tmpdir(), 'forge-vision-missing-' + Date.now() + '.png');
148
-
149
- await expect(
150
- provider.completeWithImage('hi', {
151
- images: [{ path: missing, mediaType: 'image/png' }],
152
- timeoutMs: 1000,
153
- }),
154
- ).rejects.toThrow(/failed to read image/i);
155
-
156
- // Validation must run before any API request.
157
- expect(create).not.toHaveBeenCalled();
158
- });
159
-
160
- it('rejects unsupported media types up front', async () => {
161
- const create = vi.fn().mockResolvedValue(makeOkResponse());
162
- installMockClient(provider, create);
163
-
164
- const imgPath = await writeTmpImage(Buffer.from('xx'), '.bmp');
165
-
166
- await expect(
167
- provider.completeWithImage('hi', {
168
- // @ts-expect-error — intentionally invalid to test runtime guard
169
- images: [{ path: imgPath, mediaType: 'image/bmp' }],
170
- timeoutMs: 1000,
171
- }),
172
- ).rejects.toThrow(/unsupported media type/i);
173
-
174
- expect(create).not.toHaveBeenCalled();
175
- });
176
-
177
- it('throws when images array is empty', async () => {
178
- const create = vi.fn().mockResolvedValue(makeOkResponse());
179
- installMockClient(provider, create);
180
-
181
- await expect(
182
- provider.completeWithImage('hi', { images: [], timeoutMs: 1000 }),
183
- ).rejects.toThrow(/at least one image/i);
184
-
185
- expect(create).not.toHaveBeenCalled();
186
- });
187
-
188
- it('reuses retry pipeline: retries on 429 then succeeds', async () => {
189
- const imgPath = await writeTmpImage(Buffer.from('z'), '.png');
190
-
191
- const create = vi
192
- .fn()
193
- .mockRejectedValueOnce(apiError(429, 'rate limited'))
194
- .mockResolvedValueOnce(makeOkResponse('done'));
195
- installMockClient(provider, create);
196
-
197
- const text = await provider.completeWithImage('hi', {
198
- images: [{ path: imgPath, mediaType: 'image/png' }],
199
- maxRetries: 2,
200
- timeoutMs: 1000,
201
- });
202
-
203
- expect(text).toBe('done');
204
- expect(create).toHaveBeenCalledTimes(2);
205
- expect(delaySpy).toHaveBeenCalledTimes(1);
206
- });
207
-
208
- it('reuses retry pipeline: surfaces non-retriable 400 immediately', async () => {
209
- const imgPath = await writeTmpImage(Buffer.from('z'), '.png');
210
-
211
- const create = vi.fn().mockRejectedValue(apiError(400, 'bad image'));
212
- installMockClient(provider, create);
213
-
214
- await expect(
215
- provider.completeWithImage('hi', {
216
- images: [{ path: imgPath, mediaType: 'image/png' }],
217
- maxRetries: 3,
218
- timeoutMs: 1000,
219
- }),
220
- ).rejects.toThrow('bad image');
221
-
222
- expect(create).toHaveBeenCalledTimes(1);
223
- });
224
- });
@@ -1,68 +0,0 @@
1
- /**
2
- * Characterization tests for ClaudeMdGenerator — safety-net phase.
3
- *
4
- * These tests lock down the observable behavior of the SWARM_PROTOCOL
5
- * template embedded in claudemd-generator.ts BEFORE the template sync fix.
6
- *
7
- * NOTE: assertions marked "OLD BEHAVIOR" will be updated in the fix phase
8
- * to reflect the new template.
9
- */
10
- import { describe, it, expect, vi, beforeEach } from 'vitest';
11
- import { ClaudeMdGenerator } from '../../src/claudemd/claudemd-generator.js';
12
- import type { ClaudeProvider } from '../../src/core/ai/provider.js';
13
-
14
- function makeMockAi(response?: string): ClaudeProvider {
15
- return {
16
- complete: vi.fn().mockRejectedValue(new Error('ai disabled for tests')),
17
- } as unknown as ClaudeProvider;
18
- }
19
-
20
- describe('ClaudeMdGenerator — SWARM_PROTOCOL template characterization', () => {
21
- let generator: ClaudeMdGenerator;
22
-
23
- beforeEach(() => {
24
- generator = new ClaudeMdGenerator(makeMockAi());
25
- });
26
-
27
- it('generates output that contains the core behavior rules section', async () => {
28
- const result = await generator.generate('/tmp');
29
- expect(result).toContain('## 核心行为规则');
30
- });
31
-
32
- it('generated output contains the Two-Phase Workflow section with NEW title (BMAD 默认路径)', async () => {
33
- const result = await generator.generate('/tmp');
34
- // NEW behavior after template sync fix
35
- expect(result).toContain('Two-Phase Workflow (BMAD 默认路径)');
36
- });
37
-
38
- it('generated output contains the 工作流升级判定 section', async () => {
39
- const result = await generator.generate('/tmp');
40
- expect(result).toContain('工作流升级判定');
41
- });
42
-
43
- it('generated output contains harness-hotfix workflow entry', async () => {
44
- const result = await generator.generate('/tmp');
45
- expect(result).toContain('harness-hotfix');
46
- });
47
-
48
- it('generated output contains refactor-safe workflow entry', async () => {
49
- const result = await generator.generate('/tmp');
50
- expect(result).toContain('refactor-safe');
51
- });
52
-
53
- it('generated output contains 测试覆盖率 < 50% condition', async () => {
54
- const result = await generator.generate('/tmp');
55
- expect(result).toContain('测试覆盖率 < 50%');
56
- });
57
-
58
- it('generated output contains Agent 委托规则 section', async () => {
59
- const result = await generator.generate('/tmp');
60
- expect(result).toContain('## Agent 委托规则');
61
- });
62
-
63
- it('generated output does NOT contain OLD Two-Phase title (Design-First)', async () => {
64
- const result = await generator.generate('/tmp');
65
- // After fix, old title must be gone
66
- expect(result).not.toContain('Two-Phase Workflow (Design-First)');
67
- });
68
- });
@@ -1,141 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
-
3
- // Mock 必须在 import 被测模块之前
4
- vi.mock('node:child_process', () => ({
5
- execSync: vi.fn(),
6
- }));
7
- vi.mock('node:fs', async (importOriginal) => {
8
- const actual = await importOriginal<typeof import('node:fs')>();
9
- return {
10
- ...actual,
11
- existsSync: vi.fn(),
12
- readFileSync: vi.fn(),
13
- };
14
- });
15
-
16
- import { execSync } from 'node:child_process';
17
- import { existsSync, readFileSync } from 'node:fs';
18
- import {
19
- isClaudeCodeInstalled,
20
- isMcpRegistered,
21
- getMcpServerUrl,
22
- } from '../../src/cli/commands/mcp.js';
23
-
24
- const mockExecSync = vi.mocked(execSync);
25
- const mockExistsSync = vi.mocked(existsSync);
26
- const mockReadFileSync = vi.mocked(readFileSync);
27
-
28
- describe('cf mcp command', () => {
29
- beforeEach(() => {
30
- vi.clearAllMocks();
31
- });
32
-
33
- afterEach(() => {
34
- vi.restoreAllMocks();
35
- });
36
-
37
- describe('isClaudeCodeInstalled', () => {
38
- it('returns true when `command -v claude` succeeds', () => {
39
- mockExecSync.mockReturnValue(Buffer.from(''));
40
- expect(isClaudeCodeInstalled()).toBe(true);
41
- // 用 command -v 而不是 which(POSIX 标准)
42
- expect(mockExecSync).toHaveBeenCalledWith('command -v claude', { stdio: 'ignore' });
43
- });
44
-
45
- it('returns false when execSync throws', () => {
46
- mockExecSync.mockImplementation(() => {
47
- throw new Error('not found');
48
- });
49
- expect(isClaudeCodeInstalled()).toBe(false);
50
- });
51
- });
52
-
53
- describe('isMcpRegistered', () => {
54
- it('returns false when `claude mcp get` throws (not registered)', () => {
55
- mockExecSync.mockImplementation(() => {
56
- throw new Error('No MCP server found');
57
- });
58
- expect(isMcpRegistered()).toEqual({ registered: false });
59
- });
60
-
61
- it('returns true with extracted URL when registered', () => {
62
- mockExecSync.mockReturnValue(
63
- 'claude-forge:\n Scope: User config\n Status: ✓ Connected\n Type: http\n URL: http://127.0.0.1:3456/mcp\n',
64
- );
65
- const result = isMcpRegistered();
66
- expect(result.registered).toBe(true);
67
- expect(result.url).toBe('http://127.0.0.1:3456/mcp');
68
- });
69
-
70
- it('returns registered=true with undefined url when output lacks URL line', () => {
71
- mockExecSync.mockReturnValue('claude-forge:\n Scope: User config\n');
72
- const result = isMcpRegistered();
73
- expect(result.registered).toBe(true);
74
- expect(result.url).toBeUndefined();
75
- });
76
-
77
- it('uses `claude mcp get claude-forge` command', () => {
78
- mockExecSync.mockReturnValue('claude-forge:\n URL: http://x\n');
79
- isMcpRegistered();
80
- expect(mockExecSync).toHaveBeenCalledWith(
81
- 'claude mcp get claude-forge',
82
- expect.objectContaining({ encoding: 'utf-8' }),
83
- );
84
- });
85
- });
86
-
87
- describe('getMcpServerUrl', () => {
88
- it('builds URL from default port', () => {
89
- const url = getMcpServerUrl();
90
- // 默认端口是 3456(来自 DEFAULTS.WEB_PORT)
91
- expect(url).toMatch(/^http:\/\/127\.0\.0\.1:\d+\/mcp$/);
92
- });
93
- });
94
-
95
- describe('command syntax sanity', () => {
96
- // 这组测试是"防回归"——锁定 cf mcp register 实际执行的命令格式,
97
- // 避免再次出现 `--url` 这种不存在的选项
98
- it('register command syntax must use --transport http and -H header', () => {
99
- // 单纯检查源文件里的 execSync 字符串模板(black-box sanity check)
100
- // 不在这里执行 registerCommand,因为它会 process.exit
101
- const mcpSource = readFileSync('src/cli/commands/mcp.ts', 'utf-8');
102
- // 注意:这里调用的是真实 readFileSync(mock 不影响 import 之外的调用),
103
- // 但因为我们 mock 了 readFileSync,这条测试需要 setup 真实读
104
- // —— 为了简化,我们做静态字符串断言
105
- // (skip if mock returned a fake value)
106
- });
107
- });
108
- });
109
-
110
- // 单独 describe:通过读取真实源代码验证命令格式
111
- describe('cf mcp register: command format static check', () => {
112
- it('source code uses `claude mcp add --transport http`', async () => {
113
- // 用 dynamic import 拿到 node:fs 的真实模块(绕过文件级 mock)
114
- const fs = await vi.importActual<typeof import('node:fs')>('node:fs');
115
- const src = fs.readFileSync('src/cli/commands/mcp.ts', 'utf-8');
116
-
117
- expect(src, 'must NOT contain deprecated --url flag').not.toMatch(/--url\s/);
118
- expect(src, 'must use --transport http').toMatch(/--transport http/);
119
- expect(src, 'must use -s user (user scope)').toMatch(/-s\s+\$\{SCOPE\}|-s user/);
120
- expect(src, 'must pass Authorization header').toMatch(/Authorization: Bearer/);
121
- });
122
-
123
- it('source code uses correct `claude mcp remove` syntax', async () => {
124
- const fs = await vi.importActual<typeof import('node:fs')>('node:fs');
125
- const src = fs.readFileSync('src/cli/commands/mcp.ts', 'utf-8');
126
-
127
- expect(src).toMatch(/claude mcp remove -s \$\{SCOPE\}|claude mcp remove -s user/);
128
- });
129
-
130
- it('source code uses `claude mcp get` for status check (not settings.json)', async () => {
131
- const fs = await vi.importActual<typeof import('node:fs')>('node:fs');
132
- const src = fs.readFileSync('src/cli/commands/mcp.ts', 'utf-8');
133
-
134
- expect(src).toMatch(/claude mcp get/);
135
- // 不应该再用 ~/.claude/settings.json 路径来检查注册状态
136
- // (只允许在注释里出现作为历史对比,但 join() 调用不能拼这个路径)
137
- expect(src, 'must not join settings.json in code').not.toMatch(
138
- /join\([^)]*['"]settings\.json['"]\)/,
139
- );
140
- });
141
- });