mstro-app 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/dist/server/cli/headless/claude-invoker-process.d.ts +11 -0
  2. package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -0
  3. package/dist/server/cli/headless/claude-invoker-process.js +140 -0
  4. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -0
  5. package/dist/server/cli/headless/claude-invoker-stall.d.ts +40 -0
  6. package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -0
  7. package/dist/server/cli/headless/claude-invoker-stall.js +98 -0
  8. package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -0
  9. package/dist/server/cli/headless/claude-invoker-stream.d.ts +44 -0
  10. package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -0
  11. package/dist/server/cli/headless/claude-invoker-stream.js +276 -0
  12. package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -0
  13. package/dist/server/cli/headless/claude-invoker-tools.d.ts +21 -0
  14. package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -0
  15. package/dist/server/cli/headless/claude-invoker-tools.js +137 -0
  16. package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -0
  17. package/dist/server/cli/headless/claude-invoker.d.ts +6 -4
  18. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  19. package/dist/server/cli/headless/claude-invoker.js +10 -807
  20. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  21. package/dist/server/cli/headless/haiku-assessments.d.ts +62 -0
  22. package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -0
  23. package/dist/server/cli/headless/haiku-assessments.js +281 -0
  24. package/dist/server/cli/headless/haiku-assessments.js.map +1 -0
  25. package/dist/server/cli/headless/headless-logger.d.ts +3 -2
  26. package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
  27. package/dist/server/cli/headless/headless-logger.js +28 -5
  28. package/dist/server/cli/headless/headless-logger.js.map +1 -1
  29. package/dist/server/cli/headless/native-timeout-detector.d.ts +44 -0
  30. package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -0
  31. package/dist/server/cli/headless/native-timeout-detector.js +99 -0
  32. package/dist/server/cli/headless/native-timeout-detector.js.map +1 -0
  33. package/dist/server/cli/headless/stall-assessor.d.ts +2 -110
  34. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  35. package/dist/server/cli/headless/stall-assessor.js +65 -457
  36. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  37. package/dist/server/cli/improvisation-attachments.d.ts +21 -0
  38. package/dist/server/cli/improvisation-attachments.d.ts.map +1 -0
  39. package/dist/server/cli/improvisation-attachments.js +116 -0
  40. package/dist/server/cli/improvisation-attachments.js.map +1 -0
  41. package/dist/server/cli/improvisation-retry.d.ts +52 -0
  42. package/dist/server/cli/improvisation-retry.d.ts.map +1 -0
  43. package/dist/server/cli/improvisation-retry.js +434 -0
  44. package/dist/server/cli/improvisation-retry.js.map +1 -0
  45. package/dist/server/cli/improvisation-session-manager.d.ts +10 -266
  46. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  47. package/dist/server/cli/improvisation-session-manager.js +117 -1079
  48. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  49. package/dist/server/cli/improvisation-types.d.ts +86 -0
  50. package/dist/server/cli/improvisation-types.d.ts.map +1 -0
  51. package/dist/server/cli/improvisation-types.js +10 -0
  52. package/dist/server/cli/improvisation-types.js.map +1 -0
  53. package/dist/server/cli/prompt-builders.d.ts +68 -0
  54. package/dist/server/cli/prompt-builders.d.ts.map +1 -0
  55. package/dist/server/cli/prompt-builders.js +312 -0
  56. package/dist/server/cli/prompt-builders.js.map +1 -0
  57. package/dist/server/index.js +33 -212
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/mcp/bouncer-haiku.d.ts +10 -0
  60. package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -0
  61. package/dist/server/mcp/bouncer-haiku.js +152 -0
  62. package/dist/server/mcp/bouncer-haiku.js.map +1 -0
  63. package/dist/server/mcp/bouncer-integration.d.ts +3 -4
  64. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  65. package/dist/server/mcp/bouncer-integration.js +50 -196
  66. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  67. package/dist/server/mcp/security-analysis.d.ts +38 -0
  68. package/dist/server/mcp/security-analysis.d.ts.map +1 -0
  69. package/dist/server/mcp/security-analysis.js +183 -0
  70. package/dist/server/mcp/security-analysis.js.map +1 -0
  71. package/dist/server/mcp/security-audit.d.ts +1 -1
  72. package/dist/server/mcp/security-audit.d.ts.map +1 -1
  73. package/dist/server/mcp/security-patterns.d.ts +1 -25
  74. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  75. package/dist/server/mcp/security-patterns.js +55 -260
  76. package/dist/server/mcp/security-patterns.js.map +1 -1
  77. package/dist/server/server-setup.d.ts +22 -0
  78. package/dist/server/server-setup.d.ts.map +1 -0
  79. package/dist/server/server-setup.js +101 -0
  80. package/dist/server/server-setup.js.map +1 -0
  81. package/dist/server/services/file-explorer-ops.d.ts +24 -0
  82. package/dist/server/services/file-explorer-ops.d.ts.map +1 -0
  83. package/dist/server/services/file-explorer-ops.js +211 -0
  84. package/dist/server/services/file-explorer-ops.js.map +1 -0
  85. package/dist/server/services/files.d.ts +2 -85
  86. package/dist/server/services/files.d.ts.map +1 -1
  87. package/dist/server/services/files.js +7 -427
  88. package/dist/server/services/files.js.map +1 -1
  89. package/dist/server/services/plan/composer.d.ts.map +1 -1
  90. package/dist/server/services/plan/composer.js +2 -1
  91. package/dist/server/services/plan/composer.js.map +1 -1
  92. package/dist/server/services/plan/executor.d.ts.map +1 -1
  93. package/dist/server/services/plan/executor.js +3 -1
  94. package/dist/server/services/plan/executor.js.map +1 -1
  95. package/dist/server/services/plan/parser-core.d.ts +20 -0
  96. package/dist/server/services/plan/parser-core.d.ts.map +1 -0
  97. package/dist/server/services/plan/parser-core.js +350 -0
  98. package/dist/server/services/plan/parser-core.js.map +1 -0
  99. package/dist/server/services/plan/parser-migration.d.ts +5 -0
  100. package/dist/server/services/plan/parser-migration.d.ts.map +1 -0
  101. package/dist/server/services/plan/parser-migration.js +124 -0
  102. package/dist/server/services/plan/parser-migration.js.map +1 -0
  103. package/dist/server/services/plan/parser.d.ts +0 -8
  104. package/dist/server/services/plan/parser.d.ts.map +1 -1
  105. package/dist/server/services/plan/parser.js +50 -569
  106. package/dist/server/services/plan/parser.js.map +1 -1
  107. package/dist/server/services/plan/review-gate.d.ts +2 -0
  108. package/dist/server/services/plan/review-gate.d.ts.map +1 -1
  109. package/dist/server/services/plan/review-gate.js +2 -2
  110. package/dist/server/services/plan/review-gate.js.map +1 -1
  111. package/dist/server/services/plan/types.d.ts +2 -0
  112. package/dist/server/services/plan/types.d.ts.map +1 -1
  113. package/dist/server/services/platform-credentials.d.ts +24 -0
  114. package/dist/server/services/platform-credentials.d.ts.map +1 -0
  115. package/dist/server/services/platform-credentials.js +68 -0
  116. package/dist/server/services/platform-credentials.js.map +1 -0
  117. package/dist/server/services/platform.d.ts +1 -31
  118. package/dist/server/services/platform.d.ts.map +1 -1
  119. package/dist/server/services/platform.js +10 -119
  120. package/dist/server/services/platform.js.map +1 -1
  121. package/dist/server/services/terminal/pty-manager.d.ts +7 -97
  122. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  123. package/dist/server/services/terminal/pty-manager.js +53 -266
  124. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  125. package/dist/server/services/terminal/pty-utils.d.ts +57 -0
  126. package/dist/server/services/terminal/pty-utils.d.ts.map +1 -0
  127. package/dist/server/services/terminal/pty-utils.js +141 -0
  128. package/dist/server/services/terminal/pty-utils.js.map +1 -0
  129. package/dist/server/services/websocket/file-definition-handlers.d.ts +4 -0
  130. package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -0
  131. package/dist/server/services/websocket/file-definition-handlers.js +153 -0
  132. package/dist/server/services/websocket/file-definition-handlers.js.map +1 -0
  133. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  134. package/dist/server/services/websocket/file-explorer-handlers.js +52 -391
  135. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  136. package/dist/server/services/websocket/file-search-handlers.d.ts +5 -0
  137. package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -0
  138. package/dist/server/services/websocket/file-search-handlers.js +238 -0
  139. package/dist/server/services/websocket/file-search-handlers.js.map +1 -0
  140. package/dist/server/services/websocket/file-utils.js +3 -3
  141. package/dist/server/services/websocket/file-utils.js.map +1 -1
  142. package/dist/server/services/websocket/git-branch-handlers.d.ts +7 -0
  143. package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -0
  144. package/dist/server/services/websocket/git-branch-handlers.js +110 -0
  145. package/dist/server/services/websocket/git-branch-handlers.js.map +1 -0
  146. package/dist/server/services/websocket/git-diff-handlers.d.ts +6 -0
  147. package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -0
  148. package/dist/server/services/websocket/git-diff-handlers.js +123 -0
  149. package/dist/server/services/websocket/git-diff-handlers.js.map +1 -0
  150. package/dist/server/services/websocket/git-handlers.d.ts +2 -31
  151. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  152. package/dist/server/services/websocket/git-handlers.js +35 -541
  153. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  154. package/dist/server/services/websocket/git-log-handlers.d.ts +6 -0
  155. package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -0
  156. package/dist/server/services/websocket/git-log-handlers.js +128 -0
  157. package/dist/server/services/websocket/git-log-handlers.js.map +1 -0
  158. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
  159. package/dist/server/services/websocket/git-pr-handlers.js +13 -53
  160. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
  161. package/dist/server/services/websocket/git-tag-handlers.d.ts +6 -0
  162. package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -0
  163. package/dist/server/services/websocket/git-tag-handlers.js +76 -0
  164. package/dist/server/services/websocket/git-tag-handlers.js.map +1 -0
  165. package/dist/server/services/websocket/git-utils.d.ts +43 -0
  166. package/dist/server/services/websocket/git-utils.d.ts.map +1 -0
  167. package/dist/server/services/websocket/git-utils.js +201 -0
  168. package/dist/server/services/websocket/git-utils.js.map +1 -0
  169. package/dist/server/services/websocket/handler.d.ts +2 -0
  170. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  171. package/dist/server/services/websocket/handler.js +37 -126
  172. package/dist/server/services/websocket/handler.js.map +1 -1
  173. package/dist/server/services/websocket/plan-board-handlers.d.ts +11 -0
  174. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -0
  175. package/dist/server/services/websocket/plan-board-handlers.js +218 -0
  176. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -0
  177. package/dist/server/services/websocket/plan-execution-handlers.d.ts +9 -0
  178. package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -0
  179. package/dist/server/services/websocket/plan-execution-handlers.js +142 -0
  180. package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -0
  181. package/dist/server/services/websocket/plan-handlers.d.ts +7 -2
  182. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
  183. package/dist/server/services/websocket/plan-handlers.js +6 -925
  184. package/dist/server/services/websocket/plan-handlers.js.map +1 -1
  185. package/dist/server/services/websocket/plan-helpers.d.ts +19 -0
  186. package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -0
  187. package/dist/server/services/websocket/plan-helpers.js +199 -0
  188. package/dist/server/services/websocket/plan-helpers.js.map +1 -0
  189. package/dist/server/services/websocket/plan-issue-handlers.d.ts +12 -0
  190. package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -0
  191. package/dist/server/services/websocket/plan-issue-handlers.js +162 -0
  192. package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -0
  193. package/dist/server/services/websocket/plan-sprint-handlers.d.ts +7 -0
  194. package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -0
  195. package/dist/server/services/websocket/plan-sprint-handlers.js +206 -0
  196. package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -0
  197. package/dist/server/services/websocket/quality-complexity.d.ts +14 -0
  198. package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -0
  199. package/dist/server/services/websocket/quality-complexity.js +262 -0
  200. package/dist/server/services/websocket/quality-complexity.js.map +1 -0
  201. package/dist/server/services/websocket/quality-fix-agent.d.ts +16 -0
  202. package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -0
  203. package/dist/server/services/websocket/quality-fix-agent.js +140 -0
  204. package/dist/server/services/websocket/quality-fix-agent.js.map +1 -0
  205. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  206. package/dist/server/services/websocket/quality-handlers.js +34 -346
  207. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  208. package/dist/server/services/websocket/quality-linting.d.ts +9 -0
  209. package/dist/server/services/websocket/quality-linting.d.ts.map +1 -0
  210. package/dist/server/services/websocket/quality-linting.js +178 -0
  211. package/dist/server/services/websocket/quality-linting.js.map +1 -0
  212. package/dist/server/services/websocket/quality-review-agent.d.ts +19 -0
  213. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -0
  214. package/dist/server/services/websocket/quality-review-agent.js +206 -0
  215. package/dist/server/services/websocket/quality-review-agent.js.map +1 -0
  216. package/dist/server/services/websocket/quality-service.d.ts +3 -51
  217. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  218. package/dist/server/services/websocket/quality-service.js +9 -651
  219. package/dist/server/services/websocket/quality-service.js.map +1 -1
  220. package/dist/server/services/websocket/quality-tools.d.ts +23 -0
  221. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -0
  222. package/dist/server/services/websocket/quality-tools.js +208 -0
  223. package/dist/server/services/websocket/quality-tools.js.map +1 -0
  224. package/dist/server/services/websocket/quality-types.d.ts +59 -0
  225. package/dist/server/services/websocket/quality-types.d.ts.map +1 -0
  226. package/dist/server/services/websocket/quality-types.js +101 -0
  227. package/dist/server/services/websocket/quality-types.js.map +1 -0
  228. package/dist/server/services/websocket/session-handlers.d.ts +3 -4
  229. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  230. package/dist/server/services/websocket/session-handlers.js +3 -378
  231. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  232. package/dist/server/services/websocket/session-history.d.ts +4 -0
  233. package/dist/server/services/websocket/session-history.d.ts.map +1 -0
  234. package/dist/server/services/websocket/session-history.js +208 -0
  235. package/dist/server/services/websocket/session-history.js.map +1 -0
  236. package/dist/server/services/websocket/session-initialization.d.ts +5 -0
  237. package/dist/server/services/websocket/session-initialization.d.ts.map +1 -0
  238. package/dist/server/services/websocket/session-initialization.js +163 -0
  239. package/dist/server/services/websocket/session-initialization.js.map +1 -0
  240. package/dist/server/services/websocket/types.d.ts +12 -2
  241. package/dist/server/services/websocket/types.d.ts.map +1 -1
  242. package/package.json +1 -1
  243. package/server/cli/headless/claude-invoker-process.ts +204 -0
  244. package/server/cli/headless/claude-invoker-stall.ts +164 -0
  245. package/server/cli/headless/claude-invoker-stream.ts +353 -0
  246. package/server/cli/headless/claude-invoker-tools.ts +187 -0
  247. package/server/cli/headless/claude-invoker.ts +15 -1096
  248. package/server/cli/headless/haiku-assessments.ts +365 -0
  249. package/server/cli/headless/headless-logger.ts +26 -5
  250. package/server/cli/headless/native-timeout-detector.ts +117 -0
  251. package/server/cli/headless/stall-assessor.ts +65 -618
  252. package/server/cli/improvisation-attachments.ts +148 -0
  253. package/server/cli/improvisation-retry.ts +602 -0
  254. package/server/cli/improvisation-session-manager.ts +140 -1349
  255. package/server/cli/improvisation-types.ts +98 -0
  256. package/server/cli/prompt-builders.ts +370 -0
  257. package/server/index.ts +35 -246
  258. package/server/mcp/bouncer-haiku.ts +182 -0
  259. package/server/mcp/bouncer-integration.ts +87 -248
  260. package/server/mcp/security-analysis.ts +217 -0
  261. package/server/mcp/security-audit.ts +1 -1
  262. package/server/mcp/security-patterns.ts +60 -283
  263. package/server/server-setup.ts +114 -0
  264. package/server/services/file-explorer-ops.ts +293 -0
  265. package/server/services/files.ts +20 -532
  266. package/server/services/plan/composer.ts +2 -1
  267. package/server/services/plan/executor.ts +3 -1
  268. package/server/services/plan/parser-core.ts +406 -0
  269. package/server/services/plan/parser-migration.ts +128 -0
  270. package/server/services/plan/parser.ts +52 -620
  271. package/server/services/plan/review-gate.ts +4 -2
  272. package/server/services/plan/types.ts +2 -0
  273. package/server/services/platform-credentials.ts +83 -0
  274. package/server/services/platform.ts +15 -141
  275. package/server/services/terminal/pty-manager.ts +66 -313
  276. package/server/services/terminal/pty-utils.ts +176 -0
  277. package/server/services/websocket/file-definition-handlers.ts +165 -0
  278. package/server/services/websocket/file-explorer-handlers.ts +37 -452
  279. package/server/services/websocket/file-search-handlers.ts +291 -0
  280. package/server/services/websocket/file-utils.ts +3 -3
  281. package/server/services/websocket/git-branch-handlers.ts +130 -0
  282. package/server/services/websocket/git-diff-handlers.ts +140 -0
  283. package/server/services/websocket/git-handlers.ts +40 -625
  284. package/server/services/websocket/git-log-handlers.ts +149 -0
  285. package/server/services/websocket/git-pr-handlers.ts +17 -62
  286. package/server/services/websocket/git-tag-handlers.ts +91 -0
  287. package/server/services/websocket/git-utils.ts +230 -0
  288. package/server/services/websocket/handler.ts +39 -126
  289. package/server/services/websocket/plan-board-handlers.ts +277 -0
  290. package/server/services/websocket/plan-execution-handlers.ts +184 -0
  291. package/server/services/websocket/plan-handlers.ts +8 -1114
  292. package/server/services/websocket/plan-helpers.ts +215 -0
  293. package/server/services/websocket/plan-issue-handlers.ts +204 -0
  294. package/server/services/websocket/plan-sprint-handlers.ts +252 -0
  295. package/server/services/websocket/quality-complexity.ts +294 -0
  296. package/server/services/websocket/quality-fix-agent.ts +181 -0
  297. package/server/services/websocket/quality-handlers.ts +36 -404
  298. package/server/services/websocket/quality-linting.ts +187 -0
  299. package/server/services/websocket/quality-review-agent.ts +246 -0
  300. package/server/services/websocket/quality-service.ts +11 -762
  301. package/server/services/websocket/quality-tools.ts +209 -0
  302. package/server/services/websocket/quality-types.ts +169 -0
  303. package/server/services/websocket/session-handlers.ts +5 -437
  304. package/server/services/websocket/session-history.ts +222 -0
  305. package/server/services/websocket/session-initialization.ts +209 -0
  306. package/server/services/websocket/types.ts +17 -0
