@opengsd/gsd-pi 1.2.0-dev.4813ead6 → 1.2.0-dev.5457a158

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/dist/cli-style.d.ts +17 -0
  2. package/dist/cli-style.js +28 -0
  3. package/dist/cli.js +1 -1
  4. package/dist/headless-events.d.ts +4 -2
  5. package/dist/headless-events.js +7 -29
  6. package/dist/models-resolver.d.ts +3 -13
  7. package/dist/models-resolver.js +3 -22
  8. package/dist/resource-loader.js +2 -14
  9. package/dist/resources/.managed-resources-content-hash +1 -1
  10. package/dist/resources/extensions/bg-shell/utilities.js +5 -2
  11. package/dist/resources/extensions/claude-code-cli/models.js +9 -0
  12. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +35 -4
  13. package/dist/resources/extensions/gsd/auto/orchestrator.js +33 -4
  14. package/dist/resources/extensions/gsd/auto/phases.js +6 -1
  15. package/dist/resources/extensions/gsd/auto-post-unit.js +19 -8
  16. package/dist/resources/extensions/gsd/auto-prompts.js +3 -0
  17. package/dist/resources/extensions/gsd/auto-start.js +12 -14
  18. package/dist/resources/extensions/gsd/auto-tool-tracking.js +18 -0
  19. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +7 -16
  20. package/dist/resources/extensions/gsd/auto-worktree-repair.js +10 -2
  21. package/dist/resources/extensions/gsd/auto-worktree.js +35 -352
  22. package/dist/resources/extensions/gsd/auto.js +8 -20
  23. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +3 -2
  24. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -6
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +86 -6
  26. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +30 -4
  27. package/dist/resources/extensions/gsd/branch-patterns.js +2 -0
  28. package/dist/resources/extensions/gsd/captures.js +5 -15
  29. package/dist/resources/extensions/gsd/closeout-recovery.js +3 -2
  30. package/dist/resources/extensions/gsd/commands/catalog.js +6 -62
  31. package/dist/resources/extensions/gsd/crash-recovery.js +4 -12
  32. package/dist/resources/extensions/gsd/db/engine.js +755 -0
  33. package/dist/resources/extensions/gsd/db/queries.js +372 -0
  34. package/dist/resources/extensions/gsd/db/sql-constants.js +11 -0
  35. package/dist/resources/extensions/gsd/db/writers/cascades.js +194 -0
  36. package/dist/resources/extensions/gsd/db/writers/import-restore.js +182 -0
  37. package/dist/resources/extensions/gsd/db/writers/memory.js +149 -0
  38. package/dist/resources/extensions/gsd/db/writers/reconcile.js +458 -0
  39. package/dist/resources/extensions/gsd/db/writers/status.js +70 -0
  40. package/dist/resources/extensions/gsd/doctor-environment.js +5 -11
  41. package/dist/resources/extensions/gsd/doctor-format.js +9 -6
  42. package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -3
  43. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +21 -16
  44. package/dist/resources/extensions/gsd/error-classifier.js +9 -0
  45. package/dist/resources/extensions/gsd/git-service.js +1 -0
  46. package/dist/resources/extensions/gsd/gitignore.js +3 -0
  47. package/dist/resources/extensions/gsd/gsd-db.js +171 -2048
  48. package/dist/resources/extensions/gsd/guidance.js +98 -0
  49. package/dist/resources/extensions/gsd/guided-flow.js +51 -5
  50. package/dist/resources/extensions/gsd/mcp-tool-name.js +5 -13
  51. package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +1 -1
  52. package/dist/resources/extensions/gsd/migrate/safety.js +20 -9
  53. package/dist/resources/extensions/gsd/migration-auto-check.js +24 -3
  54. package/dist/resources/extensions/gsd/model-cost-table.js +1 -0
  55. package/dist/resources/extensions/gsd/model-router.js +3 -0
  56. package/dist/resources/extensions/gsd/notification-store.js +11 -4
  57. package/dist/resources/extensions/gsd/parallel-merge.js +14 -11
  58. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +11 -7
  59. package/dist/resources/extensions/gsd/paths.js +37 -24
  60. package/dist/resources/extensions/gsd/pre-execution-checks.js +91 -3
  61. package/dist/resources/extensions/gsd/preferences-models.js +12 -46
  62. package/dist/resources/extensions/gsd/preferences.js +14 -0
  63. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  64. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  65. package/dist/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  66. package/dist/resources/extensions/gsd/provider-error-guidance.js +1 -5
  67. package/dist/resources/extensions/gsd/provider-switch-observer.js +1 -1
  68. package/dist/resources/extensions/gsd/publication.js +87 -0
  69. package/dist/resources/extensions/gsd/recovery-classification.js +41 -87
  70. package/dist/resources/extensions/gsd/safety/evidence-collector.js +37 -4
  71. package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +7 -2
  72. package/dist/resources/extensions/gsd/safety/file-change-validator.js +10 -0
  73. package/dist/resources/extensions/gsd/state-transition-matrix.js +38 -0
  74. package/dist/resources/extensions/gsd/state.js +1 -20
  75. package/dist/resources/extensions/gsd/status-guards.js +56 -8
  76. package/dist/resources/extensions/gsd/stop-notice.js +57 -0
  77. package/dist/resources/extensions/gsd/tool-surface-readiness.js +56 -0
  78. package/dist/resources/extensions/gsd/tools/complete-slice.js +24 -43
  79. package/dist/resources/extensions/gsd/tools/exec-tool.js +5 -8
  80. package/dist/resources/extensions/gsd/tools/plan-slice.js +12 -6
  81. package/dist/resources/extensions/gsd/tools/reopen-milestone.js +11 -29
  82. package/dist/resources/extensions/gsd/tools/reopen-slice.js +14 -33
  83. package/dist/resources/extensions/gsd/tools/skip-slice.js +18 -36
  84. package/dist/resources/extensions/gsd/undo.js +8 -7
  85. package/dist/resources/extensions/gsd/unit-closeout.js +138 -0
  86. package/dist/resources/extensions/gsd/unit-context-composer.js +9 -1
  87. package/dist/resources/extensions/gsd/unit-context-manifest.js +4 -27
  88. package/dist/resources/extensions/gsd/unit-registry.js +350 -0
  89. package/dist/resources/extensions/gsd/unit-tool-contracts.js +9 -182
  90. package/dist/resources/extensions/gsd/workflow-tool-surface.js +1 -1
  91. package/dist/resources/extensions/gsd/worktree-git-recovery.js +293 -0
  92. package/dist/resources/extensions/gsd/worktree-lifecycle.js +9 -1
  93. package/dist/resources/extensions/gsd/worktree-manager.js +45 -28
  94. package/dist/resources/extensions/gsd/worktree-placement.js +59 -0
  95. package/dist/resources/extensions/gsd/worktree-reentry.js +12 -8
  96. package/dist/resources/extensions/gsd/worktree-root.js +28 -6
  97. package/dist/resources/extensions/gsd/worktree-safety.js +8 -5
  98. package/dist/resources/extensions/gsd/worktree-session-state.js +12 -11
  99. package/dist/resources/skills/gsd-browser/SKILL.md +1 -1
  100. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  101. package/dist/web/standalone/.next/BUILD_ID +1 -1
  102. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  103. package/dist/web/standalone/.next/build-manifest.json +2 -2
  104. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  105. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  106. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  112. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  113. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  114. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  115. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  116. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  117. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  118. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  119. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  120. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  122. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  123. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  124. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  125. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  126. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  128. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  129. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  130. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  131. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  132. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  134. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  135. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  136. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  137. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
  138. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  140. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  142. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  144. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  146. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  148. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  150. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  152. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  154. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/index.html +1 -1
  156. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  157. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  158. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  159. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  160. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  161. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  162. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  163. package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
  164. package/dist/web/standalone/.next/server/chunks/{5047.js → 5942.js} +2 -2
  165. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  166. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  168. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  169. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  170. package/dist/worktree-cli.js +3 -6
  171. package/dist/worktree-status-banner.js +7 -11
  172. package/package.json +1 -1
  173. package/packages/cloud-mcp-gateway/package.json +2 -2
  174. package/packages/contracts/dist/workflow.d.ts +4 -0
  175. package/packages/contracts/dist/workflow.d.ts.map +1 -1
  176. package/packages/contracts/dist/workflow.js.map +1 -1
  177. package/packages/contracts/package.json +1 -1
  178. package/packages/daemon/package.json +4 -4
  179. package/packages/gsd-agent-core/package.json +5 -5
  180. package/packages/gsd-agent-modes/package.json +7 -7
  181. package/packages/mcp-server/dist/cli.js +6 -3
  182. package/packages/mcp-server/dist/cli.js.map +1 -1
  183. package/packages/mcp-server/dist/workflow-tools.d.ts +8 -0
  184. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  185. package/packages/mcp-server/dist/workflow-tools.js +46 -21
  186. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  187. package/packages/mcp-server/package.json +3 -3
  188. package/packages/native/package.json +1 -1
  189. package/packages/pi-agent-core/package.json +1 -1
  190. package/packages/pi-ai/dist/image-models.generated.d.ts +0 -30
  191. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  192. package/packages/pi-ai/dist/image-models.generated.js +0 -30
  193. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  194. package/packages/pi-ai/dist/models.generated.d.ts +361 -255
  195. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  196. package/packages/pi-ai/dist/models.generated.js +311 -256
  197. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  198. package/packages/pi-ai/package.json +1 -1
  199. package/packages/pi-coding-agent/dist/core/capability-patches.d.ts.map +1 -1
  200. package/packages/pi-coding-agent/dist/core/capability-patches.js +3 -1
  201. package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
  202. package/packages/pi-coding-agent/package.json +7 -7
  203. package/packages/pi-tui/package.json +2 -2
  204. package/packages/rpc-client/package.json +2 -2
  205. package/pkg/package.json +1 -1
  206. package/src/resources/extensions/bg-shell/utilities.ts +5 -2
  207. package/src/resources/extensions/claude-code-cli/models.ts +9 -0
  208. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +37 -2
  209. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +28 -0
  210. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -1
  211. package/src/resources/extensions/gsd/auto/orchestrator.ts +39 -5
  212. package/src/resources/extensions/gsd/auto/phases.ts +10 -1
  213. package/src/resources/extensions/gsd/auto-post-unit.ts +25 -7
  214. package/src/resources/extensions/gsd/auto-prompts.ts +3 -0
  215. package/src/resources/extensions/gsd/auto-start.ts +12 -15
  216. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  217. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +10 -17
  218. package/src/resources/extensions/gsd/auto-worktree-repair.ts +13 -2
  219. package/src/resources/extensions/gsd/auto-worktree.ts +41 -364
  220. package/src/resources/extensions/gsd/auto.ts +20 -24
  221. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +3 -5
  222. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +10 -6
  223. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +87 -6
  224. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +29 -3
  225. package/src/resources/extensions/gsd/branch-patterns.ts +3 -0
  226. package/src/resources/extensions/gsd/captures.ts +5 -16
  227. package/src/resources/extensions/gsd/closeout-recovery.ts +2 -1
  228. package/src/resources/extensions/gsd/commands/catalog.ts +6 -68
  229. package/src/resources/extensions/gsd/crash-recovery.ts +3 -9
  230. package/src/resources/extensions/gsd/db/engine.ts +809 -0
  231. package/src/resources/extensions/gsd/db/queries.ts +453 -0
  232. package/src/resources/extensions/gsd/db/sql-constants.ts +12 -0
  233. package/src/resources/extensions/gsd/db/writers/cascades.ts +237 -0
  234. package/src/resources/extensions/gsd/db/writers/import-restore.ts +310 -0
  235. package/src/resources/extensions/gsd/db/writers/memory.ts +220 -0
  236. package/src/resources/extensions/gsd/db/writers/reconcile.ts +500 -0
  237. package/src/resources/extensions/gsd/db/writers/status.ts +88 -0
  238. package/src/resources/extensions/gsd/doctor-environment.ts +5 -13
  239. package/src/resources/extensions/gsd/doctor-format.ts +12 -7
  240. package/src/resources/extensions/gsd/doctor-git-checks.ts +3 -3
  241. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +22 -17
  242. package/src/resources/extensions/gsd/error-classifier.ts +11 -0
  243. package/src/resources/extensions/gsd/git-service.ts +1 -0
  244. package/src/resources/extensions/gsd/gitignore.ts +3 -0
  245. package/src/resources/extensions/gsd/gsd-db.ts +173 -2373
  246. package/src/resources/extensions/gsd/guidance.ts +139 -0
  247. package/src/resources/extensions/gsd/guided-flow.ts +50 -5
  248. package/src/resources/extensions/gsd/mcp-tool-name.ts +6 -11
  249. package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +1 -1
  250. package/src/resources/extensions/gsd/migrate/safety.ts +18 -7
  251. package/src/resources/extensions/gsd/migration-auto-check.ts +28 -3
  252. package/src/resources/extensions/gsd/model-cost-table.ts +1 -0
  253. package/src/resources/extensions/gsd/model-router.ts +3 -0
  254. package/src/resources/extensions/gsd/notification-store.ts +26 -3
  255. package/src/resources/extensions/gsd/parallel-merge.ts +12 -9
  256. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +10 -7
  257. package/src/resources/extensions/gsd/paths.ts +42 -22
  258. package/src/resources/extensions/gsd/pre-execution-checks.ts +109 -3
  259. package/src/resources/extensions/gsd/preferences-models.ts +10 -46
  260. package/src/resources/extensions/gsd/preferences.ts +18 -0
  261. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  262. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  263. package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
  264. package/src/resources/extensions/gsd/provider-error-guidance.ts +4 -9
  265. package/src/resources/extensions/gsd/provider-switch-observer.ts +1 -1
  266. package/src/resources/extensions/gsd/publication.ts +122 -0
  267. package/src/resources/extensions/gsd/recovery-classification.ts +47 -88
  268. package/src/resources/extensions/gsd/safety/evidence-collector.ts +36 -4
  269. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +7 -2
  270. package/src/resources/extensions/gsd/safety/file-change-validator.ts +14 -0
  271. package/src/resources/extensions/gsd/state-transition-matrix.ts +42 -0
  272. package/src/resources/extensions/gsd/state.ts +4 -21
  273. package/src/resources/extensions/gsd/status-guards.ts +59 -8
  274. package/src/resources/extensions/gsd/stop-notice.ts +75 -0
  275. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +123 -0
  276. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +3 -1
  277. package/src/resources/extensions/gsd/tests/auto-post-unit-evidence-crossref-4909.test.ts +46 -0
  278. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +2 -2
  279. package/src/resources/extensions/gsd/tests/auto-worktree-repair.test.ts +4 -2
  280. package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +66 -1
  281. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +44 -0
  282. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +8 -7
  283. package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +157 -0
  284. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +33 -1
  285. package/src/resources/extensions/gsd/tests/guidance.test.ts +125 -0
  286. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +51 -4
  287. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +54 -1
  288. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +3 -2
  289. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +85 -1
  290. package/src/resources/extensions/gsd/tests/notification-store.test.ts +32 -0
  291. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +193 -1
  292. package/src/resources/extensions/gsd/tests/provider-error-guidance.test.ts +3 -3
  293. package/src/resources/extensions/gsd/tests/publication.test.ts +120 -0
  294. package/src/resources/extensions/gsd/tests/recovery-classification-illegal-transition.test.ts +30 -0
  295. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +248 -1
  296. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +1 -0
  297. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +38 -0
  298. package/src/resources/extensions/gsd/tests/session-switch-clears-pending-autostart.test.ts +108 -0
  299. package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +43 -6
  300. package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +36 -0
  301. package/src/resources/extensions/gsd/tests/status-guards.test.ts +38 -0
  302. package/src/resources/extensions/gsd/tests/stop-notice.test.ts +70 -0
  303. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +8 -0
  304. package/src/resources/extensions/gsd/tests/tool-surface-readiness.test.ts +155 -0
  305. package/src/resources/extensions/gsd/tests/unit-closeout.test.ts +209 -0
  306. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +23 -2
  307. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +163 -0
  308. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
  309. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +2 -2
  310. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +41 -4
  311. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +22 -1
  312. package/src/resources/extensions/gsd/tests/worktree-placement.test.ts +113 -0
  313. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +1 -1
  314. package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +3 -1
  315. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +12 -6
  316. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +2 -2
  317. package/src/resources/extensions/gsd/tests/write-gate.test.ts +42 -0
  318. package/src/resources/extensions/gsd/tool-surface-readiness.ts +76 -0
  319. package/src/resources/extensions/gsd/tools/complete-slice.ts +23 -58
  320. package/src/resources/extensions/gsd/tools/exec-tool.ts +5 -8
  321. package/src/resources/extensions/gsd/tools/plan-slice.ts +12 -6
  322. package/src/resources/extensions/gsd/tools/reopen-milestone.ts +11 -38
  323. package/src/resources/extensions/gsd/tools/reopen-slice.ts +14 -42
  324. package/src/resources/extensions/gsd/tools/skip-slice.ts +18 -44
  325. package/src/resources/extensions/gsd/undo.ts +9 -8
  326. package/src/resources/extensions/gsd/unit-closeout.ts +201 -0
  327. package/src/resources/extensions/gsd/unit-context-composer.ts +12 -1
  328. package/src/resources/extensions/gsd/unit-context-manifest.ts +4 -28
  329. package/src/resources/extensions/gsd/unit-registry.ts +425 -0
  330. package/src/resources/extensions/gsd/unit-tool-contracts.ts +27 -192
  331. package/src/resources/extensions/gsd/workflow-tool-surface.ts +4 -1
  332. package/src/resources/extensions/gsd/worktree-git-recovery.ts +314 -0
  333. package/src/resources/extensions/gsd/worktree-lifecycle.ts +10 -1
  334. package/src/resources/extensions/gsd/worktree-manager.ts +47 -28
  335. package/src/resources/extensions/gsd/worktree-placement.ts +63 -0
  336. package/src/resources/extensions/gsd/worktree-reentry.ts +10 -7
  337. package/src/resources/extensions/gsd/worktree-root.ts +29 -6
  338. package/src/resources/extensions/gsd/worktree-safety.ts +8 -5
  339. package/src/resources/extensions/gsd/worktree-session-state.ts +11 -11
  340. package/src/resources/skills/gsd-browser/SKILL.md +1 -1
  341. /package/dist/web/standalone/.next/static/{tkLHUSzPA2kMmWz4DmGwI → 2p9Rv9pQflAxCBbGVI2vb}/_buildManifest.js +0 -0
  342. /package/dist/web/standalone/.next/static/{tkLHUSzPA2kMmWz4DmGwI → 2p9Rv9pQflAxCBbGVI2vb}/_ssgManifest.js +0 -0
