@winspan/claude-forge 8.53.2 → 8.54.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (390) hide show
  1. package/DEVELOPMENT.md +290 -221
  2. package/README.md +50 -8
  3. package/dist/cli/commands/skills.d.ts.map +1 -1
  4. package/dist/cli/commands/skills.js +7 -3
  5. package/dist/cli/commands/skills.js.map +1 -1
  6. package/dist/cli/init/hook-manager.d.ts +1 -1
  7. package/dist/cli/init/hook-manager.d.ts.map +1 -1
  8. package/dist/cli/init/hook-manager.js +1 -0
  9. package/dist/cli/init/hook-manager.js.map +1 -1
  10. package/dist/core/storage/events.d.ts.map +1 -1
  11. package/dist/core/storage/events.js +0 -1
  12. package/dist/core/storage/events.js.map +1 -1
  13. package/dist/core/storage/maintenance.d.ts +25 -3
  14. package/dist/core/storage/maintenance.d.ts.map +1 -1
  15. package/dist/core/storage/maintenance.js +33 -4
  16. package/dist/core/storage/maintenance.js.map +1 -1
  17. package/dist/core/storage/routing.d.ts +4 -0
  18. package/dist/core/storage/routing.d.ts.map +1 -1
  19. package/dist/core/storage/routing.js +10 -4
  20. package/dist/core/storage/routing.js.map +1 -1
  21. package/dist/core/storage/sessions.d.ts +17 -0
  22. package/dist/core/storage/sessions.d.ts.map +1 -1
  23. package/dist/core/storage/sessions.js +64 -0
  24. package/dist/core/storage/sessions.js.map +1 -1
  25. package/dist/core/storage/skills.d.ts +4 -0
  26. package/dist/core/storage/skills.d.ts.map +1 -1
  27. package/dist/core/storage/skills.js +10 -2
  28. package/dist/core/storage/skills.js.map +1 -1
  29. package/dist/core/storage/sqlite.d.ts +5 -0
  30. package/dist/core/storage/sqlite.d.ts.map +1 -1
  31. package/dist/core/storage/sqlite.js +6 -0
  32. package/dist/core/storage/sqlite.js.map +1 -1
  33. package/dist/core/storage/tasks.d.ts.map +1 -1
  34. package/dist/core/storage/tasks.js +2 -0
  35. package/dist/core/storage/tasks.js.map +1 -1
  36. package/dist/core/types.d.ts +7 -0
  37. package/dist/core/types.d.ts.map +1 -1
  38. package/dist/daemon/index.d.ts.map +1 -1
  39. package/dist/daemon/index.js +19 -4
  40. package/dist/daemon/index.js.map +1 -1
  41. package/dist/skills/registry.d.ts.map +1 -1
  42. package/dist/skills/registry.js +13 -2
  43. package/dist/skills/registry.js.map +1 -1
  44. package/dist/skills/semantic-matcher.d.ts +2 -2
  45. package/dist/skills/semantic-matcher.d.ts.map +1 -1
  46. package/dist/skills/semantic-matcher.js +14 -19
  47. package/dist/skills/semantic-matcher.js.map +1 -1
  48. package/dist/skills/upgrade-engine.d.ts +3 -1
  49. package/dist/skills/upgrade-engine.d.ts.map +1 -1
  50. package/dist/skills/upgrade-engine.js +25 -14
  51. package/dist/skills/upgrade-engine.js.map +1 -1
  52. package/dist/web/analytics/weekly-report.d.ts.map +1 -1
  53. package/dist/web/analytics/weekly-report.js +21 -29
  54. package/dist/web/analytics/weekly-report.js.map +1 -1
  55. package/dist/web/routes/patch.d.ts.map +1 -1
  56. package/dist/web/routes/patch.js +32 -2
  57. package/dist/web/routes/patch.js.map +1 -1
  58. package/dist/web/routes/sessions.d.ts.map +1 -1
  59. package/dist/web/routes/sessions.js +9 -7
  60. package/dist/web/routes/sessions.js.map +1 -1
  61. package/dist/web/routes/trace.d.ts.map +1 -1
  62. package/dist/web/routes/trace.js +2 -3
  63. package/dist/web/routes/trace.js.map +1 -1
  64. package/dist/web/server.d.ts.map +1 -1
  65. package/dist/web/server.js +3 -2
  66. package/dist/web/server.js.map +1 -1
  67. package/package.json +12 -2
  68. package/scripts/postinstall.cjs +21 -0
  69. package/.claude/CLAUDE.md +0 -17
  70. package/.eslintrc.js +0 -23
  71. package/.prettierrc +0 -8
  72. package/ARCHITECTURE_ISSUES.md +0 -249
  73. package/CLAUDE.md +0 -265
  74. package/CLAUDE.md.backup +0 -488
  75. package/docs/concurrent-agents.md +0 -129
  76. package/docs/design/architecture-review-20260516.md +0 -232
  77. package/docs/design/fix-skills-data-and-set-leak-spec-20260516-1300.md +0 -219
  78. package/docs/design/h1-storage-aggregation-spec-20260518-1121.md +0 -299
  79. package/docs/design/h2-getdatabase-encapsulation-spec-20260518-1450.md +0 -191
  80. package/docs/design/h3-fallback-removal-spec-20260518-1245.md +0 -76
  81. package/docs/design/h4-index-dedup-spec-20260518-1230.md +0 -109
  82. package/docs/design/h6-services-migration-spec-20260518-1355.md +0 -82
  83. package/docs/design/hook-failure-queue-spec-20260516-1530.md +0 -204
  84. package/docs/design/l1-swarm-protocol-extract-spec-20260518-1605.md +0 -106
  85. package/docs/design/m10-forge-paths-spec-20260518-1320.md +0 -121
  86. package/docs/design/m2-m3-tool-input-spec-20260518-1425.md +0 -131
  87. package/docs/design/m7-routing-event-association-spec-20260518-1545.md +0 -103
  88. package/docs/design/project-path-gitroot-spec-20260518-1715.md +0 -134
  89. package/docs/design/refactor-phase1-spec-20260515-1600.md +0 -543
  90. package/docs/design/refactor-phase2-spec-20260515-1700.md +0 -424
  91. package/docs/design/skill-ai-upgrade-spec-20260518-1930.md +0 -297
  92. package/docs/design/task-active-gc-spec-20260518-1745.md +0 -146
  93. package/docs/design/tasks-list-filter-pagination-spec-20260518-0930.md +0 -208
  94. package/docs/implementation/daemon-skill-sync-changelog-20260518-2000.md +0 -22
  95. package/docs/implementation/fix-skills-data-and-set-leak-changelog-20260516-1300.md +0 -104
  96. package/docs/implementation/h1-storage-aggregation-changelog-20260518-1121.md +0 -82
  97. package/docs/implementation/h2-final-changelog-20260518-1530.md +0 -61
  98. package/docs/implementation/h2-phase1-safety-net-changelog-20260518-1450.md +0 -70
  99. package/docs/implementation/h2-phase2-operations-changelog-20260518-1450.md +0 -120
  100. package/docs/implementation/h2-phase3-callsites-changelog-20260518-1450.md +0 -71
  101. package/docs/implementation/h3-fallback-removal-changelog-20260518-1245.md +0 -71
  102. package/docs/implementation/h4-index-dedup-changelog-20260518-1230.md +0 -60
  103. package/docs/implementation/h6-services-migration-changelog-20260518-1355.md +0 -46
  104. package/docs/implementation/h7-m9-defaults-changelog-20260518-1300.md +0 -46
  105. package/docs/implementation/hook-failure-queue-changelog-20260516-1530.md +0 -196
  106. package/docs/implementation/hotfix-daemon-event-reject-20260516-1430.md +0 -56
  107. package/docs/implementation/l1-swarm-protocol-extract-changelog-20260518-1605.md +0 -45
  108. package/docs/implementation/l3-l4-daemon-perf-changelog-20260518-1410.md +0 -63
  109. package/docs/implementation/l6-l8-final-cleanup-changelog-20260518-1640.md +0 -38
  110. package/docs/implementation/m1-m4-m5-l7-cleanup-changelog-20260518-1310.md +0 -58
  111. package/docs/implementation/m10-forge-paths-changelog-20260518-1320.md +0 -60
  112. package/docs/implementation/m2-m3-tool-input-changelog-20260518-1425.md +0 -43
  113. package/docs/implementation/m6-m8-naming-shutdown-changelog-20260518-1340.md +0 -56
  114. package/docs/implementation/m7-routing-association-changelog-20260518-1545.md +0 -69
  115. package/docs/implementation/project-path-gitroot-changelog-20260518-1715.md +0 -63
  116. package/docs/implementation/refactor-phase1-changelog-20260515-1630.md +0 -354
  117. package/docs/implementation/refactor-phase2-changelog-20260515-1705.md +0 -421
  118. package/docs/implementation/skill-ai-upgrade-changelog-20260518-1930.md +0 -49
  119. package/docs/implementation/task-active-gc-changelog-20260518-1745.md +0 -35
  120. package/docs/implementation/task-title-summary-changelog-20260518-1130.md +0 -39
  121. package/docs/implementation/tasks-detail-back-loses-filters-changelog-20260518-1100.md +0 -22
  122. package/docs/implementation/tasks-list-filter-pagination-changelog-20260518-0930.md +0 -72
  123. package/docs/implementation/tasks-page-white-screen-hotfix-changelog-20260518-1015.md +0 -56
  124. package/docs/reviews/claudemd-template-sync.md +0 -54
  125. package/docs/reviews/task-title-summary.md +0 -92
  126. package/docs/reviews/tasks-detail-back-loses-filters.md +0 -58
  127. package/docs/reviews/tasks-filter-pagination.md +0 -80
  128. package/docs/reviews/tasks-page-white-screen-hotfix.md +0 -126
  129. package/docs/ruflo-learning-strategy.md +0 -322
  130. package/docs/skills-deduplication-analysis.md +0 -83
  131. package/docs/skills-multiformat-support.md +0 -177
  132. package/docs/skills-third-party.md +0 -183
  133. package/docs/testing/tasks-filter-pagination-test-report.md +0 -86
  134. package/forge +0 -321
  135. package/playwright.config.ts +0 -40
  136. package/scripts/demo-v2.ts +0 -91
  137. package/scripts/dev-daemon.sh +0 -232
  138. package/scripts/dev-web.ts +0 -109
  139. package/scripts/e2e-mcp-link.ts +0 -423
  140. package/scripts/e2e-methodology-quality.ts +0 -253
  141. package/scripts/e2e-routing.ts +0 -456
  142. package/scripts/e2e-user-methodology.ts +0 -326
  143. package/scripts/e2e-web-workflows.ts +0 -299
  144. package/scripts/migrate-legacy-to-dynamic.sql +0 -108
  145. package/scripts/regenerate-execution-docs.ts +0 -116
  146. package/scripts/sync-agent-skills.ts +0 -193
  147. package/scripts/test-hook.sh +0 -71
  148. package/scripts/verify-skill-loading.ts +0 -62
  149. package/src/claudemd/claudemd-generator.ts +0 -568
  150. package/src/claudemd/convention-extractor.ts +0 -69
  151. package/src/claudemd/index.ts +0 -35
  152. package/src/claudemd/persona-manager.ts +0 -88
  153. package/src/claudemd/resume-manager.ts +0 -236
  154. package/src/claudemd/tech-detector.ts +0 -220
  155. package/src/claudemd/templates/swarm-protocol.md +0 -222
  156. package/src/cli/commands/claudemd.ts +0 -84
  157. package/src/cli/commands/config.ts +0 -46
  158. package/src/cli/commands/daemon.ts +0 -310
  159. package/src/cli/commands/executions.ts +0 -115
  160. package/src/cli/commands/init.ts +0 -204
  161. package/src/cli/commands/logs.ts +0 -181
  162. package/src/cli/commands/mcp.ts +0 -242
  163. package/src/cli/commands/menu.ts +0 -357
  164. package/src/cli/commands/skills.ts +0 -328
  165. package/src/cli/commands/stats.ts +0 -73
  166. package/src/cli/commands/status.ts +0 -69
  167. package/src/cli/commands/template.ts +0 -77
  168. package/src/cli/commands/trace.ts +0 -148
  169. package/src/cli/index.ts +0 -42
  170. package/src/cli/init/hook-manager.ts +0 -132
  171. package/src/core/ai/provider.ts +0 -308
  172. package/src/core/ai/types.ts +0 -51
  173. package/src/core/config.ts +0 -124
  174. package/src/core/constants.ts +0 -67
  175. package/src/core/event-fields.ts +0 -32
  176. package/src/core/queue/index.ts +0 -192
  177. package/src/core/storage/base.ts +0 -302
  178. package/src/core/storage/events.ts +0 -434
  179. package/src/core/storage/injections.ts +0 -78
  180. package/src/core/storage/maintenance.ts +0 -59
  181. package/src/core/storage/migrations/002_add_skill_tracking.sql +0 -6
  182. package/src/core/storage/migrations/003_add_skill_invocations.sql +0 -23
  183. package/src/core/storage/performance-indexes.sql +0 -23
  184. package/src/core/storage/routing.ts +0 -322
  185. package/src/core/storage/rows.ts +0 -112
  186. package/src/core/storage/schema.sql +0 -224
  187. package/src/core/storage/sessions.ts +0 -168
  188. package/src/core/storage/skills.ts +0 -233
  189. package/src/core/storage/sqlite.ts +0 -293
  190. package/src/core/storage/tasks.ts +0 -318
  191. package/src/core/storage/token-usage.ts +0 -93
  192. package/src/core/types.ts +0 -181
  193. package/src/core/utils/error-handler.ts +0 -257
  194. package/src/core/utils/forge-resume-block.ts +0 -74
  195. package/src/core/utils/format.ts +0 -69
  196. package/src/core/utils/git.ts +0 -23
  197. package/src/core/utils/logger.ts +0 -134
  198. package/src/core/utils/lru-cache.ts +0 -54
  199. package/src/core/utils/path.ts +0 -19
  200. package/src/core/utils/session.ts +0 -26
  201. package/src/core/utils/time.ts +0 -37
  202. package/src/core/utils/token-tracker.ts +0 -97
  203. package/src/daemon/event-parser.ts +0 -36
  204. package/src/daemon/handlers/history-exporter.ts +0 -117
  205. package/src/daemon/handlers/post-tool-use.ts +0 -54
  206. package/src/daemon/handlers/stop.ts +0 -208
  207. package/src/daemon/handlers/user-prompt.ts +0 -178
  208. package/src/daemon/hook-sync.ts +0 -91
  209. package/src/daemon/index.ts +0 -312
  210. package/src/daemon/launchd/com.claude-forge.daemon.plist.template +0 -47
  211. package/src/daemon/launchd-installer.ts +0 -260
  212. package/src/daemon/lifecycle.ts +0 -128
  213. package/src/daemon/router.ts +0 -40
  214. package/src/daemon/server.ts +0 -196
  215. package/src/daemon/services/task-segmenter.ts +0 -112
  216. package/src/daemon/skill-sync.ts +0 -88
  217. package/src/hooks/hook-lib.sh +0 -118
  218. package/src/hooks/notification.sh +0 -35
  219. package/src/hooks/post-tool-use.sh +0 -61
  220. package/src/hooks/pre-tool-use.sh +0 -63
  221. package/src/hooks/stop.sh +0 -43
  222. package/src/hooks/user-prompt-submit.sh +0 -69
  223. package/src/mcp/server.ts +0 -322
  224. package/src/skills/index.ts +0 -2
  225. package/src/skills/invocation-guard.ts +0 -177
  226. package/src/skills/matcher.ts +0 -148
  227. package/src/skills/official/code-simplifier.md +0 -52
  228. package/src/skills/official/find-skills.md +0 -142
  229. package/src/skills/official/official-api-design.md +0 -30
  230. package/src/skills/official/official-architecture-decision.md +0 -41
  231. package/src/skills/official/official-bmad.md +0 -118
  232. package/src/skills/official/official-db-schema-design.md +0 -34
  233. package/src/skills/official/official-debug.md +0 -25
  234. package/src/skills/official/official-doc-driven.md +0 -31
  235. package/src/skills/official/official-harness-engineering.md +0 -108
  236. package/src/skills/official/official-performance-optimization.md +0 -30
  237. package/src/skills/official/official-pr-review.md +0 -35
  238. package/src/skills/official/official-release-checklist.md +0 -30
  239. package/src/skills/official/official-security-hardening.md +0 -32
  240. package/src/skills/official/official-spec-driven-design.md +0 -31
  241. package/src/skills/official/planning-with-files.md +0 -241
  242. package/src/skills/official/ui-ux-pro-max.md +0 -105
  243. package/src/skills/official/webapp-testing.md +0 -96
  244. package/src/skills/official-skills.ts +0 -89
  245. package/src/skills/registry.ts +0 -355
  246. package/src/skills/semantic-matcher.ts +0 -234
  247. package/src/skills/tools/pipeline-suggest.ts +0 -226
  248. package/src/skills/tools/skill-invoke.ts +0 -168
  249. package/src/skills/tools/skill-list.ts +0 -59
  250. package/src/skills/upgrade-engine.ts +0 -541
  251. package/src/skills/upgrade-prompt.ts +0 -84
  252. package/src/templates/go.yaml +0 -53
  253. package/src/templates/python.yaml +0 -59
  254. package/src/templates/react.yaml +0 -55
  255. package/src/templates/template-manager.ts +0 -170
  256. package/src/web/analytics/anti-pattern-detector.ts +0 -367
  257. package/src/web/analytics/drift-detector.ts +0 -219
  258. package/src/web/analytics/weekly-report.ts +0 -431
  259. package/src/web/auth-middleware.ts +0 -54
  260. package/src/web/routes/_helpers.ts +0 -34
  261. package/src/web/routes/ai.ts +0 -204
  262. package/src/web/routes/auth.ts +0 -22
  263. package/src/web/routes/drift.ts +0 -25
  264. package/src/web/routes/error-handler.ts +0 -120
  265. package/src/web/routes/events.ts +0 -47
  266. package/src/web/routes/insights.ts +0 -43
  267. package/src/web/routes/patch.ts +0 -117
  268. package/src/web/routes/reports.ts +0 -34
  269. package/src/web/routes/rules.ts +0 -76
  270. package/src/web/routes/sessions.ts +0 -250
  271. package/src/web/routes/skill-stats.ts +0 -92
  272. package/src/web/routes/skills.ts +0 -350
  273. package/src/web/routes/static.ts +0 -67
  274. package/src/web/routes/stats.ts +0 -50
  275. package/src/web/routes/status.ts +0 -30
  276. package/src/web/routes/tasks.ts +0 -193
  277. package/src/web/routes/token-usage.ts +0 -20
  278. package/src/web/routes/trace.ts +0 -126
  279. package/src/web/routes/types.ts +0 -57
  280. package/src/web/server.ts +0 -134
  281. package/src/web/ssrf-guard.ts +0 -112
  282. package/src/web/static/index.html +0 -3251
  283. package/src/web/static/vendor/chart.umd.min.js +0 -20
  284. package/tests/e2e/dashboard.spec.ts +0 -205
  285. package/tests/e2e/routing-skill-e2e.test.ts +0 -39
  286. package/tests/helpers/mock-ai.ts +0 -92
  287. package/tests/helpers/mock-storage.ts +0 -159
  288. package/tests/integration/claudemd-generator.test.ts +0 -90
  289. package/tests/integration/queue-replay.integration.test.ts +0 -193
  290. package/tests/integration/tasks-filter.integration.test.ts +0 -154
  291. package/tests/integration/web-analytics.integration.test.ts +0 -133
  292. package/tests/integration/web-stats.integration.test.ts +0 -135
  293. package/tests/integration/web-trace.integration.test.ts +0 -175
  294. package/tests/performance/database.benchmark.ts +0 -161
  295. package/tests/semantic-matcher.test.ts +0 -99
  296. package/tests/skill-matcher.test.ts +0 -110
  297. package/tests/unit/ai-provider-retry.test.ts +0 -194
  298. package/tests/unit/ai-provider-vision.test.ts +0 -224
  299. package/tests/unit/claudemd-generator.test.ts +0 -68
  300. package/tests/unit/cli-mcp.test.ts +0 -141
  301. package/tests/unit/core/forge-paths.test.ts +0 -99
  302. package/tests/unit/daemon/hook-sync.test.ts +0 -71
  303. package/tests/unit/daemon/post-tool-use.test.ts +0 -121
  304. package/tests/unit/daemon/skill-sync.test.ts +0 -75
  305. package/tests/unit/daemon/stop-handler-behavior-summary.test.ts +0 -202
  306. package/tests/unit/daemon/task-segmenter-recover.test.ts +0 -84
  307. package/tests/unit/event-fields.test.ts +0 -88
  308. package/tests/unit/event-parser.test.ts +0 -55
  309. package/tests/unit/handlers.test.ts +0 -171
  310. package/tests/unit/hooks/resolve-project-path.test.ts +0 -122
  311. package/tests/unit/invocation-guard.test.ts +0 -125
  312. package/tests/unit/queue.test.ts +0 -272
  313. package/tests/unit/router.test.ts +0 -138
  314. package/tests/unit/security.test.ts +0 -128
  315. package/tests/unit/skill-invocations-workflow.test.ts +0 -495
  316. package/tests/unit/skill-registry.test.ts +0 -94
  317. package/tests/unit/skills/invocation-guard-ttl.test.ts +0 -211
  318. package/tests/unit/skills/official-skills-loader.test.ts +0 -126
  319. package/tests/unit/skills/registry-multiformat.test.ts +0 -92
  320. package/tests/unit/skills/upgrade-engine-parse.test.ts +0 -138
  321. package/tests/unit/skills/upgrade-engine.test.ts +0 -401
  322. package/tests/unit/skills/upgrade-prompt.test.ts +0 -89
  323. package/tests/unit/socket-server.test.ts +0 -183
  324. package/tests/unit/storage/event-operations-aggregates.test.ts +0 -342
  325. package/tests/unit/storage/migration-idempotent.test.ts +0 -304
  326. package/tests/unit/storage/routing-aggregates.test.ts +0 -276
  327. package/tests/unit/storage/routing.test.ts +0 -117
  328. package/tests/unit/storage/schema-missing.test.ts +0 -81
  329. package/tests/unit/storage/session-operations-aggregates.test.ts +0 -120
  330. package/tests/unit/storage/sessions-aggregate.test.ts +0 -435
  331. package/tests/unit/storage/skill-operations-counts.test.ts +0 -106
  332. package/tests/unit/storage/skills-aggregates.test.ts +0 -104
  333. package/tests/unit/storage/sqlite-refactor-harness.test.ts +0 -314
  334. package/tests/unit/storage/task-operations-counts.test.ts +0 -46
  335. package/tests/unit/storage/tasks-getById.test.ts +0 -343
  336. package/tests/unit/storage/tasks-stale-gc.test.ts +0 -86
  337. package/tests/unit/storage.test.ts +0 -172
  338. package/tests/unit/token-usage.test.ts +0 -144
  339. package/tests/unit/type-guards.test.ts +0 -201
  340. package/tests/unit/utils/format.test.ts +0 -189
  341. package/tests/unit/utils/session.test.ts +0 -89
  342. package/tests/unit/utils/time.test.ts +0 -112
  343. package/tests/unit/web/navigation-back-contract.test.ts +0 -134
  344. package/tests/unit/web/routes-auth.test.ts +0 -93
  345. package/tests/unit/web/routes-events.test.ts +0 -101
  346. package/tests/unit/web/routes-rules.test.ts +0 -182
  347. package/tests/unit/web/routes-sessions.test.ts +0 -181
  348. package/tests/unit/web/routes-skill-stats.test.ts +0 -179
  349. package/tests/unit/web/routes-stats.test.ts +0 -92
  350. package/tests/unit/web/routes-tasks.test.ts +0 -385
  351. package/tests/unit/web/task-title-contract.test.ts +0 -210
  352. package/tests/unit/web/tasks-component-contract.test.ts +0 -179
  353. package/tsconfig.json +0 -22
  354. package/vitest.config.ts +0 -21
  355. package/vitest.integration.config.ts +0 -16
  356. package/web/CLAUDE.md +0 -20
  357. package/web/index.html +0 -13
  358. package/web/package-lock.json +0 -4854
  359. package/web/package.json +0 -35
  360. package/web/postcss.config.js +0 -6
  361. package/web/src/App.tsx +0 -110
  362. package/web/src/components/CodeBlock.tsx +0 -31
  363. package/web/src/components/Confirm.tsx +0 -96
  364. package/web/src/components/Drawer.tsx +0 -60
  365. package/web/src/components/Layout.tsx +0 -145
  366. package/web/src/components/MarkdownRenderer.tsx +0 -77
  367. package/web/src/components/SearchInput.tsx +0 -31
  368. package/web/src/components/SessionDetailContent.tsx +0 -157
  369. package/web/src/components/Toast.tsx +0 -92
  370. package/web/src/index.css +0 -19
  371. package/web/src/main.tsx +0 -31
  372. package/web/src/pages/AIConfig.tsx +0 -233
  373. package/web/src/pages/Dashboard.tsx +0 -572
  374. package/web/src/pages/Events.tsx +0 -271
  375. package/web/src/pages/Reports.tsx +0 -428
  376. package/web/src/pages/SessionDetail.tsx +0 -162
  377. package/web/src/pages/Sessions.tsx +0 -205
  378. package/web/src/pages/Skills.tsx +0 -180
  379. package/web/src/pages/TaskDetail.tsx +0 -515
  380. package/web/src/pages/Tasks.tsx +0 -415
  381. package/web/src/utils/auth.ts +0 -59
  382. package/web/src/utils/export.ts +0 -54
  383. package/web/src/utils/navigation.ts +0 -25
  384. package/web/src/utils/task-title.ts +0 -49
  385. package/web/src/utils/time.ts +0 -13
  386. package/web/tailwind.config.js +0 -11
  387. package/web/tsconfig.json +0 -21
  388. package/web/tsconfig.node.json +0 -10
  389. package/web/vite.config.ts +0 -76
  390. package/winspan-claude-forge-8.43.0.tgz +0 -0