@@ -1,522 +1,20 @@
1
1
  // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
2
  // Licensed under the MIT License. See LICENSE file for details.
3
3
  /**
4
- * PPS Parser — Parses .pm/ (or legacy .plan/) directory files into structured TypeScript objects.
4
+ * PPS Parser — Public API for reading .pm/ (or legacy .plan/) directories.
5
5
  *
6
- * Handles YAML front matter extraction and markdown body parsing.
6
+ * Entity parsing lives in parser-core.ts; migration in parser-migration.ts.
7
7
  */
8
- import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
8
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
- function stripQuotes(v) {
11
- if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
12
- return v.slice(1, -1);
13
- }
14
- return v;
15
- }
16
- function parseYamlValue(v) {
17
- if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
18
- return v.slice(1, -1);
19
- }
20
- if (v.startsWith('[') && v.endsWith(']')) {
21
- return v.slice(1, -1).split(',').map(s => stripQuotes(s.trim())).filter(Boolean);
22
- }
23
- if (v === 'true')
24
- return true;
25
- if (v === 'false')
26
- return false;
27
- if (v === 'null' || v === '~' || v === '')
28
- return null;
29
- if (/^-?\d+(\.\d+)?$/.test(v))
30
- return Number(v);
31
- return v;
32
- }
33
- /** Consume indented YAML list items starting after the current index. Returns [items, newIndex]. */
34
- function consumeIndentedList(lines, startIdx) {
35
- const items = [];
36
- let i = startIdx;
37
- while (i + 1 < lines.length && /^\s+-\s/.test(lines[i + 1])) {
38
- i++;
39
- const item = lines[i].trim().replace(/^-\s+/, '');
40
- items.push(stripQuotes(item));
41
- }
42
- return [items, i];
43
- }
44
- function parseFrontMatter(content) {
45
- const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
46
- if (!match) {
47
- return { frontMatter: {}, body: content };
48
- }
49
- const frontMatter = {};
50
- const lines = match[1].split('\n');
51
- for (let i = 0; i < lines.length; i++) {
52
- const trimmed = lines[i].trim();
53
- if (!trimmed || trimmed.startsWith('#'))
54
- continue;
55
- const colonIdx = trimmed.indexOf(':');
56
- if (colonIdx === -1)
57
- continue;
58
- const key = trimmed.slice(0, colonIdx).trim();
59
- const rawValue = trimmed.slice(colonIdx + 1).trim();
60
- if (!rawValue) {
61
- const [items, newIdx] = consumeIndentedList(lines, i);
62
- i = newIdx;
63
- frontMatter[key] = items.length > 0 ? items : null;
64
- }
65
- else {
66
- frontMatter[key] = parseYamlValue(rawValue);
67
- }
68
- }
69
- return { frontMatter, body: match[2] };
70
- }
71
- // ============================================================================
72
- // Section Extraction
73
- // ============================================================================
74
- function extractSections(body) {
75
- const sections = new Map();
76
- const lines = body.split('\n');
77
- let currentSection = '';
78
- let currentContent = [];
79
- for (const line of lines) {
80
- if (line.startsWith('## ')) {
81
- if (currentSection) {
82
- sections.set(currentSection, currentContent.join('\n').trim());
83
- }
84
- currentSection = line.slice(3).trim();
85
- currentContent = [];
86
- }
87
- else {
88
- currentContent.push(line);
89
- }
90
- }
91
- if (currentSection) {
92
- sections.set(currentSection, currentContent.join('\n').trim());
93
- }
94
- return sections;
95
- }
96
- function parseCheckboxes(content) {
97
- const items = [];
98
- for (const line of content.split('\n')) {
99
- const match = line.match(/^[-*]\s+\[([ xX])\]\s+(.+)$/);
100
- if (match) {
101
- items.push({ text: match[2].trim(), checked: match[1] !== ' ' });
102
- }
103
- }
104
- return items;
105
- }
106
- function parseListItems(content) {
107
- const items = [];
108
- for (const line of content.split('\n')) {
109
- const match = line.match(/^[-*]\s+(.+)$/);
110
- if (match)
111
- items.push(match[1].trim());
112
- }
113
- return items;
114
- }
115
- function parseIssueSummaries(content) {
116
- const summaries = [];
117
- for (const line of content.split('\n')) {
118
- // Match: 1. [IS-003](backlog/IS-003.md) — Title (P1)
119
- const match = line.match(/\d+\.\s+\[([^\]]+)\]\(([^)]+)\)\s*[—–-]\s*(.+?)(?:\s*\((\w+)\))?\s*$/);
120
- if (match) {
121
- summaries.push({
122
- id: match[1],
123
- path: match[2],
124
- title: match[3].trim(),
125
- priority: match[4] || '',
126
- });
127
- continue;
128
- }
129
- // Match: - [IS-001](backlog/IS-001.md) — Title
130
- const match2 = line.match(/^[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*[—–-]\s*(.+?)(?:\s*[→→]\s*blocked by\s+\[([^\]]+)\])?\s*$/i);
131
- if (match2) {
132
- summaries.push({
133
- id: match2[1],
134
- path: match2[2],
135
- title: match2[3].trim(),
136
- priority: '',
137
- blockedBy: match2[4] || undefined,
138
- });
139
- }
140
- }
141
- return summaries;
142
- }
143
- function parseCompletedSummaries(content) {
144
- const summaries = [];
145
- for (const line of content.split('\n')) {
146
- const match = line.match(/^[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*[—–-]\s*(.+?)(?:\s*✓)?\s*$/);
147
- if (match) {
148
- summaries.push({
149
- id: match[1],
150
- path: match[2],
151
- title: match[3].trim(),
152
- priority: '',
153
- });
154
- }
155
- }
156
- return summaries;
157
- }
158
- // ============================================================================
159
- // Entity Parsers
160
- // ============================================================================
161
- function parseWorkflows(section) {
162
- if (!section)
163
- return [];
164
- const workflows = [];
165
- for (const line of section.split('\n')) {
166
- const match = line.match(/\|\s*(\w+)\s*\|\s*(\w+)\s*\|\s*(.+?)\s*\|/);
167
- if (match && match[1] !== 'Status') {
168
- workflows.push({
169
- status: match[1],
170
- category: match[2],
171
- description: match[3].trim(),
172
- });
173
- }
174
- }
175
- return workflows;
176
- }
177
- function parseTeams(section) {
178
- if (!section)
179
- return [];
180
- const teams = [];
181
- for (const line of section.split('\n')) {
182
- const match = line.match(/^[-*]\s+(\w+)(?:\s*[—–-]\s*(.+))?$/);
183
- if (match)
184
- teams.push({ name: match[1], description: match[2]?.trim() });
185
- }
186
- return teams;
187
- }
188
- function parseProjectConfig(content) {
189
- const { frontMatter, body } = parseFrontMatter(content);
190
- const sections = extractSections(body);
191
- const idPrefixes = {};
192
- const rawPrefixes = frontMatter.id_prefixes;
193
- if (rawPrefixes && typeof rawPrefixes === 'object') {
194
- Object.assign(idPrefixes, rawPrefixes);
195
- }
196
- return {
197
- name: String(frontMatter.name || ''),
198
- id: String(frontMatter.id || ''),
199
- created: String(frontMatter.created || ''),
200
- status: frontMatter.status || 'active',
201
- estimation: frontMatter.estimation || 'none',
202
- idPrefixes,
203
- workflows: parseWorkflows(sections.get('Workflows')),
204
- labels: (Array.isArray(frontMatter.labels) ? frontMatter.labels : []),
205
- teams: parseTeams(sections.get('Teams')),
206
- };
207
- }
208
- function parseProjectState(content) {
209
- const { frontMatter, body } = parseFrontMatter(content);
210
- const sections = extractSections(body);
211
- return {
212
- project: String(frontMatter.project || ''),
213
- currentSprint: frontMatter.current_sprint || null,
214
- activeMilestone: frontMatter.active_milestone || null,
215
- paused: frontMatter.paused === true,
216
- lastSession: frontMatter.last_session || null,
217
- readyToWork: parseIssueSummaries(sections.get('Ready to Work') || ''),
218
- inProgress: parseIssueSummaries(sections.get('In Progress') || ''),
219
- blocked: parseIssueSummaries(sections.get('Blocked') || ''),
220
- recentlyCompleted: parseCompletedSummaries(sections.get('Recently Completed') || ''),
221
- warnings: parseListItems(sections.get('Warnings') || ''),
222
- };
223
- }
224
- function toStringArray(val) {
225
- return Array.isArray(val) ? val.map(String) : [];
226
- }
227
- function optionalString(val) {
228
- if (val == null)
229
- return null;
230
- const s = String(val);
231
- return s === '' ? null : s;
232
- }
233
- function parseIssue(content, filePath) {
234
- const { frontMatter: fm, body } = parseFrontMatter(content);
235
- const sections = extractSections(body);
236
- return {
237
- id: String(fm.id || ''),
238
- title: String(fm.title || ''),
239
- type: fm.type || 'issue',
240
- status: String(fm.status || 'backlog'),
241
- priority: String(fm.priority || 'P2'),
242
- estimate: fm.estimate != null ? fm.estimate : null,
243
- labels: toStringArray(fm.labels),
244
- epic: optionalString(fm.epic),
245
- sprint: optionalString(fm.sprint),
246
- milestone: optionalString(fm.milestone),
247
- assigned: optionalString(fm.assigned),
248
- created: String(fm.created || ''),
249
- updated: optionalString(fm.updated),
250
- due: optionalString(fm.due),
251
- blockedBy: toStringArray(fm.blocked_by),
252
- blocks: toStringArray(fm.blocks),
253
- relatesTo: toStringArray(fm.relates_to),
254
- children: toStringArray(fm.children),
255
- progress: optionalString(fm.progress),
256
- description: sections.get('Description') || '',
257
- acceptanceCriteria: parseCheckboxes(sections.get('Acceptance Criteria') || ''),
258
- technicalNotes: sections.get('Technical Notes') || null,
259
- filesToModify: parseListItems(sections.get('Files to Modify') || ''),
260
- activity: parseListItems(sections.get('Activity') || ''),
261
- reviewGate: (['none', 'auto', 'required'].includes(String(fm.review_gate)) ? String(fm.review_gate) : 'auto'),
262
- outputFile: optionalString(fm.output_file),
263
- body,
264
- path: filePath,
265
- };
266
- }
267
- function parseSprintIssues(section) {
268
- if (!section)
269
- return [];
270
- const issues = [];
271
- for (const line of section.split('\n')) {
272
- const match = line.match(/\|\s*\[([^\]]+)\]\(([^)]+)\)\s*\|\s*(.+?)\s*\|\s*(\S+)\s*\|\s*(\S+)\s*\|/);
273
- if (match) {
274
- issues.push({
275
- id: match[1],
276
- path: match[2],
277
- title: match[3].trim(),
278
- points: /^\d+$/.test(match[4]) ? Number(match[4]) : match[4],
279
- status: match[5],
280
- });
281
- }
282
- }
283
- return issues;
284
- }
285
- function optionalNumber(val) {
286
- return val != null ? Number(val) : null;
287
- }
288
- function parseSprint(content, filePath) {
289
- const { frontMatter: fm, body } = parseFrontMatter(content);
290
- const sections = extractSections(body);
291
- // Table-based parsing (markdown links in table rows)
292
- let issues = parseSprintIssues(sections.get('Issues'));
293
- // Fallback: front matter issues array (e.g., ["backlog/IS-001.md", ...])
294
- if (issues.length === 0 && Array.isArray(fm.issues)) {
295
- issues = fm.issues.map(path => {
296
- const id = path.replace(/^backlog\//, '').replace(/\.md$/, '');
297
- return { id, path, title: '', points: null, status: '' };
298
- });
299
- }
300
- // Parse execution_summary if present (JSON object in front matter)
301
- let executionSummary = null;
302
- if (fm.execution_summary && typeof fm.execution_summary === 'object') {
303
- const es = fm.execution_summary;
304
- executionSummary = {
305
- totalIssues: Number(es.total_issues ?? 0),
306
- completedIssues: Number(es.completed_issues ?? 0),
307
- failedIssues: Number(es.failed_issues ?? 0),
308
- totalDuration: Number(es.total_duration ?? 0),
309
- waves: Number(es.waves ?? 0),
310
- };
311
- }
312
- return {
313
- id: String(fm.id || ''),
314
- title: String(fm.title || ''),
315
- status: fm.status || 'planned',
316
- start: String(fm.start || fm.start_date || ''),
317
- end: String(fm.end || fm.end_date || ''),
318
- goal: String(fm.goal || sections.get('Goal') || sections.get('Sprint Goal') || ''),
319
- capacity: optionalNumber(fm.capacity),
320
- committed: optionalNumber(fm.committed),
321
- completed: optionalNumber(fm.completed),
322
- issues,
323
- path: filePath,
324
- completedAt: optionalString(fm.completed_at),
325
- executionSummary,
326
- };
327
- }
328
- function parseMilestone(content, filePath) {
329
- const { frontMatter, body } = parseFrontMatter(content);
330
- const sections = extractSections(body);
331
- const epics = [];
332
- const epicSection = sections.get('Epics');
333
- if (epicSection) {
334
- for (const line of epicSection.split('\n')) {
335
- const match = line.match(/\|\s*\[([^\]]+)\]\(([^)]+)\)\s*\|\s*(.+?)\s*\|\s*(\S+)\s*\|/);
336
- if (match) {
337
- epics.push({
338
- id: match[1],
339
- path: match[2],
340
- title: match[3].trim(),
341
- progress: match[4],
342
- });
343
- }
344
- }
345
- }
346
- return {
347
- id: String(frontMatter.id || ''),
348
- title: String(frontMatter.title || ''),
349
- status: frontMatter.status || 'planned',
350
- targetDate: frontMatter.target_date || null,
351
- progress: frontMatter.progress || null,
352
- definition: sections.get('Definition of Done') || '',
353
- epics,
354
- path: filePath,
355
- };
356
- }
10
+ import { parseBoard, parseIssue, parseMilestone, parseProjectConfig, parseProjectState, parseSprint, parseWorkspace } from './parser-core.js';
11
+ import { isLegacyFormat, migrateToBoards } from './parser-migration.js';
357
12
  // ============================================================================