@@ -1,18 +1,23 @@
1
1
  // Project/App: gsd-pi
2
2
  // File Purpose: ADR-015 Recovery Classification module for runtime failure taxonomy.
3
3
 
4
+ import { isToolUnavailableError } from "./auto-tool-tracking.js";
4
5
  import { classifyError, isTransient, type ErrorClass } from "./error-classifier.js";
6
+ import { recoveryRemediation } from "./guidance.js";
5
7
  import { ReconciliationFailedError } from "./state-reconciliation.js";
8
+ import { IllegalPhaseTransitionError } from "./state-transition-matrix.js";
6
9
 
7
10
  export type RecoveryFailureKind =
8
11
  | "tool-schema"
9
12
  | "tool-contract"
13
+ | "tool-unavailable"
10
14
  | "deterministic-policy"
11
15
  | "lifecycle-progression"
12
16
  | "stale-worker"
13
17
  | "worktree-invalid"
14
18
  | "verification-drift"
15
19
  | "reconciliation-drift"
20
+ | "illegal-transition"
16
21
  | "provider"
17
22
  | "runtime-unknown";
18
23
 
@@ -43,99 +48,53 @@ export function classifyFailure(input: RecoveryClassificationInput): RecoveryCla
43
48
  const failureKind =
44
49
  input.error instanceof ReconciliationFailedError
