@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,128 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import crypto from 'crypto';
4
- import { execSync, execFileSync } from 'child_process';
5
- import { logger } from '../core/utils/logger.js';
6
- import { FORGE_PATHS } from '../core/constants.js';
7
-
8
- const FORGE_HOME = FORGE_PATHS.home();
9
- const TOKEN_FILE = FORGE_PATHS.daemonToken();
10
- const PID_FILE = FORGE_PATHS.daemonPid();
11
-
12
- export function writePidFile(): void {
13
- if (!fs.existsSync(FORGE_HOME)) {
14
- fs.mkdirSync(FORGE_HOME, { recursive: true });
15
- }
16
- fs.writeFileSync(PID_FILE, String(process.pid));
17
- }
18
-
19
- export function removePidFile(): void {
20
- if (fs.existsSync(PID_FILE)) {
21
- fs.unlinkSync(PID_FILE);
22
- }
23
- }
24
-
25
- export function readPid(): number | null {
26
- if (!fs.existsSync(PID_FILE)) {
27
- return null;
28
- }
29
-
30
- try {
31
- const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim(), 10);
32
-
33
- // 检查进程是否存活
34
- try {
35
- process.kill(pid, 0);
36
- return pid;
37
- } catch {
38
- // 进程不存在,清理过期 PID 文件
39
- removePidFile();
40
- return null;
41
- }
42
- } catch {
43
- return null;
44
- }
45
- }
46
-
47
- export function isRunning(): boolean {
48
- return readPid() !== null;
49
- }
50
-
51
- export function getSocketPath(): string {
52
- return FORGE_PATHS.daemonSocket();
53
- }
54
-
55
- export function cleanSocket(): void {
56
- const socketPath = getSocketPath();
57
- if (fs.existsSync(socketPath)) {
58
- fs.unlinkSync(socketPath);
59
- }
60
- }
61
-
62
- /** daemon 启动时生成随机 auth token,写入 chmod 600 文件 */
63
- export function writeAuthToken(): string {
64
- const token = crypto.randomBytes(32).toString('hex');
65
- if (!fs.existsSync(FORGE_HOME)) {
66
- fs.mkdirSync(FORGE_HOME, { recursive: true });
67
- }
68
- fs.writeFileSync(TOKEN_FILE, token, { mode: 0o600 });
69
- return token;
70
- }
71
-
72
- /** 读取当前 auth token */
73
- export function readAuthToken(): string | null {
74
- if (!fs.existsSync(TOKEN_FILE)) return null;
75
- try {
76
- return fs.readFileSync(TOKEN_FILE, 'utf-8').trim();
77
- } catch {
78
- return null;
79
- }
80
- }
81
-
82
- /** daemon 关闭时清理 token 文件 */
83
- export function removeAuthToken(): void {
84
- if (fs.existsSync(TOKEN_FILE)) {
85
- fs.unlinkSync(TOKEN_FILE);
86
- }
87
- }
88
-
89
- /**
90
- * Sync MCP registration token after daemon restart.
91
- *
92
- * Problem: Each daemon start generates a new token, but the MCP registration
93
- * in Claude Code still has the old token → 401 → "Failed to connect".
94
- *
95
- * Solution: After writing the new token, re-register the MCP server with
96
- * the updated Bearer header. This is a best-effort operation — if Claude CLI
97
- * is not installed or registration fails, we log a warning and continue.
98
- */
99
- export function syncMcpToken(token: string, port: number): void {
100
- try {
101
- execSync('command -v claude', { stdio: 'ignore' });
102
- } catch {
103
- return; // Claude CLI not installed, skip
104
- }
105
-
106
- try {
107
- const checkOutput = execSync('claude mcp get claude-forge', {
108
- stdio: ['ignore', 'pipe', 'ignore'],
109
- encoding: 'utf-8',
110
- });
111
- if (!checkOutput.includes('claude-forge')) return; // Not registered
112
- } catch {
113
- return; // Not registered, skip
114
- }
115
-
116
- const mcpUrl = `http://127.0.0.1:${port}/mcp`;
117
- try {
118
- execFileSync('claude', ['mcp', 'remove', 'claude-forge', '-s', 'user'], { stdio: 'ignore' });
119
- execFileSync('claude', [
120
- 'mcp', 'add', '--transport', 'http', '-s', 'user',
121
- 'claude-forge', mcpUrl,
122
- '-H', `Authorization: Bearer ${token}`,
123
- ], { stdio: 'ignore' });
124
- logger.info('[Lifecycle] MCP token synced successfully');
125
- } catch (err) {
126
- logger.warn(`[Lifecycle] MCP token sync failed (non-fatal): ${err instanceof Error ? err.message : err}`);
127
- }
128
- }
@@ -1,40 +0,0 @@
1
- import type { ForgeEvent, HookResult } from '../core/types.js';
2
- import { isUserPromptSubmit, isPostToolUse, isStop } from '../core/types.js';
3
- import type { UserPromptHandler } from './handlers/user-prompt.js';
4
- import type { PostToolUseHandler } from './handlers/post-tool-use.js';
5
- import type { StopHandler } from './handlers/stop.js';
6
- import { logger } from '../core/utils/logger.js';
7
- import { formatError } from '../core/utils/format.js';
8
- import { truncateSessionId } from '../core/utils/session.js';
9
-
10
- export interface Handlers {
11
- UserPromptSubmit: UserPromptHandler;
12
- PostToolUse: PostToolUseHandler;
13
- Stop: StopHandler;
14
- }
15
-
16
- /**
17
- * Dispatch an event to the matching handler with hook-type narrowing.
18
- *
19
- * The narrow union (PreToolUseEvent / PostToolUseEvent / ...) is enforced at
20
- * the handler entry, not at storage / wire level — so handlers can rely on
21
- * required fields without `!` assertions.
22
- *
23
- * Error boundary: catches and logs handler errors to prevent daemon crashes.
24
- */
25
- export async function routeEvent(event: ForgeEvent, handlers: Handlers): Promise<HookResult | void> {
26
- try {
27
- if (isUserPromptSubmit(event)) return await handlers.UserPromptSubmit.handle(event);
28
- if (isPostToolUse(event)) return await handlers.PostToolUse.handle(event);
29
- if (isStop(event)) return await handlers.Stop.handle(event);
30
- // PreToolUse / Notification: handled inline in daemon/index.ts (no dedicated handler).
31
- } catch (err) {
32
- logger.error(
33
- `[Router] Handler error for ${event.hook_type}: ${formatError(err)} ` +
34
- `(session: ${truncateSessionId(event.session_id)})`
35
- );
36
-
37
- // Return safe default to prevent daemon crash
38
- return { allow: true };
39
- }
40
- }
@@ -1,196 +0,0 @@
1
- import net from 'net';
2
- import fs from 'fs';
3
- import { EventParser } from './event-parser.js';
4
- import { logger } from '../core/utils/logger.js';
5
- import { formatError, truncateString } from '../core/utils/format.js';
6
-
7
- export interface HookResponse {
8
- allow: boolean;
9
- reason?: string;
10
- additionalContext?: string;
11
- systemMessage?: string;
12
- hookSpecificOutput?: {
13
- hookEventName: string;
14
- permissionDecision: 'allow' | 'deny' | 'ask' | 'defer';
15
- permissionDecisionReason?: string;
16
- };
17
- }
18
-
19
- export type EventHandler = (
20
- event: import('../core/types.js').ForgeEvent
21
- ) => HookResponse | void | Promise<HookResponse | void>;
22
-
23
- export class SocketServer {
24
- private server: net.Server;
25
- private parser: EventParser;
26
- private handler: EventHandler;
27
- private authToken: string | null;
28
- private static readonly MAX_BUFFER_SIZE = 512 * 1024; // 512KB
29
-
30
- constructor(socketPath: string, handler: EventHandler, authToken?: string) {
31
- this.parser = new EventParser();
32
- this.handler = handler;
33
- this.authToken = authToken ?? null;
34
-
35
- this.server = net.createServer(this.handleConnection.bind(this));
36
- this.server.listen(socketPath, () => {
37
- // 限制 socket 文件仅当前用户可读写,防止同机其他用户注入伪造事件
38
- try {
39
- fs.chmodSync(socketPath, 0o600);
40
- } catch (err) {
41
- logger.warn(`[Socket] 设置权限失败:${formatError(err)}`);
42
- }
43
- logger.info(`Socket 服务器已监听:${socketPath}`);
44
- });
45
-
46
- this.server.on('error', (err) => {
47
- logger.error(`Socket 服务器错误:${formatError(err)}`);
48
- });
49
- }
50
-
51
- private handleConnection(socket: net.Socket): void {
52
- let buffer = '';
53
- let connectionTimeout: NodeJS.Timeout | null = null;
54
-
55
- // 设置 10 秒超时,防止连接挂起
56
- connectionTimeout = setTimeout(() => {
57
- if (buffer.length > 0) {
58
- logger.warn(`连接超时,数据不完整(${buffer.length} 字节)`);
59
- }
60
- socket.destroy();
61
- }, 10000);
62
-
63
- socket.on('data', async (chunk) => {
64
- buffer += chunk.toString();
65
-
66
- // 主动检查 buffer 大小,防止内存溢出
67
- if (buffer.length > SocketServer.MAX_BUFFER_SIZE) {
68
- logger.warn(`事件过大(${buffer.length} 字节),关闭连接`);
69
- buffer = '';
70
- if (connectionTimeout) clearTimeout(connectionTimeout);
71
- socket.destroy();
72
- return;
73
- }
74
-
75
- // 性能修复(L3):hook 端用 `echo` 发送 JSON,结尾自带换行符。
76
- // 只在 buffer 中遇到换行符时才尝试解析,避免大事件(接近 512KB)
77
- // 在分片到达时每个 chunk 都跑一次完整 JSON.parse 造成的 N² 行为。
78
- // 兼容:如果数据无换行(旧客户端、其他来源),等到 socket 关闭时
79
- // 由 'end' 事件做最后一次解析尝试。
80
- const newlineIdx = buffer.indexOf('\n');
81
- if (newlineIdx === -1) {
82
- // 尚未收到完整消息,继续等待
83
- return;
84
- }
85
-
86
- // 取换行前的完整消息;忽略行后可能存在的多余数据(hook 单连接一事件)
87
- const message = buffer.slice(0, newlineIdx);
88
- buffer = '';
89
-
90
- try {
91
- await this.processMessage(socket, message);
92
- } finally {
93
- if (connectionTimeout) clearTimeout(connectionTimeout);
94
- }
95
- });
96
-
97
- // 兜底:连接关闭时若 buffer 仍有内容(无换行结尾的旧客户端),尝试解析一次
98
- socket.on('end', async () => {
99
- const remaining = buffer.trim();
100
- buffer = '';
101
- if (remaining.length === 0) return;
102
- try {
103
- await this.processMessage(socket, remaining);
104
- } catch {
105
- // processMessage 内部已记录错误
106
- } finally {
107
- if (connectionTimeout) clearTimeout(connectionTimeout);
108
- }
109
- });
110
-
111
- socket.on('error', (err) => {
112
- logger.debug(`Socket 连接错误:${formatError(err)}`);
113
- if (connectionTimeout) clearTimeout(connectionTimeout);
114
- });
115
-
116
- socket.on('close', () => {
117
- if (connectionTimeout) clearTimeout(connectionTimeout);
118
- });
119
- }
120
-
121
- /**
122
- * 解析单条完整消息(一次性,无 N² 行为)并执行 handler。
123
- *
124
- * 注意:调用方需确保 message 是一条完整的 JSON 文本(已根据换行符切分)。
125
- */
126
- private async processMessage(socket: net.Socket, message: string): Promise<void> {
127
- try {
128
- // 认证检查:在 EventParser 之前提取 _auth 字段(zod 会剥离未知字段)
129
- if (this.authToken) {
130
- try {
131
- const raw = JSON.parse(message);
132
- if (raw._auth !== this.authToken) {
133
- logger.warn('[Socket] 认证失败,关闭连接');
134
- socket.destroy();
135
- return;
136
- }
137
- } catch {
138
- // JSON 解析失败,交给下面的 EventParser 处理
139
- }
140
- }
141
-
142
- const event = this.parser.parse(message);
143
- const result = await this.handler(event);
144
-
145
- // 双向通信:如果 handler 返回了结果,写回给 hook 脚本
146
- if (result) {
147
- // 清理无意义内容,避免 Claude Code 注入干扰上下文
148
- const cleaned = { ...result } as Record<string, unknown>;
149
- if (cleaned['additionalContext'] && !this.isValidContent(cleaned['additionalContext'] as string)) {
150
- delete cleaned['additionalContext'];
151
- }
152
- if (cleaned['systemMessage'] && !this.isValidContent(cleaned['systemMessage'] as string)) {
153
- delete cleaned['systemMessage'];
154
- }
155
-
156
- const payload = JSON.stringify(cleaned);
157
- socket.write(payload, () => socket.end());
158
- } else {
159
- socket.end();
160
- }
161
- } catch (err) {
162
- logger.error(`事件解析失败:${formatError(err)}`);
163
- logger.debug(`无效消息内容:${truncateString(message, 500)}`);
164
- socket.destroy();
165
- }
166
- }
167
-
168
- /**
169
- * 判断内容是否有意义,过滤空字符串、纯标点/符号等
170
- */
171
- private isValidContent(content: string): boolean {
172
- if (!content) return false;
173
- const trimmed = content.trim();
174
- if (trimmed.length === 0) return false;
175
-
176
- const stripped = trimmed
177
- .split('\n')
178
- .map(line => line.replace(/^>\s*/, '').trim())
179
- .join('\n')
180
- .trim();
181
-
182
- if (stripped.length === 0) return false;
183
- if (!/[\p{L}\p{N}]/u.test(stripped)) return false;
184
-
185
- return true;
186
- }
187
-
188
- close(): Promise<void> {
189
- return new Promise((resolve) => {
190
- this.server.close(() => {
191
- logger.info('Socket 服务器已关闭');
192
- resolve();
193
- });
194
- });
195
- }
196
- }
@@ -1,112 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import type { SQLiteStorage } from '../../core/storage/sqlite.js';
3
- import { logger } from '../../core/utils/logger.js';
4
- import { normalizeTimestamp, timestampToMs } from '../../core/utils/time.js';
5
- import { truncateSessionId } from '../../core/utils/session.js';
6
- import { truncateString } from '../../core/utils/format.js';
7
-
8
- const NEW_TASK_KEYWORDS = [
9
- '帮我', '实现', '修复', '添加', '重构', '优化', '创建', '删除',
10
- '升级', '迁移', '部署', '配置', '设计', '提交', '推送',
11
- 'fix', 'add', 'implement', 'refactor', 'create', 'delete', 'deploy',
12
- ];
13
-
14
- const CONTINUE_KEYWORDS = [
15
- '继续', '然后', '还有', '另外', '这个', '那个', '它',
16
- '对', '好的', '可以', '是的', '不是', '不对', '改一下',
17
- ];
18
-
19
- const TASK_SWITCH_MINUTES = 10;
20
-
21
- export class TaskSegmenter {
22
- private currentTasks = new Map<string, { id: string; lastTime: number }>();
23
-
24
- constructor(private storage: SQLiteStorage) {}
25
-
26
- processPrompt(sessionId: string, prompt: string, timestamp: string, eventId?: string): string {
27
- const key = sessionId;
28
- const current = this.currentTasks.get(key) ?? this.recoverActiveTask(key);
29
- const now = timestampToMs(timestamp);
30
-
31
- if (!current || this.shouldStartNewTask(prompt, now, current)) {
32
- if (current) {
33
- this.storage.updateTask(current.id, { status: 'completed', end_time: timestamp });
34
- }
35
- const taskId = this.createTask(sessionId, prompt, timestamp);
36
- this.currentTasks.set(key, { id: taskId, lastTime: now });
37
- if (eventId) this.storage.linkEventToTask(taskId, eventId);
38
- return taskId;
39
- }
40
-
41
- current.lastTime = now;
42
- if (eventId) this.storage.linkEventToTask(current.id, eventId);
43
- return current.id;
44
- }
45
-
46
- linkEvent(sessionId: string, eventId: string): void {
47
- const current = this.currentTasks.get(sessionId) ?? this.recoverActiveTask(sessionId);
48
- if (current) {
49
- this.storage.linkEventToTask(current.id, eventId);
50
- }
51
- }
52
-
53
- /**
54
- * 从数据库恢复 session 的 active 任务到内存 Map。
55
- * 用于 daemon 重启后内存丢失场景的 lazy 恢复。
56
- * 找不到 active 任务时返回 null(不报错)。
57
- */
58
- private recoverActiveTask(sessionId: string): { id: string; lastTime: number } | null {
59
- const tasks = this.storage.queryTasks({ session_id: sessionId, limit: 5 });
60
- const activeTask = tasks.find(t => t.status === 'active');
61
- if (!activeTask) return null;
62
-
63
- const anchor = activeTask.end_time ?? activeTask.start_time;
64
- const lastTime = timestampToMs(anchor);
65
- const entry = { id: activeTask.id, lastTime };
66
- this.currentTasks.set(sessionId, entry);
67
- logger.info(
68
- `[TaskSegmenter] Recovered active task ${truncateString(activeTask.id, 8)} for session ${truncateSessionId(sessionId)} from DB`,
69
- );
70
- return entry;
71
- }
72
-
73
- getCurrentTaskId(sessionId: string): string | null {
74
- return this.currentTasks.get(sessionId)?.id ?? null;
75
- }
76
-
77
- completeCurrentTask(sessionId: string, timestamp: string): void {
78
- const current = this.currentTasks.get(sessionId) ?? this.recoverActiveTask(sessionId);
79
- if (!current) return;
80
- this.storage.updateTask(current.id, {
81
- status: 'completed',
82
- end_time: timestamp,
83
- });
84
- this.currentTasks.delete(sessionId);
85
- }
86
-
87
- private shouldStartNewTask(prompt: string, now: number, current: { lastTime: number }): boolean {
88
- const minutesDiff = (now - current.lastTime) / 60000;
89
- if (minutesDiff > TASK_SWITCH_MINUTES) return true;
90
-
91
- const hasContinue = CONTINUE_KEYWORDS.some(kw => prompt.includes(kw));
92
- if (hasContinue) return false;
93
-
94
- const hasNewTask = NEW_TASK_KEYWORDS.some(kw => prompt.includes(kw));
95
- return hasNewTask;
96
- }
97
-
98
- private createTask(sessionId: string, prompt: string, timestamp: string): string {
99
- const taskId = randomUUID();
100
- const title = this.extractTitle(prompt);
101
- this.storage.writeTask({ id: taskId, session_id: sessionId, title, start_time: timestamp });
102
- logger.info(`[TaskSegmenter] New task: "${title}" (${truncateString(taskId, 8)})`);
103
- return taskId;
104
- }
105
-
106
- private extractTitle(prompt: string): string {
107
- const cleaned = prompt.replace(/\[Image #\d+\]/g, '').trim();
108
- const firstLine = cleaned.split('\n')[0].trim();
109
- if (firstLine.length <= 60) return firstLine || '(无标题)';
110
- return truncateString(firstLine, 60);
111
- }
112
- }
@@ -1,118 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Claude Forge - Hook shared library
3
- # Source this file at the top of each hook script:
4
- # source "$(dirname "$0")/hook-lib.sh"
5
- #
6
- # Provides:
7
- # - SOCKET_PATH, QUEUE_DIR shared variables
8
- # - send_event_or_enqueue() function
9
-
10
- SOCKET_PATH="${CLAUDE_FORGE_SOCKET:-$HOME/.claude-forge/daemon.sock}"
11
- QUEUE_DIR="$HOME/.claude-forge/queue"
12
-
13
- # resolve_project_path <input_cwd>
14
- #
15
- # Walk up the directory tree from <input_cwd> to find the nearest ancestor that
16
- # contains a `.git` entry (directory for normal clones, file for worktrees /
17
- # submodules). Print the resolved git-root path on stdout.
18
- #
19
- # Fallback rules:
20
- # - Empty input → start from $PWD
21
- # - Relative input → prefixed with $PWD to absolutise
22
- # - No .git found within 64 levels → print original input unchanged
23
- #
24
- # POSIX-only: uses `dirname` and `case`; does NOT depend on `realpath` or any
25
- # GNU coreutils extensions, so it works on macOS BSD and Linux alike.
26
- resolve_project_path() {
27
- local input="${1:-}"
28
- local dir="${input:-$PWD}"
29
-
30
- # Absolutise: prefix relative paths with $PWD (no realpath dependency)
31
- case "$dir" in
32
- /*) ;;
33
- *) dir="$PWD/$dir" ;;
34
- esac
35
-
36
- local guard=0
37
- while [ "$dir" != "/" ] && [ "$dir" != "." ] && [ $guard -lt 64 ]; do
38
- if [ -d "$dir/.git" ] || [ -f "$dir/.git" ]; then
39
- printf '%s' "$dir"
40
- return 0
41
- fi
42
- dir=$(dirname "$dir")
43
- guard=$((guard + 1))
44
- done
45
-
46
- # No git ancestor found → fall back to the caller's original cwd
47
- printf '%s' "${input:-$PWD}"
48
- }
49
-
50
- # send_event_or_enqueue <event_json> [timeout_seconds]
51
- #
52
- # 1. If socket file does not exist → enqueue in background → return empty string
53
- # 2. If socket exists → send via nc with timeout
54
- # - nc exit code != 0 → enqueue in background → return empty string
55
- # - nc exit code == 0 → return daemon response on stdout
56
- #
57
- # The caller reads REPLY_RESPONSE after calling this function:
58
- # send_event_or_enqueue "$EVENT" 10
59
- # RESPONSE="$HOOK_RESPONSE"
60
- #
61
- # macOS BSD nc notes:
62
- # - `nc -U` opens a Unix domain socket connection
63
- # - `-w N` sets idle timeout (seconds); on macOS this is the "connection timeout"
64
- # for UDP but for TCP/Unix it's inactivity timeout after connection
65
- # - Exit code: 0 = success (data sent + connection closed by remote), 1 = error
66
- # - An empty response (daemon returns nothing) still exits 0 if the connection
67
- # was established and closed cleanly → we check exit code, NOT response content
68
-
69
- HOOK_RESPONSE=""
70
-
71
- send_event_or_enqueue() {
72
- local event_json="$1"
73
- local timeout_secs="${2:-10}"
74
-
75
- HOOK_RESPONSE=""
76
-
77
- # Fast path: no socket file → daemon is not running
78
- if [ ! -S "$SOCKET_PATH" ]; then
79
- _enqueue_event_bg "$event_json"
80
- return 0
81
- fi
82
-
83
- # Socket exists: try to send; capture exit code separately from stdout
84
- local tmp_response
85
- tmp_response=$(echo "$event_json" | nc -U -w "$timeout_secs" "$SOCKET_PATH" 2>/dev/null)
86
- local nc_exit=$?
87
-
88
- if [ $nc_exit -ne 0 ]; then
89
- # nc failed (connection refused, socket disappeared between check and send, etc.)
90
- _enqueue_event_bg "$event_json"
91
- return 0
92
- fi
93
-
94
- # Success: pass response back to caller
95
- HOOK_RESPONSE="$tmp_response"
96
- }
97
-
98
- # _enqueue_event_bg <event_json>
99
- # Forks a background subshell to write the event JSON to the queue directory.
100
- # The main hook process is never blocked by this operation.
101
- _enqueue_event_bg() {
102
- local event_json="$1"
103
- (
104
- mkdir -p "$QUEUE_DIR"
105
- # Timestamp with second precision (BSD date has no %N nanoseconds)
106
- local ts
107
- ts=$(date -u +"%Y%m%dT%H%M%S")
108
- # UUID: prefer uuidgen (macOS), fall back to /proc on Linux
109
- local uuid
110
- uuid=$(uuidgen 2>/dev/null \
111
- || cat /proc/sys/kernel/random/uuid 2>/dev/null \
112
- || printf '%s-%s' "$$" "$RANDOM")
113
- local filename="${ts}-${uuid}.json"
114
- # Atomic write: write to temp file, then rename
115
- local tmp_file="$QUEUE_DIR/.tmp-${filename}"
116
- printf '%s' "$event_json" > "$tmp_file" && mv "$tmp_file" "$QUEUE_DIR/${filename}"
117
- ) &
118
- }
@@ -1,35 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Claude Forge - Notification Hook
3
-
4
- # shellcheck source=./hook-lib.sh
5
- source "$(dirname "$0")/hook-lib.sh"
6
-
7
- AUTH_TOKEN=$(cat "$HOME/.claude-forge/daemon.token" 2>/dev/null || echo '')
8
-
9
- # 读取 stdin
10
- INPUT=$(cat)
11
-
12
- # 提取 cwd(jq 替代 python3)
13
- RAW_CWD=$(echo "$INPUT" | jq -r '.cwd // ""')
14
- PROJECT_PATH=$(resolve_project_path "${RAW_CWD:-$PWD}")
15
- SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // .sessionId // ""')
16
- SESSION_ID="${SESSION_ID:-${CLAUDE_CODE_SESSION_ID:-cli}}"
17
-
18
- # 构造事件 JSON(原始 INPUT 作为 tool_input,保留完整通知内容)
19
- TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
20
- TOOL_INPUT=$(echo "$INPUT" | jq -c '.')
21
- EVENT=$(jq -n \
22
- --arg hook_type "Notification" \
23
- --arg timestamp "$TIMESTAMP" \
24
- --arg session_id "$SESSION_ID" \
25
- --arg project_path "$PROJECT_PATH" \
26
- --argjson tool_input "$TOOL_INPUT" \
27
- --arg auth "$AUTH_TOKEN" \
28
- '{hook_type: $hook_type, timestamp: $timestamp, session_id: $session_id,
29
- project_path: $project_path, tool_name: "notification",
30
- tool_input: $tool_input, _auth: $auth}')
31
-
32
- # 发送到 daemon(1 秒超时;失败时入队)
33
- send_event_or_enqueue "$EVENT" 1
34
-
35
- exit 0
@@ -1,61 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Claude Forge - PostToolUse Hook
3
-
4
- # shellcheck source=./hook-lib.sh
5
- source "$(dirname "$0")/hook-lib.sh"
6
-
7
- AUTH_TOKEN=$(cat "$HOME/.claude-forge/daemon.token" 2>/dev/null || echo '')
8
-
9
- # 读取 stdin
10
- INPUT=$(cat)
11
-
12
- # 提取字段(单次 jq 调用,替代 4 个 python3 进程)
13
- # tool_response 是 Claude Code 的字段名
14
- TOOL_NAME="${CLAUDE_TOOL_NAME:-$(echo "$INPUT" | jq -r '.tool_name // ""')}"
15
- TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}')
16
- TOOL_OUTPUT=$(echo "$INPUT" | jq -c '.tool_response // {}')
17
- RAW_CWD=$(echo "$INPUT" | jq -r '.cwd // ""')
18
- PROJECT_PATH=$(resolve_project_path "${RAW_CWD:-$PWD}")
19
- SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // .sessionId // ""')
20
- SESSION_ID="${SESSION_ID:-${CLAUDE_CODE_SESSION_ID:-cli}}"
21
-
22
- # 构造事件 JSON
23
- TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
24
- EVENT=$(jq -n \
25
- --arg hook_type "PostToolUse" \
26
- --arg timestamp "$TIMESTAMP" \
27
- --arg session_id "$SESSION_ID" \
28
- --arg project_path "$PROJECT_PATH" \
29
- --arg tool_name "$TOOL_NAME" \
30
- --argjson tool_input "$TOOL_INPUT" \
31
- --argjson tool_output "$TOOL_OUTPUT" \
32
- --arg auth "$AUTH_TOKEN" \
33
- '{hook_type: $hook_type, timestamp: $timestamp, session_id: $session_id,
34
- project_path: $project_path, tool_name: $tool_name, tool_input: $tool_input,
35
- tool_output: $tool_output, _auth: $auth}')
36
-
37
- # 发送到 daemon(PostToolUse 也需要等待响应以支持通知注入;失败时入队)
38
- send_event_or_enqueue "$EVENT" 5
39
- RESPONSE="$HOOK_RESPONSE"
40
-
41
- if [ -n "$RESPONSE" ]; then
42
- # 硬阻断:优先使用 hookSpecificOutput(Claude Code 官方格式)
43
- ALLOW=$(echo "$RESPONSE" | jq -r '.allow // true')
44
- if [ "$ALLOW" = "false" ]; then
45
- HAS_HOOK_OUTPUT=$(echo "$RESPONSE" | jq -r 'if .hookSpecificOutput then "yes" else "no" end')
46
- if [ "$HAS_HOOK_OUTPUT" = "yes" ]; then
47
- echo "$RESPONSE" | jq '{hookSpecificOutput: .hookSpecificOutput}'
48
- exit 0
49
- else
50
- REASON=$(echo "$RESPONSE" | jq -r '.reason // "Pipeline 已阻断"')
51
- echo "$REASON" >&2
52
- exit 2
53
- fi
54
- fi
55
-
56
- HAS_CONTEXT=$(echo "$RESPONSE" | jq -r 'if .additionalContext and (.additionalContext != "") then "yes" else "no" end')
57
- if [ "$HAS_CONTEXT" = "yes" ]; then
58
- echo "$RESPONSE"
59
- fi
60
- fi
61
- exit 0