mstro-app 0.4.2 → 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 (342) hide show
  1. package/bin/mstro.js +119 -40
  2. package/dist/server/cli/headless/claude-invoker-process.d.ts +11 -0
  3. package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -0
  4. package/dist/server/cli/headless/claude-invoker-process.js +140 -0
  5. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -0
  6. package/dist/server/cli/headless/claude-invoker-stall.d.ts +40 -0
  7. package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -0
  8. package/dist/server/cli/headless/claude-invoker-stall.js +98 -0
  9. package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -0
  10. package/dist/server/cli/headless/claude-invoker-stream.d.ts +44 -0
  11. package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -0
  12. package/dist/server/cli/headless/claude-invoker-stream.js +276 -0
  13. package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -0
  14. package/dist/server/cli/headless/claude-invoker-tools.d.ts +21 -0
  15. package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -0
  16. package/dist/server/cli/headless/claude-invoker-tools.js +137 -0
  17. package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -0
  18. package/dist/server/cli/headless/claude-invoker.d.ts +6 -4
  19. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  20. package/dist/server/cli/headless/claude-invoker.js +10 -804
  21. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  22. package/dist/server/cli/headless/haiku-assessments.d.ts +62 -0
  23. package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -0
  24. package/dist/server/cli/headless/haiku-assessments.js +281 -0
  25. package/dist/server/cli/headless/haiku-assessments.js.map +1 -0
  26. package/dist/server/cli/headless/headless-logger.d.ts +3 -2
  27. package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
  28. package/dist/server/cli/headless/headless-logger.js +28 -5
  29. package/dist/server/cli/headless/headless-logger.js.map +1 -1
  30. package/dist/server/cli/headless/native-timeout-detector.d.ts +44 -0
  31. package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -0
  32. package/dist/server/cli/headless/native-timeout-detector.js +99 -0
  33. package/dist/server/cli/headless/native-timeout-detector.js.map +1 -0
  34. package/dist/server/cli/headless/stall-assessor.d.ts +2 -110
  35. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  36. package/dist/server/cli/headless/stall-assessor.js +65 -457
  37. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  38. package/dist/server/cli/headless/types.d.ts +4 -1
  39. package/dist/server/cli/headless/types.d.ts.map +1 -1
  40. package/dist/server/cli/improvisation-attachments.d.ts +21 -0
  41. package/dist/server/cli/improvisation-attachments.d.ts.map +1 -0
  42. package/dist/server/cli/improvisation-attachments.js +116 -0
  43. package/dist/server/cli/improvisation-attachments.js.map +1 -0
  44. package/dist/server/cli/improvisation-retry.d.ts +52 -0
  45. package/dist/server/cli/improvisation-retry.d.ts.map +1 -0
  46. package/dist/server/cli/improvisation-retry.js +434 -0
  47. package/dist/server/cli/improvisation-retry.js.map +1 -0
  48. package/dist/server/cli/improvisation-session-manager.d.ts +10 -266
  49. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  50. package/dist/server/cli/improvisation-session-manager.js +117 -1079
  51. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  52. package/dist/server/cli/improvisation-types.d.ts +86 -0
  53. package/dist/server/cli/improvisation-types.d.ts.map +1 -0
  54. package/dist/server/cli/improvisation-types.js +10 -0
  55. package/dist/server/cli/improvisation-types.js.map +1 -0
  56. package/dist/server/cli/prompt-builders.d.ts +68 -0
  57. package/dist/server/cli/prompt-builders.d.ts.map +1 -0
  58. package/dist/server/cli/prompt-builders.js +312 -0
  59. package/dist/server/cli/prompt-builders.js.map +1 -0
  60. package/dist/server/index.js +33 -212
  61. package/dist/server/index.js.map +1 -1
  62. package/dist/server/mcp/bouncer-haiku.d.ts +10 -0
  63. package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -0
  64. package/dist/server/mcp/bouncer-haiku.js +152 -0
  65. package/dist/server/mcp/bouncer-haiku.js.map +1 -0
  66. package/dist/server/mcp/bouncer-integration.d.ts +3 -4
  67. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  68. package/dist/server/mcp/bouncer-integration.js +50 -196
  69. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  70. package/dist/server/mcp/security-analysis.d.ts +38 -0
  71. package/dist/server/mcp/security-analysis.d.ts.map +1 -0
  72. package/dist/server/mcp/security-analysis.js +183 -0
  73. package/dist/server/mcp/security-analysis.js.map +1 -0
  74. package/dist/server/mcp/security-audit.d.ts +1 -1
  75. package/dist/server/mcp/security-audit.d.ts.map +1 -1
  76. package/dist/server/mcp/security-patterns.d.ts +1 -25
  77. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  78. package/dist/server/mcp/security-patterns.js +55 -260
  79. package/dist/server/mcp/security-patterns.js.map +1 -1
  80. package/dist/server/server-setup.d.ts +22 -0
  81. package/dist/server/server-setup.d.ts.map +1 -0
  82. package/dist/server/server-setup.js +101 -0
  83. package/dist/server/server-setup.js.map +1 -0
  84. package/dist/server/services/file-explorer-ops.d.ts +24 -0
  85. package/dist/server/services/file-explorer-ops.d.ts.map +1 -0
  86. package/dist/server/services/file-explorer-ops.js +211 -0
  87. package/dist/server/services/file-explorer-ops.js.map +1 -0
  88. package/dist/server/services/files.d.ts +2 -85
  89. package/dist/server/services/files.d.ts.map +1 -1
  90. package/dist/server/services/files.js +7 -427
  91. package/dist/server/services/files.js.map +1 -1
  92. package/dist/server/services/plan/composer.d.ts +1 -1
  93. package/dist/server/services/plan/composer.d.ts.map +1 -1
  94. package/dist/server/services/plan/composer.js +118 -32
  95. package/dist/server/services/plan/composer.js.map +1 -1
  96. package/dist/server/services/plan/config-installer.d.ts +25 -0
  97. package/dist/server/services/plan/config-installer.d.ts.map +1 -0
  98. package/dist/server/services/plan/config-installer.js +182 -0
  99. package/dist/server/services/plan/config-installer.js.map +1 -0
  100. package/dist/server/services/plan/dependency-resolver.d.ts +1 -1
  101. package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
  102. package/dist/server/services/plan/dependency-resolver.js +4 -1
  103. package/dist/server/services/plan/dependency-resolver.js.map +1 -1
  104. package/dist/server/services/plan/executor.d.ts +38 -74
  105. package/dist/server/services/plan/executor.d.ts.map +1 -1
  106. package/dist/server/services/plan/executor.js +274 -460
  107. package/dist/server/services/plan/executor.js.map +1 -1
  108. package/dist/server/services/plan/front-matter.d.ts +18 -0
  109. package/dist/server/services/plan/front-matter.d.ts.map +1 -0
  110. package/dist/server/services/plan/front-matter.js +44 -0
  111. package/dist/server/services/plan/front-matter.js.map +1 -0
  112. package/dist/server/services/plan/output-manager.d.ts +22 -0
  113. package/dist/server/services/plan/output-manager.d.ts.map +1 -0
  114. package/dist/server/services/plan/output-manager.js +97 -0
  115. package/dist/server/services/plan/output-manager.js.map +1 -0
  116. package/dist/server/services/plan/parser-core.d.ts +20 -0
  117. package/dist/server/services/plan/parser-core.d.ts.map +1 -0
  118. package/dist/server/services/plan/parser-core.js +350 -0
  119. package/dist/server/services/plan/parser-core.js.map +1 -0
  120. package/dist/server/services/plan/parser-migration.d.ts +5 -0
  121. package/dist/server/services/plan/parser-migration.d.ts.map +1 -0
  122. package/dist/server/services/plan/parser-migration.js +124 -0
  123. package/dist/server/services/plan/parser-migration.js.map +1 -0
  124. package/dist/server/services/plan/parser.d.ts +11 -3
  125. package/dist/server/services/plan/parser.d.ts.map +1 -1
  126. package/dist/server/services/plan/parser.js +184 -369
  127. package/dist/server/services/plan/parser.js.map +1 -1
  128. package/dist/server/services/plan/prompt-builder.d.ts +17 -0
  129. package/dist/server/services/plan/prompt-builder.d.ts.map +1 -0
  130. package/dist/server/services/plan/prompt-builder.js +137 -0
  131. package/dist/server/services/plan/prompt-builder.js.map +1 -0
  132. package/dist/server/services/plan/review-gate.d.ts +28 -0
  133. package/dist/server/services/plan/review-gate.d.ts.map +1 -0
  134. package/dist/server/services/plan/review-gate.js +191 -0
  135. package/dist/server/services/plan/review-gate.js.map +1 -0
  136. package/dist/server/services/plan/state-reconciler.d.ts +1 -1
  137. package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
  138. package/dist/server/services/plan/state-reconciler.js +59 -7
  139. package/dist/server/services/plan/state-reconciler.js.map +1 -1
  140. package/dist/server/services/plan/types.d.ts +68 -0
  141. package/dist/server/services/plan/types.d.ts.map +1 -1
  142. package/dist/server/services/platform-credentials.d.ts +24 -0
  143. package/dist/server/services/platform-credentials.d.ts.map +1 -0
  144. package/dist/server/services/platform-credentials.js +68 -0
  145. package/dist/server/services/platform-credentials.js.map +1 -0
  146. package/dist/server/services/platform.d.ts +1 -31
  147. package/dist/server/services/platform.d.ts.map +1 -1
  148. package/dist/server/services/platform.js +11 -109
  149. package/dist/server/services/platform.js.map +1 -1
  150. package/dist/server/services/terminal/pty-manager.d.ts +7 -97
  151. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  152. package/dist/server/services/terminal/pty-manager.js +53 -266
  153. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  154. package/dist/server/services/terminal/pty-utils.d.ts +57 -0
  155. package/dist/server/services/terminal/pty-utils.d.ts.map +1 -0
  156. package/dist/server/services/terminal/pty-utils.js +141 -0
  157. package/dist/server/services/terminal/pty-utils.js.map +1 -0
  158. package/dist/server/services/websocket/file-definition-handlers.d.ts +4 -0
  159. package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -0
  160. package/dist/server/services/websocket/file-definition-handlers.js +153 -0
  161. package/dist/server/services/websocket/file-definition-handlers.js.map +1 -0
  162. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  163. package/dist/server/services/websocket/file-explorer-handlers.js +52 -391
  164. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  165. package/dist/server/services/websocket/file-search-handlers.d.ts +5 -0
  166. package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -0
  167. package/dist/server/services/websocket/file-search-handlers.js +238 -0
  168. package/dist/server/services/websocket/file-search-handlers.js.map +1 -0
  169. package/dist/server/services/websocket/file-utils.js +3 -3
  170. package/dist/server/services/websocket/file-utils.js.map +1 -1
  171. package/dist/server/services/websocket/git-branch-handlers.d.ts +7 -0
  172. package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -0
  173. package/dist/server/services/websocket/git-branch-handlers.js +110 -0
  174. package/dist/server/services/websocket/git-branch-handlers.js.map +1 -0
  175. package/dist/server/services/websocket/git-diff-handlers.d.ts +6 -0
  176. package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -0
  177. package/dist/server/services/websocket/git-diff-handlers.js +123 -0
  178. package/dist/server/services/websocket/git-diff-handlers.js.map +1 -0
  179. package/dist/server/services/websocket/git-handlers.d.ts +2 -31
  180. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  181. package/dist/server/services/websocket/git-handlers.js +35 -541
  182. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  183. package/dist/server/services/websocket/git-log-handlers.d.ts +6 -0
  184. package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -0
  185. package/dist/server/services/websocket/git-log-handlers.js +128 -0
  186. package/dist/server/services/websocket/git-log-handlers.js.map +1 -0
  187. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
  188. package/dist/server/services/websocket/git-pr-handlers.js +13 -53
  189. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
  190. package/dist/server/services/websocket/git-tag-handlers.d.ts +6 -0
  191. package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -0
  192. package/dist/server/services/websocket/git-tag-handlers.js +76 -0
  193. package/dist/server/services/websocket/git-tag-handlers.js.map +1 -0
  194. package/dist/server/services/websocket/git-utils.d.ts +43 -0
  195. package/dist/server/services/websocket/git-utils.d.ts.map +1 -0
  196. package/dist/server/services/websocket/git-utils.js +201 -0
  197. package/dist/server/services/websocket/git-utils.js.map +1 -0
  198. package/dist/server/services/websocket/handler.d.ts +2 -0
  199. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  200. package/dist/server/services/websocket/handler.js +37 -112
  201. package/dist/server/services/websocket/handler.js.map +1 -1
  202. package/dist/server/services/websocket/plan-board-handlers.d.ts +11 -0
  203. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -0
  204. package/dist/server/services/websocket/plan-board-handlers.js +218 -0
  205. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -0
  206. package/dist/server/services/websocket/plan-execution-handlers.d.ts +9 -0
  207. package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -0
  208. package/dist/server/services/websocket/plan-execution-handlers.js +142 -0
  209. package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -0
  210. package/dist/server/services/websocket/plan-handlers.d.ts +7 -2
  211. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
  212. package/dist/server/services/websocket/plan-handlers.js +21 -462
  213. package/dist/server/services/websocket/plan-handlers.js.map +1 -1
  214. package/dist/server/services/websocket/plan-helpers.d.ts +19 -0
  215. package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -0
  216. package/dist/server/services/websocket/plan-helpers.js +199 -0
  217. package/dist/server/services/websocket/plan-helpers.js.map +1 -0
  218. package/dist/server/services/websocket/plan-issue-handlers.d.ts +12 -0
  219. package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -0
  220. package/dist/server/services/websocket/plan-issue-handlers.js +162 -0
  221. package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -0
  222. package/dist/server/services/websocket/plan-sprint-handlers.d.ts +7 -0
  223. package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -0
  224. package/dist/server/services/websocket/plan-sprint-handlers.js +206 -0
  225. package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -0
  226. package/dist/server/services/websocket/quality-complexity.d.ts +14 -0
  227. package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -0
  228. package/dist/server/services/websocket/quality-complexity.js +262 -0
  229. package/dist/server/services/websocket/quality-complexity.js.map +1 -0
  230. package/dist/server/services/websocket/quality-fix-agent.d.ts +16 -0
  231. package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -0
  232. package/dist/server/services/websocket/quality-fix-agent.js +140 -0
  233. package/dist/server/services/websocket/quality-fix-agent.js.map +1 -0
  234. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  235. package/dist/server/services/websocket/quality-handlers.js +34 -346
  236. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  237. package/dist/server/services/websocket/quality-linting.d.ts +9 -0
  238. package/dist/server/services/websocket/quality-linting.d.ts.map +1 -0
  239. package/dist/server/services/websocket/quality-linting.js +178 -0
  240. package/dist/server/services/websocket/quality-linting.js.map +1 -0
  241. package/dist/server/services/websocket/quality-review-agent.d.ts +19 -0
  242. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -0
  243. package/dist/server/services/websocket/quality-review-agent.js +206 -0
  244. package/dist/server/services/websocket/quality-review-agent.js.map +1 -0
  245. package/dist/server/services/websocket/quality-service.d.ts +3 -51
  246. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  247. package/dist/server/services/websocket/quality-service.js +9 -651
  248. package/dist/server/services/websocket/quality-service.js.map +1 -1
  249. package/dist/server/services/websocket/quality-tools.d.ts +23 -0
  250. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -0
  251. package/dist/server/services/websocket/quality-tools.js +208 -0
  252. package/dist/server/services/websocket/quality-tools.js.map +1 -0
  253. package/dist/server/services/websocket/quality-types.d.ts +59 -0
  254. package/dist/server/services/websocket/quality-types.d.ts.map +1 -0
  255. package/dist/server/services/websocket/quality-types.js +101 -0
  256. package/dist/server/services/websocket/quality-types.js.map +1 -0
  257. package/dist/server/services/websocket/session-handlers.d.ts +3 -4
  258. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  259. package/dist/server/services/websocket/session-handlers.js +3 -378
  260. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  261. package/dist/server/services/websocket/session-history.d.ts +4 -0
  262. package/dist/server/services/websocket/session-history.d.ts.map +1 -0
  263. package/dist/server/services/websocket/session-history.js +208 -0
  264. package/dist/server/services/websocket/session-history.js.map +1 -0
  265. package/dist/server/services/websocket/session-initialization.d.ts +5 -0
  266. package/dist/server/services/websocket/session-initialization.d.ts.map +1 -0
  267. package/dist/server/services/websocket/session-initialization.js +163 -0
  268. package/dist/server/services/websocket/session-initialization.js.map +1 -0
  269. package/dist/server/services/websocket/types.d.ts +12 -2
  270. package/dist/server/services/websocket/types.d.ts.map +1 -1
  271. package/package.json +1 -2
  272. package/server/cli/headless/claude-invoker-process.ts +204 -0
  273. package/server/cli/headless/claude-invoker-stall.ts +164 -0
  274. package/server/cli/headless/claude-invoker-stream.ts +353 -0
  275. package/server/cli/headless/claude-invoker-tools.ts +187 -0
  276. package/server/cli/headless/claude-invoker.ts +15 -1092
  277. package/server/cli/headless/haiku-assessments.ts +365 -0
  278. package/server/cli/headless/headless-logger.ts +26 -5
  279. package/server/cli/headless/native-timeout-detector.ts +117 -0
  280. package/server/cli/headless/stall-assessor.ts +65 -618
  281. package/server/cli/headless/types.ts +4 -1
  282. package/server/cli/improvisation-attachments.ts +148 -0
  283. package/server/cli/improvisation-retry.ts +602 -0
  284. package/server/cli/improvisation-session-manager.ts +140 -1349
  285. package/server/cli/improvisation-types.ts +98 -0
  286. package/server/cli/prompt-builders.ts +370 -0
  287. package/server/index.ts +35 -246
  288. package/server/mcp/bouncer-haiku.ts +182 -0
  289. package/server/mcp/bouncer-integration.ts +87 -248
  290. package/server/mcp/security-analysis.ts +217 -0
  291. package/server/mcp/security-audit.ts +1 -1
  292. package/server/mcp/security-patterns.ts +60 -283
  293. package/server/server-setup.ts +114 -0
  294. package/server/services/file-explorer-ops.ts +293 -0
  295. package/server/services/files.ts +20 -532
  296. package/server/services/plan/composer.ts +140 -35
  297. package/server/services/plan/config-installer.ts +187 -0
  298. package/server/services/plan/dependency-resolver.ts +4 -1
  299. package/server/services/plan/executor.ts +281 -488
  300. package/server/services/plan/front-matter.ts +48 -0
  301. package/server/services/plan/output-manager.ts +113 -0
  302. package/server/services/plan/parser-core.ts +406 -0
  303. package/server/services/plan/parser-migration.ts +128 -0
  304. package/server/services/plan/parser.ts +188 -394
  305. package/server/services/plan/prompt-builder.ts +161 -0
  306. package/server/services/plan/review-gate.ts +212 -0
  307. package/server/services/plan/state-reconciler.ts +68 -7
  308. package/server/services/plan/types.ts +101 -1
  309. package/server/services/platform-credentials.ts +83 -0
  310. package/server/services/platform.ts +16 -131
  311. package/server/services/terminal/pty-manager.ts +66 -313
  312. package/server/services/terminal/pty-utils.ts +176 -0
  313. package/server/services/websocket/file-definition-handlers.ts +165 -0
  314. package/server/services/websocket/file-explorer-handlers.ts +37 -452
  315. package/server/services/websocket/file-search-handlers.ts +291 -0
  316. package/server/services/websocket/file-utils.ts +3 -3
  317. package/server/services/websocket/git-branch-handlers.ts +130 -0
  318. package/server/services/websocket/git-diff-handlers.ts +140 -0
  319. package/server/services/websocket/git-handlers.ts +40 -625
  320. package/server/services/websocket/git-log-handlers.ts +149 -0
  321. package/server/services/websocket/git-pr-handlers.ts +17 -62
  322. package/server/services/websocket/git-tag-handlers.ts +91 -0
  323. package/server/services/websocket/git-utils.ts +230 -0
  324. package/server/services/websocket/handler.ts +39 -112
  325. package/server/services/websocket/plan-board-handlers.ts +277 -0
  326. package/server/services/websocket/plan-execution-handlers.ts +184 -0
  327. package/server/services/websocket/plan-handlers.ts +23 -544
  328. package/server/services/websocket/plan-helpers.ts +215 -0
  329. package/server/services/websocket/plan-issue-handlers.ts +204 -0
  330. package/server/services/websocket/plan-sprint-handlers.ts +252 -0
  331. package/server/services/websocket/quality-complexity.ts +294 -0
  332. package/server/services/websocket/quality-fix-agent.ts +181 -0
  333. package/server/services/websocket/quality-handlers.ts +36 -404
  334. package/server/services/websocket/quality-linting.ts +187 -0
  335. package/server/services/websocket/quality-review-agent.ts +246 -0
  336. package/server/services/websocket/quality-service.ts +11 -762
  337. package/server/services/websocket/quality-tools.ts +209 -0
  338. package/server/services/websocket/quality-types.ts +169 -0
  339. package/server/services/websocket/session-handlers.ts +5 -437
  340. package/server/services/websocket/session-history.ts +222 -0
  341. package/server/services/websocket/session-initialization.ts +209 -0
  342. package/server/services/websocket/types.ts +46 -2