45
50
  ? "reconciliation-drift"
46
- : input.failureKind ?? inferFailureKind(message);
51
+ : input.error instanceof IllegalPhaseTransitionError
52
+ ? "illegal-transition"
53
+ : input.failureKind ?? inferFailureKind(message);
47
54
 
48
- switch (failureKind) {
49
- case "tool-schema":
50
- return {
51
- failureKind,
52
- action: "stop",
53
- reason: `Tool schema failure${unitSuffix(input)}: ${message}`,
54
- exitReason: "tool-schema",
55
- remediation: "Fix the Unit Tool Contract or tool schema before retrying.",
56
- };
57
- case "tool-contract":
58
- return {
59
- failureKind,
60
- action: "stop",
61
- reason: `Tool Contract failure${unitSuffix(input)}: ${message}`,
62
- exitReason: "tool-contract",
63
- remediation: "Fix the Unit Tool Contract or prompt so the Unit is only asked to use tools owned by its phase.",
64
- };
65
- case "deterministic-policy":
66
- return {
67
- failureKind,
68
- action: "stop",
69
- reason: `Deterministic policy failure${unitSuffix(input)}: ${message}`,
70
- exitReason: "deterministic-policy",
71
- remediation: "Resolve the policy blocker; retrying the same Unit will repeat the failure.",
72
- };
73
- case "lifecycle-progression":
74
- return {
75
- failureKind,
76
- action: "stop",
77
- reason: `Lifecycle progression failure${unitSuffix(input)}: ${message}`,
78
- exitReason: "lifecycle-progression",
79
- remediation: "Route to the required owning Unit or restore the missing artifact before advancing lifecycle state.",
80
- };
81
- case "stale-worker":
82
- return {
83
- failureKind,
84
- action: "stop",
85
- reason: `Stale worker failure${unitSuffix(input)}: ${message}`,
86
- exitReason: "stale-worker",
87
- remediation: "Clear or reconcile the stale worker before dispatching another Unit.",
88
- };
89
- case "worktree-invalid":
90
- return {
91
- failureKind,
92
- action: "stop",
93
- reason: `Worktree invalid${unitSuffix(input)}: ${message}`,
94
- exitReason: "worktree-invalid",
95
- remediation: "Repair or recreate the milestone worktree before launching source-writing Units.",
96
- };
97
- case "verification-drift":
98
- return {
99
- failureKind,
100
- action: "escalate",
101
- reason: `Verification drift${unitSuffix(input)}: ${message}`,
102
- exitReason: "verification-drift",
103
- remediation: "Inspect the verification artifact and reconcile the state snapshot before resuming.",
104
- };
105
- case "reconciliation-drift":
106
- return {
107
- failureKind,
108
- action: "escalate",
109
- reason: `Reconciliation drift${unitSuffix(input)}: ${message}`,
110
- exitReason: "reconciliation-drift",
111
- remediation:
112
- "Inspect the persistent or repair-failed drift kinds reported by the State Reconciliation Module before resuming.",
113
- };
114
- case "provider": {
115
- const providerClass = classifyError(message, input.retryAfterMs);
116
- return {
117
- failureKind,
118
- action: isTransient(providerClass) ? "retry" : "escalate",
119
- reason: message,
120
- exitReason: `provider-${providerClass.kind}`,
121
- remediation: isTransient(providerClass)
122
- ? "Retry after the provider/network condition clears."
123
- : "Inspect provider credentials, model entitlement, or request shape.",
124
- providerClass: providerClass.kind,
125
- };
126
- }
127
- case "runtime-unknown":
128
- return {
129
- failureKind,
130
- action: "escalate",
131
- reason: message,
132
- exitReason: "runtime-unknown",
133
- remediation: "Inspect the runtime error and add a dedicated classification if it is repeatable.",
134
- };
55
+ if (failureKind === "provider") {
56
+ const providerClass = classifyError(message, input.retryAfterMs);
57
+ const transient = isTransient(providerClass);
58
+ return {
59
+ failureKind,
60
+ action: transient ? "retry" : "escalate",
61
+ reason: message,
62
+ exitReason: `provider-${providerClass.kind}`,
63
+ remediation: recoveryRemediation(transient ? "provider-transient" : "provider-permanent"),
64
+ providerClass: providerClass.kind,
65
+ };
135
66
  }
67
+
68
+ const { action, label } = FAILURE_TAXONOMY[failureKind];
69
+ return {
70
+ failureKind,
71
+ action,
72
+ reason: label ? `${label}${unitSuffix(input)}: ${message}` : message,
73
+ exitReason: failureKind,
74
+ remediation: recoveryRemediation(failureKind),
75
+ };
136
76
  }