@@ -1,34 +0,0 @@
1
- /**
2
- * Shared helpers for web routes.
3
- */
4
-
5
- /**
6
- * Recursively truncate string/array fields inside an arbitrary value so the
7
- * serialised JSON payload stays bounded. Used to keep tool input/output
8
- * previews small in /api/tasks and /api/sessions.
9
- *
10
- * - strings longer than `maxLen` are sliced and suffixed with "..."
11
- * - arrays are sliced to the first `arrayLimit` elements (NOT recursed into,
12
- * matching the original call sites' behaviour)
13
- * - objects are walked recursively
14
- * - everything else is returned as-is (including null/undefined)
15
- */
16
- export function truncateField(
17
- obj: unknown,
18
- maxLen = 300,
19
- arrayLimit = 10,
20
- ): unknown {
21
- if (!obj) return obj;
22
- if (typeof obj === 'string') {
23
- return obj.length > maxLen ? obj.slice(0, maxLen) + '...' : obj;
24
- }
25
- if (Array.isArray(obj)) return obj.slice(0, arrayLimit);
26
- if (typeof obj === 'object') {
27
- const result: Record<string, unknown> = {};
28
- for (const [k, v] of Object.entries(obj as Record<string, unknown>)) {
29
- result[k] = truncateField(v, maxLen, arrayLimit);
30
- }
31
- return result;
32
- }
33
- return obj;
34
- }
@@ -1,204 +0,0 @@
1
- import type { Application } from 'express';
2
- import { ConfigManager } from '../../core/config.js';
3
- import { logger } from '../../core/utils/logger.js';
4
- import {
5
- validateBaseUrl,
6
- extractHostFromBaseUrl,
7
- UPSTREAM_TIMEOUT_MS,
8
- } from '../ssrf-guard.js';
9
- import type { RouteContext } from './types.js';
10
-
11
- /**
12
- * /api/ai/* and /api/config/ai — AI provider config + upstream connectivity.
13
- *
14
- * All outbound calls route through validateBaseUrl + redirect:'manual' so the
15
- * daemon cannot be tricked into reaching internal hosts (SSRF guard).
16
- */
17
- export function registerAIRoutes(app: Application, _ctx: RouteContext): void {
18
- // GET /api/config/ai — read current AI config
19
- app.get('/api/config/ai', (_req, res) => {
20
- const configManager = new ConfigManager();
21
- const config = configManager.get();
22
-
23
- res.json({
24
- api_key: config.ai.api_key || '',
25
- base_url: config.ai.base_url || '',
26
- model: config.ai.model,
27
- provider: config.ai.provider,
28
- });
29
- });
30
-
31
- // PUT /api/config/ai — update AI config
32
- app.put('/api/config/ai', (req, res) => {
33
- const { api_key, base_url, model, provider } = req.body ?? {};
34
-
35
- try {
36
- const configManager = new ConfigManager();
37
- const config = configManager.get();
38
-
39
- if (typeof api_key === 'string') config.ai.api_key = api_key;
40
- if (typeof base_url === 'string') {
41
- // Normalize http → https for upstream gateways that force HTTPS redirect
42
- // (e.g. iflytek one.iflytek.com returns 302 and drops Authorization header)
43
- let url = base_url.trim();
44
- if (url.startsWith('http://')) url = 'https://' + url.slice('http://'.length);
45
- config.ai.base_url = url;
46
- }
47
- if (typeof model === 'string') config.ai.model = model;
48
- if (typeof provider === 'string') config.ai.provider = provider;
49
-
50
- configManager.save();
51
- logger.info('[Web] AI config updated');
52
- res.json({ success: true });
53
- } catch (err) {
54
- logger.warn(`[Web] Failed to update AI config: ${err}`);
55
- res.status(500).json({ error: String(err) });
56
- }
57
- });
58
-
59
- // GET /api/ai/models — proxy to upstream /v1/models
60
- app.get('/api/ai/models', async (req, res) => {
61
- try {
62
- const configManager = new ConfigManager();
63
- const config = configManager.get();
64
-
65
- const queryKey = typeof req.query.api_key === 'string' ? req.query.api_key : '';
66
- const queryUrl = typeof req.query.base_url === 'string' ? req.query.base_url : '';
67
- const apiKey = queryKey || config.ai.api_key;
68
- let baseUrl = queryUrl || config.ai.base_url || 'https://api.anthropic.com';
69
- if (baseUrl.startsWith('http://')) baseUrl = 'https://' + baseUrl.slice('http://'.length);
70
-
71
- if (!apiKey) {
72
- res.status(400).json({ error: 'API key not configured' });
73
- return;
74
- }
75
-
76
- const configuredHost = extractHostFromBaseUrl(config.ai.base_url);
77
- const validation = validateBaseUrl(baseUrl, configuredHost ? [configuredHost] : []);
78
- if (!validation.ok) {
79
- res.status(400).json({ error: validation.error });
80
- return;
81
- }
82
-
83
- const modelsUrl = baseUrl.endsWith('/v1')
84
- ? `${baseUrl}/models`
85
- : `${baseUrl}/v1/models`;
86
-
87
- const controller = new AbortController();
88
- const timer = setTimeout(() => controller.abort(), UPSTREAM_TIMEOUT_MS);
89
- let response: Response;
90
- try {
91
- response = await fetch(modelsUrl, {
92
- redirect: 'manual',
93
- signal: controller.signal,
94
- headers: {
95
- 'Authorization': `Bearer ${apiKey}`,
96
- 'x-api-key': apiKey,
97
- },
98
- });
99
- } finally {
100
- clearTimeout(timer);
101
- }
102
-
103
- if (response.status >= 300 && response.status < 400) {
104
- res.status(400).json({
105
- error: `Upstream tried to redirect (status ${response.status}); refusing to follow`,
106
- });
107
- return;
108
- }
109
-
110
- if (!response.ok) {
111
- const text = await response.text();
112
- logger.warn(`[Web] Upstream /v1/models failed: ${response.status} ${text}`);
113
- res.status(response.status).json({ error: text });
114
- return;
115
- }
116
-
117
- const data = await response.json();
118
- res.json(data);
119
- } catch (err) {
120
- if (err instanceof Error && err.name === 'AbortError') {
121
- res.status(504).json({ error: `Upstream timeout (${UPSTREAM_TIMEOUT_MS}ms)` });
122
- return;
123
- }
124
- logger.warn(`[Web] Failed to fetch models: ${err}`);
125
- res.status(500).json({ error: String(err) });
126
- }
127
- });
128
-
129
- // POST /api/ai/test — test AI connection using provided or saved config
130
- app.post('/api/ai/test', async (req, res) => {
131
- try {
132
- const configManager = new ConfigManager();
133
- const config = configManager.get();
134
-
135
- const { api_key, base_url, model } = req.body ?? {};
136
- const apiKey = (typeof api_key === 'string' && api_key) || config.ai.api_key;
137
- let baseUrl = (typeof base_url === 'string' && base_url) || config.ai.base_url || 'https://api.anthropic.com';
138
- const useModel = (typeof model === 'string' && model) || config.ai.model;
139
- if (baseUrl.startsWith('http://')) baseUrl = 'https://' + baseUrl.slice('http://'.length);
140
-
141
- if (!apiKey) {
142
- res.status(400).json({ error: 'API key not configured' });
143
- return;
144
- }
145
-
146
- const configuredHost = extractHostFromBaseUrl(config.ai.base_url);
147
- const validation = validateBaseUrl(baseUrl, configuredHost ? [configuredHost] : []);
148
- if (!validation.ok) {
149
- res.status(400).json({ error: validation.error });
150
- return;
151
- }
152
-
153
- const messagesUrl = baseUrl.endsWith('/v1')
154
- ? `${baseUrl}/messages`
155
- : `${baseUrl}/v1/messages`;
156
-
157
- const controller = new AbortController();
158
- const timer = setTimeout(() => controller.abort(), UPSTREAM_TIMEOUT_MS);
159
- let response: Response;
160
- try {
161
- response = await fetch(messagesUrl, {
162
- method: 'POST',
163
- redirect: 'manual',
164
- signal: controller.signal,
165
- headers: {
166
- 'Content-Type': 'application/json',
167
- 'x-api-key': apiKey,
168
- 'anthropic-version': '2023-06-01',
169
- },
170
- body: JSON.stringify({
171
- model: useModel,
172
- max_tokens: 10,
173
- messages: [{ role: 'user', content: 'ping' }],
174
- }),
175
- });
176
- } finally {
177
- clearTimeout(timer);
178
- }
179
-
180
- if (response.status >= 300 && response.status < 400) {
181
- res.status(400).json({
182
- error: `Upstream tried to redirect (status ${response.status}); refusing to follow`,
183
- });
184
- return;
185
- }
186
-
187
- if (!response.ok) {
188
- const text = await response.text();
189
- res.status(response.status).json({ error: text });
190
- return;
191
- }
192
-
193
- const data = await response.json() as { model?: string };
194
- res.json({ success: true, model: data.model || useModel });
195
- } catch (err) {
196
- if (err instanceof Error && err.name === 'AbortError') {
197
- res.status(504).json({ error: `Upstream timeout (${UPSTREAM_TIMEOUT_MS}ms)` });
198
- return;
199
- }
200
- logger.warn(`[Web] AI connection test failed: ${err}`);
201
- res.status(500).json({ error: String(err) });
202
- }
203
- });
204
- }
@@ -1,22 +0,0 @@
1
- import type { Application } from 'express';
2
- import { readAuthToken } from '../auth-middleware.js';
3
- import type { RouteContext } from './types.js';
4
-
5
- /**
6
- * Auth routes — localhost bootstrap endpoint that returns the daemon token.
7
- *
8
- * This endpoint is intentionally NOT protected by requireAuth: the token file
9
- * on disk is chmod 600 (current user only), so anyone who can reach this
10
- * endpoint already has access to the file. Protecting it would create a
11
- * chicken-and-egg problem for the UI.
12
- */
13
- export function registerAuthRoutes(app: Application, _ctx: RouteContext): void {
14
- app.get('/api/auth/token', (_req, res) => {
15
- const token = readAuthToken();
16
- if (!token) {
17
- res.status(404).json({ error: 'TOKEN_NOT_FOUND' });
18
- return;
19
- }
20
- res.json({ token });
21
- });
22
- }
@@ -1,25 +0,0 @@
1
- import type { Application } from 'express';
2
- import type { RouteContext } from './types.js';
3
- import { DriftDetector } from '../analytics/drift-detector.js';
4
-
5
- /**
6
- * /api/drift/* — CLAUDE.md 漂移检测 API
7
- *
8
- * Provides compliance drift report comparing declared behavior
9
- * in CLAUDE.md against actual event stream data.
10
- */
11
- export function registerDriftRoutes(app: Application, ctx: RouteContext): void {
12
- const { storage } = ctx;
13
-
14
- app.get('/api/drift/report', (req, res) => {
15
- const days = parseInt((req.query.days as string) || '7');
16
- if (isNaN(days) || days < 1 || days > 90) {
17
- res.status(400).json({ error: 'days must be between 1 and 90' });
18
- return;
19
- }
20
-
21
- const detector = new DriftDetector(storage);
22
- const report = detector.run(days);
23
- res.json(report);
24
- });
25
- }
@@ -1,120 +0,0 @@
1
- import type { Request, Response, NextFunction } from 'express';
2
- import { logger } from '../../core/utils/logger.js';
3
-
4
- /**
5
- * Error types for classification
6
- */
7
- export enum ErrorType {
8
- VALIDATION = 'validation',
9
- NOT_FOUND = 'not_found',
10
- UNAUTHORIZED = 'unauthorized',
11
- FORBIDDEN = 'forbidden',
12
- INTERNAL = 'internal',
13
- BAD_REQUEST = 'bad_request',
14
- }
15
-
16
- /**
17
- * Structured error response
18
- */
19
- export interface ErrorResponse {
20
- error: string;
21
- type: ErrorType;
22
- details?: unknown;
23
- timestamp: string;
24
- }
25
-
26
- /**
27
- * Custom error class with type information
28
- */
29
- export class AppError extends Error {
30
- constructor(
31
- message: string,
32
- public type: ErrorType = ErrorType.INTERNAL,
33
- public statusCode: number = 500,
34
- public details?: unknown,
35
- ) {
36
- super(message);
37
- this.name = 'AppError';
38
- }
39
- }
40
-
41
- /**
42
- * Global error handler middleware
43
- */
44
- export function errorHandler(
45
- err: Error | AppError,
46
- req: Request,
47
- res: Response,
48
- _next: NextFunction,
49
- ): void {
50
- // Log error
51
- const errorMessage = err.message;
52
- const errorStack = err.stack;
53
-
54
- logger.error(`[Web] ${req.method} ${req.path} - ${errorMessage}`);
55
- if (errorStack) {
56
- logger.error(`[Web] Stack trace: ${errorStack}`);
57
- }
58
-
59
- // Determine error type and status code
60
- let statusCode = 500;
61
- let errorType = ErrorType.INTERNAL;
62
- let details: unknown = undefined;
63
-
64
- if (err instanceof AppError) {
65
- statusCode = err.statusCode;
66
- errorType = err.type;
67
- details = err.details;
68
- } else if (err.name === 'ValidationError') {
69
- statusCode = 400;
70
- errorType = ErrorType.VALIDATION;
71
- } else if (err.message.includes('not found')) {
72
- statusCode = 404;
73
- errorType = ErrorType.NOT_FOUND;
74
- } else if (err.message.includes('unauthorized') || err.message.includes('authentication')) {
75
- statusCode = 401;
76
- errorType = ErrorType.UNAUTHORIZED;
77
- } else if (err.message.includes('forbidden') || err.message.includes('permission')) {
78
- statusCode = 403;
79
- errorType = ErrorType.FORBIDDEN;
80
- } else if (err.message.includes('Invalid') || err.message.includes('invalid')) {
81
- statusCode = 400;
82
- errorType = ErrorType.BAD_REQUEST;
83
- }
84
-
85
- // Send error response
86
- const response: ErrorResponse = {
87
- error: err.message,
88
- type: errorType,
89
- timestamp: new Date().toISOString(),
90
- };
91
-
92
- if (details) {
93
- response.details = details;
94
- }
95
-
96
- res.status(statusCode).json(response);
97
- }
98
-
99
- /**
100
- * 404 handler for undefined routes
101
- */
102
- export function notFoundHandler(req: Request, res: Response): void {
103
- logger.warn(`[Web] 404 - ${req.method} ${req.path}`);
104
- res.status(404).json({
105
- error: `Route not found: ${req.method} ${req.path}`,
106
- type: ErrorType.NOT_FOUND,
107
- timestamp: new Date().toISOString(),
108
- });
109
- }
110
-
111
- /**
112
- * Async route handler wrapper to catch errors
113
- */
114
- export function asyncHandler(
115
- fn: (req: Request, res: Response, next: NextFunction) => Promise<void>,
116
- ) {
117
- return (req: Request, res: Response, next: NextFunction): void => {
118
- Promise.resolve(fn(req, res, next)).catch(next);
119
- };
120
- }
@@ -1,47 +0,0 @@
1
- import type { Application } from 'express';
2
- import type { RouteContext } from './types.js';
3
-
4
- /**
5
- * /api/events — recent events list + SSE live stream.
6
- *
7
- * Note: /api/events/stream must be registered BEFORE `/api/events` handlers
8
- * that might swallow it, but Express matches exact paths so order here is
9
- * not actually load-bearing for correctness.
10
- */
11
- export function registerEventsRoutes(app: Application, ctx: RouteContext): void {
12
- const { storage } = ctx;
13
-
14
- app.get('/api/events', (req, res) => {
15
- const limit = parseInt(req.query.limit as string) || 50;
16
- const projectPath = req.query.project as string | undefined;
17
- const sessionId = req.query.session as string | undefined;
18
- const events = storage.queryEvents({ project_path: projectPath, session_id: sessionId, limit });
19
- res.json(events);
20
- });
21
-
22
- // SSE: real-time event stream
23
- app.get('/api/events/stream', (req, res) => {
24
- res.writeHead(200, {
25
- 'Content-Type': 'text/event-stream',
26
- 'Cache-Control': 'no-cache',
27
- Connection: 'keep-alive',
28
- });
29
- res.write('data: {"type":"connected"}\n\n');
30
-
31
- const filterSession = req.query.session as string | undefined;
32
- const filterProject = req.query.project as string | undefined;
33
- const filterHook = req.query.hook as string | undefined;
34
-
35
- const onEvent = (event: Record<string, unknown>) => {
36
- if (filterSession && event.session_id !== filterSession) return;
37
- if (filterProject && event.project_path !== filterProject) return;
38
- if (filterHook && event.hook_type !== filterHook) return;
39
- res.write(`data: ${JSON.stringify(event)}\n\n`);
40
- };
41
-
42
- storage.on('event', onEvent);
43
- req.on('close', () => {
44
- storage.removeListener('event', onEvent);
45
- });
46
- });
47
- }
@@ -1,43 +0,0 @@
1
- import type { Application } from 'express';
2
- import type { RouteContext } from './types.js';
3
- import {
4
- AntiPatternDetector,
5
- type AntiPattern,
6
- type AntiPatternSeverity,
7
- } from '../analytics/anti-pattern-detector.js';
8
-
9
- /**
10
- * /api/insights — 工作流反模式检测 API
11
- *
12
- * 扫描事件流,识别可操作的反模式(编辑循环 / 工具失败 / 无 commit / 事件爆量)。
13
- */
14
- export function registerInsightsRoutes(app: Application, ctx: RouteContext): void {
15
- const { storage } = ctx;
16
-
17
- app.get('/api/insights', (req, res) => {
18
- const raw = req.query.days;
19
- const days = parseInt(typeof raw === 'string' ? raw : '7', 10);
20
- if (Number.isNaN(days) || days < 1 || days > 90) {
21
- res.status(400).json({ error: 'days must be between 1 and 90' });
22
- return;
23
- }
24
-
25
- const detector = new AntiPatternDetector(storage);
26
- const patterns = detector.detectAll(days);
27
-
28
- const summary: Record<AntiPatternSeverity, number> & { total: number } = {
29
- total: patterns.length,
30
- critical: 0,
31
- warn: 0,
32
- info: 0,
33
- };
34
- for (const p of patterns) summary[p.severity] += 1;
35
-
36
- res.json({
37
- generatedAt: new Date().toISOString(),
38
- windowDays: days,
39
- summary,
40
- patterns: patterns satisfies AntiPattern[],
41
- });
42
- });
43
- }
@@ -1,117 +0,0 @@
1
- import type { Application } from 'express';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { ConfigManager } from '../../core/config.js';
5
- import { ClaudeProvider } from '../../core/ai/provider.js';
6
- import { logger } from '../../core/utils/logger.js';
7
- import type { RouteContext } from './types.js';
8
- import { resolvePatchTarget } from './types.js';
9
-
10
- /**
11
- * /api/patch/* — AI-assisted patch preview + apply (with backup).
12
- */
13
- export function registerPatchRoutes(app: Application, _ctx: RouteContext): void {
14
- // POST /api/patch/preview — generate structured patch preview
15
- app.post('/api/patch/preview', async (req, res) => {
16
- const { targetType, targetName, recommendation, rationale } = req.body ?? {};
17
- if (!targetType || !targetName || !recommendation) {
18
- res.status(400).json({ error: 'targetType, targetName, and recommendation are required' });
19
- return;
20
- }
21
-
22
- try {
23
- const config = new ConfigManager().get();
24
- const apiKey = config.ai.api_key || process.env.ANTHROPIC_API_KEY || '';
25
- if (!apiKey) {
26
- res.status(400).json({ error: 'AI API key not configured' });
27
- return;
28
- }
29
-
30
- const { filePath } = resolvePatchTarget(targetType, targetName);
31
- if (!fs.existsSync(filePath)) {
32
- res.status(404).json({ error: `Target file not found: ${filePath}` });
33
- return;
34
- }
35
-
36
- const currentContent = fs.readFileSync(filePath, 'utf-8');
37
- const ai = new ClaudeProvider(apiKey, config.ai.model, config.ai.base_url);
38
-
39
- const prompt = `You are a code/config optimization assistant. Given the current file content and a recommended change, generate the updated content.
40
-
41
- Current file (${targetType}/${targetName}):
42
- \`\`\`
43
- ${currentContent}
44
- \`\`\`
45
-
46
- Recommendation: ${recommendation}
47
- ${rationale ? `Rationale: ${rationale}` : ''}
48
-
49
- Return ONLY a JSON object with this exact structure (no markdown, no explanation):
50
- {
51
- "summary": "brief description of what changed",
52
- "afterContent": "the complete updated file content",
53
- "risk": "low|medium|high"
54
- }`;
55
-
56
- const response = await ai.complete(prompt, { maxTokens: 4096 });
57
- const parsed = JSON.parse(response.trim());
58
-
59
- res.json({
60
- targetType,
61
- targetName,
62
- filePath,
63
- before: currentContent,
64
- after: parsed.afterContent,
65
- summary: parsed.summary,
66
- risk: parsed.risk || 'medium',
67
- recommendation,
68
- rationale: rationale || null,
69
- });
70
- } catch (err) {
71
- logger.warn(`[Web] Patch preview failed: ${err}`);
72
- res.status(500).json({ error: String(err) });
73
- }
74
- });
75
-
76
- // POST /api/patch/apply — apply patch with backup
77
- app.post('/api/patch/apply', (req, res) => {
78
- const { targetType, targetName, afterContent } = req.body ?? {};
79
- if (!targetType || !targetName || typeof afterContent !== 'string') {
80
- res.status(400).json({ error: 'targetType, targetName, and afterContent are required' });
81
- return;
82
- }
83
-
84
- try {
85
- const { filePath, backupDir } = resolvePatchTarget(targetType, targetName);
86
- if (!fs.existsSync(filePath)) {
87
- res.status(404).json({ error: `Target file not found: ${filePath}` });
88
- return;
89
- }
90
-
91
- // Create backup
92
- if (!fs.existsSync(backupDir)) {
93
- fs.mkdirSync(backupDir, { recursive: true });
94
- }
95
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
96
- const backupName = `${targetName}_${timestamp}${path.extname(filePath)}`;
97
- const backupPath = path.join(backupDir, backupName);
98
- fs.copyFileSync(filePath, backupPath);
99
-
100
- // Apply patch
101
- fs.writeFileSync(filePath, afterContent, 'utf-8');
102
-
103
- logger.info(`[Web] Patch applied to ${targetType}/${targetName}, backup: ${backupPath}`);
104
- res.json({
105
- success: true,
106
- targetType,
107
- targetName,
108
- filePath,
109
- backupPath,
110
- timestamp,
111
- });
112
- } catch (err) {
113
- logger.warn(`[Web] Patch apply failed: ${err}`);
114
- res.status(500).json({ error: String(err) });
115
- }
116
- });
117
- }
@@ -1,34 +0,0 @@
1
- /**
2
- * /api/reports/weekly — Weekly work report
3
- *
4
- * Aggregates the last week's activity into a structured report. Supports
5
- * both JSON (default) and Markdown output formats.
6
- */
7
-
8
- import type { Application } from 'express';
9
- import type { RouteContext } from './types.js';
10
- import { WeeklyReportGenerator } from '../analytics/weekly-report.js';
11
-
12
- export function registerReportsRoutes(app: Application, ctx: RouteContext): void {
13
- const { storage, skillRegistry } = ctx;
14
-
15
- app.get('/api/reports/weekly', (req, res) => {
16
- const weekOffsetRaw = (req.query.weekOffset as string) ?? '0';
17
- const weekOffset = Number.isFinite(Number(weekOffsetRaw)) ? Number(weekOffsetRaw) : 0;
18
- const format = (req.query.format as string) === 'markdown' ? 'markdown' : 'json';
19
-
20
- const generator = new WeeklyReportGenerator(storage, skillRegistry);
21
- const report = generator.generate(weekOffset);
22
-
23
- if (format === 'markdown') {
24
- const md = generator.toMarkdown(report);
25
- const filename = `weekly-report-w${report.week.isoWeekNumber}.md`;
26
- res.setHeader('Content-Type', 'text/markdown; charset=utf-8');
27
- res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
28
- res.send(md);
29
- return;
30
- }
31
-
32
- res.json(report);
33
- });
34
- }