mstro-app 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/dist/server/cli/headless/claude-invoker-process.d.ts +11 -0
  2. package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -0
  3. package/dist/server/cli/headless/claude-invoker-process.js +140 -0
  4. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -0
  5. package/dist/server/cli/headless/claude-invoker-stall.d.ts +40 -0
  6. package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -0
  7. package/dist/server/cli/headless/claude-invoker-stall.js +98 -0
  8. package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -0
  9. package/dist/server/cli/headless/claude-invoker-stream.d.ts +44 -0
  10. package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -0
  11. package/dist/server/cli/headless/claude-invoker-stream.js +276 -0
  12. package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -0
  13. package/dist/server/cli/headless/claude-invoker-tools.d.ts +21 -0
  14. package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -0
  15. package/dist/server/cli/headless/claude-invoker-tools.js +137 -0
  16. package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -0
  17. package/dist/server/cli/headless/claude-invoker.d.ts +6 -4
  18. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  19. package/dist/server/cli/headless/claude-invoker.js +10 -807
  20. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  21. package/dist/server/cli/headless/haiku-assessments.d.ts +62 -0
  22. package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -0
  23. package/dist/server/cli/headless/haiku-assessments.js +281 -0
  24. package/dist/server/cli/headless/haiku-assessments.js.map +1 -0
  25. package/dist/server/cli/headless/headless-logger.d.ts +3 -2
  26. package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
  27. package/dist/server/cli/headless/headless-logger.js +28 -5
  28. package/dist/server/cli/headless/headless-logger.js.map +1 -1
  29. package/dist/server/cli/headless/native-timeout-detector.d.ts +44 -0
  30. package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -0
  31. package/dist/server/cli/headless/native-timeout-detector.js +99 -0
  32. package/dist/server/cli/headless/native-timeout-detector.js.map +1 -0
  33. package/dist/server/cli/headless/stall-assessor.d.ts +2 -110
  34. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  35. package/dist/server/cli/headless/stall-assessor.js +65 -457
  36. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  37. package/dist/server/cli/improvisation-attachments.d.ts +21 -0
  38. package/dist/server/cli/improvisation-attachments.d.ts.map +1 -0
  39. package/dist/server/cli/improvisation-attachments.js +116 -0
  40. package/dist/server/cli/improvisation-attachments.js.map +1 -0
  41. package/dist/server/cli/improvisation-retry.d.ts +52 -0
  42. package/dist/server/cli/improvisation-retry.d.ts.map +1 -0
  43. package/dist/server/cli/improvisation-retry.js +434 -0
  44. package/dist/server/cli/improvisation-retry.js.map +1 -0
  45. package/dist/server/cli/improvisation-session-manager.d.ts +10 -266
  46. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  47. package/dist/server/cli/improvisation-session-manager.js +117 -1079
  48. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  49. package/dist/server/cli/improvisation-types.d.ts +86 -0
  50. package/dist/server/cli/improvisation-types.d.ts.map +1 -0
  51. package/dist/server/cli/improvisation-types.js +10 -0
  52. package/dist/server/cli/improvisation-types.js.map +1 -0
  53. package/dist/server/cli/prompt-builders.d.ts +68 -0
  54. package/dist/server/cli/prompt-builders.d.ts.map +1 -0
  55. package/dist/server/cli/prompt-builders.js +312 -0
  56. package/dist/server/cli/prompt-builders.js.map +1 -0
  57. package/dist/server/index.js +33 -212
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/mcp/bouncer-haiku.d.ts +10 -0
  60. package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -0
  61. package/dist/server/mcp/bouncer-haiku.js +152 -0
  62. package/dist/server/mcp/bouncer-haiku.js.map +1 -0
  63. package/dist/server/mcp/bouncer-integration.d.ts +3 -4
  64. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  65. package/dist/server/mcp/bouncer-integration.js +50 -196
  66. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  67. package/dist/server/mcp/security-analysis.d.ts +38 -0
  68. package/dist/server/mcp/security-analysis.d.ts.map +1 -0
  69. package/dist/server/mcp/security-analysis.js +183 -0
  70. package/dist/server/mcp/security-analysis.js.map +1 -0
  71. package/dist/server/mcp/security-audit.d.ts +1 -1
  72. package/dist/server/mcp/security-audit.d.ts.map +1 -1
  73. package/dist/server/mcp/security-patterns.d.ts +1 -25
  74. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  75. package/dist/server/mcp/security-patterns.js +55 -260
  76. package/dist/server/mcp/security-patterns.js.map +1 -1
  77. package/dist/server/server-setup.d.ts +22 -0
  78. package/dist/server/server-setup.d.ts.map +1 -0
  79. package/dist/server/server-setup.js +101 -0
  80. package/dist/server/server-setup.js.map +1 -0
  81. package/dist/server/services/file-explorer-ops.d.ts +24 -0
  82. package/dist/server/services/file-explorer-ops.d.ts.map +1 -0
  83. package/dist/server/services/file-explorer-ops.js +211 -0
  84. package/dist/server/services/file-explorer-ops.js.map +1 -0
  85. package/dist/server/services/files.d.ts +2 -85
  86. package/dist/server/services/files.d.ts.map +1 -1
  87. package/dist/server/services/files.js +7 -427
  88. package/dist/server/services/files.js.map +1 -1
  89. package/dist/server/services/plan/composer.d.ts.map +1 -1
  90. package/dist/server/services/plan/composer.js +2 -1
  91. package/dist/server/services/plan/composer.js.map +1 -1
  92. package/dist/server/services/plan/executor.d.ts.map +1 -1
  93. package/dist/server/services/plan/executor.js +3 -1
  94. package/dist/server/services/plan/executor.js.map +1 -1
  95. package/dist/server/services/plan/parser-core.d.ts +20 -0
  96. package/dist/server/services/plan/parser-core.d.ts.map +1 -0
  97. package/dist/server/services/plan/parser-core.js +350 -0
  98. package/dist/server/services/plan/parser-core.js.map +1 -0
  99. package/dist/server/services/plan/parser-migration.d.ts +5 -0
  100. package/dist/server/services/plan/parser-migration.d.ts.map +1 -0
  101. package/dist/server/services/plan/parser-migration.js +124 -0
  102. package/dist/server/services/plan/parser-migration.js.map +1 -0
  103. package/dist/server/services/plan/parser.d.ts +0 -8
  104. package/dist/server/services/plan/parser.d.ts.map +1 -1
  105. package/dist/server/services/plan/parser.js +50 -569
  106. package/dist/server/services/plan/parser.js.map +1 -1
  107. package/dist/server/services/plan/review-gate.d.ts +2 -0
  108. package/dist/server/services/plan/review-gate.d.ts.map +1 -1
  109. package/dist/server/services/plan/review-gate.js +2 -2
  110. package/dist/server/services/plan/review-gate.js.map +1 -1
  111. package/dist/server/services/plan/types.d.ts +2 -0
  112. package/dist/server/services/plan/types.d.ts.map +1 -1
  113. package/dist/server/services/platform-credentials.d.ts +24 -0
  114. package/dist/server/services/platform-credentials.d.ts.map +1 -0
  115. package/dist/server/services/platform-credentials.js +68 -0
  116. package/dist/server/services/platform-credentials.js.map +1 -0
  117. package/dist/server/services/platform.d.ts +1 -31
  118. package/dist/server/services/platform.d.ts.map +1 -1
  119. package/dist/server/services/platform.js +10 -119
  120. package/dist/server/services/platform.js.map +1 -1
  121. package/dist/server/services/terminal/pty-manager.d.ts +7 -97
  122. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  123. package/dist/server/services/terminal/pty-manager.js +53 -266
  124. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  125. package/dist/server/services/terminal/pty-utils.d.ts +57 -0
  126. package/dist/server/services/terminal/pty-utils.d.ts.map +1 -0
  127. package/dist/server/services/terminal/pty-utils.js +141 -0
  128. package/dist/server/services/terminal/pty-utils.js.map +1 -0
  129. package/dist/server/services/websocket/file-definition-handlers.d.ts +4 -0
  130. package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -0
  131. package/dist/server/services/websocket/file-definition-handlers.js +153 -0
  132. package/dist/server/services/websocket/file-definition-handlers.js.map +1 -0
  133. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  134. package/dist/server/services/websocket/file-explorer-handlers.js +52 -391
  135. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  136. package/dist/server/services/websocket/file-search-handlers.d.ts +5 -0
  137. package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -0
  138. package/dist/server/services/websocket/file-search-handlers.js +238 -0
  139. package/dist/server/services/websocket/file-search-handlers.js.map +1 -0
  140. package/dist/server/services/websocket/file-utils.js +3 -3
  141. package/dist/server/services/websocket/file-utils.js.map +1 -1
  142. package/dist/server/services/websocket/git-branch-handlers.d.ts +7 -0
  143. package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -0
  144. package/dist/server/services/websocket/git-branch-handlers.js +110 -0
  145. package/dist/server/services/websocket/git-branch-handlers.js.map +1 -0
  146. package/dist/server/services/websocket/git-diff-handlers.d.ts +6 -0
  147. package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -0
  148. package/dist/server/services/websocket/git-diff-handlers.js +123 -0
  149. package/dist/server/services/websocket/git-diff-handlers.js.map +1 -0
  150. package/dist/server/services/websocket/git-handlers.d.ts +2 -31
  151. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  152. package/dist/server/services/websocket/git-handlers.js +35 -541
  153. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  154. package/dist/server/services/websocket/git-log-handlers.d.ts +6 -0
  155. package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -0
  156. package/dist/server/services/websocket/git-log-handlers.js +128 -0
  157. package/dist/server/services/websocket/git-log-handlers.js.map +1 -0
  158. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
  159. package/dist/server/services/websocket/git-pr-handlers.js +13 -53
  160. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
  161. package/dist/server/services/websocket/git-tag-handlers.d.ts +6 -0
  162. package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -0
  163. package/dist/server/services/websocket/git-tag-handlers.js +76 -0
  164. package/dist/server/services/websocket/git-tag-handlers.js.map +1 -0
  165. package/dist/server/services/websocket/git-utils.d.ts +43 -0
  166. package/dist/server/services/websocket/git-utils.d.ts.map +1 -0
  167. package/dist/server/services/websocket/git-utils.js +201 -0
  168. package/dist/server/services/websocket/git-utils.js.map +1 -0
  169. package/dist/server/services/websocket/handler.d.ts +2 -0
  170. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  171. package/dist/server/services/websocket/handler.js +37 -126
  172. package/dist/server/services/websocket/handler.js.map +1 -1
  173. package/dist/server/services/websocket/plan-board-handlers.d.ts +11 -0
  174. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -0
  175. package/dist/server/services/websocket/plan-board-handlers.js +218 -0
  176. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -0
  177. package/dist/server/services/websocket/plan-execution-handlers.d.ts +9 -0
  178. package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -0
  179. package/dist/server/services/websocket/plan-execution-handlers.js +142 -0
  180. package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -0
  181. package/dist/server/services/websocket/plan-handlers.d.ts +7 -2
  182. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
  183. package/dist/server/services/websocket/plan-handlers.js +6 -925
  184. package/dist/server/services/websocket/plan-handlers.js.map +1 -1
  185. package/dist/server/services/websocket/plan-helpers.d.ts +19 -0
  186. package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -0
  187. package/dist/server/services/websocket/plan-helpers.js +199 -0
  188. package/dist/server/services/websocket/plan-helpers.js.map +1 -0
  189. package/dist/server/services/websocket/plan-issue-handlers.d.ts +12 -0
  190. package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -0
  191. package/dist/server/services/websocket/plan-issue-handlers.js +162 -0
  192. package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -0
  193. package/dist/server/services/websocket/plan-sprint-handlers.d.ts +7 -0
  194. package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -0
  195. package/dist/server/services/websocket/plan-sprint-handlers.js +206 -0
  196. package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -0
  197. package/dist/server/services/websocket/quality-complexity.d.ts +14 -0
  198. package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -0
  199. package/dist/server/services/websocket/quality-complexity.js +262 -0
  200. package/dist/server/services/websocket/quality-complexity.js.map +1 -0
  201. package/dist/server/services/websocket/quality-fix-agent.d.ts +16 -0
  202. package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -0
  203. package/dist/server/services/websocket/quality-fix-agent.js +140 -0
  204. package/dist/server/services/websocket/quality-fix-agent.js.map +1 -0
  205. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  206. package/dist/server/services/websocket/quality-handlers.js +34 -346
  207. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  208. package/dist/server/services/websocket/quality-linting.d.ts +9 -0
  209. package/dist/server/services/websocket/quality-linting.d.ts.map +1 -0
  210. package/dist/server/services/websocket/quality-linting.js +178 -0
  211. package/dist/server/services/websocket/quality-linting.js.map +1 -0
  212. package/dist/server/services/websocket/quality-review-agent.d.ts +19 -0
  213. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -0
  214. package/dist/server/services/websocket/quality-review-agent.js +206 -0
  215. package/dist/server/services/websocket/quality-review-agent.js.map +1 -0
  216. package/dist/server/services/websocket/quality-service.d.ts +3 -51
  217. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  218. package/dist/server/services/websocket/quality-service.js +9 -651
  219. package/dist/server/services/websocket/quality-service.js.map +1 -1
  220. package/dist/server/services/websocket/quality-tools.d.ts +23 -0
  221. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -0
  222. package/dist/server/services/websocket/quality-tools.js +208 -0
  223. package/dist/server/services/websocket/quality-tools.js.map +1 -0
  224. package/dist/server/services/websocket/quality-types.d.ts +59 -0
  225. package/dist/server/services/websocket/quality-types.d.ts.map +1 -0
  226. package/dist/server/services/websocket/quality-types.js +101 -0
  227. package/dist/server/services/websocket/quality-types.js.map +1 -0
  228. package/dist/server/services/websocket/session-handlers.d.ts +3 -4
  229. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  230. package/dist/server/services/websocket/session-handlers.js +3 -378
  231. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  232. package/dist/server/services/websocket/session-history.d.ts +4 -0
  233. package/dist/server/services/websocket/session-history.d.ts.map +1 -0
  234. package/dist/server/services/websocket/session-history.js +208 -0
  235. package/dist/server/services/websocket/session-history.js.map +1 -0
  236. package/dist/server/services/websocket/session-initialization.d.ts +5 -0
  237. package/dist/server/services/websocket/session-initialization.d.ts.map +1 -0
  238. package/dist/server/services/websocket/session-initialization.js +163 -0
  239. package/dist/server/services/websocket/session-initialization.js.map +1 -0
  240. package/dist/server/services/websocket/types.d.ts +12 -2
  241. package/dist/server/services/websocket/types.d.ts.map +1 -1
  242. package/package.json +1 -1
  243. package/server/cli/headless/claude-invoker-process.ts +204 -0
  244. package/server/cli/headless/claude-invoker-stall.ts +164 -0
  245. package/server/cli/headless/claude-invoker-stream.ts +353 -0
  246. package/server/cli/headless/claude-invoker-tools.ts +187 -0
  247. package/server/cli/headless/claude-invoker.ts +15 -1096
  248. package/server/cli/headless/haiku-assessments.ts +365 -0
  249. package/server/cli/headless/headless-logger.ts +26 -5
  250. package/server/cli/headless/native-timeout-detector.ts +117 -0
  251. package/server/cli/headless/stall-assessor.ts +65 -618
  252. package/server/cli/improvisation-attachments.ts +148 -0
  253. package/server/cli/improvisation-retry.ts +602 -0
  254. package/server/cli/improvisation-session-manager.ts +140 -1349
  255. package/server/cli/improvisation-types.ts +98 -0
  256. package/server/cli/prompt-builders.ts +370 -0
  257. package/server/index.ts +35 -246
  258. package/server/mcp/bouncer-haiku.ts +182 -0
  259. package/server/mcp/bouncer-integration.ts +87 -248
  260. package/server/mcp/security-analysis.ts +217 -0
  261. package/server/mcp/security-audit.ts +1 -1
  262. package/server/mcp/security-patterns.ts +60 -283
  263. package/server/server-setup.ts +114 -0
  264. package/server/services/file-explorer-ops.ts +293 -0
  265. package/server/services/files.ts +20 -532
  266. package/server/services/plan/composer.ts +2 -1
  267. package/server/services/plan/executor.ts +3 -1
  268. package/server/services/plan/parser-core.ts +406 -0
  269. package/server/services/plan/parser-migration.ts +128 -0
  270. package/server/services/plan/parser.ts +52 -620
  271. package/server/services/plan/review-gate.ts +4 -2
  272. package/server/services/plan/types.ts +2 -0
  273. package/server/services/platform-credentials.ts +83 -0
  274. package/server/services/platform.ts +15 -141
  275. package/server/services/terminal/pty-manager.ts +66 -313
  276. package/server/services/terminal/pty-utils.ts +176 -0
  277. package/server/services/websocket/file-definition-handlers.ts +165 -0
  278. package/server/services/websocket/file-explorer-handlers.ts +37 -452
  279. package/server/services/websocket/file-search-handlers.ts +291 -0
  280. package/server/services/websocket/file-utils.ts +3 -3
  281. package/server/services/websocket/git-branch-handlers.ts +130 -0
  282. package/server/services/websocket/git-diff-handlers.ts +140 -0
  283. package/server/services/websocket/git-handlers.ts +40 -625
  284. package/server/services/websocket/git-log-handlers.ts +149 -0
  285. package/server/services/websocket/git-pr-handlers.ts +17 -62
  286. package/server/services/websocket/git-tag-handlers.ts +91 -0
  287. package/server/services/websocket/git-utils.ts +230 -0
  288. package/server/services/websocket/handler.ts +39 -126
  289. package/server/services/websocket/plan-board-handlers.ts +277 -0
  290. package/server/services/websocket/plan-execution-handlers.ts +184 -0
  291. package/server/services/websocket/plan-handlers.ts +8 -1114
  292. package/server/services/websocket/plan-helpers.ts +215 -0
  293. package/server/services/websocket/plan-issue-handlers.ts +204 -0
  294. package/server/services/websocket/plan-sprint-handlers.ts +252 -0
  295. package/server/services/websocket/quality-complexity.ts +294 -0
  296. package/server/services/websocket/quality-fix-agent.ts +181 -0
  297. package/server/services/websocket/quality-handlers.ts +36 -404
  298. package/server/services/websocket/quality-linting.ts +187 -0
  299. package/server/services/websocket/quality-review-agent.ts +246 -0
  300. package/server/services/websocket/quality-service.ts +11 -762
  301. package/server/services/websocket/quality-tools.ts +209 -0
  302. package/server/services/websocket/quality-types.ts +169 -0
  303. package/server/services/websocket/session-handlers.ts +5 -437
  304. package/server/services/websocket/session-history.ts +222 -0
  305. package/server/services/websocket/session-initialization.ts +209 -0
  306. package/server/services/websocket/types.ts +17 -0
