mstro-app 0.4.52 → 0.5.1

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 (590) hide show
  1. package/LICENSE +129 -190
  2. package/PRIVACY.md +3 -3
  3. package/README.md +15 -6
  4. package/bin/commands/config.js +0 -1
  5. package/bin/mstro.js +1 -2
  6. package/bin/postinstall.js +0 -1
  7. package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -1
  8. package/dist/server/cli/headless/claude-invoker-process.js +0 -1
  9. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -1
  10. package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -1
  11. package/dist/server/cli/headless/claude-invoker-stall.js +7 -3
  12. package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -1
  13. package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -1
  14. package/dist/server/cli/headless/claude-invoker-stream.js +0 -1
  15. package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -1
  16. package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -1
  17. package/dist/server/cli/headless/claude-invoker-tools.js +0 -1
  18. package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -1
  19. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  20. package/dist/server/cli/headless/claude-invoker.js +1 -2
  21. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  22. package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -1
  23. package/dist/server/cli/headless/haiku-assessments.js +0 -1
  24. package/dist/server/cli/headless/haiku-assessments.js.map +1 -1
  25. package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
  26. package/dist/server/cli/headless/headless-logger.js +0 -1
  27. package/dist/server/cli/headless/headless-logger.js.map +1 -1
  28. package/dist/server/cli/headless/index.d.ts.map +1 -1
  29. package/dist/server/cli/headless/index.js +0 -1
  30. package/dist/server/cli/headless/index.js.map +1 -1
  31. package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -1
  32. package/dist/server/cli/headless/native-timeout-detector.js +0 -1
  33. package/dist/server/cli/headless/native-timeout-detector.js.map +1 -1
  34. package/dist/server/cli/headless/output-utils.d.ts.map +1 -1
  35. package/dist/server/cli/headless/output-utils.js +0 -1
  36. package/dist/server/cli/headless/output-utils.js.map +1 -1
  37. package/dist/server/cli/headless/prompt-utils.d.ts.map +1 -1
  38. package/dist/server/cli/headless/prompt-utils.js +0 -1
  39. package/dist/server/cli/headless/prompt-utils.js.map +1 -1
  40. package/dist/server/cli/headless/resilient-runner.d.ts.map +1 -1
  41. package/dist/server/cli/headless/resilient-runner.js +0 -1
  42. package/dist/server/cli/headless/resilient-runner.js.map +1 -1
  43. package/dist/server/cli/headless/retry-strategies.d.ts.map +1 -1
  44. package/dist/server/cli/headless/retry-strategies.js +0 -1
  45. package/dist/server/cli/headless/retry-strategies.js.map +1 -1
  46. package/dist/server/cli/headless/runner.d.ts.map +1 -1
  47. package/dist/server/cli/headless/runner.js +63 -68
  48. package/dist/server/cli/headless/runner.js.map +1 -1
  49. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  50. package/dist/server/cli/headless/stall-assessor.js +9 -5
  51. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  52. package/dist/server/cli/headless/tool-watchdog.d.ts.map +1 -1
  53. package/dist/server/cli/headless/tool-watchdog.js +0 -1
  54. package/dist/server/cli/headless/tool-watchdog.js.map +1 -1
  55. package/dist/server/cli/headless/types.d.ts.map +1 -1
  56. package/dist/server/cli/headless/types.js +0 -1
  57. package/dist/server/cli/headless/types.js.map +1 -1
  58. package/dist/server/cli/improvisation-attachments.d.ts.map +1 -1
  59. package/dist/server/cli/improvisation-attachments.js +0 -1
  60. package/dist/server/cli/improvisation-attachments.js.map +1 -1
  61. package/dist/server/cli/improvisation-history-store.d.ts +16 -0
  62. package/dist/server/cli/improvisation-history-store.d.ts.map +1 -0
  63. package/dist/server/cli/improvisation-history-store.js +51 -0
  64. package/dist/server/cli/improvisation-history-store.js.map +1 -0
  65. package/dist/server/cli/improvisation-movements.d.ts +31 -0
  66. package/dist/server/cli/improvisation-movements.d.ts.map +1 -0
  67. package/dist/server/cli/improvisation-movements.js +92 -0
  68. package/dist/server/cli/improvisation-movements.js.map +1 -0
  69. package/dist/server/cli/improvisation-output-queue.d.ts +13 -0
  70. package/dist/server/cli/improvisation-output-queue.d.ts.map +1 -0
  71. package/dist/server/cli/improvisation-output-queue.js +39 -0
  72. package/dist/server/cli/improvisation-output-queue.js.map +1 -0
  73. package/dist/server/cli/improvisation-retry.d.ts +21 -51
  74. package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
  75. package/dist/server/cli/improvisation-retry.js +18 -434
  76. package/dist/server/cli/improvisation-retry.js.map +1 -1
  77. package/dist/server/cli/improvisation-session-manager.d.ts +10 -8
  78. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  79. package/dist/server/cli/improvisation-session-manager.js +53 -149
  80. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  81. package/dist/server/cli/improvisation-types.d.ts.map +1 -1
  82. package/dist/server/cli/improvisation-types.js +0 -1
  83. package/dist/server/cli/improvisation-types.js.map +1 -1
  84. package/dist/server/cli/retry/retry-best-result.d.ts +4 -0
  85. package/dist/server/cli/retry/retry-best-result.d.ts.map +1 -0
  86. package/dist/server/cli/retry/retry-best-result.js +60 -0
  87. package/dist/server/cli/retry/retry-best-result.js.map +1 -0
  88. package/dist/server/cli/retry/retry-context-loss.d.ts +6 -0
  89. package/dist/server/cli/retry/retry-context-loss.d.ts.map +1 -0
  90. package/dist/server/cli/retry/retry-context-loss.js +67 -0
  91. package/dist/server/cli/retry/retry-context-loss.js.map +1 -0
  92. package/dist/server/cli/retry/retry-premature-completion.d.ts +5 -0
  93. package/dist/server/cli/retry/retry-premature-completion.d.ts.map +1 -0
  94. package/dist/server/cli/retry/retry-premature-completion.js +80 -0
  95. package/dist/server/cli/retry/retry-premature-completion.js.map +1 -0
  96. package/dist/server/cli/retry/retry-recovery-strategies.d.ts +13 -0
  97. package/dist/server/cli/retry/retry-recovery-strategies.d.ts.map +1 -0
  98. package/dist/server/cli/retry/retry-recovery-strategies.js +165 -0
  99. package/dist/server/cli/retry/retry-recovery-strategies.js.map +1 -0
  100. package/dist/server/cli/retry/retry-resume-strategy.d.ts +12 -0
  101. package/dist/server/cli/retry/retry-resume-strategy.d.ts.map +1 -0
  102. package/dist/server/cli/retry/retry-resume-strategy.js +21 -0
  103. package/dist/server/cli/retry/retry-resume-strategy.js.map +1 -0
  104. package/dist/server/cli/retry/retry-runner-factory.d.ts +11 -0
  105. package/dist/server/cli/retry/retry-runner-factory.d.ts.map +1 -0
  106. package/dist/server/cli/retry/retry-runner-factory.js +59 -0
  107. package/dist/server/cli/retry/retry-runner-factory.js.map +1 -0
  108. package/dist/server/cli/retry/retry-tool-results.d.ts +9 -0
  109. package/dist/server/cli/retry/retry-tool-results.d.ts.map +1 -0
  110. package/dist/server/cli/retry/retry-tool-results.js +23 -0
  111. package/dist/server/cli/retry/retry-tool-results.js.map +1 -0
  112. package/dist/server/cli/retry/retry-types.d.ts +30 -0
  113. package/dist/server/cli/retry/retry-types.d.ts.map +1 -0
  114. package/dist/server/cli/retry/retry-types.js +3 -0
  115. package/dist/server/cli/retry/retry-types.js.map +1 -0
  116. package/dist/server/index.js +21 -110
  117. package/dist/server/index.js.map +1 -1
  118. package/dist/server/mcp/bouncer-cli.js +0 -1
  119. package/dist/server/mcp/bouncer-cli.js.map +1 -1
  120. package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -1
  121. package/dist/server/mcp/bouncer-haiku.js +0 -1
  122. package/dist/server/mcp/bouncer-haiku.js.map +1 -1
  123. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  124. package/dist/server/mcp/bouncer-integration.js +0 -1
  125. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  126. package/dist/server/mcp/security-analysis.d.ts.map +1 -1
  127. package/dist/server/mcp/security-analysis.js +0 -1
  128. package/dist/server/mcp/security-analysis.js.map +1 -1
  129. package/dist/server/mcp/security-audit.d.ts.map +1 -1
  130. package/dist/server/mcp/security-audit.js +0 -1
  131. package/dist/server/mcp/security-audit.js.map +1 -1
  132. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  133. package/dist/server/mcp/security-patterns.js +0 -1
  134. package/dist/server/mcp/security-patterns.js.map +1 -1
  135. package/dist/server/mcp/server.js +0 -1
  136. package/dist/server/mcp/server.js.map +1 -1
  137. package/dist/server/routes/files.d.ts.map +1 -1
  138. package/dist/server/routes/files.js +0 -1
  139. package/dist/server/routes/files.js.map +1 -1
  140. package/dist/server/routes/improvise.d.ts.map +1 -1
  141. package/dist/server/routes/improvise.js +0 -1
  142. package/dist/server/routes/improvise.js.map +1 -1
  143. package/dist/server/routes/index.d.ts.map +1 -1
  144. package/dist/server/routes/index.js +0 -1
  145. package/dist/server/routes/index.js.map +1 -1
  146. package/dist/server/routes/instances.d.ts.map +1 -1
  147. package/dist/server/routes/instances.js +0 -1
  148. package/dist/server/routes/instances.js.map +1 -1
  149. package/dist/server/routes/notifications.d.ts.map +1 -1
  150. package/dist/server/routes/notifications.js +0 -1
  151. package/dist/server/routes/notifications.js.map +1 -1
  152. package/dist/server/server-setup.d.ts +16 -1
  153. package/dist/server/server-setup.d.ts.map +1 -1
  154. package/dist/server/server-setup.js +107 -1
  155. package/dist/server/server-setup.js.map +1 -1
  156. package/dist/server/services/analytics.d.ts.map +1 -1
  157. package/dist/server/services/analytics.js +0 -1
  158. package/dist/server/services/analytics.js.map +1 -1
  159. package/dist/server/services/auth.d.ts.map +1 -1
  160. package/dist/server/services/auth.js +0 -1
  161. package/dist/server/services/auth.js.map +1 -1
  162. package/dist/server/services/client-id.d.ts.map +1 -1
  163. package/dist/server/services/client-id.js +0 -1
  164. package/dist/server/services/client-id.js.map +1 -1
  165. package/dist/server/services/file-explorer-ops.d.ts.map +1 -1
  166. package/dist/server/services/file-explorer-ops.js +0 -1
  167. package/dist/server/services/file-explorer-ops.js.map +1 -1
  168. package/dist/server/services/files.d.ts.map +1 -1
  169. package/dist/server/services/files.js +0 -1
  170. package/dist/server/services/files.js.map +1 -1
  171. package/dist/server/services/instances.d.ts.map +1 -1
  172. package/dist/server/services/instances.js +0 -1
  173. package/dist/server/services/instances.js.map +1 -1
  174. package/dist/server/services/pathUtils.d.ts.map +1 -1
  175. package/dist/server/services/pathUtils.js +0 -1
  176. package/dist/server/services/pathUtils.js.map +1 -1
  177. package/dist/server/services/plan/agent-loader.d.ts.map +1 -1
  178. package/dist/server/services/plan/agent-loader.js +0 -1
  179. package/dist/server/services/plan/agent-loader.js.map +1 -1
  180. package/dist/server/services/plan/board-config.d.ts +21 -0
  181. package/dist/server/services/plan/board-config.d.ts.map +1 -0
  182. package/dist/server/services/plan/board-config.js +111 -0
  183. package/dist/server/services/plan/board-config.js.map +1 -0
  184. package/dist/server/services/plan/composer.d.ts +1 -1
  185. package/dist/server/services/plan/composer.d.ts.map +1 -1
  186. package/dist/server/services/plan/composer.js +7 -6
  187. package/dist/server/services/plan/composer.js.map +1 -1
  188. package/dist/server/services/plan/config-installer.d.ts.map +1 -1
  189. package/dist/server/services/plan/config-installer.js +0 -1
  190. package/dist/server/services/plan/config-installer.js.map +1 -1
  191. package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
  192. package/dist/server/services/plan/dependency-resolver.js +0 -1
  193. package/dist/server/services/plan/dependency-resolver.js.map +1 -1
  194. package/dist/server/services/plan/executor.d.ts +48 -48
  195. package/dist/server/services/plan/executor.d.ts.map +1 -1
  196. package/dist/server/services/plan/executor.js +202 -458
  197. package/dist/server/services/plan/executor.js.map +1 -1
  198. package/dist/server/services/plan/front-matter.d.ts.map +1 -1
  199. package/dist/server/services/plan/front-matter.js +0 -1
  200. package/dist/server/services/plan/front-matter.js.map +1 -1
  201. package/dist/server/services/plan/issue-classification.d.ts.map +1 -1
  202. package/dist/server/services/plan/issue-classification.js +0 -1
  203. package/dist/server/services/plan/issue-classification.js.map +1 -1
  204. package/dist/server/services/plan/issue-loader.d.ts +16 -0
  205. package/dist/server/services/plan/issue-loader.d.ts.map +1 -0
  206. package/dist/server/services/plan/issue-loader.js +45 -0
  207. package/dist/server/services/plan/issue-loader.js.map +1 -0
  208. package/dist/server/services/plan/issue-prompt-builder.d.ts.map +1 -1
  209. package/dist/server/services/plan/issue-prompt-builder.js +0 -1
  210. package/dist/server/services/plan/issue-prompt-builder.js.map +1 -1
  211. package/dist/server/services/plan/issue-retry.d.ts +3 -1
  212. package/dist/server/services/plan/issue-retry.d.ts.map +1 -1
  213. package/dist/server/services/plan/issue-retry.js +2 -1
  214. package/dist/server/services/plan/issue-retry.js.map +1 -1
  215. package/dist/server/services/plan/issue-writer.d.ts +34 -0
  216. package/dist/server/services/plan/issue-writer.d.ts.map +1 -0
  217. package/dist/server/services/plan/issue-writer.js +109 -0
  218. package/dist/server/services/plan/issue-writer.js.map +1 -0
  219. package/dist/server/services/plan/output-manager.js +2 -2
  220. package/dist/server/services/plan/output-manager.js.map +1 -1
  221. package/dist/server/services/plan/parser-core.d.ts.map +1 -1
  222. package/dist/server/services/plan/parser-core.js +0 -1
  223. package/dist/server/services/plan/parser-core.js.map +1 -1
  224. package/dist/server/services/plan/parser-migration.d.ts.map +1 -1
  225. package/dist/server/services/plan/parser-migration.js +0 -1
  226. package/dist/server/services/plan/parser-migration.js.map +1 -1
  227. package/dist/server/services/plan/parser.d.ts.map +1 -1
  228. package/dist/server/services/plan/parser.js +0 -1
  229. package/dist/server/services/plan/parser.js.map +1 -1
  230. package/dist/server/services/plan/progress-log.d.ts +11 -0
  231. package/dist/server/services/plan/progress-log.d.ts.map +1 -0
  232. package/dist/server/services/plan/progress-log.js +80 -0
  233. package/dist/server/services/plan/progress-log.js.map +1 -0
  234. package/dist/server/services/plan/prompt-builder.d.ts.map +1 -1
  235. package/dist/server/services/plan/prompt-builder.js +48 -32
  236. package/dist/server/services/plan/prompt-builder.js.map +1 -1
  237. package/dist/server/services/plan/readiness-planner.d.ts +15 -0
  238. package/dist/server/services/plan/readiness-planner.d.ts.map +1 -0
  239. package/dist/server/services/plan/readiness-planner.js +40 -0
  240. package/dist/server/services/plan/readiness-planner.js.map +1 -0
  241. package/dist/server/services/plan/review-gate.d.ts +31 -0
  242. package/dist/server/services/plan/review-gate.d.ts.map +1 -1
  243. package/dist/server/services/plan/review-gate.js +52 -3
  244. package/dist/server/services/plan/review-gate.js.map +1 -1
  245. package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
  246. package/dist/server/services/plan/state-reconciler.js +0 -1
  247. package/dist/server/services/plan/state-reconciler.js.map +1 -1
  248. package/dist/server/services/plan/types.d.ts.map +1 -1
  249. package/dist/server/services/plan/types.js +0 -1
  250. package/dist/server/services/plan/types.js.map +1 -1
  251. package/dist/server/services/plan/watcher.d.ts.map +1 -1
  252. package/dist/server/services/plan/watcher.js +0 -1
  253. package/dist/server/services/plan/watcher.js.map +1 -1
  254. package/dist/server/services/platform-credentials.d.ts.map +1 -1
  255. package/dist/server/services/platform-credentials.js +0 -1
  256. package/dist/server/services/platform-credentials.js.map +1 -1
  257. package/dist/server/services/platform-token-lifecycle.d.ts +70 -0
  258. package/dist/server/services/platform-token-lifecycle.d.ts.map +1 -0
  259. package/dist/server/services/platform-token-lifecycle.js +156 -0
  260. package/dist/server/services/platform-token-lifecycle.js.map +1 -0
  261. package/dist/server/services/platform.d.ts +25 -4
  262. package/dist/server/services/platform.d.ts.map +1 -1
  263. package/dist/server/services/platform.js +150 -92
  264. package/dist/server/services/platform.js.map +1 -1
  265. package/dist/server/services/sentry.d.ts.map +1 -1
  266. package/dist/server/services/sentry.js +0 -1
  267. package/dist/server/services/sentry.js.map +1 -1
  268. package/dist/server/services/settings.d.ts.map +1 -1
  269. package/dist/server/services/settings.js +0 -1
  270. package/dist/server/services/settings.js.map +1 -1
  271. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  272. package/dist/server/services/terminal/pty-manager.js +0 -1
  273. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  274. package/dist/server/services/terminal/pty-utils.d.ts.map +1 -1
  275. package/dist/server/services/terminal/pty-utils.js +0 -1
  276. package/dist/server/services/terminal/pty-utils.js.map +1 -1
  277. package/dist/server/services/websocket/autocomplete.d.ts.map +1 -1
  278. package/dist/server/services/websocket/autocomplete.js +0 -1
  279. package/dist/server/services/websocket/autocomplete.js.map +1 -1
  280. package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -1
  281. package/dist/server/services/websocket/file-definition-handlers.js +0 -1
  282. package/dist/server/services/websocket/file-definition-handlers.js.map +1 -1
  283. package/dist/server/services/websocket/file-download-handler.d.ts +17 -0
  284. package/dist/server/services/websocket/file-download-handler.d.ts.map +1 -0
  285. package/dist/server/services/websocket/file-download-handler.js +164 -0
  286. package/dist/server/services/websocket/file-download-handler.js.map +1 -0
  287. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  288. package/dist/server/services/websocket/file-explorer-handlers.js +0 -1
  289. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  290. package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -1
  291. package/dist/server/services/websocket/file-search-handlers.js +0 -1
  292. package/dist/server/services/websocket/file-search-handlers.js.map +1 -1
  293. package/dist/server/services/websocket/file-upload-handler.d.ts +2 -3
  294. package/dist/server/services/websocket/file-upload-handler.d.ts.map +1 -1
  295. package/dist/server/services/websocket/file-upload-handler.js +4 -7
  296. package/dist/server/services/websocket/file-upload-handler.js.map +1 -1
  297. package/dist/server/services/websocket/file-utils.d.ts.map +1 -1
  298. package/dist/server/services/websocket/file-utils.js +0 -1
  299. package/dist/server/services/websocket/file-utils.js.map +1 -1
  300. package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -1
  301. package/dist/server/services/websocket/git-branch-handlers.js +0 -1
  302. package/dist/server/services/websocket/git-branch-handlers.js.map +1 -1
  303. package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -1
  304. package/dist/server/services/websocket/git-diff-handlers.js +0 -1
  305. package/dist/server/services/websocket/git-diff-handlers.js.map +1 -1
  306. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  307. package/dist/server/services/websocket/git-handlers.js +58 -6
  308. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  309. package/dist/server/services/websocket/git-head-watcher.d.ts.map +1 -1
  310. package/dist/server/services/websocket/git-head-watcher.js +0 -1
  311. package/dist/server/services/websocket/git-head-watcher.js.map +1 -1
  312. package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -1
  313. package/dist/server/services/websocket/git-log-handlers.js +0 -1
  314. package/dist/server/services/websocket/git-log-handlers.js.map +1 -1
  315. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
  316. package/dist/server/services/websocket/git-pr-handlers.js +0 -1
  317. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
  318. package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -1
  319. package/dist/server/services/websocket/git-tag-handlers.js +0 -1
  320. package/dist/server/services/websocket/git-tag-handlers.js.map +1 -1
  321. package/dist/server/services/websocket/git-utils.d.ts +18 -3
  322. package/dist/server/services/websocket/git-utils.d.ts.map +1 -1
  323. package/dist/server/services/websocket/git-utils.js +58 -8
  324. package/dist/server/services/websocket/git-utils.js.map +1 -1
  325. package/dist/server/services/websocket/git-worktree-handlers.d.ts.map +1 -1
  326. package/dist/server/services/websocket/git-worktree-handlers.js +258 -16
  327. package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -1
  328. package/dist/server/services/websocket/handler-context.d.ts +15 -0
  329. package/dist/server/services/websocket/handler-context.d.ts.map +1 -1
  330. package/dist/server/services/websocket/handler-context.js +0 -1
  331. package/dist/server/services/websocket/handler-context.js.map +1 -1
  332. package/dist/server/services/websocket/handler.d.ts +7 -0
  333. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  334. package/dist/server/services/websocket/handler.js +76 -15
  335. package/dist/server/services/websocket/handler.js.map +1 -1
  336. package/dist/server/services/websocket/index.d.ts.map +1 -1
  337. package/dist/server/services/websocket/index.js +0 -1
  338. package/dist/server/services/websocket/index.js.map +1 -1
  339. package/dist/server/services/websocket/msg-id-tracker.d.ts +21 -0
  340. package/dist/server/services/websocket/msg-id-tracker.d.ts.map +1 -0
  341. package/dist/server/services/websocket/msg-id-tracker.js +76 -0
  342. package/dist/server/services/websocket/msg-id-tracker.js.map +1 -0
  343. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -1
  344. package/dist/server/services/websocket/plan-board-handlers.js +0 -1
  345. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -1
  346. package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -1
  347. package/dist/server/services/websocket/plan-execution-handlers.js +6 -2
  348. package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -1
  349. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
  350. package/dist/server/services/websocket/plan-handlers.js +0 -1
  351. package/dist/server/services/websocket/plan-handlers.js.map +1 -1
  352. package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -1
  353. package/dist/server/services/websocket/plan-helpers.js +0 -1
  354. package/dist/server/services/websocket/plan-helpers.js.map +1 -1
  355. package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -1
  356. package/dist/server/services/websocket/plan-issue-handlers.js +0 -1
  357. package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -1
  358. package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -1
  359. package/dist/server/services/websocket/plan-sprint-handlers.js +0 -1
  360. package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -1
  361. package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -1
  362. package/dist/server/services/websocket/quality-complexity.js +0 -1
  363. package/dist/server/services/websocket/quality-complexity.js.map +1 -1
  364. package/dist/server/services/websocket/quality-grading.d.ts +46 -0
  365. package/dist/server/services/websocket/quality-grading.d.ts.map +1 -0
  366. package/dist/server/services/websocket/quality-grading.js +482 -0
  367. package/dist/server/services/websocket/quality-grading.js.map +1 -0
  368. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  369. package/dist/server/services/websocket/quality-handlers.js +15 -4
  370. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  371. package/dist/server/services/websocket/quality-linting.d.ts.map +1 -1
  372. package/dist/server/services/websocket/quality-linting.js +0 -1
  373. package/dist/server/services/websocket/quality-linting.js.map +1 -1
  374. package/dist/server/services/websocket/quality-persistence.d.ts +14 -0
  375. package/dist/server/services/websocket/quality-persistence.d.ts.map +1 -1
  376. package/dist/server/services/websocket/quality-persistence.js +28 -12
  377. package/dist/server/services/websocket/quality-persistence.js.map +1 -1
  378. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -1
  379. package/dist/server/services/websocket/quality-review-agent.js +2 -3
  380. package/dist/server/services/websocket/quality-review-agent.js.map +1 -1
  381. package/dist/server/services/websocket/quality-service.d.ts +3 -1
  382. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  383. package/dist/server/services/websocket/quality-service.js +53 -58
  384. package/dist/server/services/websocket/quality-service.js.map +1 -1
  385. package/dist/server/services/websocket/quality-tools.d.ts +1 -1
  386. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -1
  387. package/dist/server/services/websocket/quality-tools.js +6 -3
  388. package/dist/server/services/websocket/quality-tools.js.map +1 -1
  389. package/dist/server/services/websocket/quality-types.d.ts +18 -2
  390. package/dist/server/services/websocket/quality-types.d.ts.map +1 -1
  391. package/dist/server/services/websocket/quality-types.js +0 -1
  392. package/dist/server/services/websocket/quality-types.js.map +1 -1
  393. package/dist/server/services/websocket/session-handlers.d.ts +48 -2
  394. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  395. package/dist/server/services/websocket/session-handlers.js +204 -66
  396. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  397. package/dist/server/services/websocket/session-history.d.ts.map +1 -1
  398. package/dist/server/services/websocket/session-history.js +0 -1
  399. package/dist/server/services/websocket/session-history.js.map +1 -1
  400. package/dist/server/services/websocket/session-initialization.d.ts +2 -2
  401. package/dist/server/services/websocket/session-initialization.d.ts.map +1 -1
  402. package/dist/server/services/websocket/session-initialization.js +75 -18
  403. package/dist/server/services/websocket/session-initialization.js.map +1 -1
  404. package/dist/server/services/websocket/session-registry.d.ts +29 -1
  405. package/dist/server/services/websocket/session-registry.d.ts.map +1 -1
  406. package/dist/server/services/websocket/session-registry.js +53 -5
  407. package/dist/server/services/websocket/session-registry.js.map +1 -1
  408. package/dist/server/services/websocket/settings-handlers.d.ts.map +1 -1
  409. package/dist/server/services/websocket/settings-handlers.js +0 -1
  410. package/dist/server/services/websocket/settings-handlers.js.map +1 -1
  411. package/dist/server/services/websocket/skill-handlers.d.ts.map +1 -1
  412. package/dist/server/services/websocket/skill-handlers.js +0 -1
  413. package/dist/server/services/websocket/skill-handlers.js.map +1 -1
  414. package/dist/server/services/websocket/skill-watcher.d.ts.map +1 -1
  415. package/dist/server/services/websocket/skill-watcher.js +0 -1
  416. package/dist/server/services/websocket/skill-watcher.js.map +1 -1
  417. package/dist/server/services/websocket/tab-broadcast.d.ts +24 -0
  418. package/dist/server/services/websocket/tab-broadcast.d.ts.map +1 -0
  419. package/dist/server/services/websocket/tab-broadcast.js +12 -0
  420. package/dist/server/services/websocket/tab-broadcast.js.map +1 -0
  421. package/dist/server/services/websocket/tab-event-buffer.d.ts +103 -0
  422. package/dist/server/services/websocket/tab-event-buffer.d.ts.map +1 -0
  423. package/dist/server/services/websocket/tab-event-buffer.js +106 -0
  424. package/dist/server/services/websocket/tab-event-buffer.js.map +1 -0
  425. package/dist/server/services/websocket/tab-event-replay.d.ts +20 -0
  426. package/dist/server/services/websocket/tab-event-replay.d.ts.map +1 -0
  427. package/dist/server/services/websocket/tab-event-replay.js +20 -0
  428. package/dist/server/services/websocket/tab-event-replay.js.map +1 -0
  429. package/dist/server/services/websocket/tab-handlers.d.ts +0 -1
  430. package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -1
  431. package/dist/server/services/websocket/tab-handlers.js +2 -10
  432. package/dist/server/services/websocket/tab-handlers.js.map +1 -1
  433. package/dist/server/services/websocket/terminal-handlers.d.ts.map +1 -1
  434. package/dist/server/services/websocket/terminal-handlers.js +39 -4
  435. package/dist/server/services/websocket/terminal-handlers.js.map +1 -1
  436. package/dist/server/services/websocket/types.d.ts +17 -8
  437. package/dist/server/services/websocket/types.d.ts.map +1 -1
  438. package/dist/server/services/websocket/types.js +8 -7
  439. package/dist/server/services/websocket/types.js.map +1 -1
  440. package/dist/server/utils/agent-manager.d.ts.map +1 -1
  441. package/dist/server/utils/agent-manager.js +0 -1
  442. package/dist/server/utils/agent-manager.js.map +1 -1
  443. package/dist/server/utils/paths.d.ts.map +1 -1
  444. package/dist/server/utils/paths.js +0 -1
  445. package/dist/server/utils/paths.js.map +1 -1
  446. package/dist/server/utils/port-manager.d.ts.map +1 -1
  447. package/dist/server/utils/port-manager.js +0 -1
  448. package/dist/server/utils/port-manager.js.map +1 -1
  449. package/dist/server/utils/port.d.ts.map +1 -1
  450. package/dist/server/utils/port.js +0 -1
  451. package/dist/server/utils/port.js.map +1 -1
  452. package/package.json +2 -2
  453. package/server/README.md +1 -1
  454. package/server/cli/headless/claude-invoker-process.ts +0 -1
  455. package/server/cli/headless/claude-invoker-stall.ts +7 -3
  456. package/server/cli/headless/claude-invoker-stream.ts +0 -1
  457. package/server/cli/headless/claude-invoker-tools.ts +0 -1
  458. package/server/cli/headless/claude-invoker.ts +1 -2
  459. package/server/cli/headless/haiku-assessments.ts +0 -1
  460. package/server/cli/headless/headless-logger.ts +0 -1
  461. package/server/cli/headless/index.ts +0 -1
  462. package/server/cli/headless/native-timeout-detector.ts +0 -1
  463. package/server/cli/headless/output-utils.ts +0 -1
  464. package/server/cli/headless/prompt-utils.ts +0 -1
  465. package/server/cli/headless/resilient-runner.ts +0 -1
  466. package/server/cli/headless/retry-strategies.ts +0 -1
  467. package/server/cli/headless/runner.ts +67 -73
  468. package/server/cli/headless/stall-assessor.ts +9 -5
  469. package/server/cli/headless/tool-watchdog.ts +0 -1
  470. package/server/cli/headless/types.ts +1 -2
  471. package/server/cli/improvisation-attachments.ts +0 -1
  472. package/server/cli/improvisation-history-store.ts +61 -0
  473. package/server/cli/improvisation-movements.ts +119 -0
  474. package/server/cli/improvisation-output-queue.ts +41 -0
  475. package/server/cli/improvisation-retry.ts +25 -601
  476. package/server/cli/improvisation-session-manager.ts +74 -161
  477. package/server/cli/improvisation-types.ts +0 -1
  478. package/server/cli/retry/retry-best-result.ts +69 -0
  479. package/server/cli/retry/retry-context-loss.ts +86 -0
  480. package/server/cli/retry/retry-premature-completion.ts +112 -0
  481. package/server/cli/retry/retry-recovery-strategies.ts +246 -0
  482. package/server/cli/retry/retry-resume-strategy.ts +32 -0
  483. package/server/cli/retry/retry-runner-factory.ts +69 -0
  484. package/server/cli/retry/retry-tool-results.ts +30 -0
  485. package/server/cli/retry/retry-types.ts +31 -0
  486. package/server/index.ts +37 -124
  487. package/server/mcp/bouncer-cli.ts +0 -1
  488. package/server/mcp/bouncer-haiku.ts +0 -1
  489. package/server/mcp/bouncer-integration.ts +0 -1
  490. package/server/mcp/security-analysis.ts +0 -1
  491. package/server/mcp/security-audit.ts +0 -1
  492. package/server/mcp/security-patterns.ts +0 -1
  493. package/server/mcp/server.ts +0 -1
  494. package/server/routes/files.ts +0 -1
  495. package/server/routes/improvise.ts +0 -1
  496. package/server/routes/index.ts +0 -1
  497. package/server/routes/instances.ts +0 -1
  498. package/server/routes/notifications.ts +0 -1
  499. package/server/server-setup.ts +126 -2
  500. package/server/services/analytics.ts +0 -1
  501. package/server/services/auth.ts +0 -1
  502. package/server/services/client-id.ts +0 -1
  503. package/server/services/file-explorer-ops.ts +0 -1
  504. package/server/services/files.ts +0 -1
  505. package/server/services/instances.ts +0 -1
  506. package/server/services/pathUtils.ts +0 -1
  507. package/server/services/plan/agent-loader.ts +0 -1
  508. package/server/services/plan/agents/assess-stall.md +11 -4
  509. package/server/services/plan/agents/code-review.md +13 -11
  510. package/server/services/plan/board-config.ts +121 -0
  511. package/server/services/plan/composer.ts +7 -6
  512. package/server/services/plan/config-installer.ts +0 -1
  513. package/server/services/plan/dependency-resolver.ts +0 -1
  514. package/server/services/plan/executor.ts +259 -470
  515. package/server/services/plan/front-matter.ts +0 -1
  516. package/server/services/plan/issue-classification.ts +0 -1
  517. package/server/services/plan/issue-loader.ts +63 -0
  518. package/server/services/plan/issue-prompt-builder.ts +0 -1
  519. package/server/services/plan/issue-retry.ts +5 -2
  520. package/server/services/plan/issue-writer.ts +136 -0
  521. package/server/services/plan/output-manager.ts +2 -2
  522. package/server/services/plan/parser-core.ts +0 -1
  523. package/server/services/plan/parser-migration.ts +0 -1
  524. package/server/services/plan/parser.ts +0 -1
  525. package/server/services/plan/progress-log.ts +91 -0
  526. package/server/services/plan/prompt-builder.ts +73 -36
  527. package/server/services/plan/readiness-planner.ts +49 -0
  528. package/server/services/plan/review-gate.ts +102 -3
  529. package/server/services/plan/state-reconciler.ts +0 -1
  530. package/server/services/plan/types.ts +0 -1
  531. package/server/services/plan/watcher.ts +0 -1
  532. package/server/services/platform-credentials.ts +0 -1
  533. package/server/services/platform-token-lifecycle.ts +171 -0
  534. package/server/services/platform.ts +168 -105
  535. package/server/services/sentry.ts +0 -1
  536. package/server/services/settings.ts +0 -1
  537. package/server/services/terminal/pty-manager.ts +0 -1
  538. package/server/services/terminal/pty-utils.ts +0 -1
  539. package/server/services/websocket/autocomplete.ts +0 -1
  540. package/server/services/websocket/file-definition-handlers.ts +0 -1
  541. package/server/services/websocket/file-download-handler.ts +190 -0
  542. package/server/services/websocket/file-explorer-handlers.ts +0 -1
  543. package/server/services/websocket/file-search-handlers.ts +0 -1
  544. package/server/services/websocket/file-upload-handler.ts +6 -5
  545. package/server/services/websocket/file-utils.ts +0 -1
  546. package/server/services/websocket/git-branch-handlers.ts +0 -1
  547. package/server/services/websocket/git-diff-handlers.ts +0 -1
  548. package/server/services/websocket/git-handlers.ts +66 -10
  549. package/server/services/websocket/git-head-watcher.ts +0 -1
  550. package/server/services/websocket/git-log-handlers.ts +0 -1
  551. package/server/services/websocket/git-pr-handlers.ts +0 -1
  552. package/server/services/websocket/git-tag-handlers.ts +0 -1
  553. package/server/services/websocket/git-utils.ts +69 -9
  554. package/server/services/websocket/git-worktree-handlers.ts +289 -19
  555. package/server/services/websocket/handler-context.ts +15 -1
  556. package/server/services/websocket/handler.ts +79 -16
  557. package/server/services/websocket/index.ts +0 -1
  558. package/server/services/websocket/msg-id-tracker.ts +83 -0
  559. package/server/services/websocket/plan-board-handlers.ts +0 -1
  560. package/server/services/websocket/plan-execution-handlers.ts +6 -2
  561. package/server/services/websocket/plan-handlers.ts +0 -1
  562. package/server/services/websocket/plan-helpers.ts +0 -1
  563. package/server/services/websocket/plan-issue-handlers.ts +0 -1
  564. package/server/services/websocket/plan-sprint-handlers.ts +0 -1
  565. package/server/services/websocket/quality-complexity.ts +0 -1
  566. package/server/services/websocket/quality-grading.ts +611 -0
  567. package/server/services/websocket/quality-handlers.ts +16 -4
  568. package/server/services/websocket/quality-linting.ts +0 -1
  569. package/server/services/websocket/quality-persistence.ts +30 -8
  570. package/server/services/websocket/quality-review-agent.ts +2 -3
  571. package/server/services/websocket/quality-service.ts +54 -55
  572. package/server/services/websocket/quality-tools.ts +11 -3
  573. package/server/services/websocket/quality-types.ts +21 -3
  574. package/server/services/websocket/session-handlers.ts +213 -69
  575. package/server/services/websocket/session-history.ts +0 -1
  576. package/server/services/websocket/session-initialization.ts +83 -20
  577. package/server/services/websocket/session-registry.ts +61 -5
  578. package/server/services/websocket/settings-handlers.ts +0 -1
  579. package/server/services/websocket/skill-handlers.ts +0 -1
  580. package/server/services/websocket/skill-watcher.ts +0 -1
  581. package/server/services/websocket/tab-broadcast.ts +37 -0
  582. package/server/services/websocket/tab-event-buffer.ts +158 -0
  583. package/server/services/websocket/tab-event-replay.ts +41 -0
  584. package/server/services/websocket/tab-handlers.ts +2 -10
  585. package/server/services/websocket/terminal-handlers.ts +39 -3
  586. package/server/services/websocket/types.ts +19 -7
  587. package/server/utils/agent-manager.ts +0 -1
  588. package/server/utils/paths.ts +0 -1
  589. package/server/utils/port-manager.ts +0 -1
  590. package/server/utils/port.ts +0 -1