@@ -2,464 +2,190 @@
2
2
  // Licensed under the MIT License. See LICENSE file for details.
3
3
 
4
4
  /**
5
- * PPS Parser — Parses .pm/ (or legacy .plan/) directory files into structured TypeScript objects.
5
+ * PPS Parser — Public API for reading .pm/ (or legacy .plan/) directories.
6
6
  *
7
- * Handles YAML front matter extraction and markdown body parsing.
7
+ * Entity parsing lives in parser-core.ts; migration in parser-migration.ts.
8
8
  */
9
9
 
10
10
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
11
11
  import { join } from 'node:path';
12
+ import { parseBoard, parseIssue, parseMilestone, parseProjectConfig, parseProjectState, parseSprint, parseWorkspace } from './parser-core.js';
13
+ import { isLegacyFormat, migrateToBoards } from './parser-migration.js';
12
14
  import type {
13
- AcceptanceCriterion,
15
+ Board,
16
+ BoardArtifacts,
17
+ BoardFullState,
14
18
  Issue,
15
- IssueSummary,
16
19
  Milestone,
17
- MilestoneEpicSummary,
18
20
  PlanFullState,
19
21
  ProjectConfig,
20
22
  ProjectState,
23
+ ReviewResult,
21
24
  Sprint,
22
- SprintIssueSummary,
23
- Team,
24
- WorkflowStatus,
25
+ SprintArtifacts,
26
+ Workspace,
25
27
  } from './types.js';