137
77
 
78
+ /** Per-kind action and reason label. Remediation lives in the Guidance module. */
79
+ const FAILURE_TAXONOMY: Record<
80
+ Exclude<RecoveryFailureKind, "provider">,
81
+ { action: RecoveryAction; label: string | null }
82
+ > = {
83
+ "tool-schema": { action: "stop", label: "Tool schema failure" },
84
+ "tool-contract": { action: "stop", label: "Tool Contract failure" },
85
+ "tool-unavailable": { action: "retry", label: "Tool unavailable" },
86
+ "deterministic-policy": { action: "stop", label: "Deterministic policy failure" },
87
+ "lifecycle-progression": { action: "stop", label: "Lifecycle progression failure" },
88
+ "stale-worker": { action: "stop", label: "Stale worker failure" },
89
+ "worktree-invalid": { action: "stop", label: "Worktree invalid" },
90
+ "verification-drift": { action: "escalate", label: "Verification drift" },
91
+ "reconciliation-drift": { action: "escalate", label: "Reconciliation drift" },
92
+ "illegal-transition": { action: "escalate", label: "Illegal phase transition" },
93
+ "runtime-unknown": { action: "escalate", label: null },
94
+ };
95
+
138
96
  function inferFailureKind(message: string): RecoveryFailureKind {
97
+ if (isToolUnavailableError(message)) return "tool-unavailable";
139
98
  if (/tool contract|auto-unit tool scope|phase-boundary gate|not permitted.*own/i.test(message)) return "tool-contract";
140
99
  if (/lifecycle progression|required artifact|missing .*assessment|missing .*closeout|cannot legally (?:advance|progress)/i.test(message)) return "lifecycle-progression";
141
100
  if (/schema|invalid.*tool|tool.*invalid|enum/i.test(message)) return "tool-schema";
@@ -57,9 +57,10 @@ const EXECUTION_TOOL_NAMES = new Set([
57
57
  "functions.exec_command",
58
58
  "gsd_exec",
59
59
  "gsd_exec_search",
60
+ "gsd_uat_exec",
60
61
  "powershell",
61
62
  ]);
62
- const MCP_EXECUTION_TOOL_RE = /^mcp__.+__gsd_exec(?:_search)?$/;
63
+ const MCP_EXECUTION_TOOL_RE = /^mcp__.+__gsd_(?:uat_)?exec(?:_search)?$/;
63
64
 
64
65
  // ─── Module State ───────────────────────────────────────────────────────────
65
66
 
@@ -206,11 +207,17 @@ export function clearEvidenceFromDisk(
206
207
  * Exit codes and output are filled in by recordToolResult after execution.
207
208
  */
208
209
  export function recordToolCall(toolCallId: string, toolName: string, input: Record<string, unknown>): void {
210
+ // Idempotent by toolCallId: native tools reach this via both
211
+ // tool_execution_start and tool_call; external (pre-executed) tools only
212
+ // via tool_execution_start. First recording wins.
213
+ if (unitEvidence.some(e => e.toolCallId === toolCallId)) return;
209
214
  if (isExecutionToolName(toolName)) {
210
215
  unitEvidence.push({
211
216
  kind: "bash",
212
217
  toolCallId,
213
- command: String(input.command ?? input.cmd ?? input.query ?? ""),
218
+ // gsd_exec / gsd_uat_exec carry the script body in `script` (or `code`);
219
+ // bash-style tools use `command`/`cmd`; gsd_exec_search uses `query`.
220
+ command: String(input.command ?? input.script ?? input.cmd ?? input.code ?? input.query ?? ""),
214
221
  exitCode: -1,
215
222
  outputSnippet: "",
216
223
  timestamp: Date.now(),
@@ -249,11 +256,36 @@ export function recordToolResult(
249
256
  if (entry.kind === "bash") {
250
257
  const text = extractResultText(result);
251
258
  entry.outputSnippet = text.slice(0, 500);
252
- const exitMatch = text.match(/Command exited with code (\d+)/);
253
- entry.exitCode = exitMatch ? Number(exitMatch[1]) : (isError ? 1 : 0);
259
+ entry.exitCode = resolveExitCode(text, isError);
254
260
  }
255
261
  }
256
262
 
263
+ /**
264
+ * Resolve the exit code from a tool result's text. Handles the bash tool's
265
+ * prose marker, the gsd_exec / gsd_uat_exec JSON envelope (`"exit_code": N`),
266
+ * and a last-resort read of the run's persisted `.gsd/exec/<id>.meta.json`
267
+ * (covers truncated result text).
268
+ */
269
+ function resolveExitCode(text: string, isError: boolean): number {
270
+ const proseMatch = text.match(/Command exited with code (\d+)/);
271
+ if (proseMatch) return Number(proseMatch[1]);
272
+
273
+ const jsonMatch = text.match(/"exit_code"\s*:\s*(-?\d+)/);
274
+ if (jsonMatch) return Number(jsonMatch[1]);
275
+
276
+ const metaMatch = text.match(/"meta_path"\s*:\s*"([^"]+)"/);
277
+ if (metaMatch) {
278
+ try {
279
+ const meta = JSON.parse(readFileSync(metaMatch[1], "utf-8")) as Record<string, unknown>;
280
+ if (typeof meta.exit_code === "number") return meta.exit_code;
281
+ } catch {
282
+ // Fall through to the isError heuristic
283
+ }
284
+ }
285
+
286
+ return isError ? 1 : 0;
287
+ }
288
+
257
289
  // ─── Internals ──────────────────────────────────────────────────────────────
258
290
 
259
291
  function extractResultText(result: unknown): string {
@@ -121,9 +121,14 @@ function findMatches(
121
121
  const exact = bashCalls.filter(b => b.command.trim() === normalized);
122
122
  if (exact.length > 0) return exact;
123
123
 
124
- // Substring match: claimed is contained in actual or actual in claimed
124
+ // Substring match: claimed is contained in actual or actual in claimed.
125
+ // A claimed verification command typically appears verbatim inside a
126
+ // larger gsd_exec script body (cd prefix, multi-line scripts), so
127
+ // script-containing-claim is the common direction. Blank-command entries
128
+ // must be excluded — `"x".includes("")` is true, so they'd match anything.
125
129
  const substring = bashCalls.filter(
126
- b => b.command.includes(normalized) || normalized.includes(b.command),
130
+ b => b.command.trim().length > 0 &&
131
+ (b.command.includes(normalized) || normalized.includes(b.command)),
127
132
  );
128
133
  if (substring.length > 0) return substring;
129
134
 
@@ -39,6 +39,20 @@ export interface FileChangeAudit {
39
39
 
40
40
  // ─── Public API ─────────────────────────────────────────────────────────────
41
41
 
42
+ /**
43
+ * Build the effective allowlist for a unit's file-change audit.
44
+ *
45
+ * When GSD manages .gitignore (manage_gitignore unset or true), ensureGitignore()
46
+ * appends baseline patterns at auto-start and the edit rides into the task's
47
+ * auto-commit — so .gitignore changes must not be attributed to the task.
48
+ */
49
+ export function effectiveFileChangeAllowlist(
50
+ baseAllowlist: string[],
51
+ manageGitignore: boolean | undefined,
52
+ ): string[] {
53
+ return manageGitignore === false ? baseAllowlist : [...baseAllowlist, ".gitignore"];
54
+ }
55
+
42
56
  /**
43
57
  * Validate file changes after auto-commit for an execute-task unit.
44
58
  * Returns null if task data is unavailable or DB is not loaded.
@@ -131,6 +131,48 @@ export function findTransition(
131
131
  );
132
132
  }
133
133
 
134
+ /**
135
+ * Edge-keyed legality check for the Phase Transition Invariant (ADR-030).
136
+ * `advance()` derives the next Phase and asserts the (from → to) edge here.
137
+ *
138
+ * The matrix is an assertion, not a decision-maker — `deriveState` already
139
+ * chose the Phase. A self-edge is trivially legal (no transition to assert). An
140
+ * edge is legal when some matrix entry permits it, honoring the `*` wildcard
141
+ * rows (e.g. any → blocked via manual-block, any → executing via
142
+ * retryable-failure).
143
+ *
144
+ * Note: the matrix is currently a sparse hardening spec, not a complete
145
+ * legal-edge graph, so `advance()` consumes this in advisory mode (telemetry
146
+ * only). It must be expanded to cover every edge `deriveState` emits before
147
+ * enforcement flips on.
148
+ */
149
+ export function isLegalEdge(from: Phase, to: Phase): boolean {
150
+ if (from === to) return true;
151
+ return STATE_TRANSITION_MATRIX.some(
152
+ (entry) => (entry.from === from || entry.from === "*") && entry.to === to,
153
+ );
154
+ }
155
+
156
+ /**
157
+ * Thrown when an illegal derived Phase edge survives reconciliation. Carries
158
+ * both endpoints so Recovery Classification can report them. Recognized by
159
+ * class in `classifyFailure` and mapped to the `illegal-transition` kind.
160
+ */
161
+ export class IllegalPhaseTransitionError extends Error {
162
+ // Explicit fields, not constructor parameter properties — strip-types
163
+ // consumers (workspace-index subprocess, integration tests) reject the
164
+ // parameter-property syntax with ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX.
165
+ readonly from: Phase;
166
+ readonly to: Phase;
167
+
168
+ constructor(from: Phase, to: Phase) {
169
+ super(`Illegal phase transition ${from} -> ${to} survived reconciliation`);
170
+ this.name = "IllegalPhaseTransitionError";
171
+ this.from = from;
172
+ this.to = to;
173
+ }
174
+ }
175
+
134
176
  export function validateTransitionMatrix(requiredEvents: readonly string[]): MatrixValidationResult {
135
177
  const seen = new Set<string>();
136
178
  const duplicateKeys: string[] = [];
@@ -71,27 +71,10 @@ import {
71
71
  readinessNeedsDiscussion,
72
72
  } from './milestone-readiness.js';
73
73
 
74
- function formatNeedsAttentionBlocker(milestoneId: string): string {
75
- return [
76
- `Milestone ${milestoneId} is blocked because milestone validation returned needs-attention.`,
77
- `Fix options:`,
78
- `1. Review the validation details: \`/gsd status\``,
79
- `2. If you fixed the missing evidence or issue, re-run milestone validation: \`/gsd validate-milestone\``,
80
- `3. If the finding is acceptable, override it: \`/gsd verdict pass --rationale "why this is okay"\``,
81
- `4. If this should wait, defer it explicitly: \`/gsd park ${milestoneId}\``,
82
- `After validation or override passes, run \`/gsd auto\` to complete and merge the milestone.`,
83
- ].join("\n");
84
- }
85
-
86
- function formatNeedsRemediationBlocker(milestoneId: string): string {
87
- return [
88
- `Milestone ${milestoneId} is blocked because milestone validation returned needs-remediation, but all slices are complete.`,
89
- `Fix options:`,
90
- `1. Run \`/gsd dispatch reassess\` to add remediation slices, then run \`/gsd auto\``,
91
- `2. If the finding is acceptable, override it: \`/gsd verdict pass --rationale "why this is okay"\``,
92
- `3. If this should wait, defer it explicitly: \`/gsd park ${milestoneId}\``,
93
- ].join("\n");
94
- }
74
+ import {
75
+ needsAttentionBlockerGuidance as formatNeedsAttentionBlocker,
76
+ needsRemediationBlockerGuidance as formatNeedsRemediationBlocker,
77
+ } from './guidance.js';
95
78
 
96
79
  /**
97
80
  * A "ghost" milestone directory contains only META.json (and no substantive
@@ -1,17 +1,68 @@
1
1
  /**
2
- * Status predicates for GSD state-machine guards.
2
+ * Status predicates and the canonical status vocabulary for GSD state-machine
3
+ * guards (ADR-030).
3
4
  *
4
- * The DB stores status as free-form strings. Three values indicate
5
- * "closed": "complete" (canonical), "done" (legacy / alias),
6
- * "closed" (legacy/imported), and
7
- * "skipped" (user-directed skip via rethink or backtrack).
8
- * Every inline `status === "complete" || status === "done"` should
9
- * use isClosedStatus() instead.
5
+ * The DB column is free-form `string` so legacy/imported rows still load. Three
6
+ * raw values besides canonical "complete"/"skipped" indicate "closed": "done"
7
+ * (legacy alias), "closed" (legacy/imported), and "skipped" (user-directed skip
8
+ * via rethink or backtrack). `RAW_CLOSED_STATUSES` is the single source for both
9
+ * `isClosedStatus()` and the SQL terminal-status fragment
10
+ * (`db/sql-constants.ts` derives `TERMINAL_STATUS_SQL` from it), replacing the
11
+ * prior independent definitions.
12
+ *
13
+ * `toStatus()` is the single seam where a free-form string becomes the canonical
14
+ * `Status` vocabulary; the Status Transition Core writes canonical, so the store
15
+ * converges over time without a forced migration.
16
+ */
17
+
18
+ /**
19
+ * Canonical, normalized entity-status vocabulary across milestones, slices, and
20
+ * tasks — the single source for both the `Status` type and the runtime
21
+ * membership set. The in-memory domain speaks `Status`; the DB column stays
22
+ * free-form.
23
+ */
24
+ export const CANONICAL_STATUSES = [
25
+ "pending", "queued", "active", "parked", "in_progress", "blocked", "complete", "skipped", "deferred",
26
+ ] as const;
27
+ export type Status = (typeof CANONICAL_STATUSES)[number];
28
+ const CANONICAL_STATUS_SET: ReadonlySet<string> = new Set(CANONICAL_STATUSES);
29
+
30
+ /**
31
+ * Raw status values that mean a unit is closed — the single source of truth.
32
+ * Includes legacy/imported aliases ("done", "closed") alongside canonical
33
+ * "complete"/"skipped" because the DB column is free-form and older rows /
34
+ * imports still carry them. Order matters: `TERMINAL_STATUS_SQL` is derived
35
+ * from this array verbatim.
36
+ */
37
+ export const RAW_CLOSED_STATUSES = ["complete", "done", "skipped", "closed"] as const;
38
+ const RAW_CLOSED_SET: ReadonlySet<string> = new Set(RAW_CLOSED_STATUSES);
39
+
40
+ /** Free-form aliases mapped to their canonical Status on read. */
41
+ const ALIAS_TO_CANONICAL: Readonly<Record<string, Status>> = {
42
+ done: "complete",
43
+ closed: "complete",
44
+ planned: "pending",
45
+ "in-progress": "in_progress",
46
+ };
47
+
48
+ /**
49
+ * Normalize a free-form DB status string into the canonical `Status`
50
+ * vocabulary. Maps known aliases (done/closed → complete, planned → pending,
51
+ * in-progress → in_progress). An unrecognized/legacy value is **quarantined** —
52
+ * preserved verbatim rather than silently remapped to a wrong canonical state —
53
+ * so reads never fail and reconciliation/telemetry can surface it.
10
54
  */
55
+ export function toStatus(raw: string): Status {
56
+ const value = raw.trim();
57
+ if (CANONICAL_STATUS_SET.has(value)) return value as Status;
58
+ const alias = ALIAS_TO_CANONICAL[value];
59
+ if (alias) return alias;
60
+ return value as Status;
61
+ }
11
62
 
12
63
  /** Returns true when a milestone, slice, or task status indicates closure. */
13
64
  export function isClosedStatus(status: string): boolean {
14
- return status === "complete" || status === "done" || status === "skipped" || status === "closed";
65
+ return RAW_CLOSED_SET.has(status);
15
66
  }
16
67
 
17
68
  /** Returns true when a slice status indicates it was deferred by a decision. */
@@ -0,0 +1,75 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Stop Notice module — single owner of the auto/step-mode
3
+ // stop/pause notice vocabulary. Both sides of the wire live here: the
4
+ // formatters that produce the canonical prefixes (used by stopAuto/pauseAuto)
5
+ // and the classifiers that recognize them (used by the headless host to pick
6
+ // exit codes). Wording changes in this file keep emitter and detector in
7
+ // lockstep; round-trip tests enforce it.
8
+
9
+ export type StopNoticeKind = "stopped" | "blocked";
10
+
11
+ /** A reason string of the form "Blocked: …" marks a blocked stop. */
12
+ export function isBlockedStopReason(reason?: string | null): boolean {
13
+ return /^Blocked:\s*/i.test(reason ?? "");
14
+ }
15
+
16
+ /** Strip the "Blocked: " marker for display. */
17
+ export function stopNoticeDisplayReason(reason?: string | null): string {
18
+ return (reason ?? "").replace(/^Blocked:\s*/i, "").trim();
19
+ }
20
+
21
+ export function stopNoticeKind(reason?: string | null): StopNoticeKind {
22
+ return isBlockedStopReason(reason) ? "blocked" : "stopped";
23
+ }
24
+
25
+ /** Canonical stop-notice prefix: "Auto-mode blocked — reason" / "Auto-mode stopped". */
26
+ export function formatStopNoticePrefix(reason?: string | null): string {
27
+ const displayReason = stopNoticeDisplayReason(reason);
28
+ const prefix = stopNoticeKind(reason) === "blocked" ? "Auto-mode blocked" : "Auto-mode stopped";
29
+ return displayReason ? `${prefix} — ${displayReason}` : prefix;
30
+ }
31
+
32
+ // ─── Classification (headless host side) ────────────────────────────────
33
+ // The canonical lowercase prefixes the headless event loop recognizes in
34
+ // notify messages. Emitters above and ad-hoc emitters elsewhere must start
35
+ // their terminal notices with one of these.
36
+
37
+ export const PAUSED_NOTICE_PREFIXES = ["auto-mode paused", "step-mode paused"] as const;
38
+
39
+ export const TERMINAL_NOTICE_PREFIXES = [
40
+ "auto-mode stopped",
41
+ "step-mode stopped",
42
+ "auto-mode complete",
43
+ "no active milestone",
44
+ "auto-mode idle",
45
+ ] as const;
46
+
47
+ /** Manual-resolution notices emitted before auto-mode can formally pause/stop. */
48
+ export function isManualResolutionNotice(message: string): boolean {
49
+ return (
50
+ message.includes("resolve manually and re-run /gsd auto") ||
51
+ message.includes("resolve conflicts manually and run /gsd auto to resume") ||
52
+ message.includes("resolve and run /gsd auto to resume")
53
+ );
54
+ }
55
+
56
+ export function isPauseNotice(message: string): boolean {
57
+ return PAUSED_NOTICE_PREFIXES.some((prefix) => message.startsWith(prefix));
58
+ }
59
+
60
+ export function isTerminalNotice(message: string): boolean {
61
+ return TERMINAL_NOTICE_PREFIXES.some((prefix) => message.startsWith(prefix));
62
+ }
63
+
64
+ /** Pauses that do not require operator intervention in headless mode. */
65
+ export function isNonBlockingPauseNotice(message: string): boolean {
66
+ return message.includes("idempotent advance: unit already active");
67
+ }
68
+
69
+ export function isBlockedNoticeMessage(message: string): boolean {
70
+ return (
71
+ message.includes("blocked:") ||
72
+ (isPauseNotice(message) && !isNonBlockingPauseNotice(message)) ||
73
+ isManualResolutionNotice(message)
74
+ );
75
+ }
@@ -5517,6 +5517,129 @@ test("dispatch Worktree Safety wins before stuck detection for execute-task with
5517
5517
  );
5518
5518
  });
5519
5519
 
5520
+ test("dispatch Worktree Safety honors degraded branch fallback instead of demanding the canonical worktree root", async (t) => {
5521
+ _resetPendingResolve();
5522
+
5523
+ const ctx = makeMockCtx();
5524
+ const pi = makeMockPi();
5525
+ const notifications: string[] = [];
5526
+ ctx.ui.notify = (msg: string) => { notifications.push(msg); };
5527
+
5528
+ // Worktree creation failed and the lifecycle fell back to the milestone
5529
+ // branch in the project root. The safety gate must validate against that
5530
+ // effective branch mode, not the configured worktree mode.
5531
+ const projectRoot = mkdtempSync(join(tmpdir(), "gsd-wt-safety-degraded-"));
5532
+ t.after(() => rmSync(projectRoot, { recursive: true, force: true }));
5533
+
5534
+ const s = makeLoopSession({
5535
+ basePath: projectRoot,
5536
+ originalBasePath: projectRoot,
5537
+ canonicalProjectRoot: projectRoot,
5538
+ isolationDegraded: true,
5539
+ });
5540
+ const deps = makeMockDeps({
5541
+ getIsolationMode: () => "worktree",
5542
+ });
5543
+ const result = await runDispatch(
5544
+ {
5545
+ ctx,
5546
+ pi,
5547
+ s,
5548
+ deps,
5549
+ prefs: undefined,
5550
+ iteration: 1,
5551
+ flowId: "test-flow",
5552
+ nextSeq: () => 1,
5553
+ },
5554
+ {
5555
+ state: {
5556
+ phase: "executing",
5557
+ activeMilestone: { id: "M001", title: "Test", status: "active" },
5558
+ activeSlice: { id: "S01", title: "Slice 1" },
5559
+ activeTask: { id: "T01" },
5560
+ registry: [{ id: "M001", status: "active" }],
5561
+ blockers: [],
5562
+ } as any,
5563
+ mid: "M001",
5564
+ midTitle: "Test",
5565
+ },
5566
+ {
5567
+ recentUnits: [],
5568
+ stuckRecoveryAttempts: 0,
5569
+ consecutiveFinalizeTimeouts: 0,
5570
+ },
5571
+ );
5572
+
5573
+ assert.equal(result.action, "next", "dispatch must proceed under degraded branch isolation");
5574
+ assert.ok(
5575
+ !notifications.some((n) => n.includes("Worktree Safety failed")),
5576
+ "degraded branch fallback must not trip a false invalid-root",
5577
+ );
5578
+ assert.ok(!deps.callLog.includes("stopAuto"), "auto-mode must not stop on the degraded fallback");
5579
+ });
5580
+
5581
+ test("dispatch Worktree Safety honors stranded branch recovery instead of demanding the canonical worktree root", async (t) => {
5582
+ _resetPendingResolve();
5583
+
5584
+ const ctx = makeMockCtx();
5585
+ const pi = makeMockPi();
5586
+ const notifications: string[] = [];
5587
+ ctx.ui.notify = (msg: string) => { notifications.push(msg); };
5588
+
5589
+ // Bootstrap adopted stranded work by checking out the milestone branch in
5590
+ // the project root (strandedRecoveryIsolationMode = "branch"). Isolation is
5591
+ // NOT degraded — the adoption is intentional. The safety gate must validate
5592
+ // against the effective branch mode, not the configured worktree mode.
5593
+ const projectRoot = mkdtempSync(join(tmpdir(), "gsd-wt-safety-stranded-"));
5594
+ t.after(() => rmSync(projectRoot, { recursive: true, force: true }));
5595
+
5596
+ const s = makeLoopSession({
5597
+ basePath: projectRoot,
5598
+ originalBasePath: projectRoot,
5599
+ canonicalProjectRoot: projectRoot,
5600
+ strandedRecoveryIsolationMode: "branch",
5601
+ });
5602
+ const deps = makeMockDeps({
5603
+ getIsolationMode: () => "worktree",
5604
+ });
5605
+ const result = await runDispatch(
5606
+ {
5607
+ ctx,
5608
+ pi,
5609
+ s,
5610
+ deps,
5611
+ prefs: undefined,
5612
+ iteration: 1,
5613
+ flowId: "test-flow",
5614
+ nextSeq: () => 1,
5615
+ },
5616
+ {
5617
+ state: {
5618
+ phase: "executing",
5619
+ activeMilestone: { id: "M001", title: "Test", status: "active" },
5620
+ activeSlice: { id: "S01", title: "Slice 1" },
5621
+ activeTask: { id: "T01" },
5622
+ registry: [{ id: "M001", status: "active" }],
5623
+ blockers: [],
5624
+ } as any,
5625
+ mid: "M001",
5626
+ midTitle: "Test",
5627
+ },
5628
+ {
5629
+ recentUnits: [],
5630
+ stuckRecoveryAttempts: 0,
5631
+ consecutiveFinalizeTimeouts: 0,
5632
+ },
5633
+ );
5634
+
5635
+ assert.equal(result.action, "next", "dispatch must proceed under stranded branch recovery");
5636
+ assert.ok(
5637
+ !notifications.some((n) => n.includes("Worktree Safety failed")),
5638
+ "stranded branch recovery must not trip a false invalid-root",
5639
+ );
5640
+ assert.ok(!deps.callLog.includes("stopAuto"), "auto-mode must not stop on stranded branch recovery");
5641
+ });
5642
+
5520
5643
  test("runDispatch runs stuck detection while artifact verification retry is pending (#5719)", async (t) => {
5521
5644
  _resetPendingResolve();
5522
5645
 
@@ -306,7 +306,9 @@ test("pauseAuto records the expected worktree path when paused from project root
306
306
 
307
307
  const meta = readPausedSessionMetadata(base);
308
308
  assert.ok(meta);
309
- assert.equal(meta.worktreePath, join(base, ".gsd", "worktrees", "M001"));
309
+ // No worktree exists yet, so the recorded path is the canonical
310
+ // .gsd-worktrees/ creation location (worktree-placement seam).
311
+ assert.equal(meta.worktreePath, join(base, ".gsd-worktrees", "M001"));
310
312
  } finally {
311
313
  autoSession.reset();
312
314
  try {