@@ -1,5 +1,4 @@
1
1
  // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
- // Licensed under the MIT License. See LICENSE file for details.
3
2
  /**
4
3
  * Plan Executor — Wave-based execution with parallel headless Claude Code instances.
5
4
  *
@@ -8,56 +7,84 @@
8
7
  * reconciles state, and repeats.
9
8
  *
10
9
  * Implementation is split across focused modules:
10
+ * - board-config.ts — board.md metadata reads, workspace.json active board resolution
11
11
  * - config-installer.ts — tool permissions install/uninstall
12
12
  * - issue-prompt-builder.ts — per-issue prompt construction
13
+ * - issue-writer.ts — issue front-matter updates, recovery, revert, cancellation notes
13
14
  * - output-manager.ts — output path resolution, listing, publishing
14
- * - review-gate.ts AI-powered quality gate (review, parse, persist)
15
- * - front-matter.ts YAML front matter field editing utility
15
+ * - progress-log.ts progress.md writer + output dir creation
16
+ * - review-gate.ts AI-powered quality gate (review, parse, persist, full pipeline)
16
17
  */
17
18
  import { EventEmitter } from 'node:events';
18
- import { existsSync, readFileSync } from 'node:fs';
19
- import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
20
- import { isAbsolute, join, relative, resolve } from 'node:path';
19
+ import { readFile } from 'node:fs/promises';
20
+ import { join } from 'node:path';
21
21
  import { runWithFileLogger } from '../../cli/headless/headless-logger.js';
