@winspan/claude-forge 8.53.2 → 8.54.4

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