26
28
 
27
29
  // ============================================================================
28
- // Front Matter Extraction
30
+ // Directory Resolution
29
31
  // ============================================================================
30
32
 
31
- interface ParsedFile {
32
- frontMatter: Record<string, unknown>;
33
- body: string;
33
+ export function isBoardCentricFormat(pmDir: string): boolean {
34
+ return existsSync(join(pmDir, 'boards'));
34
35
  }
35
36
 
36
- function stripQuotes(v: string): string {
37
- if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
38
- return v.slice(1, -1);
39
- }
40
- return v;
41
- }
42
-
43
- function parseYamlValue(v: string): unknown {
44
- if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
45
- return v.slice(1, -1);
46
- }
47
- if (v.startsWith('[') && v.endsWith(']')) {
48
- return v.slice(1, -1).split(',').map(s => stripQuotes(s.trim())).filter(Boolean);
49
- }
50
- if (v === 'true') return true;
51
- if (v === 'false') return false;
52
- if (v === 'null' || v === '~' || v === '') return null;
53
- if (/^-?\d+(\.\d+)?$/.test(v)) return Number(v);
54
- return v;
37
+ export function resolvePmDir(workingDir: string): string | null {
38
+ const mstroPmDir = join(workingDir, '.mstro', 'pm');
39
+ if (existsSync(mstroPmDir)) return mstroPmDir;
40
+ const legacyPmDir = join(workingDir, '.pm');
41
+ if (existsSync(legacyPmDir)) return legacyPmDir;
42
+ const legacyPlanDir = join(workingDir, '.plan');
43
+ if (existsSync(legacyPlanDir)) return legacyPlanDir;
44
+ return null;
55
45
  }