22
+ import { DEFAULT_MAX_PARALLEL_AGENTS, getBoardMaxParallelAgents, resolveActiveBoardId, resolveBoardDir, tryCompleteBoardIfDone, } from './board-config.js';
22
23
  import { ConfigInstaller } from './config-installer.js';
23
24
  import { resolveReadyToWork } from './dependency-resolver.js';
24
- import { checkAllAcceptanceCriteria, replaceFrontMatterField, setFrontMatterFieldAsync } from './front-matter.js';
25
+ import { loadBoardIssues, loadProjectIssues } from './issue-loader.js';
25
26
  import { buildIssuePrompt } from './issue-prompt-builder.js';
26
27
  import { runIssueWithRetry } from './issue-retry.js';
28
+ import { extractIssueStatus, recoverStaleIssues, revertIncompleteIssues, updateIssueFrontMatter, validateIssuePath, } from './issue-writer.js';
27
29
  import { listExistingDocs, publishOutputs, resolveOutputPath } from './output-manager.js';
28
- import { parseBoardDirectory, parsePlanDirectory, resolvePmDir } from './parser.js';
29
- import { appendReviewFeedback, getReviewAttemptCount, MAX_REVIEW_ATTEMPTS, persistReviewResult, reviewIssue } from './review-gate.js';
30
+ import { resolvePmDir } from './parser.js';
31
+ import { appendProgressEntry, ensureOutputDirs } from './progress-log.js';
32
+ import { buildCompletionReason, detectDeadState, hasBlockedIssues } from './readiness-planner.js';
33
+ import { runReviewPipeline } from './review-gate.js';
30
34
  import { reconcileState } from './state-reconciler.js';
