gsd-pi 2.33.1-dev.ee47f1b → 2.34.0-dev.0150ae9

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 (356) hide show
  1. package/dist/bundled-resource-path.d.ts +8 -0
  2. package/dist/bundled-resource-path.js +14 -0
  3. package/dist/headless-query.js +6 -6
  4. package/dist/resources/extensions/gsd/auto/session.js +27 -32
  5. package/dist/resources/extensions/gsd/auto-dashboard.js +29 -109
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +6 -1
  7. package/dist/resources/extensions/gsd/auto-dispatch.js +52 -81
  8. package/dist/resources/extensions/gsd/auto-loop.js +956 -0
  9. package/dist/resources/extensions/gsd/auto-observability.js +4 -2
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +75 -185
  11. package/dist/resources/extensions/gsd/auto-prompts.js +133 -101
  12. package/dist/resources/extensions/gsd/auto-recovery.js +59 -97
  13. package/dist/resources/extensions/gsd/auto-start.js +330 -309
  14. package/dist/resources/extensions/gsd/auto-supervisor.js +5 -11
  15. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +7 -7
  16. package/dist/resources/extensions/gsd/auto-timers.js +3 -4
  17. package/dist/resources/extensions/gsd/auto-verification.js +35 -73
  18. package/dist/resources/extensions/gsd/auto-worktree-sync.js +167 -0
  19. package/dist/resources/extensions/gsd/auto-worktree.js +291 -126
  20. package/dist/resources/extensions/gsd/auto.js +283 -1013
  21. package/dist/resources/extensions/gsd/captures.js +10 -4
  22. package/dist/resources/extensions/gsd/dispatch-guard.js +7 -8
  23. package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  24. package/dist/resources/extensions/gsd/doctor-checks.js +3 -4
  25. package/dist/resources/extensions/gsd/git-service.js +1 -1
  26. package/dist/resources/extensions/gsd/gsd-db.js +296 -151
  27. package/dist/resources/extensions/gsd/index.js +92 -228
  28. package/dist/resources/extensions/gsd/post-unit-hooks.js +13 -13
  29. package/dist/resources/extensions/gsd/progress-score.js +61 -156
  30. package/dist/resources/extensions/gsd/quick.js +98 -122
  31. package/dist/resources/extensions/gsd/session-lock.js +13 -0
  32. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  33. package/dist/resources/extensions/gsd/undo.js +43 -48
  34. package/dist/resources/extensions/gsd/unit-runtime.js +16 -15
  35. package/dist/resources/extensions/gsd/verification-evidence.js +0 -1
  36. package/dist/resources/extensions/gsd/verification-gate.js +6 -35
  37. package/dist/resources/extensions/gsd/worktree-command.js +30 -24
  38. package/dist/resources/extensions/gsd/worktree-manager.js +2 -3
  39. package/dist/resources/extensions/gsd/worktree-resolver.js +344 -0
  40. package/dist/resources/extensions/gsd/worktree.js +7 -44
  41. package/dist/resources/extensions/mcp-client/index.js +2 -1
  42. package/dist/tool-bootstrap.js +59 -11
  43. package/dist/worktree-cli.js +7 -7
  44. package/package.json +1 -1
  45. package/packages/native/dist/native.d.ts +0 -2
  46. package/packages/native/dist/native.js +0 -2
  47. package/packages/native/src/native.ts +0 -3
  48. package/packages/pi-agent-core/dist/proxy.d.ts +1 -25
  49. package/packages/pi-agent-core/dist/proxy.d.ts.map +1 -1
  50. package/packages/pi-agent-core/dist/proxy.js +1 -1
  51. package/packages/pi-agent-core/dist/proxy.js.map +1 -1
  52. package/packages/pi-agent-core/src/proxy.ts +1 -1
  53. package/packages/pi-ai/dist/api-registry.d.ts +0 -2
  54. package/packages/pi-ai/dist/api-registry.d.ts.map +1 -1
  55. package/packages/pi-ai/dist/api-registry.js +0 -10
  56. package/packages/pi-ai/dist/api-registry.js.map +1 -1
  57. package/packages/pi-ai/dist/models.generated.d.ts +3630 -5483
  58. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  59. package/packages/pi-ai/dist/models.generated.js +735 -2588
  60. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  61. package/packages/pi-ai/dist/providers/anthropic.d.ts +0 -8
  62. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  63. package/packages/pi-ai/dist/providers/anthropic.js +1 -1
  64. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  65. package/packages/pi-ai/dist/providers/github-copilot-headers.d.ts +0 -1
  66. package/packages/pi-ai/dist/providers/github-copilot-headers.d.ts.map +1 -1
  67. package/packages/pi-ai/dist/providers/github-copilot-headers.js +1 -1
  68. package/packages/pi-ai/dist/providers/github-copilot-headers.js.map +1 -1
  69. package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts +1 -43
  70. package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
  71. package/packages/pi-ai/dist/providers/google-gemini-cli.js +2 -2
  72. package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
  73. package/packages/pi-ai/dist/providers/google-shared.d.ts +0 -4
  74. package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
  75. package/packages/pi-ai/dist/providers/google-shared.js +1 -1
  76. package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
  77. package/packages/pi-ai/dist/providers/register-builtins.d.ts +0 -1
  78. package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
  79. package/packages/pi-ai/dist/providers/register-builtins.js +1 -1
  80. package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
  81. package/packages/pi-ai/dist/utils/event-stream.d.ts +0 -2
  82. package/packages/pi-ai/dist/utils/event-stream.d.ts.map +1 -1
  83. package/packages/pi-ai/dist/utils/event-stream.js +0 -4
  84. package/packages/pi-ai/dist/utils/event-stream.js.map +1 -1
  85. package/packages/pi-ai/dist/utils/overflow.d.ts +0 -4
  86. package/packages/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  87. package/packages/pi-ai/dist/utils/overflow.js +0 -6
  88. package/packages/pi-ai/dist/utils/overflow.js.map +1 -1
  89. package/packages/pi-ai/dist/utils/validation.d.ts +0 -8
  90. package/packages/pi-ai/dist/utils/validation.d.ts.map +1 -1
  91. package/packages/pi-ai/dist/utils/validation.js +0 -14
  92. package/packages/pi-ai/dist/utils/validation.js.map +1 -1
  93. package/packages/pi-ai/src/api-registry.ts +0 -12
  94. package/packages/pi-ai/src/models.generated.ts +1039 -2892
  95. package/packages/pi-ai/src/providers/anthropic.ts +1 -1
  96. package/packages/pi-ai/src/providers/github-copilot-headers.ts +1 -1
  97. package/packages/pi-ai/src/providers/google-gemini-cli.ts +2 -2
  98. package/packages/pi-ai/src/providers/google-shared.ts +1 -1
  99. package/packages/pi-ai/src/providers/register-builtins.ts +1 -1
  100. package/packages/pi-ai/src/utils/event-stream.ts +0 -5
  101. package/packages/pi-ai/src/utils/overflow.ts +1 -8
  102. package/packages/pi-ai/src/utils/validation.ts +0 -15
  103. package/packages/pi-coding-agent/dist/config.d.ts +0 -9
  104. package/packages/pi-coding-agent/dist/config.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/config.js +4 -8
  106. package/packages/pi-coding-agent/dist/config.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/export-html/ansi-to-html.d.ts +0 -4
  108. package/packages/pi-coding-agent/dist/core/export-html/ansi-to-html.d.ts.map +1 -1
  109. package/packages/pi-coding-agent/dist/core/export-html/ansi-to-html.js +1 -1
  110. package/packages/pi-coding-agent/dist/core/export-html/ansi-to-html.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +0 -5
  112. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  113. package/packages/pi-coding-agent/dist/core/extensions/runner.js +0 -13
  114. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/core/keybindings.d.ts +0 -8
  116. package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  117. package/packages/pi-coding-agent/dist/core/keybindings.js +2 -2
  118. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +0 -17
  120. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/lsp/client.js +3 -62
  122. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +0 -2
  124. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/lsp/config.js +0 -7
  126. package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/lsp/edits.d.ts +0 -5
  128. package/packages/pi-coding-agent/dist/core/lsp/edits.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/lsp/edits.js +1 -1
  130. package/packages/pi-coding-agent/dist/core/lsp/edits.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts +0 -1
  132. package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +1 -1
  134. package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +1 -6
  136. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/lsp/utils.js +1 -28
  138. package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/messages.d.ts +0 -8
  140. package/packages/pi-coding-agent/dist/core/messages.d.ts.map +1 -1
  141. package/packages/pi-coding-agent/dist/core/messages.js +5 -5
  142. package/packages/pi-coding-agent/dist/core/messages.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +0 -3
  144. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/core/model-registry.js +1 -3
  146. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts +1 -26
  148. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -59
  150. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  151. package/packages/pi-coding-agent/dist/core/prompt-templates.d.ts +0 -17
  152. package/packages/pi-coding-agent/dist/core/prompt-templates.d.ts.map +1 -1
  153. package/packages/pi-coding-agent/dist/core/prompt-templates.js +2 -2
  154. package/packages/pi-coding-agent/dist/core/prompt-templates.js.map +1 -1
  155. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +0 -4
  156. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  157. package/packages/pi-coding-agent/dist/core/session-manager.js +2 -4
  158. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  159. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +0 -12
  160. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  161. package/packages/pi-coding-agent/dist/core/settings-manager.js +2 -2
  162. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  163. package/packages/pi-coding-agent/dist/migrations.d.ts +0 -16
  164. package/packages/pi-coding-agent/dist/migrations.d.ts.map +1 -1
  165. package/packages/pi-coding-agent/dist/migrations.js +2 -2
  166. package/packages/pi-coding-agent/dist/migrations.js.map +1 -1
  167. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector-search.d.ts +0 -2
  168. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
  169. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector-search.js +2 -2
  170. package/packages/pi-coding-agent/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  171. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -24
  172. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  173. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +50 -512
  174. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  175. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts +71 -0
  176. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -0
  177. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +514 -0
  178. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -0
  179. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts +65 -4
  180. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  181. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +6 -23
  182. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  183. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts +12 -0
  184. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -0
  185. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +175 -0
  186. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -0
  187. package/packages/pi-coding-agent/dist/utils/changelog.d.ts +0 -4
  188. package/packages/pi-coding-agent/dist/utils/changelog.d.ts.map +1 -1
  189. package/packages/pi-coding-agent/dist/utils/changelog.js +1 -1
  190. package/packages/pi-coding-agent/dist/utils/changelog.js.map +1 -1
  191. package/packages/pi-coding-agent/dist/utils/clipboard-image.d.ts +0 -1
  192. package/packages/pi-coding-agent/dist/utils/clipboard-image.d.ts.map +1 -1
  193. package/packages/pi-coding-agent/dist/utils/clipboard-image.js +1 -1
  194. package/packages/pi-coding-agent/dist/utils/clipboard-image.js.map +1 -1
  195. package/packages/pi-coding-agent/dist/utils/photon.d.ts +0 -19
  196. package/packages/pi-coding-agent/dist/utils/photon.d.ts.map +1 -1
  197. package/packages/pi-coding-agent/dist/utils/photon.js +1 -120
  198. package/packages/pi-coding-agent/dist/utils/photon.js.map +1 -1
  199. package/packages/pi-coding-agent/dist/utils/tools-manager.d.ts +0 -1
  200. package/packages/pi-coding-agent/dist/utils/tools-manager.d.ts.map +1 -1
  201. package/packages/pi-coding-agent/dist/utils/tools-manager.js +1 -1
  202. package/packages/pi-coding-agent/dist/utils/tools-manager.js.map +1 -1
  203. package/packages/pi-coding-agent/package.json +1 -1
  204. package/packages/pi-coding-agent/src/config.ts +5 -10
  205. package/packages/pi-coding-agent/src/core/export-html/ansi-to-html.ts +1 -1
  206. package/packages/pi-coding-agent/src/core/extensions/runner.ts +0 -13
  207. package/packages/pi-coding-agent/src/core/keybindings.ts +2 -2
  208. package/packages/pi-coding-agent/src/core/lsp/client.ts +3 -73
  209. package/packages/pi-coding-agent/src/core/lsp/config.ts +0 -11
  210. package/packages/pi-coding-agent/src/core/lsp/edits.ts +1 -1
  211. package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +1 -1
  212. package/packages/pi-coding-agent/src/core/lsp/utils.ts +1 -33
  213. package/packages/pi-coding-agent/src/core/messages.ts +5 -5
  214. package/packages/pi-coding-agent/src/core/model-registry.ts +0 -2
  215. package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -77
  216. package/packages/pi-coding-agent/src/core/prompt-templates.ts +2 -2
  217. package/packages/pi-coding-agent/src/core/session-manager.ts +2 -4
  218. package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
  219. package/packages/pi-coding-agent/src/migrations.ts +2 -2
  220. package/packages/pi-coding-agent/src/modes/interactive/components/session-selector-search.ts +2 -2
  221. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +50 -561
  222. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +653 -0
  223. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +7 -26
  224. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +196 -0
  225. package/packages/pi-coding-agent/src/utils/changelog.ts +1 -1
  226. package/packages/pi-coding-agent/src/utils/clipboard-image.ts +1 -1
  227. package/packages/pi-coding-agent/src/utils/photon.ts +0 -137
  228. package/packages/pi-coding-agent/src/utils/tools-manager.ts +1 -1
  229. package/packages/pi-tui/dist/components/editor.d.ts +0 -10
  230. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  231. package/packages/pi-tui/dist/components/editor.js +1 -1
  232. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  233. package/packages/pi-tui/dist/overlay-layout.d.ts +55 -0
  234. package/packages/pi-tui/dist/overlay-layout.d.ts.map +1 -0
  235. package/packages/pi-tui/dist/overlay-layout.js +288 -0
  236. package/packages/pi-tui/dist/overlay-layout.js.map +1 -0
  237. package/packages/pi-tui/dist/tui.d.ts +0 -22
  238. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  239. package/packages/pi-tui/dist/tui.js +6 -272
  240. package/packages/pi-tui/dist/tui.js.map +1 -1
  241. package/packages/pi-tui/dist/utils.d.ts +0 -7
  242. package/packages/pi-tui/dist/utils.d.ts.map +1 -1
  243. package/packages/pi-tui/dist/utils.js +0 -44
  244. package/packages/pi-tui/dist/utils.js.map +1 -1
  245. package/packages/pi-tui/src/components/editor.ts +1 -1
  246. package/packages/pi-tui/src/overlay-layout.ts +372 -0
  247. package/packages/pi-tui/src/tui.ts +11 -312
  248. package/packages/pi-tui/src/utils.ts +0 -43
  249. package/pkg/dist/core/export-html/ansi-to-html.d.ts +0 -4
  250. package/pkg/dist/core/export-html/ansi-to-html.d.ts.map +1 -1
  251. package/pkg/dist/core/export-html/ansi-to-html.js +1 -1
  252. package/pkg/dist/core/export-html/ansi-to-html.js.map +1 -1
  253. package/pkg/dist/modes/interactive/theme/theme.d.ts +65 -4
  254. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  255. package/pkg/dist/modes/interactive/theme/theme.js +6 -23
  256. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  257. package/pkg/dist/modes/interactive/theme/themes.d.ts +12 -0
  258. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -0
  259. package/pkg/dist/modes/interactive/theme/themes.js +175 -0
  260. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -0
  261. package/pkg/package.json +1 -1
  262. package/src/resources/extensions/gsd/auto/session.ts +47 -30
  263. package/src/resources/extensions/gsd/auto-dashboard.ts +28 -131
  264. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +6 -1
  265. package/src/resources/extensions/gsd/auto-dispatch.ts +135 -91
  266. package/src/resources/extensions/gsd/auto-loop.ts +1665 -0
  267. package/src/resources/extensions/gsd/auto-observability.ts +4 -2
  268. package/src/resources/extensions/gsd/auto-post-unit.ts +85 -228
  269. package/src/resources/extensions/gsd/auto-prompts.ts +138 -109
  270. package/src/resources/extensions/gsd/auto-recovery.ts +124 -118
  271. package/src/resources/extensions/gsd/auto-start.ts +440 -354
  272. package/src/resources/extensions/gsd/auto-supervisor.ts +5 -12
  273. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +8 -8
  274. package/src/resources/extensions/gsd/auto-timers.ts +3 -4
  275. package/src/resources/extensions/gsd/auto-verification.ts +76 -90
  276. package/src/resources/extensions/gsd/auto-worktree-sync.ts +204 -0
  277. package/src/resources/extensions/gsd/auto-worktree.ts +389 -141
  278. package/src/resources/extensions/gsd/auto.ts +515 -1199
  279. package/src/resources/extensions/gsd/captures.ts +10 -4
  280. package/src/resources/extensions/gsd/dispatch-guard.ts +13 -9
  281. package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  282. package/src/resources/extensions/gsd/doctor-checks.ts +3 -4
  283. package/src/resources/extensions/gsd/git-service.ts +8 -1
  284. package/src/resources/extensions/gsd/gitignore.ts +4 -2
  285. package/src/resources/extensions/gsd/gsd-db.ts +375 -180
  286. package/src/resources/extensions/gsd/index.ts +104 -263
  287. package/src/resources/extensions/gsd/post-unit-hooks.ts +13 -13
  288. package/src/resources/extensions/gsd/progress-score.ts +65 -200
  289. package/src/resources/extensions/gsd/quick.ts +121 -125
  290. package/src/resources/extensions/gsd/session-lock.ts +11 -0
  291. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  292. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +32 -59
  293. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +75 -27
  294. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
  295. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +37 -0
  296. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1458 -0
  297. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +8 -162
  298. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -108
  299. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +1 -3
  300. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +0 -3
  301. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  302. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -55
  303. package/src/resources/extensions/gsd/tests/headless-query.test.ts +22 -0
  304. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +8 -11
  305. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +4 -6
  306. package/src/resources/extensions/gsd/tests/run-uat.test.ts +3 -3
  307. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +64 -0
  308. package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +181 -0
  309. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +0 -3
  310. package/src/resources/extensions/gsd/tests/token-profile.test.ts +6 -6
  311. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -6
  312. package/src/resources/extensions/gsd/tests/undo.test.ts +6 -0
  313. package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
  314. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +7 -201
  315. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  316. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  317. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +0 -3
  318. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +705 -0
  319. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +57 -106
  320. package/src/resources/extensions/gsd/tests/worktree.test.ts +5 -1
  321. package/src/resources/extensions/gsd/tests/write-gate.test.ts +43 -132
  322. package/src/resources/extensions/gsd/types.ts +90 -81
  323. package/src/resources/extensions/gsd/undo.ts +42 -46
  324. package/src/resources/extensions/gsd/unit-runtime.ts +14 -18
  325. package/src/resources/extensions/gsd/verification-evidence.ts +1 -3
  326. package/src/resources/extensions/gsd/verification-gate.ts +6 -39
  327. package/src/resources/extensions/gsd/worktree-command.ts +36 -24
  328. package/src/resources/extensions/gsd/worktree-manager.ts +2 -3
  329. package/src/resources/extensions/gsd/worktree-resolver.ts +485 -0
  330. package/src/resources/extensions/gsd/worktree.ts +7 -44
  331. package/src/resources/extensions/mcp-client/index.ts +2 -1
  332. package/dist/resources/extensions/gsd/auto-constants.js +0 -5
  333. package/dist/resources/extensions/gsd/auto-idempotency.js +0 -106
  334. package/dist/resources/extensions/gsd/auto-stuck-detection.js +0 -165
  335. package/dist/resources/extensions/gsd/mechanical-completion.js +0 -351
  336. package/packages/pi-coding-agent/dist/modes/interactive/theme/dark.json +0 -85
  337. package/packages/pi-coding-agent/dist/modes/interactive/theme/light.json +0 -84
  338. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme-schema.json +0 -335
  339. package/packages/pi-coding-agent/src/modes/interactive/theme/dark.json +0 -85
  340. package/packages/pi-coding-agent/src/modes/interactive/theme/light.json +0 -84
  341. package/packages/pi-coding-agent/src/modes/interactive/theme/theme-schema.json +0 -335
  342. package/pkg/dist/modes/interactive/theme/dark.json +0 -85
  343. package/pkg/dist/modes/interactive/theme/light.json +0 -84
  344. package/pkg/dist/modes/interactive/theme/theme-schema.json +0 -335
  345. package/src/resources/extensions/gsd/auto-constants.ts +0 -6
  346. package/src/resources/extensions/gsd/auto-idempotency.ts +0 -151
  347. package/src/resources/extensions/gsd/auto-stuck-detection.ts +0 -221
  348. package/src/resources/extensions/gsd/mechanical-completion.ts +0 -430
  349. package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +0 -691
  350. package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +0 -127
  351. package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +0 -123
  352. package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +0 -126
  353. package/src/resources/extensions/gsd/tests/loop-regression.test.ts +0 -874
  354. package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +0 -356
  355. package/src/resources/extensions/gsd/tests/progress-score.test.ts +0 -206
  356. package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -434