358
- // Board Parser
13
+ // Directory Resolution
359
14
  // ============================================================================
360
- function parseBoard(content, filePath) {
361
- const { frontMatter: fm, body } = parseFrontMatter(content);
362
- const sections = extractSections(body);
363
- let executionSummary = null;
364
- if (fm.execution_summary && typeof fm.execution_summary === 'object') {
365
- const es = fm.execution_summary;
366
- executionSummary = {
367
- totalIssues: Number(es.total_issues ?? 0),
368
- completedIssues: Number(es.completed_issues ?? 0),
369
- failedIssues: Number(es.failed_issues ?? 0),
370
- totalDuration: Number(es.total_duration ?? 0),
371
- waves: Number(es.waves ?? 0),
372
- };
373
- }
374
- return {
375
- id: String(fm.id || ''),
376
- title: String(fm.title || ''),
377
- status: fm.status || 'draft',
378
- created: String(fm.created || ''),
379
- completedAt: optionalString(fm.completed_at),
380
- goal: String(fm.goal || sections.get('Goal') || ''),
381
- executionSummary,
382
- path: filePath,
383
- };
384
- }
385
- function parseWorkspace(content) {
386
- try {
387
- const parsed = JSON.parse(content);
388
- return {
389
- activeBoardId: typeof parsed.activeBoardId === 'string' ? parsed.activeBoardId : null,
390
- boardOrder: Array.isArray(parsed.boardOrder) ? parsed.boardOrder.map(String) : [],
391
- };
392
- }
393
- catch {
394
- return { activeBoardId: null, boardOrder: [] };
395
- }
396
- }
397
- /** Check whether a .pm/ directory uses the board-centric format (has boards/ subdirectory). */
398
15
  export function isBoardCentricFormat(pmDir) {
399
16
  return existsSync(join(pmDir, 'boards'));
400
17
  }