31
- /** Default max parallel agents when board doesn't specify. */
32
- const DEFAULT_MAX_PARALLEL_AGENTS = 3;
33
35
  /** Stop after this many consecutive waves with zero completions. */
34
36
  const MAX_CONSECUTIVE_EMPTY_WAVES = 3;
37
+ /**
38
+ * Render a tool-call event as a short single-line label for the verbose
39
+ * Output stream — `Read · package.json`, `Bash · npm test`, etc. The web UI
40
+ * splits the label on the first ` · ` to render the tool name as a badge.
41
+ */
42
+ function formatToolCallLine(toolName, input) {
43
+ const arg = pickPrimaryArg(toolName, input);
44
+ return arg ? `${toolName} · ${arg}` : toolName;
45
+ }
46
+ function pickPrimaryArg(toolName, input) {
47
+ if (!input)
48
+ return '';
49
+ // Map common Claude Code tools to their most informative single argument.
50
+ const candidates = {
51
+ Read: ['file_path'],
52
+ Write: ['file_path'],
53
+ Edit: ['file_path'],
54
+ Glob: ['pattern'],
55
+ Grep: ['pattern'],
56
+ Bash: ['command'],
57
+ WebFetch: ['url'],
58
+ WebSearch: ['query'],
59
+ Task: ['description'],
60
+ };
61
+ const keys = candidates[toolName] ?? ['command', 'file_path', 'path', 'pattern', 'url', 'query'];
62
+ for (const key of keys) {
63
+ const value = input[key];
64
+ if (typeof value === 'string' && value.length > 0) {
65
+ return value.length > 120 ? `${value.slice(0, 117)}...` : value;
66
+ }
67
+ }
68
+ return '';
69
+ }
35
70
  /** Per-issue stall timeouts (ms) — shorter than Agent Teams wave timeouts */
