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
@@ -0,0 +1,406 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * YAML front-matter parsing and entity parsers for PPS (.pm/) files.
6
+ */
7
+
8
+ import type {
9
+ AcceptanceCriterion,
10
+ Board,
11
+ BoardExecutionSummary,
12
+ Issue,
13
+ IssueSummary,
14
+ Milestone,
15
+ MilestoneEpicSummary,
16
+ ProjectConfig,
17
+ ProjectState,
18
+ Sprint,
19
+ SprintExecutionSummary,
20
+ SprintIssueSummary,
21
+ Team,
22
+ WorkflowStatus,
23
+ Workspace,
24
+ } from './types.js';
25
+
26
+ // ============================================================================
27
+ // Front Matter Extraction
28
+ // ============================================================================
29
+
30
+ export interface ParsedFile {
31
+ frontMatter: Record<string, unknown>;
32
+ body: string;
33
+ }
34
+
35
+ function stripQuotes(v: string): string {
36
+ if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
37
+ return v.slice(1, -1);
38
+ }
39
+ return v;
40
+ }
41
+
42
+ function parseYamlValue(v: string): unknown {
43
+ if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
44
+ return v.slice(1, -1);
45
+ }
46
+ if (v.startsWith('[') && v.endsWith(']')) {
47
+ return v.slice(1, -1).split(',').map(s => stripQuotes(s.trim())).filter(Boolean);
48
+ }
49
+ if (v === 'true') return true;
50
+ if (v === 'false') return false;
51
+ if (v === 'null' || v === '~' || v === '') return null;
52
+ if (/^-?\d+(\.\d+)?$/.test(v)) return Number(v);
53
+ return v;
54
+ }
55
+
56
+ function consumeIndentedList(lines: string[], startIdx: number): [string[], number] {
57
+ const items: string[] = [];
58
+ let i = startIdx;
59
+ while (i + 1 < lines.length && /^\s+-\s/.test(lines[i + 1])) {
60
+ i++;
61
+ const item = lines[i].trim().replace(/^-\s+/, '');
62
+ items.push(stripQuotes(item));
63
+ }
64
+ return [items, i];
65
+ }
66
+
67
+ export function parseFrontMatter(content: string): ParsedFile {
68
+ const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
69
+ if (!match) {
70
+ return { frontMatter: {}, body: content };
71
+ }
72
+ const frontMatter: Record<string, unknown> = {};
73
+ const lines = match[1].split('\n');
74
+
75
+ for (let i = 0; i < lines.length; i++) {
76
+ const trimmed = lines[i].trim();
77
+ if (!trimmed || trimmed.startsWith('#')) continue;
78
+ const colonIdx = trimmed.indexOf(':');
79
+ if (colonIdx === -1) continue;
80
+
81
+ const key = trimmed.slice(0, colonIdx).trim();
82
+ const rawValue = trimmed.slice(colonIdx + 1).trim();
83
+
84
+ if (!rawValue) {
85
+ const [items, newIdx] = consumeIndentedList(lines, i);
86
+ i = newIdx;
87
+ frontMatter[key] = items.length > 0 ? items : null;
88
+ } else {
89
+ frontMatter[key] = parseYamlValue(rawValue);
90
+ }
91
+ }
92
+
93
+ return { frontMatter, body: match[2] };
94
+ }
95
+
96
+ // ============================================================================
97
+ // Section Extraction
98
+ // ============================================================================
99
+
100
+ export function extractSections(body: string): Map<string, string> {
101
+ const sections = new Map<string, string>();
102
+ const lines = body.split('\n');
103
+ let currentSection = '';
104
+ let currentContent: string[] = [];
105
+
106
+ for (const line of lines) {
107
+ if (line.startsWith('## ')) {
108
+ if (currentSection) {
109
+ sections.set(currentSection, currentContent.join('\n').trim());
110
+ }
111
+ currentSection = line.slice(3).trim();
112
+ currentContent = [];
113
+ } else {
114
+ currentContent.push(line);
115
+ }
116
+ }
117
+ if (currentSection) {
118
+ sections.set(currentSection, currentContent.join('\n').trim());
119
+ }
120
+
121
+ return sections;
122
+ }
123
+
124
+ export function parseCheckboxes(content: string): AcceptanceCriterion[] {
125
+ const items: AcceptanceCriterion[] = [];
126
+ for (const line of content.split('\n')) {
127
+ const match = line.match(/^[-*]\s+\[([ xX])\]\s+(.+)$/);
128
+ if (match) {
129
+ items.push({ text: match[2].trim(), checked: match[1] !== ' ' });
130
+ }
131
+ }
132
+ return items;
133
+ }
134
+
135
+ export function parseListItems(content: string): string[] {
136
+ const items: string[] = [];
137
+ for (const line of content.split('\n')) {
138
+ const match = line.match(/^[-*]\s+(.+)$/);
139
+ if (match) items.push(match[1].trim());
140
+ }
141
+ return items;
142
+ }
143
+
144
+ function parseIssueSummaries(content: string): IssueSummary[] {
145
+ const summaries: IssueSummary[] = [];
146
+ for (const line of content.split('\n')) {
147
+ const match = line.match(/\d+\.\s+\[([^\]]+)\]\(([^)]+)\)\s*[—–-]\s*(.+?)(?:\s*\((\w+)\))?\s*$/);
148
+ if (match) {
149
+ summaries.push({ id: match[1], path: match[2], title: match[3].trim(), priority: match[4] || '' });
150
+ continue;
151
+ }
152
+ const match2 = line.match(/^[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*[—–-]\s*(.+?)(?:\s*[→→]\s*blocked by\s+\[([^\]]+)\])?\s*$/i);
153
+ if (match2) {
154
+ summaries.push({ id: match2[1], path: match2[2], title: match2[3].trim(), priority: '', blockedBy: match2[4] || undefined });
155
+ }
156
+ }
157
+ return summaries;
158
+ }
159
+
160
+ function parseCompletedSummaries(content: string): IssueSummary[] {
161
+ const summaries: IssueSummary[] = [];
162
+ for (const line of content.split('\n')) {
163
+ const match = line.match(/^[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*[—–-]\s*(.+?)(?:\s*✓)?\s*$/);
164
+ if (match) {
165
+ summaries.push({ id: match[1], path: match[2], title: match[3].trim(), priority: '' });
166
+ }
167
+ }
168
+ return summaries;
169
+ }
170
+
171
+ // ============================================================================
172
+ // Entity Parsers
173
+ // ============================================================================
174
+
175
+ function parseWorkflows(section: string | undefined): WorkflowStatus[] {
176
+ if (!section) return [];
177
+ const workflows: WorkflowStatus[] = [];
178
+ for (const line of section.split('\n')) {
179
+ const match = line.match(/\|\s*(\w+)\s*\|\s*(\w+)\s*\|\s*(.+?)\s*\|/);
180
+ if (match && match[1] !== 'Status') {
181
+ workflows.push({ status: match[1], category: match[2] as WorkflowStatus['category'], description: match[3].trim() });
182
+ }
183
+ }
184
+ return workflows;
185
+ }
186
+
187
+ function parseTeams(section: string | undefined): Team[] {
188
+ if (!section) return [];
189
+ const teams: Team[] = [];
190
+ for (const line of section.split('\n')) {
191
+ const match = line.match(/^[-*]\s+(\w+)(?:\s*[—–-]\s*(.+))?$/);
192
+ if (match) teams.push({ name: match[1], description: match[2]?.trim() });
193
+ }
194
+ return teams;
195
+ }
196
+
197
+ function toStringArray(val: unknown): string[] {
198
+ return Array.isArray(val) ? val.map(String) : [];
199
+ }
200
+
201
+ function optionalString(val: unknown): string | null {
202
+ if (val == null) return null;
203
+ const s = String(val);
204
+ return s === '' ? null : s;
205
+ }
206
+
207
+ function optionalNumber(val: unknown): number | null {
208
+ return val != null ? Number(val) : null;
209
+ }
210
+
211
+ export function parseProjectConfig(content: string): ProjectConfig {
212
+ const { frontMatter, body } = parseFrontMatter(content);
213
+ const sections = extractSections(body);
214
+
215
+ const idPrefixes: Record<string, string> = {};
216
+ const rawPrefixes = frontMatter.id_prefixes;
217
+ if (rawPrefixes && typeof rawPrefixes === 'object') {
218
+ Object.assign(idPrefixes, rawPrefixes);
219
+ }
220
+
221
+ return {
222
+ name: String(frontMatter.name || ''),
223
+ id: String(frontMatter.id || ''),
224
+ created: String(frontMatter.created || ''),
225
+ status: (frontMatter.status as ProjectConfig['status']) || 'active',
226
+ estimation: (frontMatter.estimation as ProjectConfig['estimation']) || 'none',
227
+ idPrefixes,
228
+ workflows: parseWorkflows(sections.get('Workflows')),
229
+ labels: (Array.isArray(frontMatter.labels) ? frontMatter.labels : []) as string[],
230
+ teams: parseTeams(sections.get('Teams')),
231
+ };
232
+ }
233
+
234
+ export function parseProjectState(content: string): ProjectState {
235
+ const { frontMatter, body } = parseFrontMatter(content);
236
+ const sections = extractSections(body);
237
+
238
+ return {
239
+ project: String(frontMatter.project || ''),
240
+ currentSprint: (frontMatter.current_sprint as string) || null,
241
+ activeMilestone: (frontMatter.active_milestone as string) || null,
242
+ paused: frontMatter.paused === true,
243
+ lastSession: (frontMatter.last_session as string) || null,
244
+ readyToWork: parseIssueSummaries(sections.get('Ready to Work') || ''),
245
+ inProgress: parseIssueSummaries(sections.get('In Progress') || ''),
246
+ blocked: parseIssueSummaries(sections.get('Blocked') || ''),
247
+ recentlyCompleted: parseCompletedSummaries(sections.get('Recently Completed') || ''),
248
+ warnings: parseListItems(sections.get('Warnings') || ''),
249
+ };
250
+ }
251
+
252
+ export function parseIssue(content: string, filePath: string): Issue {
253
+ const { frontMatter: fm, body } = parseFrontMatter(content);
254
+ const sections = extractSections(body);
255
+
256
+ return {
257
+ id: String(fm.id || ''),
258
+ title: String(fm.title || ''),
259
+ type: (fm.type as Issue['type']) || 'issue',
260
+ status: String(fm.status || 'backlog'),
261
+ priority: String(fm.priority || 'P2'),
262
+ estimate: fm.estimate != null ? fm.estimate as number | string : null,
263
+ labels: toStringArray(fm.labels),
264
+ epic: optionalString(fm.epic),
265
+ sprint: optionalString(fm.sprint),
266
+ milestone: optionalString(fm.milestone),
267
+ assigned: optionalString(fm.assigned),
268
+ created: String(fm.created || ''),
269
+ updated: optionalString(fm.updated),
270
+ due: optionalString(fm.due),
271
+ blockedBy: toStringArray(fm.blocked_by),
272
+ blocks: toStringArray(fm.blocks),
273
+ relatesTo: toStringArray(fm.relates_to),
274
+ children: toStringArray(fm.children),
275
+ progress: optionalString(fm.progress),
276
+ description: sections.get('Description') || '',
277
+ acceptanceCriteria: parseCheckboxes(sections.get('Acceptance Criteria') || ''),
278
+ technicalNotes: sections.get('Technical Notes') || null,
279
+ filesToModify: parseListItems(sections.get('Files to Modify') || ''),
280
+ activity: parseListItems(sections.get('Activity') || ''),
281
+ reviewGate: (['none', 'auto', 'required'].includes(String(fm.review_gate)) ? String(fm.review_gate) : 'auto') as Issue['reviewGate'],
282
+ outputFile: optionalString(fm.output_file),
283
+ body,
284
+ path: filePath,
285
+ };
286
+ }
287
+
288
+ function parseSprintIssues(section: string | undefined): SprintIssueSummary[] {
289
+ if (!section) return [];
290
+ const issues: SprintIssueSummary[] = [];
291
+ for (const line of section.split('\n')) {
292
+ const match = line.match(/\|\s*\[([^\]]+)\]\(([^)]+)\)\s*\|\s*(.+?)\s*\|\s*(\S+)\s*\|\s*(\S+)\s*\|/);
293
+ if (match) {
294
+ issues.push({ id: match[1], path: match[2], title: match[3].trim(), points: /^\d+$/.test(match[4]) ? Number(match[4]) : match[4], status: match[5] });
295
+ }
296
+ }
297
+ return issues;
298
+ }
299
+
300
+ export function parseSprint(content: string, filePath: string): Sprint {
301
+ const { frontMatter: fm, body } = parseFrontMatter(content);
302
+ const sections = extractSections(body);
303
+
304
+ let issues = parseSprintIssues(sections.get('Issues'));
305
+ if (issues.length === 0 && Array.isArray(fm.issues)) {
306
+ issues = (fm.issues as string[]).map(path => {
307
+ const id = path.replace(/^backlog\//, '').replace(/\.md$/, '');
308
+ return { id, path, title: '', points: null, status: '' };
309
+ });
310
+ }
311
+
312
+ let executionSummary: SprintExecutionSummary | null = null;
313
+ if (fm.execution_summary && typeof fm.execution_summary === 'object') {
314
+ const es = fm.execution_summary as Record<string, unknown>;
315
+ executionSummary = {
316
+ totalIssues: Number(es.total_issues ?? 0),
317
+ completedIssues: Number(es.completed_issues ?? 0),
318
+ failedIssues: Number(es.failed_issues ?? 0),
319
+ totalDuration: Number(es.total_duration ?? 0),
320
+ waves: Number(es.waves ?? 0),
321
+ };
322
+ }
323
+
324
+ return {
325
+ id: String(fm.id || ''),
326
+ title: String(fm.title || ''),
327
+ status: (fm.status as Sprint['status']) || 'planned',
328
+ start: String(fm.start || fm.start_date || ''),
329
+ end: String(fm.end || fm.end_date || ''),
330
+ goal: String(fm.goal || sections.get('Goal') || sections.get('Sprint Goal') || ''),
331
+ capacity: optionalNumber(fm.capacity),
332
+ committed: optionalNumber(fm.committed),
333
+ completed: optionalNumber(fm.completed),
334
+ issues,
335
+ path: filePath,
336
+ completedAt: optionalString(fm.completed_at),
337
+ executionSummary,
338
+ };
339
+ }
340
+
341
+ export function parseMilestone(content: string, filePath: string): Milestone {
342
+ const { frontMatter, body } = parseFrontMatter(content);
343
+ const sections = extractSections(body);
344
+
345
+ const epics: MilestoneEpicSummary[] = [];
346
+ const epicSection = sections.get('Epics');
347
+ if (epicSection) {
348
+ for (const line of epicSection.split('\n')) {
349
+ const match = line.match(/\|\s*\[([^\]]+)\]\(([^)]+)\)\s*\|\s*(.+?)\s*\|\s*(\S+)\s*\|/);
350
+ if (match) {
351
+ epics.push({ id: match[1], path: match[2], title: match[3].trim(), progress: match[4] });
352
+ }
353
+ }
354
+ }
355
+
356
+ return {
357
+ id: String(frontMatter.id || ''),
358
+ title: String(frontMatter.title || ''),
359
+ status: (frontMatter.status as Milestone['status']) || 'planned',
360
+ targetDate: (frontMatter.target_date as string) || null,
361
+ progress: (frontMatter.progress as string) || null,
362
+ definition: sections.get('Definition of Done') || '',
363
+ epics,
364
+ path: filePath,
365
+ };
366
+ }
367
+
368
+ export function parseBoard(content: string, filePath: string): Board {
369
+ const { frontMatter: fm, body } = parseFrontMatter(content);
370
+ const sections = extractSections(body);
371
+
372
+ let executionSummary: BoardExecutionSummary | null = null;
373
+ if (fm.execution_summary && typeof fm.execution_summary === 'object') {
374
+ const es = fm.execution_summary as Record<string, unknown>;
375
+ executionSummary = {
376
+ totalIssues: Number(es.total_issues ?? 0),
377
+ completedIssues: Number(es.completed_issues ?? 0),
378
+ failedIssues: Number(es.failed_issues ?? 0),
379
+ totalDuration: Number(es.total_duration ?? 0),
380
+ waves: Number(es.waves ?? 0),
381
+ };
382
+ }
383
+
384
+ return {
385
+ id: String(fm.id || ''),
386
+ title: String(fm.title || ''),
387
+ status: (fm.status as Board['status']) || 'draft',
388
+ created: String(fm.created || ''),
389
+ completedAt: optionalString(fm.completed_at),
390
+ goal: String(fm.goal || sections.get('Goal') || ''),
391
+ executionSummary,
392
+ path: filePath,
393
+ };
394
+ }
395
+
396
+ export function parseWorkspace(content: string): Workspace {
397
+ try {
398
+ const parsed = JSON.parse(content) as Record<string, unknown>;
399
+ return {
400
+ activeBoardId: typeof parsed.activeBoardId === 'string' ? parsed.activeBoardId : null,
401
+ boardOrder: Array.isArray(parsed.boardOrder) ? parsed.boardOrder.map(String) : [],
402
+ };
403
+ } catch {
404
+ return { activeBoardId: null, boardOrder: [] };
405
+ }
406
+ }
@@ -0,0 +1,128 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ /**
5
+ * Legacy → Board-centric migration for .pm/ directories.
6
+ */
7
+
8
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ import { parseFrontMatter } from './parser-core.js';
11
+
12
+ /** Check whether a .pm/ directory uses the legacy flat format (has backlog/ at root, no boards/). */
13
+ export function isLegacyFormat(pmDir: string): boolean {
14
+ return existsSync(join(pmDir, 'backlog')) && !existsSync(join(pmDir, 'boards'));
15
+ }
16
+
17
+ function readFileIfExists(path: string): string | null {
18
+ try {
19
+ if (existsSync(path)) return readFileSync(path, 'utf-8');
20
+ } catch { /* skip */ }
21
+ return null;
22
+ }
23
+
24
+ /** Move all files from a legacy directory into a board subdirectory and remove the source. */
25
+ function moveLegacyDir(srcDir: string, destDir: string): void {
26
+ if (!existsSync(srcDir)) return;
27
+ for (const file of readdirSync(srcDir)) {
28
+ renameSync(join(srcDir, file), join(destDir, file));
29
+ }
30
+ rmSync(srcDir, { recursive: true });
31
+ }
32
+
33
+ /** Move a single file if it exists. */
34
+ function moveLegacyFile(src: string, dest: string): void {
35
+ if (existsSync(src)) renameSync(src, dest);
36
+ }
37
+
38
+ /** Copy review files from sprint sandbox directories into the board reviews dir. */
39
+ function copySprintReviews(sprintsDir: string, boardReviewsDir: string): void {
40
+ for (const entry of readdirSync(sprintsDir)) {
41
+ if (entry.endsWith('.md')) continue;
42
+ const reviewsDir = join(sprintsDir, entry, 'reviews');
43
+ if (!existsSync(reviewsDir)) continue;
44
+ for (const reviewFile of readdirSync(reviewsDir)) {
45
+ cpSync(join(reviewsDir, reviewFile), join(boardReviewsDir, reviewFile));
46
+ }
47
+ }
48
+ }
49
+
50
+ /** Find and return the goal from the active sprint .md file. */
51
+ function extractActiveSprintGoal(sprintsDir: string): string {
52
+ for (const entry of readdirSync(sprintsDir).filter(e => e.endsWith('.md'))) {
53
+ const content = readFileIfExists(join(sprintsDir, entry));
54
+ if (!content) continue;
55
+ const fm = parseFrontMatter(content).frontMatter;
56
+ if (fm.status === 'active') return String(fm.goal || '');
57
+ }
58
+ return '';
59
+ }
60
+
61
+ /** Migrate active sprint data into the board and archive sprints dir. */
62
+ function migrateLegacySprints(sprintsDir: string, boardReviewsDir: string): string {
63
+ if (!existsSync(sprintsDir)) return '';
64
+ const goal = extractActiveSprintGoal(sprintsDir);
65
+ copySprintReviews(sprintsDir, boardReviewsDir);
66
+ return goal;
67
+ }
68
+
69
+ /** Remove issues from the board backlog that are already tracked via paths in STATE.md */
70
+ function cleanupMigratedIssues(boardBacklogDir: string): boolean {
71
+ let hasActive = false;
72
+ for (const f of readdirSync(boardBacklogDir).filter(f => f.endsWith('.md'))) {
73
+ const content = readFileIfExists(join(boardBacklogDir, f));
74
+ if (!content) continue;
75
+ const fm = parseFrontMatter(content).frontMatter;
76
+ const status = String(fm.status || 'backlog');
77
+ if (status === 'done' || status === 'closed' || status === 'cancelled') {
78
+ rmSync(join(boardBacklogDir, f));
79
+ } else if (status !== 'backlog') {
80
+ hasActive = true;
81
+ }
82
+ }
83
+ return hasActive;
84
+ }
85
+
86
+ /** Write the board.md metadata file and optionally a STATE.md for active boards. */
87
+ function writeBoardMetadata(pmDir: string, boardDir: string, boardId: string, sprintGoal: string, hasActive: boolean): void {
88
+ const today = new Date().toISOString().split('T')[0];
89
+ const goal = sprintGoal || 'Migrated board';
90
+ writeFileSync(join(boardDir, 'board.md'), [
91
+ '---', `id: ${boardId}`, `title: Board 1`, `status: ${hasActive ? 'active' : 'draft'}`,
92
+ `created: ${today}`, `goal: ${goal}`, '---', '', `# Board 1`, '', `## Goal`, '', goal, '',
93
+ ].join('\n'));
94
+
95
+ // Copy existing STATE.md into the board directory
96
+ const legacyState = readFileIfExists(join(pmDir, 'STATE.md'));
97
+ if (legacyState) {
98
+ writeFileSync(join(boardDir, 'STATE.md'), legacyState);
99
+ }
100
+
101
+ // Write workspace.json pointing at the new board
102
+ writeFileSync(join(pmDir, 'workspace.json'), JSON.stringify({
103
+ activeBoardId: boardId, boardOrder: [boardId],
104
+ }, null, 2));
105
+ }
106
+
107
+ /** Migrate a legacy flat .pm/ directory to the board-centric format. */
108
+ export function migrateToBoards(pmDir: string): void {
109
+ const boardId = 'BOARD-001';
110
+ const boardDir = join(pmDir, 'boards', boardId);
111
+ mkdirSync(join(boardDir, 'backlog'), { recursive: true });
112
+ mkdirSync(join(boardDir, 'reviews'), { recursive: true });
113
+ mkdirSync(join(boardDir, 'out'), { recursive: true });
114
+
115
+ moveLegacyDir(join(pmDir, 'backlog'), join(boardDir, 'backlog'));
116
+ moveLegacyFile(join(pmDir, 'progress.md'), join(boardDir, 'progress.md'));
117
+
118
+ const boardReviewsDir = join(boardDir, 'reviews');
119
+ const sprintGoal = migrateLegacySprints(join(pmDir, 'sprints'), boardReviewsDir);
120
+ const hasActive = cleanupMigratedIssues(join(boardDir, 'backlog'));
121
+ writeBoardMetadata(pmDir, boardDir, boardId, sprintGoal, hasActive);
122
+
123
+ // Remove migrated top-level items (STATE.md is now inside the board)
124
+ for (const f of ['STATE.md']) {
125
+ const p = join(pmDir, f);
126
+ if (existsSync(p)) rmSync(p);
127
+ }
128
+ }