56
46
 
57
- /** Consume indented YAML list items starting after the current index. Returns [items, newIndex]. */
58
- function consumeIndentedList(lines: string[], startIdx: number): [string[], number] {
59
- const items: string[] = [];
60
- let i = startIdx;
61
- while (i + 1 < lines.length && /^\s+-\s/.test(lines[i + 1])) {
62
- i++;
63
- const item = lines[i].trim().replace(/^-\s+/, '');
64
- items.push(stripQuotes(item));
65
- }
66
- return [items, i];
47
+ export function defaultPmDir(workingDir: string): string {
48
+ return join(workingDir, '.mstro', 'pm');
67
49
  }
68
50
 
69
- function parseFrontMatter(content: string): ParsedFile {
70
- const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
71
- if (!match) {
72
- return { frontMatter: {}, body: content };
73
- }
74
- const frontMatter: Record<string, unknown> = {};
75
- const lines = match[1].split('\n');
76
-
77
- for (let i = 0; i < lines.length; i++) {
78
- const trimmed = lines[i].trim();
79
- if (!trimmed || trimmed.startsWith('#')) continue;
80
- const colonIdx = trimmed.indexOf(':');
81
- if (colonIdx === -1) continue;
82
-
83
- const key = trimmed.slice(0, colonIdx).trim();
84
- const rawValue = trimmed.slice(colonIdx + 1).trim();
85
-
86
- if (!rawValue) {
87
- const [items, newIdx] = consumeIndentedList(lines, i);
88
- i = newIdx;
89
- frontMatter[key] = items.length > 0 ? items : null;
90
- } else {
91
- frontMatter[key] = parseYamlValue(rawValue);
92
- }
93
- }
94
-
95
- return { frontMatter, body: match[2] };
51
+ export function planDirExists(workingDir: string): boolean {
52
+ return resolvePmDir(workingDir) !== null;
96
53
  }
97
54
 
98
55
  // ============================================================================
99
- // Section Extraction
56
+ // File Utilities
100
57
  // ============================================================================
101
58
 
102
- function extractSections(body: string): Map<string, string> {
103
- const sections = new Map<string, string>();
104
- const lines = body.split('\n');
105
- let currentSection = '';
106
- let currentContent: string[] = [];
107
-
108
- for (const line of lines) {
109
- if (line.startsWith('## ')) {
110
- if (currentSection) {
111
- sections.set(currentSection, currentContent.join('\n').trim());
112
- }
113
- currentSection = line.slice(3).trim();
114
- currentContent = [];
115
- } else {
116
- currentContent.push(line);
117
- }
118
- }
119
- if (currentSection) {
120
- sections.set(currentSection, currentContent.join('\n').trim());
121
- }
122
-
123
- return sections;
124
- }
125
-
126
- function parseCheckboxes(content: string): AcceptanceCriterion[] {
127
- const items: AcceptanceCriterion[] = [];
128
- for (const line of content.split('\n')) {
129
- const match = line.match(/^[-*]\s+\[([ xX])\]\s+(.+)$/);
130
- if (match) {
131
- items.push({ text: match[2].trim(), checked: match[1] !== ' ' });
132
- }
133
- }
134
- return items;
59
+ function readFileIfExists(path: string): string | null {
60
+ try {
61
+ if (existsSync(path)) return readFileSync(path, 'utf-8');
62
+ } catch { /* skip */ }
63
+ return null;
135
64
  }
136
65
 
137
- function parseListItems(content: string): string[] {
138
- const items: string[] = [];
139
- for (const line of content.split('\n')) {
140
- const match = line.match(/^[-*]\s+(.+)$/);
141
- if (match) items.push(match[1].trim());
142
- }
143
- return items;
66
+ function readMdFilesInDir(dirPath: string): Array<{ name: string; content: string }> {
67
+ if (!existsSync(dirPath)) return [];
68
+ try {
69
+ return readdirSync(dirPath)
70
+ .filter(f => f.endsWith('.md'))
71
+ .map(name => ({ name, content: readFileSync(join(dirPath, name), 'utf-8') }));
72
+ } catch { return []; }
144
73
  }
145
74
 
146
- function parseIssueSummaries(content: string): IssueSummary[] {
147
- const summaries: IssueSummary[] = [];
148
- for (const line of content.split('\n')) {
149
- // Match: 1. [IS-003](backlog/IS-003.md) Title (P1)
150
- const match = line.match(/\d+\.\s+\[([^\]]+)\]\(([^)]+)\)\s*[—–-]\s*(.+?)(?:\s*\((\w+)\))?\s*$/);
151
- if (match) {
152
- summaries.push({
153
- id: match[1],
154
- path: match[2],
155
- title: match[3].trim(),
156
- priority: match[4] || '',
157
- });
158
- continue;
159
- }
160
- // Match: - [IS-001](backlog/IS-001.md) — Title
161
- const match2 = line.match(/^[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*[—–-]\s*(.+?)(?:\s*[→→]\s*blocked by\s+\[([^\]]+)\])?\s*$/i);
162
- if (match2) {
163
- summaries.push({
164
- id: match2[1],
165
- path: match2[2],
166
- title: match2[3].trim(),
167
- priority: '',
168
- blockedBy: match2[4] || undefined,
169
- });
170
- }
171
- }
172
- return summaries;
75
+ function listDirFiles(dirPath: string, ext: string): string[] {
76
+ if (!existsSync(dirPath)) return [];
77
+ try {
78
+ return readdirSync(dirPath).filter(f => f.endsWith(ext));
79
+ } catch { return []; }
173
80
  }
174
81
 
175
- function parseCompletedSummaries(content: string): IssueSummary[] {
176
- const summaries: IssueSummary[] = [];
177
- for (const line of content.split('\n')) {
178
- const match = line.match(/^[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*[—–-]\s*(.+?)(?:\s*✓)?\s*$/);
179
- if (match) {
180
- summaries.push({
181
- id: match[1],
182
- path: match[2],
183
- title: match[3].trim(),
184
- priority: '',
185
- });
186
- }
82
+ function readReviewResults(reviewsDir: string): ReviewResult[] {
83
+ const results: ReviewResult[] = [];
84
+ for (const f of listDirFiles(reviewsDir, '.json')) {
85
+ const content = readFileIfExists(join(reviewsDir, f));
86
+ if (content) results.push(JSON.parse(content) as ReviewResult);
187
87
  }
188
- return summaries;
88
+ return results;
189
89
  }
190
90
 
191
91
  // ============================================================================
192
- // Entity Parsers
92
+ // Defaults
193
93
  // ============================================================================
194
94
 
195
- function parseWorkflows(section: string | undefined): WorkflowStatus[] {
196
- if (!section) return [];
197
- const workflows: WorkflowStatus[] = [];
198
- for (const line of section.split('\n')) {
199
- const match = line.match(/\|\s*(\w+)\s*\|\s*(\w+)\s*\|\s*(.+?)\s*\|/);
200
- if (match && match[1] !== 'Status') {
201
- workflows.push({
202
- status: match[1],
203
- category: match[2] as WorkflowStatus['category'],
204
- description: match[3].trim(),
205
- });
206
- }
207
- }
208
- return workflows;
209
- }
210
-
211
- function parseTeams(section: string | undefined): Team[] {
212
- if (!section) return [];
213
- const teams: Team[] = [];
214
- for (const line of section.split('\n')) {
215
- const match = line.match(/^[-*]\s+(\w+)(?:\s*[—–-]\s*(.+))?$/);
216
- if (match) teams.push({ name: match[1], description: match[2]?.trim() });
217
- }
218
- return teams;
219
- }
95
+ const defaultProject: ProjectConfig = {
96
+ name: '', id: '', created: '', status: 'active', estimation: 'none',
97
+ idPrefixes: {}, workflows: [], labels: [], teams: [],
98
+ };
220
99
 
221
- function parseProjectConfig(content: string): ProjectConfig {
222
- const { frontMatter, body } = parseFrontMatter(content);
223
- const sections = extractSections(body);
100
+ const defaultState: ProjectState = {
101
+ project: '', currentSprint: null, activeMilestone: null, paused: false,
102
+ lastSession: null, readyToWork: [], inProgress: [], blocked: [],
103
+ recentlyCompleted: [], warnings: [],
104
+ };
224
105
 
225
- const idPrefixes: Record<string, string> = {};
226
- const rawPrefixes = frontMatter.id_prefixes;
227
- if (rawPrefixes && typeof rawPrefixes === 'object') {
228
- Object.assign(idPrefixes, rawPrefixes);
229
- }
106
+ // ============================================================================
107
+ // Board & Plan Directory Parsing
108
+ // ============================================================================
230
109
 
231
- return {
232
- name: String(frontMatter.name || ''),
233
- id: String(frontMatter.id || ''),
234
- created: String(frontMatter.created || ''),
235
- status: (frontMatter.status as ProjectConfig['status']) || 'active',
236
- estimation: (frontMatter.estimation as ProjectConfig['estimation']) || 'none',
237
- idPrefixes,
238
- workflows: parseWorkflows(sections.get('Workflows')),
239
- labels: (Array.isArray(frontMatter.labels) ? frontMatter.labels : []) as string[],
240
- teams: parseTeams(sections.get('Teams')),
241
- };
242
- }
110
+ export function parseBoardDirectory(pmDir: string, boardId: string): BoardFullState | null {
111
+ const boardDir = join(pmDir, 'boards', boardId);
112
+ if (!existsSync(boardDir)) return null;
243
113
 
244
- function parseProjectState(content: string): ProjectState {
245
- const { frontMatter, body } = parseFrontMatter(content);
246
- const sections = extractSections(body);
114
+ const boardContent = readFileIfExists(join(boardDir, 'board.md'));
115
+ if (!boardContent) return null;
116
+ const board = parseBoard(boardContent, `boards/${boardId}/board.md`);
247
117
 
248
- return {
249
- project: String(frontMatter.project || ''),
250
- currentSprint: (frontMatter.current_sprint as string) || null,
251
- activeMilestone: (frontMatter.active_milestone as string) || null,
252
- paused: frontMatter.paused === true,
253
- lastSession: (frontMatter.last_session as string) || null,
254
- readyToWork: parseIssueSummaries(sections.get('Ready to Work') || ''),
255
- inProgress: parseIssueSummaries(sections.get('In Progress') || ''),
256
- blocked: parseIssueSummaries(sections.get('Blocked') || ''),
257
- recentlyCompleted: parseCompletedSummaries(sections.get('Recently Completed') || ''),
258
- warnings: parseListItems(sections.get('Warnings') || ''),
259
- };
260
- }
118
+ const stateContent = readFileIfExists(join(boardDir, 'STATE.md'));
119
+ const state = stateContent ? parseProjectState(stateContent) : { ...defaultState };
261
120
 
262
- function toStringArray(val: unknown): string[] {
263
- return Array.isArray(val) ? val.map(String) : [];
264
- }
121
+ const issueFiles = readMdFilesInDir(join(boardDir, 'backlog'));
122
+ const boardPrefix = `boards/${boardId}/`;
123
+ const issues = issueFiles.map(f => {
124
+ const issue = parseIssue(f.content, `${boardPrefix}backlog/${f.name}`);
125
+ issue.blockedBy = issue.blockedBy.map(bp => bp.startsWith('boards/') ? bp : `${boardPrefix}${bp}`);
126
+ issue.blocks = issue.blocks.map(bp => bp.startsWith('boards/') ? bp : `${boardPrefix}${bp}`);
127
+ if (issue.epic && !issue.epic.startsWith('boards/')) issue.epic = `${boardPrefix}${issue.epic}`;
128
+ return issue;
129
+ });
265
130
 
266
- function optionalString(val: unknown): string | null {
267
- if (val == null) return null;
268
- const s = String(val);
269
- return s === '' ? null : s;
131
+ return { board, state, issues };
270
132
  }
271
133
 
272
- function parseIssue(content: string, filePath: string): Issue {
273
- const { frontMatter: fm, body } = parseFrontMatter(content);
274
- const sections = extractSections(body);
275
-
276
- return {
277
- id: String(fm.id || ''),
278
- title: String(fm.title || ''),
279
- type: (fm.type as Issue['type']) || 'issue',
280
- status: String(fm.status || 'backlog'),
281
- priority: String(fm.priority || 'P2'),
282
- estimate: fm.estimate != null ? fm.estimate as number | string : null,
283
- labels: toStringArray(fm.labels),
284
- epic: optionalString(fm.epic),
285
- sprint: optionalString(fm.sprint),
286
- milestone: optionalString(fm.milestone),
287
- assigned: optionalString(fm.assigned),
288
- created: String(fm.created || ''),
289
- updated: optionalString(fm.updated),
290
- due: optionalString(fm.due),
291
- blockedBy: toStringArray(fm.blocked_by),
292
- blocks: toStringArray(fm.blocks),
293
- relatesTo: toStringArray(fm.relates_to),
294
- children: toStringArray(fm.children),
295
- progress: optionalString(fm.progress),
296
- description: sections.get('Description') || '',
297
- acceptanceCriteria: parseCheckboxes(sections.get('Acceptance Criteria') || ''),
298
- technicalNotes: sections.get('Technical Notes') || null,
299
- filesToModify: parseListItems(sections.get('Files to Modify') || ''),
300
- activity: parseListItems(sections.get('Activity') || ''),
301
- outputFile: optionalString(fm.output_file),
302
- body,
303
- path: filePath,
304
- };
305
- }
306
-
307
- function parseSprintIssues(section: string | undefined): SprintIssueSummary[] {
308
- if (!section) return [];
309
- const issues: SprintIssueSummary[] = [];
310
- for (const line of section.split('\n')) {
311
- const match = line.match(/\|\s*\[([^\]]+)\]\(([^)]+)\)\s*\|\s*(.+?)\s*\|\s*(\S+)\s*\|\s*(\S+)\s*\|/);
312
- if (match) {
313
- issues.push({
314
- id: match[1],
315
- path: match[2],
316
- title: match[3].trim(),
317
- points: /^\d+$/.test(match[4]) ? Number(match[4]) : match[4],
318
- status: match[5],
319
- });
134
+ function parseBoardCentricState(planDir: string): { boards: Board[]; workspace: Workspace; activeBoard: BoardFullState | null } {
135
+ const workspaceContent = readFileIfExists(join(planDir, 'workspace.json'));
136
+ const workspace = workspaceContent ? parseWorkspace(workspaceContent) : { activeBoardId: null, boardOrder: [] };
137
+
138
+ const boards: Board[] = [];
139
+ const boardsDir = join(planDir, 'boards');
140
+ if (existsSync(boardsDir)) {
141
+ for (const entry of readdirSync(boardsDir)) {
142
+ const boardMdPath = join(boardsDir, entry, 'board.md');
143
+ if (!existsSync(boardMdPath)) continue;
144
+ boards.push(parseBoard(readFileSync(boardMdPath, 'utf-8'), `boards/${entry}/board.md`));
320
145
  }
321
146
  }
322
- return issues;
323
- }
324
147
 
325
- function optionalNumber(val: unknown): number | null {
326
- return val != null ? Number(val) : null;
327
- }
148
+ const orderMap = new Map(workspace.boardOrder.map((id, i) => [id, i]));
149
+ boards.sort((a, b) => (orderMap.get(a.id) ?? 999) - (orderMap.get(b.id) ?? 999));
328
150
 
329
- function parseSprint(content: string, filePath: string): Sprint {
330
- const { frontMatter: fm, body } = parseFrontMatter(content);
331
- const sections = extractSections(body);
151
+ const activeBoard = workspace.activeBoardId ? parseBoardDirectory(planDir, workspace.activeBoardId) : null;
152
+ return { boards, workspace, activeBoard };
153
+ }
332
154
 
333
- // Table-based parsing (markdown links in table rows)
334
- let issues = parseSprintIssues(sections.get('Issues'));
155
+ export function parsePlanDirectory(workingDir: string): PlanFullState | null {
156
+ const planDir = resolvePmDir(workingDir);
157
+ if (!planDir) return null;
335
158
 
336
- // Fallback: front matter issues array (e.g., ["backlog/IS-001.md", ...])
337
- if (issues.length === 0 && Array.isArray(fm.issues)) {
338
- issues = (fm.issues as string[]).map(path => {
339
- const id = path.replace(/^backlog\//, '').replace(/\.md$/, '');
340
- return { id, path, title: '', points: null, status: '' };
341
- });
342
- }
159
+ if (isLegacyFormat(planDir)) migrateToBoards(planDir);
343
160
 
344
- return {
345
- id: String(fm.id || ''),
346
- title: String(fm.title || ''),
347
- status: (fm.status as Sprint['status']) || 'planned',
348
- start: String(fm.start || fm.start_date || ''),
349
- end: String(fm.end || fm.end_date || ''),
350
- goal: String(fm.goal || sections.get('Goal') || sections.get('Sprint Goal') || ''),
351
- capacity: optionalNumber(fm.capacity),
352
- committed: optionalNumber(fm.committed),
353
- completed: optionalNumber(fm.completed),
354
- issues,
355
- path: filePath,
356
- };
357
- }
161
+ const projectContent = readFileIfExists(join(planDir, 'project.md'));
162
+ const project = projectContent ? parseProjectConfig(projectContent) : { ...defaultProject };
163
+ const milestoneFiles = readMdFilesInDir(join(planDir, 'milestones'));
164
+ const milestones = milestoneFiles.map(f => parseMilestone(f.content, `milestones/${f.name}`));
358
165
 
359
- function parseMilestone(content: string, filePath: string): Milestone {
360
- const { frontMatter, body } = parseFrontMatter(content);
361
- const sections = extractSections(body);
362
-
363
- const epics: MilestoneEpicSummary[] = [];
364
- const epicSection = sections.get('Epics');
365
- if (epicSection) {
366
- for (const line of epicSection.split('\n')) {
367
- const match = line.match(/\|\s*\[([^\]]+)\]\(([^)]+)\)\s*\|\s*(.+?)\s*\|\s*(\S+)\s*\|/);
368
- if (match) {
369
- epics.push({
370
- id: match[1],
371
- path: match[2],
372
- title: match[3].trim(),
373
- progress: match[4],
374
- });
375
- }
376
- }
166
+ if (!isBoardCentricFormat(planDir)) {
167
+ return {
168
+ project, state: { ...defaultState }, boards: [], workspace: { activeBoardId: null, boardOrder: [] },
169
+ activeBoard: null, issues: [], sprints: [], milestones,
170
+ };
377
171
  }
378
172
 
173
+ const { boards, workspace, activeBoard } = parseBoardCentricState(planDir);
379
174
  return {
380
- id: String(frontMatter.id || ''),
381
- title: String(frontMatter.title || ''),
382
- status: (frontMatter.status as Milestone['status']) || 'planned',
383
- targetDate: (frontMatter.target_date as string) || null,
384
- progress: (frontMatter.progress as string) || null,
385
- definition: sections.get('Definition of Done') || '',
386
- epics,
387
- path: filePath,
175
+ project, state: activeBoard?.state ?? { ...defaultState },
176
+ boards, workspace, activeBoard, issues: activeBoard?.issues ?? [],
177
+ sprints: [], milestones,
388
178
  };
389
179
  }
390
180
 
391
181
  // ============================================================================
392
- // Directory Parser
182
+ // Single Entity Parsers
393
183
  // ============================================================================
394
184
 
395
- /** Resolve the PM directory — prefers .pm/, falls back to legacy .plan/ */
396
- export function resolvePmDir(workingDir: string): string | null {
397
- const pmDir = join(workingDir, '.pm');
398
- if (existsSync(pmDir)) return pmDir;
399
- const legacyDir = join(workingDir, '.plan');
400
- if (existsSync(legacyDir)) return legacyDir;
401
- return null;
402
- }
403
-
404
- export function planDirExists(workingDir: string): boolean {
405
- return resolvePmDir(workingDir) !== null;
406
- }
407
-
408
- function readFileIfExists(path: string): string | null {
409
- try {
410
- if (existsSync(path)) return readFileSync(path, 'utf-8');
411
- } catch { /* skip */ }
412
- return null;
413
- }
414
-
415
- function readMdFilesInDir(dirPath: string): Array<{ name: string; content: string }> {
416
- if (!existsSync(dirPath)) return [];
417
- try {
418
- return readdirSync(dirPath)
419
- .filter(f => f.endsWith('.md'))
420
- .map(name => {
421
- const content = readFileSync(join(dirPath, name), 'utf-8');
422
- return { name, content };
423
- });
424
- } catch { return []; }
425
- }
426
-
427
- export function parsePlanDirectory(workingDir: string): PlanFullState | null {
428
- const planDir = resolvePmDir(workingDir);
429
- if (!planDir) return null;
430
-
431
- // Parse project.md
432
- const projectContent = readFileIfExists(join(planDir, 'project.md'));
433
- const project = projectContent
434
- ? parseProjectConfig(projectContent)
435
- : { name: '', id: '', created: '', status: 'active' as const, estimation: 'none' as const, idPrefixes: {}, workflows: [], labels: [], teams: [] };
436
-
437
- // Parse STATE.md
438
- const stateContent = readFileIfExists(join(planDir, 'STATE.md'));
439
- const state = stateContent
440
- ? parseProjectState(stateContent)
441
- : { project: '', currentSprint: null, activeMilestone: null, paused: false, lastSession: null, readyToWork: [], inProgress: [], blocked: [], recentlyCompleted: [], warnings: [] };
442
-
443
- // Parse backlog issues
444
- const issueFiles = readMdFilesInDir(join(planDir, 'backlog'));
445
- const issues = issueFiles.map(f => parseIssue(f.content, `backlog/${f.name}`));
446
-
447
- // Parse sprints
448
- const sprintFiles = readMdFilesInDir(join(planDir, 'sprints'));
449
- const sprints = sprintFiles.map(f => parseSprint(f.content, `sprints/${f.name}`));
450
-
451
- // Parse milestones
452
- const milestoneFiles = readMdFilesInDir(join(planDir, 'milestones'));
453
- const milestones = milestoneFiles.map(f => parseMilestone(f.content, `milestones/${f.name}`));
454
-
455
- return { project, state, issues, sprints, milestones };
456
- }
457
-
458
185
  export function parseSingleIssue(workingDir: string, issuePath: string): Issue | null {
459
186
  const pmDir = resolvePmDir(workingDir);
460
187
  if (!pmDir) return null;
461
- const fullPath = join(pmDir, issuePath);
462
- const content = readFileIfExists(fullPath);
188
+ const content = readFileIfExists(join(pmDir, issuePath));
463
189
  if (!content) return null;
464
190
  return parseIssue(content, issuePath);
465
191
  }
@@ -467,8 +193,7 @@ export function parseSingleIssue(workingDir: string, issuePath: string): Issue |
467
193
  export function parseSingleSprint(workingDir: string, sprintPath: string): Sprint | null {
468
194
  const pmDir = resolvePmDir(workingDir);
469
195
  if (!pmDir) return null;
470
- const fullPath = join(pmDir, sprintPath);
471
- const content = readFileIfExists(fullPath);
196
+ const content = readFileIfExists(join(pmDir, sprintPath));
472
197
  if (!content) return null;
473
198
  return parseSprint(content, sprintPath);
474
199
  }
@@ -476,13 +201,15 @@ export function parseSingleSprint(workingDir: string, sprintPath: string): Sprin
476
201
  export function parseSingleMilestone(workingDir: string, milestonePath: string): Milestone | null {
477
202
  const pmDir = resolvePmDir(workingDir);
478
203
  if (!pmDir) return null;
479
- const fullPath = join(pmDir, milestonePath);
480
- const content = readFileIfExists(fullPath);
204
+ const content = readFileIfExists(join(pmDir, milestonePath));
481
205
  if (!content) return null;
482
206
  return parseMilestone(content, milestonePath);
483
207
  }
484
208
 
485
- /** Compute the next available ID for a given prefix (e.g., "IS" → "IS-004") */
209
+ // ============================================================================
210
+ // ID Generation
211
+ // ============================================================================
212
+
486
213
  export function getNextId(issues: Issue[], prefix: string): string {
487
214
  const escaped = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
488
215
  const pattern = new RegExp(`^${escaped}-(\\d+)$`);
@@ -496,3 +223,70 @@ export function getNextId(issues: Issue[], prefix: string): string {
496
223
  }
497
224
  return `${prefix}-${String(max + 1).padStart(3, '0')}`;
498
225
  }
226
+
227
+ export function getNextBoardId(boards: Board[]): string {
228
+ let max = 0;
229
+ for (const board of boards) {
230
+ const match = board.id.match(/^BOARD-(\d+)$/);
231
+ if (match) {
232
+ const num = Number.parseInt(match[1], 10);
233
+ if (num > max) max = num;
234
+ }
235
+ }
236
+ return `BOARD-${String(max + 1).padStart(3, '0')}`;
237
+ }
238
+
239
+ export function getNextBoardNumber(boards: Board[]): number {
240
+ let max = 0;
241
+ for (const board of boards) {
242
+ const match = board.title.match(/^Board (\d+)$/);
243
+ if (match) {
244
+ const num = Number.parseInt(match[1], 10);
245
+ if (num > max) max = num;
246
+ }
247
+ }
248
+ return max + 1;
249
+ }
250
+
251
+ export function parseBoardArtifacts(workingDir: string, boardId: string): BoardArtifacts | null {
252
+ const pmDir = resolvePmDir(workingDir);
253
+ if (!pmDir) return null;
254
+
255
+ const boardDir = join(pmDir, 'boards', boardId);
256
+ if (!existsSync(boardDir)) return null;
257
+
258
+ const progressLog = readFileIfExists(join(boardDir, 'progress.md')) ?? '';
259
+ const outputFiles = listDirFiles(join(boardDir, 'out'), '.md');
260
+ const reviewResults = readReviewResults(join(boardDir, 'reviews'));
261
+ const executionLogs = listDirFiles(join(boardDir, 'logs'), '.log').sort();
262
+
263
+ return { boardId, progressLog, outputFiles, reviewResults, executionLogs };
264
+ }
265
+
266
+ /** @deprecated Use getNextBoardId — kept for migration compatibility */
267
+ export function getNextSprintId(sprints: Sprint[]): string {
268
+ let max = 0;
269
+ for (const sprint of sprints) {
270
+ const match = sprint.id.match(/^SPRINT-(\d+)$/);
271
+ if (match) {
272
+ const num = Number.parseInt(match[1], 10);
273
+ if (num > max) max = num;
274
+ }
275
+ }
276
+ return `SPRINT-${String(max + 1).padStart(3, '0')}`;
277
+ }
278
+
279
+ /** @deprecated Use parseBoardArtifacts — kept for migration compatibility */
280
+ export function parseSprintArtifacts(workingDir: string, sprintId: string): SprintArtifacts | null {
281
+ const pmDir = resolvePmDir(workingDir);
282
+ if (!pmDir) return null;
283
+
284
+ const sandboxDir = join(pmDir, 'sprints', sprintId);
285
+ if (!existsSync(sandboxDir)) return null;
286
+
287
+ const progressLog = readFileIfExists(join(sandboxDir, 'progress.md')) ?? '';
288
+ const outputFiles = listDirFiles(join(sandboxDir, 'out'), '.md');
289
+ const reviewResults = readReviewResults(join(sandboxDir, 'reviews'));
290
+
291
+ return { sprintId, progressLog, outputFiles, reviewResults };
292
+ }