401
- /** Check whether a .pm/ directory uses the legacy flat format (has backlog/ at root, no boards/). */
402
- function isLegacyFormat(pmDir) {
403
- return existsSync(join(pmDir, 'backlog')) && !existsSync(join(pmDir, 'boards'));
404
- }
405
- // ============================================================================
406
- // Legacy → Board Migration
407
- // ============================================================================
408
- /** Move all files from a legacy directory into a board subdirectory and remove the source. */
409
- function moveLegacyDir(srcDir, destDir) {
410
- if (!existsSync(srcDir))
411
- return;
412
- for (const file of readdirSync(srcDir)) {
413
- renameSync(join(srcDir, file), join(destDir, file));
414
- }
415
- rmSync(srcDir, { recursive: true });
416
- }
417
- /** Move a single file if it exists. */
418
- function moveLegacyFile(src, dest) {
419
- if (existsSync(src))
420
- renameSync(src, dest);
421
- }
422
- /** Copy review files from sprint sandbox directories into the board reviews dir. */
423
- function copySprintReviews(sprintsDir, boardReviewsDir) {
424
- for (const entry of readdirSync(sprintsDir)) {
425
- if (entry.endsWith('.md'))
426
- continue;
427
- const reviewsDir = join(sprintsDir, entry, 'reviews');
428
- if (!existsSync(reviewsDir))
429
- continue;
430
- for (const reviewFile of readdirSync(reviewsDir)) {
431
- cpSync(join(reviewsDir, reviewFile), join(boardReviewsDir, reviewFile));
432
- }
433
- }
434
- }
435
- /** Find and return the goal from the active sprint .md file. */
436
- function extractActiveSprintGoal(sprintsDir) {
437
- for (const entry of readdirSync(sprintsDir).filter(e => e.endsWith('.md'))) {
438
- const content = readFileIfExists(join(sprintsDir, entry));
439
- if (!content)
440
- continue;
441
- const fm = parseFrontMatter(content).frontMatter;
442
- if (fm.status === 'active')
443
- return String(fm.goal || '');
444
- }
445
- return '';
446
- }
447
- /** Migrate sprint reviews and extract the active sprint's goal. */
448
- function migrateLegacySprints(sprintsDir, boardReviewsDir) {
449
- if (!existsSync(sprintsDir))
450
- return '';
451
- copySprintReviews(sprintsDir, boardReviewsDir);
452
- const goal = extractActiveSprintGoal(sprintsDir);
453
- rmSync(sprintsDir, { recursive: true });
454
- return goal;
455
- }
456
- /** Clean up migrated issues: remove sprint fields, detect active issues. */
457
- function cleanupMigratedIssues(boardBacklogDir) {
458
- if (!existsSync(boardBacklogDir))
459
- return false;
460
- let hasActive = false;
461
- for (const file of readdirSync(boardBacklogDir).filter(f => f.endsWith('.md'))) {
462
- const content = readFileIfExists(join(boardBacklogDir, file));
463
- if (!content)
464
- continue;
465
- if (content.match(/^status:\s*(in_progress|in_review|todo)/m))
466
- hasActive = true;
467
- if (content.match(/^sprint:\s*.+$/m)) {
468
- writeFileSync(join(boardBacklogDir, file), content.replace(/^sprint:\s*.+\n?/m, ''), 'utf-8');
469
- }
470
- }
471
- return hasActive;
472
- }
473
- /** Write the board metadata files (board.md, workspace.json, STATE.md, progress.md). */
474
- function writeBoardMetadata(pmDir, boardDir, boardId, sprintGoal, hasActive) {
475
- const today = new Date().toISOString().slice(0, 10);
476
- const boardMd = [
477
- '---', `id: ${boardId}`, 'title: "Board 1"',
478
- `status: ${hasActive ? 'active' : 'draft'}`, `created: "${today}"`,
479
- 'completed_at: null', `goal: "${sprintGoal.replace(/"/g, '\\"')}"`,
480
- '---', '', '# Board 1', '',
481
- sprintGoal ? `## Goal\n${sprintGoal}\n` : '',
482
- ].join('\n');
483
- writeFileSync(join(boardDir, 'board.md'), boardMd, 'utf-8');
484
- const workspace = { activeBoardId: boardId, boardOrder: [boardId] };
485
- writeFileSync(join(pmDir, 'workspace.json'), JSON.stringify(workspace, null, 2), 'utf-8');
486
- if (!existsSync(join(boardDir, 'STATE.md'))) {
487
- writeFileSync(join(boardDir, 'STATE.md'), [
488
- '---', 'project: ../../project.md', 'board: board.md', 'paused: false', '---', '',
489
- '# Board State', '', '## Ready to Work', '', '## In Progress', '',
490
- '## Blocked', '', '## Recently Completed', '', '## Warnings', '',
491
- ].join('\n'), 'utf-8');
492
- }
493
- if (!existsSync(join(boardDir, 'progress.md'))) {
494
- writeFileSync(join(boardDir, 'progress.md'), '# Board Progress\n', 'utf-8');
495
- }
496
- }
497
- /**
498
- * Migrate a legacy flat .pm/ directory to board-centric format.
499
- * Creates BOARD-001 from the existing backlog, state, outputs, and reviews.
500
- */
501
- function migrateToBoards(pmDir) {
502
- const boardId = 'BOARD-001';
503
- const boardDir = join(pmDir, 'boards', boardId);
504
- mkdirSync(boardDir, { recursive: true });
505
- mkdirSync(join(boardDir, 'backlog'), { recursive: true });
506
- mkdirSync(join(boardDir, 'out'), { recursive: true });
507
- mkdirSync(join(boardDir, 'reviews'), { recursive: true });
508
- moveLegacyDir(join(pmDir, 'backlog'), join(boardDir, 'backlog'));
509
- moveLegacyFile(join(pmDir, 'STATE.md'), join(boardDir, 'STATE.md'));
510
- moveLegacyDir(join(pmDir, 'out'), join(boardDir, 'out'));
511
- moveLegacyFile(join(pmDir, 'progress.md'), join(boardDir, 'progress.md'));
512
- const sprintGoal = migrateLegacySprints(join(pmDir, 'sprints'), join(boardDir, 'reviews'));
513
- const hasActive = cleanupMigratedIssues(join(boardDir, 'backlog'));
514
- writeBoardMetadata(pmDir, boardDir, boardId, sprintGoal, hasActive);
515
- }
516
- // ============================================================================
517
- // Directory Parser
518
- // ============================================================================
519
- /** Resolve the PM directory — prefers .mstro/pm/, falls back to legacy .pm/ and .plan/ */
520
18
  export function resolvePmDir(workingDir) {
521
19
  const mstroPmDir = join(workingDir, '.mstro', 'pm');
522
20
  if (existsSync(mstroPmDir))
@@ -529,13 +27,15 @@ export function resolvePmDir(workingDir) {
529
27
  return legacyPlanDir;
530
28
  return null;
531
29
  }
532
- /** Default PM directory path for new projects */
533
30
  export function defaultPmDir(workingDir) {
534
31
  return join(workingDir, '.mstro', 'pm');
535
32
  }
536
33
  export function planDirExists(workingDir) {
537
34
  return resolvePmDir(workingDir) !== null;
538
35
  }
36
+ // ============================================================================
37
+ // File Utilities
38
+ // ============================================================================
539
39
  function readFileIfExists(path) {
540
40
  try {
541
41
  if (existsSync(path))
@@ -550,15 +50,34 @@ function readMdFilesInDir(dirPath) {
550
50
  try {
551
51
  return readdirSync(dirPath)
552
52
  .filter(f => f.endsWith('.md'))
553
- .map(name => {
554
- const content = readFileSync(join(dirPath, name), 'utf-8');
555
- return { name, content };
556
- });
53
+ .map(name => ({ name, content: readFileSync(join(dirPath, name), 'utf-8') }));
557
54
  }
558
55
  catch {
559
56
  return [];
560
57
  }
561
58
  }
59
+ function listDirFiles(dirPath, ext) {
60
+ if (!existsSync(dirPath))
61
+ return [];
62
+ try {
63
+ return readdirSync(dirPath).filter(f => f.endsWith(ext));
64
+ }
65
+ catch {
66
+ return [];
67
+ }
68
+ }
69
+ function readReviewResults(reviewsDir) {
70
+ const results = [];
71
+ for (const f of listDirFiles(reviewsDir, '.json')) {
72
+ const content = readFileIfExists(join(reviewsDir, f));
73
+ if (content)
74
+ results.push(JSON.parse(content));
75
+ }
76
+ return results;
77
+ }
78
+ // ============================================================================
79
+ // Defaults
80
+ // ============================================================================
562
81
  const defaultProject = {
563
82
  name: '', id: '', created: '', status: 'active', estimation: 'none',
564
83
  idPrefixes: {}, workflows: [], labels: [], teams: [],
@@ -568,7 +87,9 @@ const defaultState = {
568
87
  lastSession: null, readyToWork: [], inProgress: [], blocked: [],
569
88
  recentlyCompleted: [], warnings: [],
570
89
  };
571
- /** Parse a single board's full state (board.md + STATE.md + backlog/). */
90
+ // ============================================================================
91
+ // Board & Plan Directory Parsing
92
+ // ============================================================================
572
93
  export function parseBoardDirectory(pmDir, boardId) {
573
94
  const boardDir = join(pmDir, 'boards', boardId);
574
95
  if (!existsSync(boardDir))
@@ -583,7 +104,6 @@ export function parseBoardDirectory(pmDir, boardId) {
583
104
  const boardPrefix = `boards/${boardId}/`;
584
105
  const issues = issueFiles.map(f => {
585
106
  const issue = parseIssue(f.content, `${boardPrefix}backlog/${f.name}`);
586
- // Normalize blocked_by/blocks to full board-relative paths so dependency resolver matches
587
107
  issue.blockedBy = issue.blockedBy.map(bp => bp.startsWith('boards/') ? bp : `${boardPrefix}${bp}`);
588
108
  issue.blocks = issue.blocks.map(bp => bp.startsWith('boards/') ? bp : `${boardPrefix}${bp}`);
589
109
  if (issue.epic && !issue.epic.startsWith('boards/'))
@@ -592,7 +112,6 @@ export function parseBoardDirectory(pmDir, boardId) {
592
112
  });
593
113
  return { board, state, issues };
594
114
  }
595
- /** Parse all boards from the boards/ directory and resolve the active board. */
596
115
  function parseBoardCentricState(planDir) {
597
116
  const workspaceContent = readFileIfExists(join(planDir, 'workspace.json'));
598
117
  const workspace = workspaceContent ? parseWorkspace(workspaceContent) : { activeBoardId: null, boardOrder: [] };
@@ -634,12 +153,14 @@ export function parsePlanDirectory(workingDir) {
634
153
  sprints: [], milestones,
635
154
  };
636
155
  }
156
+ // ============================================================================
157
+ // Single Entity Parsers
158
+ // ============================================================================
637
159
  export function parseSingleIssue(workingDir, issuePath) {
638
160
  const pmDir = resolvePmDir(workingDir);
639
161
  if (!pmDir)
640
162
  return null;
641
- const fullPath = join(pmDir, issuePath);
642
- const content = readFileIfExists(fullPath);
163
+ const content = readFileIfExists(join(pmDir, issuePath));
643
164
  if (!content)
644
165
  return null;
645
166
  return parseIssue(content, issuePath);
@@ -648,8 +169,7 @@ export function parseSingleSprint(workingDir, sprintPath) {
648
169
  const pmDir = resolvePmDir(workingDir);
649
170
  if (!pmDir)
650
171
  return null;
651
- const fullPath = join(pmDir, sprintPath);
652
- const content = readFileIfExists(fullPath);
172
+ const content = readFileIfExists(join(pmDir, sprintPath));
653
173
  if (!content)
654
174
  return null;
655
175
  return parseSprint(content, sprintPath);
@@ -658,13 +178,14 @@ export function parseSingleMilestone(workingDir, milestonePath) {
658
178
  const pmDir = resolvePmDir(workingDir);
659
179
  if (!pmDir)
660
180
  return null;
661
- const fullPath = join(pmDir, milestonePath);
662
- const content = readFileIfExists(fullPath);
181
+ const content = readFileIfExists(join(pmDir, milestonePath));
663
182
  if (!content)
664
183
  return null;
665
184
  return parseMilestone(content, milestonePath);
666
185
  }
667
- /** Compute the next available ID for a given prefix (e.g., "IS" → "IS-004") */
186
+ // ============================================================================
187
+ // ID Generation
188
+ // ============================================================================
668
189
  export function getNextId(issues, prefix) {
669
190
  const escaped = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
670
191
  const pattern = new RegExp(`^${escaped}-(\\d+)$`);
@@ -679,7 +200,6 @@ export function getNextId(issues, prefix) {
679
200
  }
680
201
  return `${prefix}-${String(max + 1).padStart(3, '0')}`;
681
202
  }
682
- /** Compute the next available board ID (e.g., "BOARD-003") */
683
203
  export function getNextBoardId(boards) {
684
204
  let max = 0;
685
205
  for (const board of boards) {
@@ -692,7 +212,6 @@ export function getNextBoardId(boards) {
692
212
  }
693
213
  return `BOARD-${String(max + 1).padStart(3, '0')}`;
694
214
  }
695
- /** Compute the next available board number for display title (e.g., "Board 3") */
696
215
  export function getNextBoardNumber(boards) {
697
216
  let max = 0;
698
217
  for (const board of boards) {
@@ -705,7 +224,6 @@ export function getNextBoardNumber(boards) {
705
224
  }
706
225
  return max + 1;
707
226
  }
708
- /** Parse board artifacts from boards/BOARD-N/ directory. */
709
227
  export function parseBoardArtifacts(workingDir, boardId) {
710
228
  const pmDir = resolvePmDir(workingDir);
711
229
  if (!pmDir)
@@ -714,28 +232,10 @@ export function parseBoardArtifacts(workingDir, boardId) {
714
232
  if (!existsSync(boardDir))
715
233
  return null;
716
234
  const progressLog = readFileIfExists(join(boardDir, 'progress.md')) ?? '';
717
- const outDir = join(boardDir, 'out');
718
- let outputFiles = [];
719
- if (existsSync(outDir)) {
720
- try {
721
- outputFiles = readdirSync(outDir).filter(f => f.endsWith('.md'));
722
- }
723
- catch { /* skip */ }
724
- }
725
- const reviewsDir = join(boardDir, 'reviews');
726
- const reviewResults = [];
727
- if (existsSync(reviewsDir)) {
728
- try {
729
- for (const f of readdirSync(reviewsDir).filter(f => f.endsWith('.json'))) {
730
- const content = readFileIfExists(join(reviewsDir, f));
731
- if (content) {
732
- reviewResults.push(JSON.parse(content));
733
- }
734
- }
735
- }
736
- catch { /* skip */ }
737
- }
738
- return { boardId, progressLog, outputFiles, reviewResults };
235
+ const outputFiles = listDirFiles(join(boardDir, 'out'), '.md');
236
+ const reviewResults = readReviewResults(join(boardDir, 'reviews'));
237
+ const executionLogs = listDirFiles(join(boardDir, 'logs'), '.log').sort();
238
+ return { boardId, progressLog, outputFiles, reviewResults, executionLogs };
739
239
  }
740
240
  /** @deprecated Use getNextBoardId — kept for migration compatibility */
741
241
  export function getNextSprintId(sprints) {
@@ -759,27 +259,8 @@ export function parseSprintArtifacts(workingDir, sprintId) {
759
259
  if (!existsSync(sandboxDir))
760
260
  return null;
761
261
  const progressLog = readFileIfExists(join(sandboxDir, 'progress.md')) ?? '';
762
- const outDir = join(sandboxDir, 'out');
763
- let outputFiles = [];
764
- if (existsSync(outDir)) {
765
- try {
766
- outputFiles = readdirSync(outDir).filter(f => f.endsWith('.md'));
767
- }
768
- catch { /* skip */ }
769
- }
770
- const reviewsDir = join(sandboxDir, 'reviews');
771
- const reviewResults = [];
772
- if (existsSync(reviewsDir)) {
773
- try {
774
- for (const f of readdirSync(reviewsDir).filter(f => f.endsWith('.json'))) {
775
- const content = readFileIfExists(join(reviewsDir, f));
776
- if (content) {
777
- reviewResults.push(JSON.parse(content));
778
- }
779
- }
780
- }
781
- catch { /* skip */ }
782
- }
262
+ const outputFiles = listDirFiles(join(sandboxDir, 'out'), '.md');
263
+ const reviewResults = readReviewResults(join(sandboxDir, 'reviews'));
783
264
  return { sprintId, progressLog, outputFiles, reviewResults };
784
265
  }
785
266
  //# sourceMappingURL=parser.js.map