@@ -1,182 +1,11 @@
1
1
  // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
2
  // Licensed under the MIT License. See LICENSE file for details.
3
- /**
4
- * Plan Handlers WebSocket message handlers for Plan view
5
- *
6
- * Routes plan* messages to the PPS parser and file operations.
7
- * Follows the same pattern as quality-handlers.ts and git-handlers.ts.
8
- */
9
- import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
10
- import { basename, join, resolve } from 'node:path';
11
- import { handlePlanPrompt } from '../plan/composer.js';
12
- import { PlanExecutor } from '../plan/executor.js';
13
- import { replaceFrontMatterField } from '../plan/front-matter.js';
14
- import { defaultPmDir, getNextBoardId, getNextBoardNumber, getNextId, getNextSprintId, parseBoardArtifacts, parseBoardDirectory, parsePlanDirectory, parseSingleIssue, parseSingleMilestone, parseSingleSprint, parseSprintArtifacts, planDirExists, resolvePmDir } from '../plan/parser.js';
15
- import { PlanWatcher } from '../plan/watcher.js';
16
- const watcherCache = new Map();
17
- const executorCache = new Map();
18
- // ============================================================================
19
- // Helpers
20
- // ============================================================================
21
- /** Validate that a user-supplied path resolves within the .pm/ (or legacy .plan/) directory. */
22
- function resolvePlanPath(workingDir, relativePath) {
23
- const pmDir = resolvePmDir(workingDir);
24
- if (!pmDir)
25
- return null;
26
- const resolved = resolve(pmDir, relativePath);
27
- if (!resolved.startsWith(`${pmDir}/`) && resolved !== pmDir)
28
- return null;
29
- return resolved;
30
- }
31
- /** Guard for write operations — returns true if denied. */
32
- function denyIfViewOnly(ctx, ws, permission) {
33
- if (permission === 'view') {
34
- ctx.send(ws, { type: 'planError', data: { error: 'Permission denied' } });
35
- return true;
36
- }
37
- return false;
38
- }
39
- function formatYamlValue(value) {
40
- if (value === null || value === undefined)
41
- return 'null';
42
- if (typeof value === 'boolean')
43
- return String(value);
44
- if (typeof value === 'number')
45
- return String(value);
46
- if (Array.isArray(value)) {
47
- if (value.length === 0)
48
- return '[]';
49
- return `[${value.map(v => typeof v === 'string' ? v : String(v)).join(', ')}]`;
50
- }
51
- return `"${String(value).replace(/"/g, '\\"')}"`;
52
- }
53
- function buildIssueMarkdown(id, title, type, priority, labels, sprint, description) {
54
- const labelsYaml = labels.length > 0 ? `[${labels.join(', ')}]` : '[]';
55
- const today = new Date().toISOString().split('T')[0];
56
- return `---
57
- id: ${id}
58
- title: "${title.replace(/"/g, '\\"')}"
59
- type: ${type}
60
- status: backlog
61
- priority: ${priority}
62
- estimate: null
63
- labels: ${labelsYaml}
64
- epic: null
65
- sprint: ${sprint || 'null'}
66
- milestone: null
67
- assigned: null
68
- created: "${today}"
69
- due: null
70
- blocked_by: []
71
- blocks: []
72
- relates_to: []
73
- ---
74
-
75
- # ${id}: ${title}
76
-
77
- ## Description
78
- ${description}
79
-
80
- ## Acceptance Criteria
81
-
82
- ## Technical Notes
83
-
84
- ## Files to Modify
85
-
86
- ## Activity
87
- `;
88
- }
89
- function buildProjectMarkdown(name) {
90
- const today = new Date().toISOString().split('T')[0];
91
- const projectId = name.toLowerCase().replace(/[^a-z0-9]+/g, '-');
92
- return `---
93
- name: "${name}"
94
- id: ${projectId}
95
- created: "${today}"
96
- status: active
97
- estimation: fibonacci
98
- id_prefixes:
99
- epic: EP
100
- issue: IS
101
- bug: BG
102
- labels: []
103
- ---
104
-
105
- # ${name}
106
-
107
- ## Goals
108
-
109
- ## Teams
110
-
111
- ## Labels
112
-
113
- ## Workflows
114
- | Status | Category | Description |
115
- |---|---|---|
116
- | backlog | unstarted | Accepted, not yet scheduled |
117
- | todo | unstarted | Scheduled for current sprint |
118
- | in_progress | started | Actively being worked on |
119
- | in_review | started | PR open, awaiting review |
120
- | done | completed | Merged and verified |
121
- | cancelled | cancelled | Will not be done |
122
- `;
123
- }
124
- function buildStateMarkdown(name) {
125
- return `---
126
- project: "${name}"
127
- current_sprint: null
128
- active_milestone: null
129
- paused: false
130
- last_session: null
131
- ---
132
-
133
- # Project State
134
-
135
- ## Current Focus
136
-
137
- ## Ready to Work
138
-
139
- ## In Progress
140
-
141
- ## Blocked
142
-
143
- ## Recently Completed
144
-
145
- ## Warnings
146
- `;
147
- }
148
- function getWatcher(workingDir, ctx) {
149
- let watcher = watcherCache.get(workingDir);
150
- if (!watcher) {
151
- watcher = new PlanWatcher(workingDir, ctx);
152
- watcherCache.set(workingDir, watcher);
153
- }
154
- return watcher;
155
- }
156
- function getExecutor(workingDir) {
157
- let executor = executorCache.get(workingDir);
158
- if (!executor) {
159
- executor = new PlanExecutor(workingDir);
160
- executorCache.set(workingDir, executor);
161
- }
162
- return executor;
163
- }
164
- /** Cleanup watchers and executors for a working directory. */
165
- export function cleanupPlanResources(workingDir) {
166
- const watcher = watcherCache.get(workingDir);
167
- if (watcher) {
168
- watcher.stop();
169
- watcherCache.delete(workingDir);
170
- }
171
- const executor = executorCache.get(workingDir);
172
- if (executor) {
173
- executor.stop();
174
- executorCache.delete(workingDir);
175
- }
176
- }
177
- // ============================================================================
178
- // Main dispatcher
179
- // ============================================================================
3
+ import { handleArchiveBoard, handleCreateBoard, handleGetBoard, handleGetBoardArtifacts, handleGetBoardState, handleReorderBoards, handleSetActiveBoard, handleUpdateBoard } from './plan-board-handlers.js';
4
+ import { handleExecute, handleExecuteEpic, handlePause, handlePrompt, handleResume, handleStop } from './plan-execution-handlers.js';
5
+ import { handleCreateIssue, handleDeleteIssue, handleGetIssue, handleGetMilestone, handleGetSprint, handleListIssues, handlePlanInit, handleScaffold, handleUpdateIssue } from './plan-issue-handlers.js';
6
+ import { handleActivateSprint, handleCompleteSprint, handleCreateSprint, handleGetSprintArtifacts } from './plan-sprint-handlers.js';
7
+ // Re-export for backward compatibility
8
+ export { cleanupPlanResources } from './plan-helpers.js';
180
9
  export function handlePlanMessage(ctx, ws, msg, _tabId, workingDir, permission) {
181
10
  const handlers = {
182
11
  planInit: () => handlePlanInit(ctx, ws, workingDir),
@@ -221,752 +50,4 @@ export function handlePlanMessage(ctx, ws, msg, _tabId, workingDir, permission)
221
50
  ctx.send(ws, { type: 'planError', data: { error: errMsg } });
222
51
  }
223
52
  }