@@ -1,874 +0,0 @@
1
- /**
2
- * Regression test suite for the auto-mode dispatch loop.
3
- * Covers phase transitions, dispatch rule matching, state derivation edge cases,
4
- * and every fix from the #1308 issue catalog.
5
- *
6
- * Run: node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs \
7
- * --experimental-strip-types --test src/resources/extensions/gsd/tests/loop-regression.test.ts
8
- */
9
-
10
- import { mkdirSync, writeFileSync, rmSync, existsSync, readFileSync } from "node:fs";
11
- import { join } from "node:path";
12
- import { tmpdir } from "node:os";
13
- import test from "node:test";
14
- import assert from "node:assert/strict";
15
- import { deriveState } from "../state.ts";
16
- import { resolveDispatch, getDispatchRuleNames } from "../auto-dispatch.ts";
17
- import type { GSDState } from "../types.ts";
18
-
19
- // ─── Helpers ──────────────────────────────────────────────────────────────
20
-
21
- function makeTmp(name: string): string {
22
- const dir = join(tmpdir(), `loop-regression-${name}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
23
- mkdirSync(dir, { recursive: true });
24
- return dir;
25
- }
26
-
27
- function writeGsdFile(base: string, ...pathParts: string[]): void {
28
- const fullPath = join(base, ".gsd", ...pathParts);
29
- mkdirSync(join(fullPath, ".."), { recursive: true });
30
- // Default to empty content; callers use writeGsdFileContent for real content
31
- }
32
-
33
- function writeGsdFileContent(base: string, relativePath: string, content: string): void {
34
- const fullPath = join(base, ".gsd", relativePath);
35
- mkdirSync(join(fullPath, ".."), { recursive: true });
36
- writeFileSync(fullPath, content, "utf-8");
37
- }
38
-
39
- function buildMinimalRoadmap(slices: Array<{ id: string; title: string; done: boolean; depends?: string[] }>): string {
40
- const lines = ["# M001: Test Milestone", "", "## Slices", ""];
41
- for (const s of slices) {
42
- const cb = s.done ? "x" : " ";
43
- const deps = s.depends?.length ? ` \`depends:[${s.depends.join(",")}]\`` : " `depends:[]`";
44
- lines.push(`- [${cb}] **${s.id}: ${s.title}** \`risk:low\`${deps}`);
45
- lines.push(` > Demo text for ${s.id}`);
46
- lines.push("");
47
- }
48
- return lines.join("\n");
49
- }
50
-
51
- function buildMinimalPlan(tasks: Array<{ id: string; title: string; done: boolean }>): string {
52
- const lines = ["# S01: Test Slice", "", "**Goal:** test", "", "## Tasks", ""];
53
- for (const t of tasks) {
54
- const cb = t.done ? "x" : " ";
55
- lines.push(`- [${cb}] **${t.id}: ${t.title}** \`est:5m\``);
56
- }
57
- return lines.join("\n");
58
- }
59
-
60
- function buildMinimalSummary(id: string): string {
61
- return [
62
- "---",
63
- `id: ${id}`,
64
- "parent: S01",
65
- "milestone: M001",
66
- "duration: 5m",
67
- "verification_result: passed",
68
- `completed_at: ${new Date().toISOString()}`,
69
- "---",
70
- "",
71
- `# ${id}: Done`,
72
- "",
73
- "Completed.",
74
- ].join("\n");
75
- }
76
-
77
- // ─── Phase 1: Dispatch Rule Ordering ──────────────────────────────────────
78
-
79
- test("dispatch rules are in the expected order", () => {
80
- const names = getDispatchRuleNames();
81
- assert.ok(names.length >= 15, `expected ≥15 rules, got ${names.length}`);
82
-
83
- // Verify critical ordering: override gate first, complete-slice before UAT,
84
- // needs-discussion before pre-planning, executing last
85
- const overrideIdx = names.indexOf("rewrite-docs (override gate)");
86
- const completeSliceIdx = names.indexOf("summarizing → complete-slice");
87
- const uatGateIdx = names.indexOf("uat-verdict-gate (non-PASS blocks progression)");
88
- const needsDiscussIdx = names.indexOf("needs-discussion → stop");
89
- const prePlanNoCtxIdx = names.indexOf("pre-planning (no context) → stop");
90
- const executeIdx = names.indexOf("executing → execute-task");
91
-
92
- assert.ok(overrideIdx === 0, "override gate should be first rule");
93
- assert.ok(completeSliceIdx < uatGateIdx, "complete-slice should fire before UAT gate");
94
- assert.ok(needsDiscussIdx < prePlanNoCtxIdx, "needs-discussion should fire before pre-planning");
95
- assert.ok(executeIdx > prePlanNoCtxIdx, "execute-task should fire after pre-planning rules");
96
- });
97
-
98
- // ─── Phase 2: State Derivation — Phase Transitions ───────────────────────
99
-
100
- test("deriveState: empty project → pre-planning with no milestones", async () => {
101
- const tmp = makeTmp("empty");
102
- try {
103
- mkdirSync(join(tmp, ".gsd", "milestones"), { recursive: true });
104
- const state = await deriveState(tmp);
105
- assert.equal(state.phase, "pre-planning");
106
- assert.equal(state.activeMilestone, null);
107
- assert.deepEqual(state.registry, []);
108
- } finally {
109
- rmSync(tmp, { recursive: true, force: true });
110
- }
111
- });
112
-
113
- test("deriveState: milestone with context but no roadmap → pre-planning", async () => {
114
- const tmp = makeTmp("no-roadmap");
115
- try {
116
- const mDir = join(tmp, ".gsd", "milestones", "M001");
117
- mkdirSync(mDir, { recursive: true });
118
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001: Test\n\nContext here.");
119
- const state = await deriveState(tmp);
120
- assert.equal(state.phase, "pre-planning");
121
- assert.equal(state.activeMilestone?.id, "M001");
122
- } finally {
123
- rmSync(tmp, { recursive: true, force: true });
124
- }
125
- });
126
-
127
- test("deriveState: milestone with CONTEXT-DRAFT.md → needs-discussion", async () => {
128
- const tmp = makeTmp("draft");
129
- try {
130
- const mDir = join(tmp, ".gsd", "milestones", "M001");
131
- mkdirSync(mDir, { recursive: true });
132
- writeFileSync(join(mDir, "M001-CONTEXT-DRAFT.md"), "# Draft\n\nSome ideas.");
133
- const state = await deriveState(tmp);
134
- assert.equal(state.phase, "needs-discussion");
135
- assert.equal(state.activeMilestone?.id, "M001");
136
- } finally {
137
- rmSync(tmp, { recursive: true, force: true });
138
- }
139
- });
140
-
141
- test("deriveState: roadmap with no plan → planning", async () => {
142
- const tmp = makeTmp("planning");
143
- try {
144
- const mDir = join(tmp, ".gsd", "milestones", "M001");
145
- mkdirSync(join(mDir, "slices", "S01"), { recursive: true });
146
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001\n\nContext.");
147
- writeFileSync(join(mDir, "M001-ROADMAP.md"), buildMinimalRoadmap([
148
- { id: "S01", title: "First Slice", done: false },
149
- ]));
150
- const state = await deriveState(tmp);
151
- assert.equal(state.phase, "planning");
152
- assert.equal(state.activeSlice?.id, "S01");
153
- } finally {
154
- rmSync(tmp, { recursive: true, force: true });
155
- }
156
- });
157
-
158
- test("deriveState: plan with incomplete tasks → executing", async () => {
159
- const tmp = makeTmp("executing");
160
- try {
161
- const mDir = join(tmp, ".gsd", "milestones", "M001");
162
- const sDir = join(mDir, "slices", "S01");
163
- mkdirSync(join(sDir, "tasks"), { recursive: true });
164
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001\n\nContext.");
165
- writeFileSync(join(mDir, "M001-ROADMAP.md"), buildMinimalRoadmap([
166
- { id: "S01", title: "First Slice", done: false },
167
- ]));
168
- writeFileSync(join(sDir, "S01-PLAN.md"), buildMinimalPlan([
169
- { id: "T01", title: "Task One", done: false },
170
- { id: "T02", title: "Task Two", done: false },
171
- ]));
172
- writeFileSync(join(sDir, "tasks", "T01-PLAN.md"), "# T01 Plan\n\nDo stuff.");
173
- writeFileSync(join(sDir, "tasks", "T02-PLAN.md"), "# T02 Plan\n\nDo more.");
174
- const state = await deriveState(tmp);
175
- assert.equal(state.phase, "executing");
176
- assert.equal(state.activeTask?.id, "T01");
177
- } finally {
178
- rmSync(tmp, { recursive: true, force: true });
179
- }
180
- });
181
-
182
- test("deriveState: all tasks done → summarizing", async () => {
183
- const tmp = makeTmp("summarizing");
184
- try {
185
- const mDir = join(tmp, ".gsd", "milestones", "M001");
186
- const sDir = join(mDir, "slices", "S01");
187
- mkdirSync(join(sDir, "tasks"), { recursive: true });
188
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001\n\nContext.");
189
- writeFileSync(join(mDir, "M001-ROADMAP.md"), buildMinimalRoadmap([
190
- { id: "S01", title: "First Slice", done: false },
191
- ]));
192
- writeFileSync(join(sDir, "S01-PLAN.md"), buildMinimalPlan([
193
- { id: "T01", title: "Task One", done: true },
194
- ]));
195
- writeFileSync(join(sDir, "tasks", "T01-SUMMARY.md"), buildMinimalSummary("T01"));
196
- const state = await deriveState(tmp);
197
- assert.equal(state.phase, "summarizing");
198
- assert.equal(state.activeSlice?.id, "S01");
199
- assert.equal(state.activeTask, null);
200
- } finally {
201
- rmSync(tmp, { recursive: true, force: true });
202
- }
203
- });
204
-
205
- test("deriveState: all slices done → validating-milestone", async () => {
206
- const tmp = makeTmp("validating");
207
- try {
208
- const mDir = join(tmp, ".gsd", "milestones", "M001");
209
- const sDir = join(mDir, "slices", "S01");
210
- mkdirSync(join(sDir, "tasks"), { recursive: true });
211
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001\n\nContext.");
212
- writeFileSync(join(mDir, "M001-ROADMAP.md"), buildMinimalRoadmap([
213
- { id: "S01", title: "First Slice", done: true },
214
- ]));
215
- writeFileSync(join(sDir, "S01-PLAN.md"), buildMinimalPlan([
216
- { id: "T01", title: "Task One", done: true },
217
- ]));
218
- writeFileSync(join(sDir, "tasks", "T01-SUMMARY.md"), buildMinimalSummary("T01"));
219
- writeFileSync(join(sDir, "S01-SUMMARY.md"), "# S01 Summary\n\nDone.");
220
- const state = await deriveState(tmp);
221
- assert.equal(state.phase, "validating-milestone");
222
- } finally {
223
- rmSync(tmp, { recursive: true, force: true });
224
- }
225
- });
226
-
227
- test("deriveState: validation terminal → completing-milestone", async () => {
228
- const tmp = makeTmp("completing");
229
- try {
230
- const mDir = join(tmp, ".gsd", "milestones", "M001");
231
- const sDir = join(mDir, "slices", "S01");
232
- mkdirSync(join(sDir, "tasks"), { recursive: true });
233
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001\n\nContext.");
234
- writeFileSync(join(mDir, "M001-ROADMAP.md"), buildMinimalRoadmap([
235
- { id: "S01", title: "First Slice", done: true },
236
- ]));
237
- writeFileSync(join(sDir, "S01-PLAN.md"), buildMinimalPlan([
238
- { id: "T01", title: "Task One", done: true },
239
- ]));
240
- writeFileSync(join(sDir, "tasks", "T01-SUMMARY.md"), buildMinimalSummary("T01"));
241
- writeFileSync(join(sDir, "S01-SUMMARY.md"), "# S01 Summary\n\nDone.");
242
- writeFileSync(join(mDir, "M001-VALIDATION.md"), "---\nverdict: pass\nremediation_round: 0\n---\n\n# Validation\n\nAll good.");
243
- const state = await deriveState(tmp);
244
- assert.equal(state.phase, "completing-milestone");
245
- } finally {
246
- rmSync(tmp, { recursive: true, force: true });
247
- }
248
- });
249
-
250
- test("deriveState: milestone with summary → complete", async () => {
251
- const tmp = makeTmp("complete");
252
- try {
253
- const mDir = join(tmp, ".gsd", "milestones", "M001");
254
- mkdirSync(mDir, { recursive: true });
255
- writeFileSync(join(mDir, "M001-ROADMAP.md"), buildMinimalRoadmap([
256
- { id: "S01", title: "First Slice", done: true },
257
- ]));
258
- writeFileSync(join(mDir, "M001-SUMMARY.md"), "# M001 Summary\n\nMilestone complete.");
259
- const state = await deriveState(tmp);
260
- assert.equal(state.phase, "complete");
261
- } finally {
262
- rmSync(tmp, { recursive: true, force: true });
263
- }
264
- });
265
-
266
- // ─── Phase 3: Regression Tests for Specific Bug Fixes ────────────────────
267
-
268
- test("#1155: completion-transition codes should NOT be fixable at task level", async () => {
269
- // Verify COMPLETION_TRANSITION_CODES exists and contains expected codes
270
- const { COMPLETION_TRANSITION_CODES } = await import("../doctor-types.ts");
271
- assert.ok(COMPLETION_TRANSITION_CODES.has("all_tasks_done_missing_slice_summary"));
272
- assert.ok(COMPLETION_TRANSITION_CODES.has("all_tasks_done_missing_slice_uat"));
273
- assert.ok(COMPLETION_TRANSITION_CODES.has("all_tasks_done_roadmap_not_checked"));
274
- });
275
-
276
- test("#1170: needs-discussion phase is correctly derived from CONTEXT-DRAFT.md", async () => {
277
- const tmp = makeTmp("needs-discussion");
278
- try {
279
- const mDir = join(tmp, ".gsd", "milestones", "M001");
280
- mkdirSync(mDir, { recursive: true });
281
- writeFileSync(join(mDir, "M001-CONTEXT-DRAFT.md"), "# Draft\n\nDraft context.");
282
- const state = await deriveState(tmp);
283
- assert.equal(state.phase, "needs-discussion");
284
- // Verify the dispatch table returns stop for needs-discussion
285
- const result = await resolveDispatch({
286
- basePath: tmp, mid: "M001", midTitle: "Test", state, prefs: undefined,
287
- });
288
- assert.equal(result.action, "stop");
289
- } finally {
290
- rmSync(tmp, { recursive: true, force: true });
291
- }
292
- });
293
-
294
- test("#1176: state.registry is always an array even with corrupt/missing state", async () => {
295
- const tmp = makeTmp("empty-registry");
296
- try {
297
- mkdirSync(join(tmp, ".gsd", "milestones"), { recursive: true });
298
- const state = await deriveState(tmp);
299
- assert.ok(Array.isArray(state.registry), "registry should be an array");
300
- assert.equal(state.registry.length, 0);
301
- } finally {
302
- rmSync(tmp, { recursive: true, force: true });
303
- }
304
- });
305
-
306
- test("#1243: prose H3 slice headers are parsed correctly", async () => {
307
- const { parseRoadmapSlices } = await import("../roadmap-slices.ts");
308
- const content = `# M001: Test
309
-
310
- ## Roadmap
311
-
312
- ### S01: First Feature
313
- Depends on: none
314
-
315
- ### S02: Second Feature
316
- Depends on: S01
317
-
318
- ### S03: Third Feature
319
- `;
320
- const slices = parseRoadmapSlices(content);
321
- assert.equal(slices.length, 3, "should parse 3 H3 slices");
322
- assert.equal(slices[0]!.id, "S01");
323
- assert.equal(slices[1]!.id, "S02");
324
- assert.equal(slices[2]!.id, "S03");
325
- assert.deepEqual(slices[1]!.depends, ["S01"]);
326
- });
327
-
328
- test("#1243: bold-wrapped and dot-separator slice headers are parsed", async () => {
329
- const { parseRoadmapSlices } = await import("../roadmap-slices.ts");
330
- const content = `# M001
331
-
332
- ## **S01: Bold Wrapped**
333
- > Demo
334
-
335
- ## S02. Dot Separator Title
336
- > Demo
337
- `;
338
- const slices = parseRoadmapSlices(content);
339
- assert.equal(slices.length, 2);
340
- assert.equal(slices[0]!.id, "S01");
341
- assert.ok(slices[0]!.title.includes("Bold"), `title should contain Bold, got: ${slices[0]!.title}`);
342
- assert.equal(slices[1]!.id, "S02");
343
- });
344
-
345
- test("slice dependency blocking → phase: blocked", async () => {
346
- const tmp = makeTmp("dep-blocked");
347
- try {
348
- const mDir = join(tmp, ".gsd", "milestones", "M001");
349
- mkdirSync(join(mDir, "slices"), { recursive: true });
350
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001\n\nContext.");
351
- // S01 depends on S02 and S02 depends on S01 — circular!
352
- writeFileSync(join(mDir, "M001-ROADMAP.md"), buildMinimalRoadmap([
353
- { id: "S01", title: "Slice A", done: false, depends: ["S02"] },
354
- { id: "S02", title: "Slice B", done: false, depends: ["S01"] },
355
- ]));
356
- const state = await deriveState(tmp);
357
- assert.equal(state.phase, "blocked");
358
- assert.ok(state.blockers.length > 0, "should have blockers");
359
- } finally {
360
- rmSync(tmp, { recursive: true, force: true });
361
- }
362
- });
363
-
364
- test("multi-milestone: M001 complete, M002 active", async () => {
365
- const tmp = makeTmp("multi-milestone");
366
- try {
367
- // M001 — complete
368
- const m1Dir = join(tmp, ".gsd", "milestones", "M001");
369
- mkdirSync(m1Dir, { recursive: true });
370
- writeFileSync(join(m1Dir, "M001-ROADMAP.md"), buildMinimalRoadmap([
371
- { id: "S01", title: "Done", done: true },
372
- ]));
373
- writeFileSync(join(m1Dir, "M001-SUMMARY.md"), "# M001 Summary\n\nComplete.");
374
-
375
- // M002 — active, needs planning
376
- const m2Dir = join(tmp, ".gsd", "milestones", "M002");
377
- mkdirSync(m2Dir, { recursive: true });
378
- writeFileSync(join(m2Dir, "M002-CONTEXT.md"), "# M002\n\nNew work.");
379
-
380
- const state = await deriveState(tmp);
381
- assert.equal(state.activeMilestone?.id, "M002");
382
- assert.equal(state.phase, "pre-planning");
383
- assert.equal(state.registry.length, 2);
384
- assert.equal(state.registry[0]!.status, "complete");
385
- assert.equal(state.registry[1]!.status, "active");
386
- } finally {
387
- rmSync(tmp, { recursive: true, force: true });
388
- }
389
- });
390
-
391
- test("blocker_discovered in task summary → replanning-slice", async () => {
392
- const tmp = makeTmp("replan");
393
- try {
394
- const mDir = join(tmp, ".gsd", "milestones", "M001");
395
- const sDir = join(mDir, "slices", "S01");
396
- mkdirSync(join(sDir, "tasks"), { recursive: true });
397
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001\n\nContext.");
398
- writeFileSync(join(mDir, "M001-ROADMAP.md"), buildMinimalRoadmap([
399
- { id: "S01", title: "Test", done: false },
400
- ]));
401
- writeFileSync(join(sDir, "S01-PLAN.md"), buildMinimalPlan([
402
- { id: "T01", title: "Done", done: true },
403
- { id: "T02", title: "Todo", done: false },
404
- ]));
405
- writeFileSync(join(sDir, "tasks", "T01-PLAN.md"), "# T01\nPlan.");
406
- writeFileSync(join(sDir, "tasks", "T02-PLAN.md"), "# T02\nPlan.");
407
- writeFileSync(join(sDir, "tasks", "T01-SUMMARY.md"), [
408
- "---",
409
- "id: T01",
410
- "parent: S01",
411
- "milestone: M001",
412
- "blocker_discovered: true",
413
- "---",
414
- "",
415
- "# T01: Blocker found",
416
- "",
417
- "API doesn't support this.",
418
- ].join("\n"));
419
-
420
- const state = await deriveState(tmp);
421
- assert.equal(state.phase, "replanning-slice");
422
- assert.ok(state.blockers[0]!.includes("T01"), "blocker should reference T01");
423
- } finally {
424
- rmSync(tmp, { recursive: true, force: true });
425
- }
426
- });
427
-
428
- // ─── Phase 4: Edge Cases ─────────────────────────────────────────────────
429
-
430
- test("empty plan file (0 tasks) → stays in planning", async () => {
431
- const tmp = makeTmp("empty-plan");
432
- try {
433
- const mDir = join(tmp, ".gsd", "milestones", "M001");
434
- const sDir = join(mDir, "slices", "S01");
435
- mkdirSync(join(sDir, "tasks"), { recursive: true });
436
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001\n\nContext.");
437
- writeFileSync(join(mDir, "M001-ROADMAP.md"), buildMinimalRoadmap([
438
- { id: "S01", title: "Test", done: false },
439
- ]));
440
- // Plan file exists but has no task entries
441
- writeFileSync(join(sDir, "S01-PLAN.md"), "# S01: Test\n\n**Goal:** test\n\n## Tasks\n");
442
-
443
- const state = await deriveState(tmp);
444
- assert.equal(state.phase, "planning");
445
- } finally {
446
- rmSync(tmp, { recursive: true, force: true });
447
- }
448
- });
449
-
450
- test("parked milestone is not treated as active or complete", async () => {
451
- const tmp = makeTmp("parked");
452
- try {
453
- const mDir = join(tmp, ".gsd", "milestones", "M001");
454
- mkdirSync(mDir, { recursive: true });
455
- writeFileSync(join(mDir, "M001-ROADMAP.md"), buildMinimalRoadmap([
456
- { id: "S01", title: "Test", done: false },
457
- ]));
458
- writeFileSync(join(mDir, "M001-PARKED.md"), "Parked for later.");
459
-
460
- const state = await deriveState(tmp);
461
- assert.equal(state.registry[0]!.status, "parked");
462
- assert.equal(state.activeMilestone, null);
463
- // Phase should be pre-planning (all milestones parked, not complete)
464
- assert.equal(state.phase, "pre-planning");
465
- } finally {
466
- rmSync(tmp, { recursive: true, force: true });
467
- }
468
- });
469
-
470
- // ─── Phase 5: Defensive Guards ───────────────────────────────────────────
471
-
472
- test("dispatch returns stop when phase=summarizing but activeSlice is null (corrupt state)", async () => {
473
- const corruptState: GSDState = {
474
- activeMilestone: { id: "M001", title: "Test" },
475
- activeSlice: null, // BUG: summarizing should always have activeSlice
476
- activeTask: null,
477
- phase: "summarizing",
478
- recentDecisions: [],
479
- blockers: [],
480
- nextAction: "",
481
- registry: [{ id: "M001", title: "Test", status: "active" }],
482
- requirements: { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 },
483
- progress: { milestones: { done: 0, total: 1 } },
484
- };
485
- const result = await resolveDispatch({
486
- basePath: "/tmp/fake", mid: "M001", midTitle: "Test", state: corruptState, prefs: undefined,
487
- });
488
- assert.equal(result.action, "stop", "should stop instead of crashing");
489
- assert.ok((result as any).reason.includes("no active slice"), `reason should mention missing slice: ${(result as any).reason}`);
490
- });
491
-
492
- test("dispatch returns stop when phase=executing but activeSlice is null (corrupt state)", async () => {
493
- const corruptState: GSDState = {
494
- activeMilestone: { id: "M001", title: "Test" },
495
- activeSlice: null,
496
- activeTask: { id: "T01", title: "Task" },
497
- phase: "executing",
498
- recentDecisions: [],
499
- blockers: [],
500
- nextAction: "",
501
- registry: [{ id: "M001", title: "Test", status: "active" }],
502
- requirements: { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 },
503
- progress: { milestones: { done: 0, total: 1 } },
504
- };
505
- const result = await resolveDispatch({
506
- basePath: "/tmp/fake", mid: "M001", midTitle: "Test", state: corruptState, prefs: undefined,
507
- });
508
- assert.equal(result.action, "stop", "should stop instead of crashing");
509
- });
510
-
511
- // ─── Phase 6: Worktree & Lock Consistency ────────────────────────────────
512
-
513
- test("repoIdentity returns a 12-char hex hash", async () => {
514
- const { repoIdentity } = await import("../repo-identity.ts");
515
- const hash = repoIdentity(process.cwd());
516
- assert.ok(hash.length === 12, `hash should be 12 hex chars, got: ${hash}`);
517
- assert.match(hash, /^[a-f0-9]{12}$/, `hash should be hex, got: ${hash}`);
518
- });
519
-
520
- test("session lock settings: retry path matches primary stale timeout", async () => {
521
- // Verify the fix for #1304 — retry lock must use same settings as primary
522
- const lockSource = (await import("node:fs")).readFileSync(
523
- "src/resources/extensions/gsd/session-lock.ts", "utf-8"
524
- );
525
- // Find all stale: settings
526
- const staleMatches = [...lockSource.matchAll(/stale:\s*(\d[\d_]*)/g)];
527
- const staleValues = staleMatches.map(m => parseInt(m[1]!.replace(/_/g, ""), 10));
528
- // All stale values should be the same (primary and retry aligned)
529
- const uniqueStale = [...new Set(staleValues)];
530
- assert.equal(uniqueStale.length, 1, `all stale timeouts should be identical, got: ${staleValues.join(", ")}`);
531
- });
532
-
533
- test("COMPLETION_TRANSITION_CODES are a subset of DoctorIssueCode", async () => {
534
- const { COMPLETION_TRANSITION_CODES } = await import("../doctor-types.ts");
535
- // Just verify the set is non-empty and contains expected codes
536
- assert.ok(COMPLETION_TRANSITION_CODES.size >= 3, "should have at least 3 transition codes");
537
- for (const code of COMPLETION_TRANSITION_CODES) {
538
- assert.ok(typeof code === "string", `code should be string: ${code}`);
539
- assert.ok(code.startsWith("all_tasks_done_"), `code should start with all_tasks_done_: ${code}`);
540
- }
541
- });
542
-
543
- // ─── Scope 2: State Derivation — Array Safety ────────────────────────────
544
-
545
- test("deriveState: registry is always an array with malformed roadmap", async () => {
546
- const tmp = makeTmp("malformed-roadmap");
547
- try {
548
- const mDir = join(tmp, ".gsd", "milestones", "M001");
549
- mkdirSync(mDir, { recursive: true });
550
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001\n\nContext.");
551
- // Roadmap exists but is completely empty
552
- writeFileSync(join(mDir, "M001-ROADMAP.md"), "");
553
- const state = await deriveState(tmp);
554
- assert.ok(Array.isArray(state.registry), "registry must be array");
555
- assert.equal(state.activeMilestone?.id, "M001");
556
- } finally {
557
- rmSync(tmp, { recursive: true, force: true });
558
- }
559
- });
560
-
561
- test("deriveState: plan with garbled content still returns valid state", async () => {
562
- const tmp = makeTmp("garbled-plan");
563
- try {
564
- const mDir = join(tmp, ".gsd", "milestones", "M001");
565
- const sDir = join(mDir, "slices", "S01");
566
- mkdirSync(join(sDir, "tasks"), { recursive: true });
567
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001\n\nContext.");
568
- writeFileSync(join(mDir, "M001-ROADMAP.md"), buildMinimalRoadmap([
569
- { id: "S01", title: "Test", done: false },
570
- ]));
571
- // Plan file exists but contains garbage
572
- writeFileSync(join(sDir, "S01-PLAN.md"), "just some random text\nno tasks here\n!!!");
573
- const state = await deriveState(tmp);
574
- // Should fall back to planning since no tasks parsed
575
- assert.equal(state.phase, "planning");
576
- assert.equal(state.activeSlice?.id, "S01");
577
- } finally {
578
- rmSync(tmp, { recursive: true, force: true });
579
- }
580
- });
581
-
582
- // ─── Scope 4: Lock Management — Exit Handler Verification ────────────────
583
-
584
- test("session lock: releaseSessionLock removes auto.lock file", async () => {
585
- const tmp = makeTmp("lock-release");
586
- try {
587
- const gsd = join(tmp, ".gsd");
588
- mkdirSync(gsd, { recursive: true });
589
- const lockFile = join(gsd, "auto.lock");
590
- writeFileSync(lockFile, JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }));
591
- assert.ok(existsSync(lockFile), "lock file should exist before release");
592
-
593
- const { releaseSessionLock } = await import("../session-lock.ts");
594
- releaseSessionLock(tmp);
595
-
596
- assert.ok(!existsSync(lockFile), "lock file should be removed after release");
597
- } finally {
598
- rmSync(tmp, { recursive: true, force: true });
599
- }
600
- });
601
-
602
- test("session lock: onCompromised handler exists in both primary and retry paths", async () => {
603
- const lockSource = readFileSync(
604
- "src/resources/extensions/gsd/session-lock.ts", "utf-8"
605
- );
606
- const compromisedMatches = [...lockSource.matchAll(/onCompromised/g)];
607
- // Should have at least 2 onCompromised handlers (primary + retry)
608
- // plus the flag declaration and the check in validateSessionLock
609
- assert.ok(compromisedMatches.length >= 3,
610
- `expected ≥3 onCompromised references (primary + retry + flag), got ${compromisedMatches.length}`);
611
- });
612
-
613
- test("session lock: both onCompromised handlers null _releaseFunction (#1315)", async () => {
614
- const lockSource = readFileSync(
615
- "src/resources/extensions/gsd/session-lock.ts", "utf-8"
616
- );
617
- // Extract onCompromised handler blocks — both should set _releaseFunction = null
618
- const handlers = lockSource.match(/onCompromised:\s*\(\)\s*=>\s*\{[^}]+\}/g) || [];
619
- assert.ok(handlers.length >= 2, `expected ≥2 onCompromised handlers, got ${handlers.length}`);
620
- for (const h of handlers) {
621
- assert.ok(h.includes("_releaseFunction = null"),
622
- `onCompromised handler should null _releaseFunction: ${h}`);
623
- }
624
- });
625
-
626
- test("session lock: exit handler uses ensureExitHandler to prevent double-registration (#1315)", async () => {
627
- const lockSource = readFileSync(
628
- "src/resources/extensions/gsd/session-lock.ts", "utf-8"
629
- );
630
- // Should use ensureExitHandler instead of direct process.once("exit") in acquire paths
631
- const directExitHandlers = (lockSource.match(/process\.once\("exit"/g) || []).length;
632
- const ensureExitCalls = (lockSource.match(/ensureExitHandler\(/g) || []).length;
633
- // Only 1 direct process.once("exit") allowed — inside ensureExitHandler itself
634
- assert.ok(directExitHandlers <= 1,
635
- `expected ≤1 direct process.once("exit") (inside ensureExitHandler), got ${directExitHandlers}`);
636
- assert.ok(ensureExitCalls >= 2,
637
- `expected ≥2 ensureExitHandler calls (primary + retry path), got ${ensureExitCalls}`);
638
- });
639
-
640
- test("signal handler: SIGINT handler registered alongside SIGTERM (#1315)", async () => {
641
- const supervisorSource = readFileSync(
642
- "src/resources/extensions/gsd/auto-supervisor.ts", "utf-8"
643
- );
644
- // registerSigtermHandler should register on both SIGTERM and SIGINT
645
- assert.ok(supervisorSource.includes('process.on("SIGINT"') || supervisorSource.includes("process.on('SIGINT'"),
646
- "registerSigtermHandler should register SIGINT handler");
647
- assert.ok(supervisorSource.includes('process.off("SIGINT"') || supervisorSource.includes("process.off('SIGINT'"),
648
- "deregisterSigtermHandler should deregister SIGINT handler");
649
- });
650
-
651
- // ─── Scope 5: Crash Recovery — Message Guidance per Unit Type ────────────
652
-
653
- test("crash recovery: formatCrashInfo includes guidance for bootstrap crash", async () => {
654
- const { formatCrashInfo } = await import("../crash-recovery.ts");
655
- const info = formatCrashInfo({
656
- pid: 12345,
657
- startedAt: new Date().toISOString(),
658
- unitType: "starting",
659
- unitId: "bootstrap",
660
- unitStartedAt: new Date().toISOString(),
661
- completedUnits: 0,
662
- });
663
- assert.ok(info.includes("bootstrap"), "should mention bootstrap");
664
- assert.ok(info.includes("No work was lost") || info.includes("/gsd auto"),
665
- "should include recovery guidance for bootstrap crash");
666
- });
667
-
668
- test("crash recovery: formatCrashInfo includes guidance for execute-task crash", async () => {
669
- const { formatCrashInfo } = await import("../crash-recovery.ts");
670
- const info = formatCrashInfo({
671
- pid: 12345,
672
- startedAt: new Date().toISOString(),
673
- unitType: "execute-task",
674
- unitId: "M001/S01/T02",
675
- unitStartedAt: new Date().toISOString(),
676
- completedUnits: 5,
677
- });
678
- assert.ok(info.includes("execute"), "should mention execute");
679
- assert.ok(info.includes("resume") || info.includes("preserved") || info.includes("/gsd auto"),
680
- "should include recovery guidance for task crash");
681
- });
682
-
683
- test("crash recovery: formatCrashInfo includes guidance for complete-slice crash", async () => {
684
- const { formatCrashInfo } = await import("../crash-recovery.ts");
685
- const info = formatCrashInfo({
686
- pid: 12345,
687
- startedAt: new Date().toISOString(),
688
- unitType: "complete-slice",
689
- unitId: "M001/S01",
690
- unitStartedAt: new Date().toISOString(),
691
- completedUnits: 10,
692
- });
693
- assert.ok(info.includes("complete"), "should mention complete");
694
- assert.ok(info.includes("finish") || info.includes("/gsd auto"),
695
- "should include recovery guidance for completion crash");
696
- });
697
-
698
- test("crash recovery: formatCrashInfo includes guidance for research crash", async () => {
699
- const { formatCrashInfo } = await import("../crash-recovery.ts");
700
- const info = formatCrashInfo({
701
- pid: 12345,
702
- startedAt: new Date().toISOString(),
703
- unitType: "research-milestone",
704
- unitId: "M001",
705
- unitStartedAt: new Date().toISOString(),
706
- completedUnits: 1,
707
- });
708
- assert.ok(info.includes("research"), "should mention research");
709
- assert.ok(info.includes("incomplete") || info.includes("re-run") || info.includes("/gsd auto"),
710
- "should include recovery guidance for research crash");
711
- });
712
-
713
- // ─── Scope 6: Milestone Transitions — Dispatch Flow ─────────────────────
714
-
715
- test("dispatch: needs-discussion stops with discussion guidance", async () => {
716
- const tmp = makeTmp("dispatch-discussion");
717
- try {
718
- const mDir = join(tmp, ".gsd", "milestones", "M001");
719
- mkdirSync(mDir, { recursive: true });
720
- writeFileSync(join(mDir, "M001-CONTEXT-DRAFT.md"), "# Draft\n\nIdeas.");
721
- const state = await deriveState(tmp);
722
- const result = await resolveDispatch({
723
- basePath: tmp, mid: "M001", midTitle: "Test", state, prefs: undefined,
724
- });
725
- assert.equal(result.action, "stop");
726
- assert.ok((result as any).reason.includes("discussion") || (result as any).reason.includes("discuss"),
727
- "stop reason should mention discussion");
728
- } finally {
729
- rmSync(tmp, { recursive: true, force: true });
730
- }
731
- });
732
-
733
- test("dispatch: pre-planning without context stops with guidance", async () => {
734
- const tmp = makeTmp("dispatch-no-context");
735
- try {
736
- const mDir = join(tmp, ".gsd", "milestones", "M001");
737
- mkdirSync(mDir, { recursive: true });
738
- // No context, no roadmap — just a bare milestone directory
739
- const state = await deriveState(tmp);
740
- const result = await resolveDispatch({
741
- basePath: tmp, mid: "M001", midTitle: "Test", state, prefs: undefined,
742
- });
743
- assert.equal(result.action, "stop");
744
- assert.ok((result as any).reason.includes("context") || (result as any).reason.includes("discuss"),
745
- "stop reason should mention missing context");
746
- } finally {
747
- rmSync(tmp, { recursive: true, force: true });
748
- }
749
- });
750
-
751
- test("dispatch: pre-planning with context dispatches research-milestone", async () => {
752
- const tmp = makeTmp("dispatch-research");
753
- try {
754
- const mDir = join(tmp, ".gsd", "milestones", "M001");
755
- mkdirSync(mDir, { recursive: true });
756
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001\n\nBuild a thing.");
757
- const state = await deriveState(tmp);
758
- const result = await resolveDispatch({
759
- basePath: tmp, mid: "M001", midTitle: "Test", state, prefs: undefined,
760
- });
761
- assert.equal(result.action, "dispatch");
762
- assert.equal((result as any).unitType, "research-milestone");
763
- } finally {
764
- rmSync(tmp, { recursive: true, force: true });
765
- }
766
- });
767
-
768
- test("dispatch: executing phase dispatches execute-task", async () => {
769
- const tmp = makeTmp("dispatch-execute");
770
- try {
771
- const mDir = join(tmp, ".gsd", "milestones", "M001");
772
- const sDir = join(mDir, "slices", "S01");
773
- mkdirSync(join(sDir, "tasks"), { recursive: true });
774
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001\n\nContext.");
775
- writeFileSync(join(mDir, "M001-ROADMAP.md"), buildMinimalRoadmap([
776
- { id: "S01", title: "Test", done: false },
777
- ]));
778
- writeFileSync(join(sDir, "S01-PLAN.md"), buildMinimalPlan([
779
- { id: "T01", title: "Do work", done: false },
780
- ]));
781
- writeFileSync(join(sDir, "tasks", "T01-PLAN.md"), "# T01\nDo the thing.");
782
- const state = await deriveState(tmp);
783
- assert.equal(state.phase, "executing");
784
- const result = await resolveDispatch({
785
- basePath: tmp, mid: "M001", midTitle: "Test", state, prefs: undefined,
786
- });
787
- assert.equal(result.action, "dispatch");
788
- assert.equal((result as any).unitType, "execute-task");
789
- assert.equal((result as any).unitId, "M001/S01/T01");
790
- } finally {
791
- rmSync(tmp, { recursive: true, force: true });
792
- }
793
- });
794
-
795
- test("dispatch: summarizing phase dispatches complete-slice", async () => {
796
- const tmp = makeTmp("dispatch-complete-slice");
797
- try {
798
- const mDir = join(tmp, ".gsd", "milestones", "M001");
799
- const sDir = join(mDir, "slices", "S01");
800
- mkdirSync(join(sDir, "tasks"), { recursive: true });
801
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001\n\nContext.");
802
- writeFileSync(join(mDir, "M001-ROADMAP.md"), buildMinimalRoadmap([
803
- { id: "S01", title: "Test", done: false },
804
- ]));
805
- writeFileSync(join(sDir, "S01-PLAN.md"), buildMinimalPlan([
806
- { id: "T01", title: "Done task", done: true },
807
- ]));
808
- writeFileSync(join(sDir, "tasks", "T01-SUMMARY.md"), buildMinimalSummary("T01"));
809
- const state = await deriveState(tmp);
810
- assert.equal(state.phase, "summarizing");
811
- const result = await resolveDispatch({
812
- basePath: tmp, mid: "M001", midTitle: "Test", state, prefs: undefined,
813
- });
814
- assert.equal(result.action, "dispatch");
815
- assert.equal((result as any).unitType, "complete-slice");
816
- } finally {
817
- rmSync(tmp, { recursive: true, force: true });
818
- }
819
- });
820
-
821
- test("dispatch: validating-milestone dispatches validate-milestone", async () => {
822
- const tmp = makeTmp("dispatch-validate");
823
- try {
824
- const mDir = join(tmp, ".gsd", "milestones", "M001");
825
- const sDir = join(mDir, "slices", "S01");
826
- mkdirSync(join(sDir, "tasks"), { recursive: true });
827
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001\n\nContext.");
828
- writeFileSync(join(mDir, "M001-ROADMAP.md"), buildMinimalRoadmap([
829
- { id: "S01", title: "Test", done: true },
830
- ]));
831
- writeFileSync(join(sDir, "S01-PLAN.md"), buildMinimalPlan([
832
- { id: "T01", title: "Done", done: true },
833
- ]));
834
- writeFileSync(join(sDir, "tasks", "T01-SUMMARY.md"), buildMinimalSummary("T01"));
835
- writeFileSync(join(sDir, "S01-SUMMARY.md"), "# Summary\nDone.");
836
- const state = await deriveState(tmp);
837
- assert.equal(state.phase, "validating-milestone");
838
- const result = await resolveDispatch({
839
- basePath: tmp, mid: "M001", midTitle: "Test", state, prefs: undefined,
840
- });
841
- assert.equal(result.action, "dispatch");
842
- assert.equal((result as any).unitType, "validate-milestone");
843
- } finally {
844
- rmSync(tmp, { recursive: true, force: true });
845
- }
846
- });
847
-
848
- test("dispatch: completing-milestone dispatches complete-milestone", async () => {
849
- const tmp = makeTmp("dispatch-complete-ms");
850
- try {
851
- const mDir = join(tmp, ".gsd", "milestones", "M001");
852
- const sDir = join(mDir, "slices", "S01");
853
- mkdirSync(join(sDir, "tasks"), { recursive: true });
854
- writeFileSync(join(mDir, "M001-CONTEXT.md"), "# M001\n\nContext.");
855
- writeFileSync(join(mDir, "M001-ROADMAP.md"), buildMinimalRoadmap([
856
- { id: "S01", title: "Test", done: true },
857
- ]));
858
- writeFileSync(join(sDir, "S01-PLAN.md"), buildMinimalPlan([
859
- { id: "T01", title: "Done", done: true },
860
- ]));
861
- writeFileSync(join(sDir, "tasks", "T01-SUMMARY.md"), buildMinimalSummary("T01"));
862
- writeFileSync(join(sDir, "S01-SUMMARY.md"), "# Summary\nDone.");
863
- writeFileSync(join(mDir, "M001-VALIDATION.md"), "---\nverdict: pass\nremediation_round: 0\n---\n# Validation\nPassed.");
864
- const state = await deriveState(tmp);
865
- assert.equal(state.phase, "completing-milestone");
866
- const result = await resolveDispatch({
867
- basePath: tmp, mid: "M001", midTitle: "Test", state, prefs: undefined,
868
- });
869
- assert.equal(result.action, "dispatch");
870
- assert.equal((result as any).unitType, "complete-milestone");
871
- } finally {
872
- rmSync(tmp, { recursive: true, force: true });
873
- }
874
- });