36
71
  const ISSUE_STALL_WARNING_MS = 900_000; // 15 min
37
72
  const ISSUE_STALL_KILL_MS = 1_800_000; // 30 min
38
- const ISSUE_STALL_HARD_CAP_MS = 3_600_000; // 1 hr hard cap
73
+ const ISSUE_STALL_HARD_CAP_MS = 14_400_000; // 4 hr backstop — only fires after stall signals flag the run
39
74
  const ISSUE_STALL_MAX_EXTENSIONS = 10;
40
75
  export class PlanExecutor extends EventEmitter {
41
76
  status = 'idle';
42
77
  workingDir;
78
+ extraEnv;
43
79
  shouldStop = false;
44
80
  shouldPause = false;
45
81
  /** AbortController for killing running HeadlessRunner processes on stop. */
46
82
  waveAbortController = null;
47
- epicScope = null;
48
- /** Cached PM directory path — resolved once per start(). */
49
- pmDir = null;
50
- /** Board directory path (e.g. /path/.mstro/pm/boards/BOARD-001). Used for outputs, reviews, progress. */
51
- boardDir = null;
52
- /** Board ID being executed (e.g. "BOARD-001") */
53
- boardId = null;
83
+ /** Resolved context for the current/last run — rebuilt each runStart(). */
84
+ context;
85
+ /** Options from the last run; replayed on resume() to preserve scope. */
86
+ lastStartOptions = {};
54
87
  configInstaller;
55
- /** Flag to prevent start() from clearing scope set by startBoard/startEpic */
56
- _scopeSetByCall = false;
57
- /** Extra environment variables forwarded to HeadlessRunner child processes (e.g. API keys) */
58
- extraEnv;
59
- /** Optional worktree directory for running AI agents. PM data is always read from workingDir. */
60
- executionDir = null;
61
88
  metrics = {
62
89
  issuesCompleted: 0,
63
90
  issuesAttempted: 0,
@@ -70,50 +97,49 @@ export class PlanExecutor extends EventEmitter {
70
97
  this.workingDir = workingDir;
71
98
  this.extraEnv = options?.extraEnv;
72
99
  this.configInstaller = new ConfigInstaller(workingDir);
73
- }
74
- validateIssuePath(issuePath, baseDir) {
75
- const resolvedBase = resolve(baseDir);
76
- const resolvedFull = resolve(resolvedBase, issuePath);
77
- const rel = relative(resolvedBase, resolvedFull);
78
- if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) {
79
- throw new Error(`Invalid issue path: path traversal detected in "${issuePath}"`);
80
- }
81
- return resolvedFull;
100
+ this.context = this.buildContext({});
82
101
  }
83
102
  getStatus() { return this.status; }
84
103
  getMetrics() { return { ...this.metrics }; }
85
- async startEpic(epicPath) {
86
- this.epicScope = epicPath;
87
- this._scopeSetByCall = true;
88
- return this.start();
104
+ startEpic(epicPath) {
105
+ return this.runStart({ epic: epicPath });
89
106
  }
90
107
  /** Start execution, optionally scoped to a specific board. */
91
- async startBoard(boardId, executionDir) {
92
- this.boardId = boardId;
93
- this.executionDir = executionDir ?? null;
94
- this._scopeSetByCall = true;
95
- return this.start();
108
+ startBoard(boardId, executionDir) {
109
+ return this.runStart({ board: boardId, executionDir });
96
110
  }
97
- async start() {
111
+ start(options = {}) {
112
+ return this.runStart(options);
113
+ }
114
+ pause() { this.shouldPause = true; }
115
+ stop() {
116
+ this.shouldStop = true;
117
+ this.status = 'stopping';
118
+ this.emit('statusChanged', this.status);
119
+ // Kill all running HeadlessRunner processes in the current wave
120
+ this.waveAbortController?.abort();
121
+ }
122
+ resume() {
123
+ if (this.status !== 'paused')
124
+ return Promise.resolve();
125
+ this.shouldPause = false;
126
+ // Replay the options from the previous run to preserve epic/board scope.
127
+ return this.runStart(this.lastStartOptions);
128
+ }
129
+ // ── Run orchestration ────────────────────────────────────────
130
+ async runStart(options) {
98
131
  if (this.status === 'executing' || this.status === 'starting')
99
132
  return;
133
+ this.lastStartOptions = options;
100
134
  this.shouldStop = false;
101
135
  this.shouldPause = false;
102
- // Reset scoping from previous runs unless explicitly set by startBoard/startEpic
103
- if (!this._scopeSetByCall) {
104
- this.epicScope = null;
105
- this.boardId = null;
106
- this.executionDir = null;
107
- }
108
- this._scopeSetByCall = false;
109
136
  this.status = 'starting';
110
137
  this.emit('statusChanged', this.status);
138
+ this.context = this.buildContext(options);
111
139
  const startTime = Date.now();
112
140
  this.status = 'executing';
113
141
  this.emit('statusChanged', this.status);
114
- this.pmDir = resolvePmDir(this.workingDir);
115
- this.boardDir = this.resolveBoardDir();
116
- await this.recoverStaleIssues();
142
+ await this.runStaleRecovery();
117
143
  const stallResult = await this.runWaveLoop();
118
144
  this.metrics.totalDuration = Date.now() - startTime;
119
145
  if (stallResult === 'stalled' || stallResult === 'dead') {
@@ -135,10 +161,39 @@ export class PlanExecutor extends EventEmitter {
135
161
  }
136
162
  this.emit('statusChanged', this.status);
137
163
  }
164
+ /** Build an immutable execution context from start options. */
165
+ buildContext(options) {
166
+ const pmDir = resolvePmDir(this.workingDir);
167
+ const boardId = options.board ?? null;
168
+ return {
169
+ workingDir: this.workingDir,
170
+ extraEnv: this.extraEnv,
171
+ epicScope: options.epic ?? null,
172
+ boardId,
173
+ executionDir: options.executionDir ?? null,
174
+ pmDir,
175
+ boardDir: resolveBoardDir(pmDir, boardId),
176
+ };
177
+ }
178
+ // ── Warning / update helpers bound to the executor's event stream ──
179
+ /**
180
+ * Forward module-emitted warnings as executor 'output' events so they flow
181
+ * through to the WebSocket broadcast like inline warnings always have.
182
+ */
183
+ emitWarn = (message, issueId) => {
184
+ this.emit('output', { issueId: issueId ?? 'system', text: message, boardId: this.context.boardId ?? null });
185
+ };
186
+ async setIssueStatus(issuePath, newStatus) {
187
+ const { pmDir } = this.context;
188
+ if (!pmDir)
189
+ return;
190
+ await updateIssueFrontMatter(pmDir, issuePath, newStatus, this.emitWarn);
191
+ }
192
+ // ── Wave loop ────────────────────────────────────────────────
138
193
  /** Run waves until done, paused, stopped, or stalled. */
139
194
  async runWaveLoop() {
140
195
  let consecutiveZeroCompletions = 0;
141
- const maxParallel = await this.getBoardMaxParallelAgents();
196
+ const maxParallel = await getBoardMaxParallelAgents(this.context.pmDir, this.effectiveBoardId(), this.emitWarn);
142
197
  while (!this.shouldStop && !this.shouldPause) {
143
198
  const readyIssues = await this.pickReadyIssues();
144
199
  if (readyIssues.length === 0) {
@@ -156,34 +211,15 @@ export class PlanExecutor extends EventEmitter {
156
211
  }
157
212
  return 'done';
158
213
  }
214
+ effectiveBoardId() {
215
+ return this.context.boardId ?? resolveActiveBoardId(this.context.pmDir);
216
+ }
159
217
  async hasDeadIssues() {
160
- const pmDir = this.pmDir;
218
+ const { pmDir } = this.context;
161
219
  if (!pmDir)
162
220
  return false;
163
- const effectiveBoardId = this.boardId ?? this.resolveActiveBoardId();
164
- const issues = effectiveBoardId
165
- ? await this.loadBoardIssues(pmDir, effectiveBoardId)
166
- : this.loadProjectIssues();
167
- if (!issues)
168
- return false;
169
- const terminalStatuses = new Set(['done', 'cancelled']);
170
- return issues.some(i => i.type !== 'epic' && !terminalStatuses.has(i.status) && i.status !== 'todo');
171
- }
172
- pause() { this.shouldPause = true; }
173
- stop() {
174
- this.shouldStop = true;
175
- this.status = 'stopping';
176
- this.emit('statusChanged', this.status);
177
- // Kill all running HeadlessRunner processes in the current wave
178
- this.waveAbortController?.abort();
179
- }
180
- resume() {
181
- if (this.status !== 'paused')
182
- return Promise.resolve();
183
- this.shouldPause = false;
184
- // Preserve board/epic scope across resume by marking as a scoped call
185
- this._scopeSetByCall = true;
186
- return this.start();
221
+ const { issues } = await this.loadScopedIssues(pmDir);
222
+ return issues ? hasBlockedIssues(issues) : false;
187
223
  }
188
224
  // ── Wave execution ───────────────────────────────────────────
189
225
  async executeWave(issues) {
@@ -195,13 +231,13 @@ export class PlanExecutor extends EventEmitter {
195
231
  this.emit('waveStarted', { issueIds: waveIds });
196
232
  // Create abort controller for this wave — stop() will abort it
197
233
  this.waveAbortController = new AbortController();
198
- await this.ensureOutputDirs();
234
+ await ensureOutputDirs(this.context.pmDir, this.context.boardDir);
199
235
  this.configInstaller.installPermissions();
200
236
  for (const issue of issues) {
201
- await this.updateIssueFrontMatter(issue.path, 'in_progress');
237
+ await this.setIssueStatus(issue.path, 'in_progress');
202
238
  }
203
- const existingDocs = listExistingDocs(this.workingDir, this.boardDir);
204
- const pmDir = this.pmDir;
239
+ const existingDocs = listExistingDocs(this.workingDir, this.context.boardDir);
240
+ const { pmDir } = this.context;
205
241
  let completedCount = 0;
206
242
  try {
207
243
  // Spawn one HeadlessRunner per issue in parallel
@@ -225,7 +261,8 @@ export class PlanExecutor extends EventEmitter {
225
261
  issueIds: waveIds,
226
262
  error: error instanceof Error ? error.message : String(error),
227
263
  });
228
- await this.revertIncompleteIssues(issues);
264
+ if (pmDir)
265
+ await revertIncompleteIssues(pmDir, issues, this.emitWarn);
229
266
  }
230
267
  finally {
231
268
  this.configInstaller.uninstallPermissions();
@@ -237,17 +274,19 @@ export class PlanExecutor extends EventEmitter {
237
274
  }
238
275
  /** Run a single issue via its own headless Claude Code instance with retry logic. */
239
276
  async runSingleIssue(issue, pmDir, existingDocs, waveLabel, abortSignal) {
240
- const effectiveDir = this.executionDir || this.workingDir;
241
- const outputPath = resolveOutputPath(issue, this.workingDir, this.boardDir);
277
+ const { executionDir, boardDir, workingDir } = this.context;
278
+ const effectiveDir = executionDir || workingDir;
279
+ const outputPath = resolveOutputPath(issue, workingDir, boardDir);
242
280
  const prompt = buildIssuePrompt({
243
281
  issue,
244
282
  workingDir: effectiveDir,
245
283
  pmDir,
246
- boardDir: this.boardDir,
284
+ boardDir,
247
285
  existingDocs,
248
286
  outputPath,
249
287
  });
250
- const boardLogDir = this.boardDir ? join(this.boardDir, 'logs') : undefined;
288
+ const boardLogDir = boardDir ? join(boardDir, 'logs') : undefined;
289
+ const emitOutput = (text) => this.emit('output', { issueId: issue.id, text });
251
290
  const result = await runWithFileLogger(`pm-issue-${issue.id}`, () => runIssueWithRetry({
252
291
  workingDir: effectiveDir,
253
292
  prompt,
@@ -255,8 +294,17 @@ export class PlanExecutor extends EventEmitter {
255
294
  stallKillMs: ISSUE_STALL_KILL_MS,
256
295
  stallHardCapMs: ISSUE_STALL_HARD_CAP_MS,
257
296
  stallMaxExtensions: ISSUE_STALL_MAX_EXTENSIONS,
258
- outputCallback: (text) => {
259
- this.emit('output', { issueId: issue.id, text });
297
+ outputCallback: emitOutput,
298
+ // Thinking deltas: marker-prefix so the web UI can classify them as a
299
+ // separate verbose-only block type without needing a new message field.
300
+ thinkingCallback: (text) => emitOutput(`[[MSTRO_THINKING]]${text}`),
301
+ // Tool calls: emit a single line per completed tool with a short arg
302
+ // summary. The 'tool_complete' event fires after input has fully
303
+ // streamed in, so we can render the tool with its real arguments.
304
+ toolUseCallback: (event) => {
305
+ if (event.type !== 'tool_complete' || !event.toolName)
306
+ return;
307
+ emitOutput(`[[MSTRO_TOOL]]${formatToolCallLine(event.toolName, event.completeInput)}`);
260
308
  },
261
309
  extraEnv: this.extraEnv,
262
310
  abortSignal,
@@ -270,8 +318,9 @@ export class PlanExecutor extends EventEmitter {
270
318
  * doesn't prevent the others or kill the while loop in start().
271
319
  */
272
320
  async finalizeWave(issues, waveStart, waveLabel) {
321
+ const { pmDir, boardDir, boardId } = this.context;
273
322
  try {
274
- reconcileState(this.workingDir, this.boardId ?? undefined);
323
+ reconcileState(this.workingDir, boardId ?? undefined);
275
324
  this.emit('stateUpdated');
276
325
  }
277
326
  catch (err) {
@@ -281,7 +330,7 @@ export class PlanExecutor extends EventEmitter {
281
330
  });
282
331
  }
283
332
  try {
284
- publishOutputs(issues, this.workingDir, this.boardDir, {
333
+ publishOutputs(issues, this.workingDir, boardDir, {
285
334
  onWarning: (issueId, text) => this.emit('output', { issueId, text: `Warning: ${text}` }),
286
335
  });
287
336
  }
@@ -292,7 +341,7 @@ export class PlanExecutor extends EventEmitter {
292
341
  });
293
342
  }
294
343
  try {
295
- await this.appendProgressEntry(issues, waveStart);
344
+ await appendProgressEntry(pmDir, boardDir, issues, waveStart, this.emitWarn);
296
345
  }
297
346
  catch (err) {
298
347
  this.emit('output', {
@@ -308,30 +357,21 @@ export class PlanExecutor extends EventEmitter {
308
357
  * and either confirmed `done` (passed) or reverted to `todo` (failed).
309
358
  */
310
359
  async reconcileWaveResults(issues) {
311
- const pmDir = this.pmDir;
360
+ const { pmDir } = this.context;
312
361
  if (!pmDir)
313
362
  return 0;
314
363
  let completed = 0;
315
364
  for (const issue of issues) {
316
- const fullPath = this.validateIssuePath(issue.path, pmDir);
365
+ const fullPath = validateIssuePath(issue.path, pmDir);
317
366
  try {
318
367
  const content = await readFile(fullPath, 'utf-8');
319
- const statusMatch = content.match(/^status:\s*(\S+)/m);
320
- const currentStatus = statusMatch?.[1] ?? 'unknown';
368
+ const currentStatus = extractIssueStatus(content) ?? 'unknown';
321
369
  if (currentStatus === 'in_review' || currentStatus === 'done') {
322
- if (issue.reviewGate === 'none') {
323
- // Skip review gate — mark done directly
324
- await this.updateIssueFrontMatter(issue.path, 'done');
325
- this.metrics.issuesCompleted++;
326
- this.emit('issueCompleted', issue);
370
+ if (await this.finalizeCompletedIssue(issue, pmDir))
327
371
  completed++;
328
- }
329
- else {
330
- completed += await this.runReviewGate(issue, pmDir);
331
- }
332
372
  }
333
373
  else {
334
- await this.updateIssueFrontMatter(issue.path, issue.status);
374
+ await this.setIssueStatus(issue.path, issue.status);
335
375
  this.emit('issueError', {
336
376
  issueId: issue.id,
337
377
  error: 'Issue did not complete during wave execution',
@@ -344,52 +384,38 @@ export class PlanExecutor extends EventEmitter {
344
384
  }
345
385
  return completed;
346
386
  }
347
- /** Run the review gate for a single issue that agents marked as done. Returns 1 if passed, 0 otherwise. */
348
- async runReviewGate(issue, pmDir) {
349
- const reviewDir = this.boardDir ?? pmDir;
350
- const attempts = getReviewAttemptCount(reviewDir, issue);
351
- if (attempts >= MAX_REVIEW_ATTEMPTS) {
352
- await this.updateIssueFrontMatter(issue.path, 'cancelled');
353
- await this.appendCancellationNote(issue, pmDir, `Cancelled after ${MAX_REVIEW_ATTEMPTS} failed reviews — issue may need restructuring`);
354
- this.emit('reviewProgress', { issueId: issue.id, status: 'max_attempts' });
355
- this.emit('issueAbandoned', {
356
- issueId: issue.id,
357
- reason: `Review failed ${MAX_REVIEW_ATTEMPTS} times — cancelled to unblock dependents`,
358
- attempts,
359
- });
360
- this.emit('output', { issueId: issue.id, text: `Review: max attempts reached, cancelling issue to unblock dependents` });
361
- return 0;
387
+ /**
388
+ * Finalize a single issue whose status reached `in_review`/`done`. Runs the
389
+ * review pipeline unless the issue opted out via `reviewGate: 'none'`.
390
+ * Returns true when the issue is confirmed done (counted toward completions).
391
+ */
392
+ async finalizeCompletedIssue(issue, pmDir) {
393
+ if (issue.reviewGate === 'none') {
394
+ await this.setIssueStatus(issue.path, 'done');
395
+ this.metrics.issuesCompleted++;
396
+ this.emit('issueCompleted', issue);
397
+ return true;
362
398
  }
363
- await this.updateIssueFrontMatter(issue.path, 'in_review');
364
- this.emit('reviewProgress', { issueId: issue.id, status: 'reviewing' });
365
- const outputPath = resolveOutputPath(issue, this.workingDir, this.boardDir);
366
- const result = await reviewIssue({
367
- workingDir: this.executionDir || this.workingDir,
399
+ const passed = await runReviewPipeline({
368
400
  issue,
369
401
  pmDir,
370
- outputPath,
371
- onOutput: (text) => this.emit('output', { issueId: issue.id, text }),
372
- logDir: this.boardDir ? join(this.boardDir, 'logs') : undefined,
373
- reviewCriteria: await this.getBoardReviewCriteria(),
374
- boardDir: this.boardDir,
402
+ workingDir: this.workingDir,
403
+ executionDir: this.context.executionDir,
404
+ boardDir: this.context.boardDir,
405
+ boardId: this.context.boardId,
375
406
  extraEnv: this.extraEnv,
407
+ }, {
408
+ setStatus: (path, status) => this.setIssueStatus(path, status),
409
+ onOutput: (issueId, text) => this.emit('output', { issueId, text }),
410
+ onReviewProgress: (issueId, status) => this.emit('reviewProgress', { issueId, status }),
411
+ onIssueAbandoned: (issueId, reason, attempts) => this.emit('issueAbandoned', { issueId, reason, attempts }),
412
+ onIssueCompleted: (completedIssue) => this.emit('issueCompleted', completedIssue),
413
+ onIssueError: (issueId, error) => this.emit('issueError', { issueId, error }),
414
+ warn: this.emitWarn,
376
415
  });
377
- persistReviewResult(reviewDir, issue, result);
378
- if (result.passed) {
379
- await this.updateIssueFrontMatter(issue.path, 'done');
416
+ if (passed)
380
417
  this.metrics.issuesCompleted++;
381
- this.emit('reviewProgress', { issueId: issue.id, status: 'passed' });
382
- this.emit('issueCompleted', issue);
383
- return 1;
384
- }
385
- await this.updateIssueFrontMatter(issue.path, 'todo');
386
- appendReviewFeedback(pmDir, issue, result);
387
- this.emit('reviewProgress', { issueId: issue.id, status: 'failed' });
388
- this.emit('issueError', {
389
- issueId: issue.id,
390
- error: `Review failed: ${result.checks.filter(c => !c.passed).map(c => c.name).join(', ')}`,
391
- });
392
- return 0;
418
+ return passed;
393
419
  }
394
420
  // ── Recovery ─────────────────────────────────────────────────
395
421
  /**
@@ -398,26 +424,14 @@ export class PlanExecutor extends EventEmitter {
398
424
  * these issues block the dependency graph and cause the executor to
399
425
  * find zero ready issues, making "Implement" appear to do nothing.
400
426
  */
401
- async recoverStaleIssues() {
402
- const pmDir = this.pmDir;
427
+ async runStaleRecovery() {
428
+ const { pmDir } = this.context;
403
429
  if (!pmDir)
404
430
  return;
405
- const effectiveBoardId = this.boardId ?? this.resolveActiveBoardId();
406
- const issues = effectiveBoardId
407
- ? await this.loadBoardIssues(pmDir, effectiveBoardId)
408
- : this.loadProjectIssues();
431
+ const { issues } = await this.loadScopedIssues(pmDir);
409
432
  if (!issues)
410
433
  return;
411
- const staleStatuses = new Set(['in_progress', 'in_review']);
412
- const recovered = [];
413
- for (const issue of issues) {
414
- if (issue.type === 'epic')
415
- continue;
416
- if (staleStatuses.has(issue.status)) {
417
- await this.updateIssueFrontMatter(issue.path, 'todo');
418
- recovered.push(`${issue.id} (${issue.status} → todo)`);
419
- }
420
- }
434
+ const recovered = await recoverStaleIssues(pmDir, issues, this.emitWarn);
421
435
  if (recovered.length > 0) {
422
436
  this.emit('output', {
423
437
  issueId: 'recovery',
@@ -426,317 +440,47 @@ export class PlanExecutor extends EventEmitter {
426
440
  this.emit('stateUpdated');
427
441
  }
428
442
  }
429
- // ── Helpers ──────────────────────────────────────────────────
430
- /** Read the board's maxParallelAgents setting, falling back to default. */
431
- async getBoardMaxParallelAgents() {
432
- const pmDir = this.pmDir;
433
- if (!pmDir)
434
- return DEFAULT_MAX_PARALLEL_AGENTS;
435
- const effectiveBoardId = this.boardId ?? this.resolveActiveBoardId();
436
- if (!effectiveBoardId)
437
- return DEFAULT_MAX_PARALLEL_AGENTS;
438
- const boardMdPath = join(pmDir, 'boards', effectiveBoardId, 'board.md');
439
- if (!existsSync(boardMdPath))
440
- return DEFAULT_MAX_PARALLEL_AGENTS;
441
- try {
442
- const content = await readFile(boardMdPath, 'utf-8');
443
- const match = content.match(/^max_parallel_agents:\s*(\d+)/m);
444
- return match ? Math.max(1, Math.min(Number(match[1]), 10)) : DEFAULT_MAX_PARALLEL_AGENTS;
445
- }
446
- catch (err) {
447
- this.emit('output', { issueId: 'system', text: `Warning: failed to read board max_parallel_agents: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
448
- return DEFAULT_MAX_PARALLEL_AGENTS;
449
- }
450
- }
451
- /** Read the board's custom review criteria, if set. */
452
- async getBoardReviewCriteria() {
453
- const pmDir = this.pmDir;
454
- if (!pmDir)
455
- return undefined;
456
- const effectiveBoardId = this.boardId ?? this.resolveActiveBoardId();
457
- if (!effectiveBoardId)
458
- return undefined;
459
- const boardMdPath = join(pmDir, 'boards', effectiveBoardId, 'board.md');
460
- if (!existsSync(boardMdPath))
461
- return undefined;
462
- try {
463
- const content = await readFile(boardMdPath, 'utf-8');
464
- const match = content.match(/^review_criteria:\s*"(.+)"/m);
465
- if (!match)
466
- return undefined;
467
- const raw = match[1].replace(/\\"/g, '"').replace(/\\n/g, '\n').trim();
468
- return raw || undefined;
469
- }
470
- catch (err) {
471
- this.emit('output', { issueId: 'system', text: `Warning: failed to read board review criteria: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
472
- return undefined;
473
- }
474
- }
443
+ // ── Issue loading & readiness ────────────────────────────────
475
444
  async pickReadyIssues() {
476
- const pmDir = this.pmDir;
445
+ const { pmDir, epicScope } = this.context;
477
446
  if (!pmDir) {
478
447
  this.emit('error', 'No PM directory found');
479
448
  return [];
480
449
  }
481
- const effectiveBoardId = this.boardId ?? this.resolveActiveBoardId();
482
- const issues = effectiveBoardId
483
- ? await this.loadBoardIssues(pmDir, effectiveBoardId)
484
- : this.loadProjectIssues();
450
+ const { issues, boardId } = await this.loadScopedIssues(pmDir);
485
451
  if (!issues)
486
452
  return [];
487
- const readyIssues = resolveReadyToWork(issues, this.epicScope ?? undefined);
453
+ const readyIssues = resolveReadyToWork(issues, epicScope ?? undefined);
488
454
  if (readyIssues.length === 0) {
489
- const deadState = this.detectDeadState(issues);
455
+ const deadState = detectDeadState(issues);
490
456
  if (deadState) {
491
457
  this.emit('error', deadState);
492
458
  }
493
459
  else {
494
- this.emit('complete', this.buildCompletionReason(issues));
495
- if (effectiveBoardId) {
496
- await this.tryCompleteBoardIfDone(pmDir, effectiveBoardId, issues);
460
+ this.emit('complete', buildCompletionReason(issues, epicScope));
461
+ if (boardId) {
462
+ await tryCompleteBoardIfDone(pmDir, boardId, issues, this.emitWarn);
497
463
  }
498
464
  }
499
465
  }
500
466
  return readyIssues;
501
467
  }
502
- /** Load issues from a specific board, auto-activating draft boards. Returns null on error. */
503
- async loadBoardIssues(pmDir, boardId) {
504
- const boardState = parseBoardDirectory(pmDir, boardId);
505
- if (!boardState) {
506
- this.emit('error', `Board not found: ${boardId}`);
507
- return null;
508
- }
509
- if (boardState.state.paused) {
510
- this.emit('error', 'Board is paused');
511
- return null;
512
- }
513
- if (boardState.board.status === 'draft') {
514
- await this.activateBoard(pmDir, boardId);
515
- }
516
- else if (boardState.board.status !== 'active') {
517
- this.emit('error', `Board ${boardId} is not active (status: ${boardState.board.status})`);
518
- return null;
519
- }
520
- return boardState.issues;
521
- }
522
- /** Load project-level issues (legacy or no boards). Returns null on error. */
523
- loadProjectIssues() {
524
- const fullState = parsePlanDirectory(this.workingDir);
525
- if (!fullState) {
526
- this.emit('error', 'No PM directory found');
527
- return null;
528
- }
529
- if (fullState.state.paused) {
530
- this.emit('error', 'Project is paused');
531
- return null;
532
- }
533
- return fullState.issues;
534
- }
535
- /** Activate a draft board by updating its status in board.md. */
536
- async activateBoard(pmDir, boardId) {
537
- const boardMdPath = join(pmDir, 'boards', boardId, 'board.md');
538
- if (!existsSync(boardMdPath))
539
- return;
540
- try {
541
- const content = await readFile(boardMdPath, 'utf-8');
542
- await writeFile(boardMdPath, replaceFrontMatterField(content, 'status', 'active'), 'utf-8');
543
- }
544
- catch (err) {
545
- this.emit('output', { issueId: 'system', text: `Warning: failed to activate board ${boardId}: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
546
- }
547
- }
548
- /** Check if all issues in a board are done and mark board as completed. */
549
- async tryCompleteBoardIfDone(pmDir, boardId, issues) {
550
- const allDone = issues.length > 0 && issues.every(i => i.status === 'done' || i.status === 'cancelled');
551
- if (!allDone)
552
- return;
553
- const boardMdPath = join(pmDir, 'boards', boardId, 'board.md');
554
- if (!existsSync(boardMdPath))
555
- return;
556
- try {
557
- let content = await readFile(boardMdPath, 'utf-8');
558
- content = replaceFrontMatterField(content, 'status', 'completed');
559
- content = replaceFrontMatterField(content, 'completed_at', `"${new Date().toISOString()}"`);
560
- await writeFile(boardMdPath, content, 'utf-8');
561
- }
562
- catch (err) {
563
- this.emit('output', { issueId: 'system', text: `Warning: failed to mark board ${boardId} as completed: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
564
- }
565
- }
566
- resolveActiveBoardId() {
567
- const pmDir = this.pmDir;
568
- if (!pmDir)
569
- return null;
570
- try {
571
- const workspacePath = join(pmDir, 'workspace.json');
572
- if (!existsSync(workspacePath))
573
- return null;
574
- const workspace = JSON.parse(readFileSync(workspacePath, 'utf-8'));
575
- return workspace.activeBoardId ?? null;
576
- }
577
- catch {
578
- return null;
579
- }
580
- }
581
- buildCompletionReason(issues) {
582
- const nonEpic = issues.filter(i => i.type !== 'epic');
583
- const done = nonEpic.filter(i => i.status === 'done' || i.status === 'cancelled').length;
584
- const blocked = nonEpic.filter(i => i.status === 'todo').length;
585
- if (done === nonEpic.length)
586
- return this.epicScope ? 'All epic issues are done' : 'All issues are done';
587
- if (blocked > 0)
588
- return `${done}/${nonEpic.length} issues done, ${blocked} blocked by incomplete dependencies`;
589
- return this.epicScope ? 'All epic issues are done or blocked' : 'All work is done or blocked';
590
- }
591
- /** Detect issues stuck in non-terminal states with no path to completion. */
592
- detectDeadState(issues) {
593
- const nonEpic = issues.filter(i => i.type !== 'epic');
594
- const terminalStatuses = new Set(['done', 'cancelled']);
595
- const stuck = nonEpic.filter(i => !terminalStatuses.has(i.status) && i.status !== 'todo');
596
- if (stuck.length === 0)
597
- return null;
598
- const stuckIds = stuck.map(i => `${i.id} (${i.status})`).join(', ');
599
- const issueByPath = new Map(issues.map(i => [i.path, i]));
600
- const blockedByStuck = nonEpic.filter(i => {
601
- if (i.status !== 'todo')
602
- return false;
603
- return i.blockedBy.some(bp => {
604
- const blocker = issueByPath.get(bp);
605
- return blocker && !terminalStatuses.has(blocker.status);
606
- });
607
- });
608
- const blockedIds = blockedByStuck.map(i => i.id).join(', ');
609
- return `Board stuck: ${stuckIds} cannot progress${blockedIds ? `. Blocking: ${blockedIds}` : ''}`;
610
- }
611
- async revertIncompleteIssues(issues) {
612
- const pmDir = this.pmDir;
613
- if (!pmDir)
614
- return;
615
- for (const issue of issues) {
616
- const fullPath = this.validateIssuePath(issue.path, pmDir);
617
- try {
618
- const content = await readFile(fullPath, 'utf-8');
619
- if (content.match(/^status:\s*in_progress$/m)) {
620
- await this.updateIssueFrontMatter(issue.path, issue.status);
621
- }
622
- }
623
- catch (err) {
624
- this.emit('output', { issueId: issue.id, text: `Warning: failed to revert issue status: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
625
- }
626
- }
627
- }
628
- async appendCancellationNote(issue, pmDir, reason) {
629
- const fullPath = this.validateIssuePath(issue.path, pmDir);
630
- try {
631
- let content = await readFile(fullPath, 'utf-8');
632
- const entry = `- Cancelled (${new Date().toISOString().split('T')[0]}): ${reason}`;
633
- if (content.includes('## Activity')) {
634
- content = content.replace(/## Activity/, `## Activity\n${entry}`);
635
- }
636
- else {
637
- content += `\n\n## Activity\n${entry}`;
638
- }
639
- await writeFile(fullPath, content, 'utf-8');
640
- }
641
- catch (err) {
642
- this.emit('output', { issueId: issue.id, text: `Warning: failed to append cancellation note: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
643
- }
644
- }
645
- async updateIssueFrontMatter(issuePath, newStatus) {
646
- const pmDir = this.pmDir;
647
- if (!pmDir)
648
- return;
649
- try {
650
- const fullPath = this.validateIssuePath(issuePath, pmDir);
651
- await setFrontMatterFieldAsync(fullPath, 'status', newStatus);
652
- if (newStatus === 'done') {
653
- const content = await readFile(fullPath, 'utf-8');
654
- const updated = checkAllAcceptanceCriteria(content);
655
- if (updated !== content)
656
- await writeFile(fullPath, updated, 'utf-8');
657
- }
658
- }
659
- catch (err) {
660
- this.emit('output', { issueId: 'system', text: `Warning: failed to update issue front matter for ${issuePath}: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
661
- }
662
- }
663
- async ensureOutputDirs() {
664
- if (this.boardDir) {
665
- const boardOutDir = join(this.boardDir, 'out');
666
- if (!existsSync(boardOutDir))
667
- await mkdir(boardOutDir, { recursive: true });
668
- }
669
- else {
670
- const pmDir = this.pmDir;
671
- if (pmDir) {
672
- const outDir = join(pmDir, 'out');
673
- if (!existsSync(outDir))
674
- await mkdir(outDir, { recursive: true });
675
- }
676
- }
677
- }
678
- async appendProgressEntry(issues, waveStart) {
679
- const pmDir = this.pmDir;
680
- if (!pmDir)
681
- return;
682
- const progressPath = this.boardDir
683
- ? join(this.boardDir, 'progress.md')
684
- : join(pmDir, 'progress.md');
685
- const durationMin = Math.round((Date.now() - waveStart) / 60_000);
686
- const timestamp = new Date().toISOString().replace('T', ' ').slice(0, 16);
687
- const completed = [];
688
- const failed = [];
689
- for (const issue of issues) {
690
- try {
691
- const content = await readFile(this.validateIssuePath(issue.path, pmDir), 'utf-8');
692
- const statusMatch = content.match(/^status:\s*(\S+)/m);
693
- if (statusMatch?.[1] === 'done') {
694
- completed.push(issue.id);
695
- }
696
- else {
697
- failed.push(issue.id);
698
- }
699
- }
700
- catch {
701
- failed.push(issue.id);
702
- }
703
- }
704
- const lines = [
705
- '',
706
- `## ${timestamp} — Wave [${issues.map(i => i.id).join(', ')}]`,
707
- '',
708
- `- **Duration**: ${durationMin} min`,
709
- `- **Completed**: ${completed.length}/${issues.length}${completed.length > 0 ? ` (${completed.join(', ')})` : ''}`,
710
- ];
711
- if (failed.length > 0) {
712
- lines.push(`- **Failed**: ${failed.join(', ')}`);
713
- }
714
- lines.push('');
715
- await this.writeProgressLines(progressPath, lines);
716
- }
717
- async writeProgressLines(filePath, lines) {
718
- try {
719
- if (existsSync(filePath)) {
720
- await appendFile(filePath, `\n${lines.join('\n')}`, 'utf-8');
721
- }
722
- else {
723
- await writeFile(filePath, `# Board Progress\n${lines.join('\n')}`, 'utf-8');
724
- }
725
- }
726
- catch (err) {
727
- this.emit('output', { issueId: 'system', text: `Warning: failed to write progress log: ${err instanceof Error ? err.message : String(err)}`, boardId: this.boardId ?? null });
728
- }
729
- }
730
- /** Resolve the active board's directory path for outputs, reviews, and progress. */
731
- resolveBoardDir() {
732
- const pmDir = this.pmDir;
733
- if (!pmDir)
734
- return null;
735
- const effectiveBoardId = this.boardId ?? this.resolveActiveBoardId();
736
- if (!effectiveBoardId)
737
- return null;
738
- const boardDir = join(pmDir, 'boards', effectiveBoardId);
739
- return existsSync(boardDir) ? boardDir : null;
468
+ /**
469
+ * Load issues for the active execution scope. Returns the resolved boardId
470
+ * alongside the issues so callers can branch on board-specific logic without
471
+ * re-resolving the scope.
472
+ */
473
+ async loadScopedIssues(pmDir) {
474
+ const boardId = this.effectiveBoardId();
475
+ const issues = boardId
476
+ ? await loadBoardIssues(pmDir, boardId, {
477
+ onError: msg => this.emit('error', msg),
478
+ warn: this.emitWarn,
479
+ })
480
+ : loadProjectIssues(this.workingDir, { onError: msg => this.emit('error', msg) });
481
+ return { issues, boardId };
740
482
  }
741
483
  }
484
+ // Re-export for backwards compatibility with modules that imported the constant from here.
485
+ export { DEFAULT_MAX_PARALLEL_AGENTS };
742
486
  //# sourceMappingURL=executor.js.map