224
- // ============================================================================
225
- // Read-only handlers
226
- // ============================================================================
227
- /** Create the .mstro/pm/ directory structure with a default board. */
228
- function scaffoldPmDirectory(workingDir, name) {
229
- const planDir = defaultPmDir(workingDir);
230
- const boardId = 'BOARD-001';
231
- const boardDir = join(planDir, 'boards', boardId);
232
- for (const dir of ['milestones', 'templates']) {
233
- mkdirSync(join(planDir, dir), { recursive: true });
234
- }
235
- for (const dir of ['backlog', 'out', 'reviews']) {
236
- mkdirSync(join(boardDir, dir), { recursive: true });
237
- }
238
- writeFileSync(join(planDir, 'project.md'), buildProjectMarkdown(name), 'utf-8');
239
- const workspace = { activeBoardId: boardId, boardOrder: [boardId] };
240
- writeFileSync(join(planDir, 'workspace.json'), JSON.stringify(workspace, null, 2), 'utf-8');
241
- const today = new Date().toISOString().split('T')[0];
242
- writeFileSync(join(boardDir, 'board.md'), `---
243
- id: ${boardId}
244
- title: "Board 1"
245
- status: draft
246
- created: "${today}"
247
- completed_at: null
248
- goal: ""
249
- ---
250
-
251
- # Board 1
252
-
253
- ## Goal
254
-
255
- ## Notes
256
- `, 'utf-8');
257
- writeFileSync(join(boardDir, 'STATE.md'), buildStateMarkdown(name), 'utf-8');
258
- writeFileSync(join(boardDir, 'progress.md'), '# Board Progress\n', 'utf-8');
259
- }
260
- function handlePlanInit(ctx, ws, workingDir) {
261
- // Auto-scaffold if .mstro/pm/ doesn't exist
262
- if (!planDirExists(workingDir)) {
263
- const projectName = basename(workingDir) || 'My Project';
264
- scaffoldPmDirectory(workingDir, projectName);
265
- }
266
- const fullState = parsePlanDirectory(workingDir);
267
- if (!fullState) {
268
- ctx.send(ws, { type: 'planNotFound', data: {} });
269
- return;
270
- }
271
- ctx.send(ws, { type: 'planState', data: fullState });
272
- const watcher = getWatcher(workingDir, ctx);
273
- watcher.start();
274
- }
275
- function handleListIssues(ctx, ws, workingDir) {
276
- const fullState = parsePlanDirectory(workingDir);
277
- if (!fullState) {
278
- ctx.send(ws, { type: 'planNotFound', data: {} });
279
- return;
280
- }
281
- ctx.send(ws, { type: 'planIssueList', data: { issues: fullState.issues } });
282
- }
283
- function handleGetIssue(ctx, ws, msg, workingDir) {
284
- const path = msg.data?.path;
285
- if (!path || !resolvePlanPath(workingDir, path)) {
286
- ctx.send(ws, { type: 'planError', data: { error: 'Invalid issue path' } });
287
- return;
288
- }
289
- const issue = parseSingleIssue(workingDir, path);
290
- if (!issue) {
291
- ctx.send(ws, { type: 'planError', data: { error: `Issue not found: ${path}` } });
292
- return;
293
- }
294
- ctx.send(ws, { type: 'planIssue', data: issue });
295
- }
296
- function handleGetSprint(ctx, ws, msg, workingDir) {
297
- const path = msg.data?.path;
298
- if (!path || !resolvePlanPath(workingDir, path)) {
299
- ctx.send(ws, { type: 'planError', data: { error: 'Invalid sprint path' } });
300
- return;
301
- }
302
- const sprint = parseSingleSprint(workingDir, path);
303
- if (!sprint) {
304
- ctx.send(ws, { type: 'planError', data: { error: `Sprint not found: ${path}` } });
305
- return;
306
- }
307
- ctx.send(ws, { type: 'planSprint', data: sprint });
308
- }
309
- function handleGetMilestone(ctx, ws, msg, workingDir) {
310
- const path = msg.data?.path;
311
- if (!path || !resolvePlanPath(workingDir, path)) {
312
- ctx.send(ws, { type: 'planError', data: { error: 'Invalid milestone path' } });
313
- return;
314
- }
315
- const milestone = parseSingleMilestone(workingDir, path);
316
- if (!milestone) {
317
- ctx.send(ws, { type: 'planError', data: { error: `Milestone not found: ${path}` } });
318
- return;
319
- }
320
- ctx.send(ws, { type: 'planMilestone', data: milestone });
321
- }
322
- // ============================================================================
323
- // Mutation handlers
324
- // ============================================================================
325
- /** Resolve backlog directory and existing issues for a board or legacy layout. */
326
- function resolveBacklogContext(pmDir, workingDir, boardId) {
327
- const fullState = parsePlanDirectory(workingDir);
328
- const effectiveBoardId = boardId || fullState?.workspace?.activeBoardId;
329
- if (effectiveBoardId && existsSync(join(pmDir, 'boards', effectiveBoardId))) {
330
- const boardState = parseBoardDirectory(pmDir, effectiveBoardId);
331
- return {
332
- backlogDir: join(pmDir, 'boards', effectiveBoardId, 'backlog'),
333
- issues: boardState?.issues ?? [],
334
- pathPrefix: `boards/${effectiveBoardId}/backlog`,
335
- };
336
- }
337
- return {
338
- backlogDir: join(pmDir, 'backlog'),
339
- issues: fullState?.issues ?? [],
340
- pathPrefix: 'backlog',
341
- };
342
- }
343
- function handleCreateIssue(ctx, ws, msg, workingDir, permission) {
344
- if (denyIfViewOnly(ctx, ws, permission))
345
- return;
346
- const { title, type = 'issue', priority = 'P2', labels = [], sprint, description = '', boardId } = msg.data || {};
347
- if (!title) {
348
- ctx.send(ws, { type: 'planError', data: { error: 'Title required' } });
349
- return;
350
- }
351
- const pmDir = resolvePmDir(workingDir) ?? defaultPmDir(workingDir);
352
- const { backlogDir, issues, pathPrefix } = resolveBacklogContext(pmDir, workingDir, boardId);
353
- if (!existsSync(backlogDir))
354
- mkdirSync(backlogDir, { recursive: true });
355
- const prefix = type === 'bug' ? 'BG' : type === 'epic' ? 'EP' : 'IS';
356
- const id = getNextId(issues, prefix);
357
- const fileName = `${id}.md`;
358
- writeFileSync(join(backlogDir, fileName), buildIssueMarkdown(id, title, type, priority, labels, sprint, description), 'utf-8');
359
- const issue = parseSingleIssue(workingDir, `${pathPrefix}/${fileName}`);
360
- ctx.broadcastToAll({ type: 'planIssueCreated', data: issue });
361
- }
362
- function handleUpdateIssue(ctx, ws, msg, workingDir, permission) {
363
- if (denyIfViewOnly(ctx, ws, permission))
364
- return;
365
- const { path, fields } = msg.data || {};
366
- if (!path || !fields) {
367
- ctx.send(ws, { type: 'planError', data: { error: 'Path and fields required' } });
368
- return;
369
- }
370
- const fullPath = resolvePlanPath(workingDir, path);
371
- if (!fullPath || !existsSync(fullPath)) {
372
- ctx.send(ws, { type: 'planError', data: { error: `File not found: ${path}` } });
373
- return;
374
- }
375
- let content = readFileSync(fullPath, 'utf-8');
376
- if (!content.match(/^---\n/)) {
377
- ctx.send(ws, { type: 'planError', data: { error: 'Invalid file format' } });
378
- return;
379
- }
380
- for (const [key, value] of Object.entries(fields)) {
381
- const yamlKey = key.replace(/([A-Z])/g, '_$1').toLowerCase();
382
- content = replaceFrontMatterField(content, yamlKey, formatYamlValue(value));
383
- }
384
- writeFileSync(fullPath, content, 'utf-8');
385
- const issue = parseSingleIssue(workingDir, path);
386
- ctx.broadcastToAll({ type: 'planIssueUpdated', data: issue });
387
- }
388
- function handleDeleteIssue(ctx, ws, msg, workingDir, permission) {
389
- if (denyIfViewOnly(ctx, ws, permission))
390
- return;
391
- const path = msg.data?.path;
392
- if (!path) {
393
- ctx.send(ws, { type: 'planError', data: { error: 'Path required' } });
394
- return;
395
- }
396
- const fullPath = resolvePlanPath(workingDir, path);
397
- if (!fullPath || !existsSync(fullPath)) {
398
- ctx.send(ws, { type: 'planError', data: { error: `File not found: ${path}` } });
399
- return;
400
- }
401
- unlinkSync(fullPath);
402
- ctx.broadcastToAll({ type: 'planIssueDeleted', data: { path } });
403
- }
404
- function handleScaffold(ctx, ws, msg, workingDir, permission) {
405
- if (denyIfViewOnly(ctx, ws, permission))
406
- return;
407
- const name = msg.data?.name || 'My Project';
408
- scaffoldPmDirectory(workingDir, name);
409
- const fullState = parsePlanDirectory(workingDir);
410
- ctx.broadcastToAll({ type: 'planScaffolded', data: fullState });
411
- }
412
- // ============================================================================
413
- // Composer + Execution handlers
414
- // ============================================================================
415
- function handlePrompt(ctx, ws, msg, workingDir, permission) {
416
- if (denyIfViewOnly(ctx, ws, permission))
417
- return;
418
- const prompt = msg.data?.prompt;
419
- const boardId = msg.data?.boardId;
420
- if (!prompt) {
421
- ctx.send(ws, { type: 'planError', data: { error: 'Prompt required' } });
422
- return;
423
- }
424
- handlePlanPrompt(ctx, ws, prompt, workingDir, boardId).catch(error => {
425
- ctx.send(ws, {
426
- type: 'planError',
427
- data: { error: error instanceof Error ? error.message : String(error) },
428
- });
429
- });
430
- }
431
- function wireExecutorEvents(executor, ctx, workingDir) {
432
- executor.removeAllListeners();
433
- executor.on('statusChanged', (status) => {
434
- ctx.broadcastToAll({ type: 'planExecutionProgress', data: { status } });
435
- });
436
- executor.on('issueStarted', (issue) => {
437
- ctx.broadcastToAll({
438
- type: 'planExecutionProgress',
439
- data: { issueId: issue.id, status: 'executing', title: issue.title },
440
- });
441
- });
442
- executor.on('output', (data) => {
443
- ctx.broadcastToAll({ type: 'planExecutionOutput', data });
444
- });
445
- executor.on('issueCompleted', () => {
446
- ctx.broadcastToAll({ type: 'planExecutionMetrics', data: executor.getMetrics() });
447
- const fullState = parsePlanDirectory(workingDir);
448
- if (fullState) {
449
- ctx.broadcastToAll({ type: 'planStateUpdated', data: fullState });
450
- }
451
- });
452
- executor.on('issueError', (data) => {
453
- ctx.broadcastToAll({ type: 'planExecutionError', data });
454
- });
455
- executor.on('waveStarted', (data) => {
456
- ctx.broadcastToAll({
457
- type: 'planExecutionProgress',
458
- data: { status: 'wave', issueIds: data.issueIds },
459
- });
460
- });
461
- executor.on('waveError', (data) => {
462
- ctx.broadcastToAll({ type: 'planExecutionError', data });
463
- });
464
- executor.on('stateUpdated', () => {
465
- const fullState = parsePlanDirectory(workingDir);
466
- if (fullState) {
467
- ctx.broadcastToAll({ type: 'planStateUpdated', data: fullState });
468
- }
469
- });
470
- executor.on('reviewProgress', (data) => {
471
- ctx.broadcastToAll({ type: 'planReviewProgress', data });
472
- });
473
- executor.on('complete', (reason) => {
474
- ctx.broadcastToAll({ type: 'planExecutionComplete', data: { reason, metrics: executor.getMetrics() } });
475
- });
476
- executor.on('error', (error) => {
477
- ctx.broadcastToAll({ type: 'planExecutionError', data: { error } });
478
- });
479
- }
480
- function handleExecute(ctx, ws, msg, workingDir, permission) {
481
- if (denyIfViewOnly(ctx, ws, permission))
482
- return;
483
- const executor = getExecutor(workingDir);
484
- if (executor.getStatus() === 'executing' || executor.getStatus() === 'starting') {
485
- ctx.send(ws, { type: 'planError', data: { error: 'Execution already in progress' } });
486
- return;
487
- }
488
- wireExecutorEvents(executor, ctx, workingDir);
489
- // Execute the board the user is looking at, falling back to workspace.json activeBoardId
490
- const boardId = msg.data?.boardId;
491
- ctx.send(ws, { type: 'planExecutionStarted', data: { status: 'executing', boardId } });
492
- const startPromise = boardId ? executor.startBoard(boardId) : executor.start();
493
- startPromise.catch(error => {
494
- ctx.send(ws, {
495
- type: 'planExecutionError',
496
- data: { error: error instanceof Error ? error.message : String(error) },
497
- });
498
- });
499
- }
500
- function handleExecuteEpic(ctx, ws, msg, workingDir, permission) {
501
- if (denyIfViewOnly(ctx, ws, permission))
502
- return;
503
- const epicPath = msg.data?.epicPath;
504
- if (!epicPath) {
505
- ctx.send(ws, { type: 'planError', data: { error: 'Epic path required' } });
506
- return;
507
- }
508
- const executor = getExecutor(workingDir);
509
- if (executor.getStatus() === 'executing' || executor.getStatus() === 'starting') {
510
- ctx.send(ws, { type: 'planError', data: { error: 'Execution already in progress' } });
511
- return;
512
- }
513
- wireExecutorEvents(executor, ctx, workingDir);
514
- ctx.send(ws, { type: 'planExecutionStarted', data: { status: 'executing', epicPath } });
515
- executor.startEpic(epicPath).catch(error => {
516
- ctx.send(ws, {
517
- type: 'planExecutionError',
518
- data: { error: error instanceof Error ? error.message : String(error) },
519
- });
520
- });
521
- }
522
- function handlePause(ctx, ws, workingDir, permission) {
523
- if (denyIfViewOnly(ctx, ws, permission))
524
- return;
525
- const executor = executorCache.get(workingDir);
526
- if (executor)
527
- executor.pause();
528
- }
529
- function handleStop(ctx, ws, workingDir, permission) {
530
- if (denyIfViewOnly(ctx, ws, permission))
531
- return;
532
- const executor = executorCache.get(workingDir);
533
- if (executor)
534
- executor.stop();
535
- }
536
- function handleResume(ctx, ws, workingDir, permission) {
537
- if (denyIfViewOnly(ctx, ws, permission))
538
- return;
539
- const executor = executorCache.get(workingDir);
540
- if (executor) {
541
- executor.resume().catch(error => {
542
- ctx.send(ws, {
543
- type: 'planExecutionError',
544
- data: { error: error instanceof Error ? error.message : String(error) },
545
- });
546
- });
547
- }
548
- }
549
- // ============================================================================
550
- // Board lifecycle handlers
551
- // ============================================================================
552
- function handleCreateBoard(ctx, ws, msg, workingDir, permission) {
553
- if (denyIfViewOnly(ctx, ws, permission))
554
- return;
555
- const pmDir = resolvePmDir(workingDir);
556
- if (!pmDir) {
557
- ctx.send(ws, { type: 'planError', data: { error: 'No PM directory found' } });
558
- return;
559
- }
560
- const fullState = parsePlanDirectory(workingDir);
561
- if (!fullState)
562
- return;
563
- const boardId = getNextBoardId(fullState.boards);
564
- const boardNum = getNextBoardNumber(fullState.boards);
565
- const title = msg.data?.title || `Board ${boardNum}`;
566
- const goal = msg.data?.goal || '';
567
- const boardDir = join(pmDir, 'boards', boardId);
568
- // Create directory structure
569
- for (const dir of ['backlog', 'out', 'reviews']) {
570
- mkdirSync(join(boardDir, dir), { recursive: true });
571
- }
572
- // Create board.md
573
- const today = new Date().toISOString().split('T')[0];
574
- writeFileSync(join(boardDir, 'board.md'), `---
575
- id: ${boardId}
576
- title: "${title.replace(/"/g, '\\"')}"
577
- status: draft
578
- created: "${today}"
579
- completed_at: null
580
- goal: "${goal.replace(/"/g, '\\"')}"
581
- ---
582
-
583
- # ${title}
584
-
585
- ## Goal
586
- ${goal}
587
-
588
- ## Notes
589
- `, 'utf-8');
590
- // Create STATE.md
591
- writeFileSync(join(boardDir, 'STATE.md'), `---
592
- project: ../../project.md
593
- board: board.md
594
- paused: false
595
- ---
596
-
597
- # Board State
598
-
599
- ## Ready to Work
600
-
601
- ## In Progress
602
-
603
- ## Blocked
604
-
605
- ## Recently Completed
606
-
607
- ## Warnings
608
- `, 'utf-8');
609
- // Create progress.md
610
- writeFileSync(join(boardDir, 'progress.md'), '# Board Progress\n', 'utf-8');
611
- // Update workspace.json
612
- const wsPath = join(pmDir, 'workspace.json');
613
- if (!existsSync(wsPath)) {
614
- writeFileSync(wsPath, JSON.stringify({ activeBoardId: null, boardOrder: [] }, null, 2), 'utf-8');
615
- }
616
- const workspaceContent = readFileSync(wsPath, 'utf-8');
617
- const workspace = JSON.parse(workspaceContent);
618
- workspace.boardOrder.push(boardId);
619
- if (!workspace.activeBoardId) {
620
- workspace.activeBoardId = boardId;
621
- }
622
- writeFileSync(join(pmDir, 'workspace.json'), JSON.stringify(workspace, null, 2), 'utf-8');
623
- const boardState = parseBoardDirectory(pmDir, boardId);
624
- if (boardState) {
625
- ctx.broadcastToAll({ type: 'planBoardCreated', data: boardState.board });
626
- ctx.broadcastToAll({ type: 'planWorkspaceUpdated', data: workspace });
627
- }
628
- }
629
- function handleUpdateBoard(ctx, ws, msg, workingDir, permission) {
630
- if (denyIfViewOnly(ctx, ws, permission))
631
- return;
632
- const { boardId, fields } = msg.data || {};
633
- if (!boardId || !fields) {
634
- ctx.send(ws, { type: 'planError', data: { error: 'Board ID and fields required' } });
635
- return;
636
- }
637
- const pmDir = resolvePmDir(workingDir);
638
- if (!pmDir)
639
- return;
640
- const boardMdPath = join(pmDir, 'boards', boardId, 'board.md');
641
- if (!existsSync(boardMdPath)) {
642
- ctx.send(ws, { type: 'planError', data: { error: `Board not found: ${boardId}` } });
643
- return;
644
- }
645
- let content = readFileSync(boardMdPath, 'utf-8');
646
- for (const [key, value] of Object.entries(fields)) {
647
- const yamlKey = key.replace(/([A-Z])/g, '_$1').toLowerCase();
648
- content = replaceFrontMatterField(content, yamlKey, formatYamlValue(value));
649
- }
650
- writeFileSync(boardMdPath, content, 'utf-8');
651
- const boardState = parseBoardDirectory(pmDir, boardId);
652
- if (boardState) {
653
- ctx.broadcastToAll({ type: 'planBoardUpdated', data: boardState.board });
654
- }
655
- }
656
- function handleArchiveBoard(ctx, ws, msg, workingDir, permission) {
657
- if (denyIfViewOnly(ctx, ws, permission))
658
- return;
659
- const boardId = msg.data?.boardId;
660
- if (!boardId) {
661
- ctx.send(ws, { type: 'planError', data: { error: 'Board ID required' } });
662
- return;
663
- }
664
- const pmDir = resolvePmDir(workingDir);
665
- if (!pmDir)
666
- return;
667
- const boardMdPath = join(pmDir, 'boards', boardId, 'board.md');
668
- if (!existsSync(boardMdPath)) {
669
- ctx.send(ws, { type: 'planError', data: { error: `Board not found: ${boardId}` } });
670
- return;
671
- }
672
- // Set status to archived
673
- let content = readFileSync(boardMdPath, 'utf-8');
674
- content = replaceFrontMatterField(content, 'status', 'archived');
675
- writeFileSync(boardMdPath, content, 'utf-8');
676
- // Remove from workspace.json boardOrder and update activeBoardId if needed
677
- const workspacePath = join(pmDir, 'workspace.json');
678
- if (existsSync(workspacePath)) {
679
- const workspace = JSON.parse(readFileSync(workspacePath, 'utf-8'));
680
- workspace.boardOrder = workspace.boardOrder.filter(id => id !== boardId);
681
- if (workspace.activeBoardId === boardId) {
682
- workspace.activeBoardId = workspace.boardOrder[0] || null;
683
- }
684
- writeFileSync(workspacePath, JSON.stringify(workspace, null, 2), 'utf-8');
685
- ctx.broadcastToAll({ type: 'planWorkspaceUpdated', data: workspace });
686
- }
687
- const boardState = parseBoardDirectory(pmDir, boardId);
688
- if (boardState) {
689
- ctx.broadcastToAll({ type: 'planBoardArchived', data: boardState.board });
690
- }
691
- }
692
- function handleGetBoard(ctx, ws, msg, workingDir) {
693
- const boardId = msg.data?.boardId;
694
- if (!boardId) {
695
- ctx.send(ws, { type: 'planError', data: { error: 'Board ID required' } });
696
- return;
697
- }
698
- const pmDir = resolvePmDir(workingDir);
699
- if (!pmDir)
700
- return;
701
- const boardState = parseBoardDirectory(pmDir, boardId);
702
- if (!boardState) {
703
- ctx.send(ws, { type: 'planError', data: { error: `Board not found: ${boardId}` } });
704
- return;
705
- }
706
- ctx.send(ws, { type: 'planBoardState', data: boardState });
707
- }
708
- function handleGetBoardState(ctx, ws, msg, workingDir) {
709
- handleGetBoard(ctx, ws, msg, workingDir);
710
- }
711
- function handleReorderBoards(ctx, ws, msg, workingDir, permission) {
712
- if (denyIfViewOnly(ctx, ws, permission))
713
- return;
714
- const boardOrder = msg.data?.boardOrder;
715
- if (!Array.isArray(boardOrder)) {
716
- ctx.send(ws, { type: 'planError', data: { error: 'boardOrder array required' } });
717
- return;
718
- }
719
- const pmDir = resolvePmDir(workingDir);
720
- if (!pmDir)
721
- return;
722
- const workspacePath = join(pmDir, 'workspace.json');
723
- if (!existsSync(workspacePath))
724
- return;
725
- const workspace = JSON.parse(readFileSync(workspacePath, 'utf-8'));
726
- workspace.boardOrder = boardOrder;
727
- writeFileSync(workspacePath, JSON.stringify(workspace, null, 2), 'utf-8');
728
- ctx.broadcastToAll({ type: 'planWorkspaceUpdated', data: workspace });
729
- }
730
- function handleSetActiveBoard(ctx, ws, msg, workingDir, permission) {
731
- if (denyIfViewOnly(ctx, ws, permission))
732
- return;
733
- const boardId = msg.data?.boardId;
734
- if (!boardId) {
735
- ctx.send(ws, { type: 'planError', data: { error: 'Board ID required' } });
736
- return;
737
- }
738
- const pmDir = resolvePmDir(workingDir);
739
- if (!pmDir)
740
- return;
741
- const workspacePath = join(pmDir, 'workspace.json');
742
- if (!existsSync(workspacePath))
743
- return;
744
- const workspace = JSON.parse(readFileSync(workspacePath, 'utf-8'));
745
- workspace.activeBoardId = boardId;
746
- writeFileSync(workspacePath, JSON.stringify(workspace, null, 2), 'utf-8');
747
- ctx.broadcastToAll({ type: 'planWorkspaceUpdated', data: workspace });
748
- // Also send the active board's full state
749
- const boardState = parseBoardDirectory(pmDir, boardId);
750
- if (boardState) {
751
- ctx.send(ws, { type: 'planBoardState', data: boardState });
752
- }
753
- }
754
- function handleGetBoardArtifacts(ctx, ws, msg, workingDir) {
755
- const boardId = msg.data?.boardId;
756
- if (!boardId) {
757
- ctx.send(ws, { type: 'planError', data: { error: 'Board ID required' } });
758
- return;
759
- }
760
- const artifacts = parseBoardArtifacts(workingDir, boardId);
761
- if (!artifacts) {
762
- ctx.send(ws, { type: 'planBoardArtifacts', data: { boardId, progressLog: '', outputFiles: [], reviewResults: [] } });
763
- return;
764
- }
765
- ctx.send(ws, { type: 'planBoardArtifacts', data: artifacts });
766
- }
767
- // ============================================================================
768
- // Sprint lifecycle handlers (legacy — kept for backward compatibility)
769
- // ============================================================================
770
- function buildSprintMarkdown(id, title, goal, start, end, issueRefs) {
771
- const issuesYaml = issueRefs.length > 0
772
- ? `\n${issueRefs.map(p => ` - ${p}`).join('\n')}`
773
- : ' []';
774
- return `---
775
- id: ${id}
776
- title: "${title.replace(/"/g, '\\"')}"
777
- status: planned
778
- start: "${start}"
779
- end: "${end}"
780
- goal: "${goal.replace(/"/g, '\\"')}"
781
- capacity: null
782
- committed: null
783
- completed: null
784
- completed_at: null
785
- issues:${issuesYaml}
786
- ---
787
-
788
- # ${id}: ${title}
789
-
790
- ## Sprint Goal
791
- ${goal}
792
-
793
- ## Issues
794
- | Issue | Title | Points | Status |
795
- |---|---|---|---|
796
- `;
797
- }
798
- /** Assign issues to a sprint by updating their front matter sprint field. */
799
- function assignIssuesToSprint(workingDir, issues, issueIds, sprintPath) {
800
- for (const issueId of issueIds) {
801
- const issue = issues.find(i => i.id === issueId);
802
- if (!issue)
803
- continue;
804
- const fullPath = resolvePlanPath(workingDir, issue.path);
805
- if (!fullPath || !existsSync(fullPath))
806
- continue;
807
- const content = replaceFrontMatterField(readFileSync(fullPath, 'utf-8'), 'sprint', sprintPath);
808
- writeFileSync(fullPath, content, 'utf-8');
809
- }
810
- }
811
- function handleCreateSprint(ctx, ws, msg, workingDir, permission) {
812
- if (denyIfViewOnly(ctx, ws, permission))
813
- return;
814
- const { title, goal = '', start = '', end = '', issueIds = [] } = msg.data || {};
815
- if (!title) {
816
- ctx.send(ws, { type: 'planError', data: { error: 'Sprint title required' } });
817
- return;
818
- }
819
- const pmDir = resolvePmDir(workingDir) ?? defaultPmDir(workingDir);
820
- const sprintsDir = join(pmDir, 'sprints');
821
- if (!existsSync(sprintsDir))
822
- mkdirSync(sprintsDir, { recursive: true });
823
- const fullState = parsePlanDirectory(workingDir);
824
- const id = fullState ? getNextSprintId(fullState.sprints) : 'SPRINT-001';
825
- const issueRefs = issueIds.map((issueId) => {
826
- const issue = fullState?.issues.find(i => i.id === issueId);
827
- return issue ? issue.path : `backlog/${issueId}.md`;
828
- });
829
- writeFileSync(join(sprintsDir, `${id}.md`), buildSprintMarkdown(id, title, goal, start, end, issueRefs), 'utf-8');
830
- const sandboxDir = join(sprintsDir, id);
831
- mkdirSync(join(sandboxDir, 'out'), { recursive: true });
832
- mkdirSync(join(sandboxDir, 'reviews'), { recursive: true });
833
- writeFileSync(join(sandboxDir, 'progress.md'), `# ${id}: ${title} — Progress Log\n`, 'utf-8');
834
- if (issueRefs.length > 0 && fullState) {
835
- assignIssuesToSprint(workingDir, fullState.issues, issueIds, `sprints/${id}.md`);
836
- }
837
- const sprint = parseSingleSprint(workingDir, `sprints/${id}.md`);
838
- ctx.broadcastToAll({ type: 'planSprintCreated', data: sprint });
839
- }
840
- /** Promote sprint issues from 'backlog' to 'todo' status. */
841
- function promoteSprintIssues(pmDir, sprint, allIssues) {
842
- for (const issueSummary of sprint.issues) {
843
- const issue = allIssues.find(i => i.id === issueSummary.id || i.path === issueSummary.path);
844
- if (!issue || issue.status !== 'backlog')
845
- continue;
846
- const issuePath = join(pmDir, issue.path);
847
- if (!existsSync(issuePath))
848
- continue;
849
- writeFileSync(issuePath, replaceFrontMatterField(readFileSync(issuePath, 'utf-8'), 'status', 'todo'), 'utf-8');
850
- }
851
- }
852
- /** Update a file's front matter field if the file exists. */
853
- function updateFileField(filePath, field, value) {
854
- if (!existsSync(filePath))
855
- return;
856
- writeFileSync(filePath, replaceFrontMatterField(readFileSync(filePath, 'utf-8'), field, value), 'utf-8');
857
- }
858
- function handleActivateSprint(ctx, ws, msg, workingDir, permission) {
859
- if (denyIfViewOnly(ctx, ws, permission))
860
- return;
861
- const sprintId = msg.data?.sprintId;
862
- if (!sprintId) {
863
- ctx.send(ws, { type: 'planError', data: { error: 'Sprint ID required' } });
864
- return;
865
- }
866
- const fullState = parsePlanDirectory(workingDir);
867
- if (!fullState) {
868
- ctx.send(ws, { type: 'planError', data: { error: 'No project found' } });
869
- return;
870
- }
871
- const currentActive = fullState.sprints.find(s => s.status === 'active');
872
- if (currentActive && currentActive.id !== sprintId) {
873
- ctx.send(ws, { type: 'planError', data: { error: `Sprint ${currentActive.id} is already active. Complete it first.` } });
874
- return;
875
- }
876
- const sprint = fullState.sprints.find(s => s.id === sprintId);
877
- if (!sprint) {
878
- ctx.send(ws, { type: 'planError', data: { error: `Sprint not found: ${sprintId}` } });
879
- return;
880
- }
881
- const pmDir = resolvePmDir(workingDir);
882
- if (!pmDir)
883
- return;
884
- updateFileField(join(pmDir, sprint.path), 'status', 'active');
885
- updateFileField(join(pmDir, 'STATE.md'), 'current_sprint', `"${sprint.path}"`);
886
- promoteSprintIssues(pmDir, sprint, fullState.issues);
887
- const updatedSprint = parseSingleSprint(workingDir, sprint.path);
888
- ctx.broadcastToAll({ type: 'planSprintUpdated', data: updatedSprint });
889
- const updatedState = parsePlanDirectory(workingDir);
890
- if (updatedState) {
891
- ctx.broadcastToAll({ type: 'planStateUpdated', data: updatedState });
892
- }
893
- }
894
- function handleCompleteSprint(ctx, ws, msg, workingDir, permission) {
895
- if (denyIfViewOnly(ctx, ws, permission))
896
- return;
897
- const sprintId = msg.data?.sprintId;
898
- if (!sprintId) {
899
- ctx.send(ws, { type: 'planError', data: { error: 'Sprint ID required' } });
900
- return;
901
- }
902
- const fullState = parsePlanDirectory(workingDir);
903
- if (!fullState) {
904
- ctx.send(ws, { type: 'planError', data: { error: 'No project found' } });
905
- return;
906
- }
907
- const sprint = fullState.sprints.find(s => s.id === sprintId);
908
- if (!sprint) {
909
- ctx.send(ws, { type: 'planError', data: { error: `Sprint not found: ${sprintId}` } });
910
- return;
911
- }
912
- const pmDir = resolvePmDir(workingDir);
913
- if (!pmDir)
914
- return;
915
- const now = new Date().toISOString();
916
- // Compute execution summary from sprint issues
917
- const sprintIssues = fullState.issues.filter(i => i.sprint === sprint.path);
918
- const completedIssues = sprintIssues.filter(i => i.status === 'done').length;
919
- const failedIssues = sprintIssues.filter(i => i.status !== 'done' && i.status !== 'cancelled').length;
920
- // Update sprint file with completion data
921
- const sprintPath = join(pmDir, sprint.path);
922
- if (existsSync(sprintPath)) {
923
- let content = readFileSync(sprintPath, 'utf-8');
924
- content = replaceFrontMatterField(content, 'status', 'completed');
925
- content = replaceFrontMatterField(content, 'completed_at', `"${now}"`);
926
- content = replaceFrontMatterField(content, 'completed', String(completedIssues));
927
- // Write execution summary if not already present
928
- if (!content.includes('execution_summary:')) {
929
- const summaryYaml = [
930
- 'execution_summary:',
931
- ` total_issues: ${sprintIssues.length}`,
932
- ` completed_issues: ${completedIssues}`,
933
- ` failed_issues: ${failedIssues}`,
934
- ].join('\n');
935
- // Insert before the closing --- of front matter (second occurrence)
936
- const fmClose = content.indexOf('\n---', content.indexOf('---') + 3);
937
- if (fmClose !== -1) {
938
- content = `${content.slice(0, fmClose)}\n${summaryYaml}${content.slice(fmClose)}`;
939
- }
940
- }
941
- writeFileSync(sprintPath, content, 'utf-8');
942
- }
943
- // Clear STATE.md current_sprint
944
- const statePath = join(pmDir, 'STATE.md');
945
- if (existsSync(statePath)) {
946
- let stateContent = readFileSync(statePath, 'utf-8');
947
- stateContent = replaceFrontMatterField(stateContent, 'current_sprint', 'null');
948
- writeFileSync(statePath, stateContent, 'utf-8');
949
- }
950
- const updatedSprint = parseSingleSprint(workingDir, sprint.path);
951
- ctx.broadcastToAll({ type: 'planSprintCompleted', data: updatedSprint });
952
- // Refresh full state
953
- const updatedState = parsePlanDirectory(workingDir);
954
- if (updatedState) {
955
- ctx.broadcastToAll({ type: 'planStateUpdated', data: updatedState });
956
- }
957
- }
958
- function handleGetSprintArtifacts(ctx, ws, msg, workingDir) {
959
- const sprintId = msg.data?.sprintId;
960
- if (!sprintId) {
961
- ctx.send(ws, { type: 'planError', data: { error: 'Sprint ID required' } });
962
- return;
963
- }
964
- const artifacts = parseSprintArtifacts(workingDir, sprintId);
965
- if (!artifacts) {
966
- // Fall back to empty artifacts if sandbox dir doesn't exist yet
967
- ctx.send(ws, { type: 'planSprintArtifacts', data: { sprintId, progressLog: '', outputFiles: [], reviewResults: [] } });
968
- return;
969
- }
970
- ctx.send(ws, { type: 'planSprintArtifacts', data: artifacts });
971
- }
972
53
  //# sourceMappingURL=plan-handlers.js.map