@opengsd/gsd-pi 1.5.0-dev.553dc518 → 1.5.0-dev.c4c8885f

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 (302) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/ask-user-questions.js +1 -1
  3. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +114 -36
  4. package/dist/resources/extensions/gsd/activity-log.js +1 -1
  5. package/dist/resources/extensions/gsd/auto/orchestrator.js +16 -10
  6. package/dist/resources/extensions/gsd/auto-artifact-paths.js +14 -5
  7. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +7 -2
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +69 -17
  9. package/dist/resources/extensions/gsd/auto-model-selection.js +28 -1
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +4 -3
  11. package/dist/resources/extensions/gsd/auto-prompts.js +29 -117
  12. package/dist/resources/extensions/gsd/auto-recovery.js +50 -6
  13. package/dist/resources/extensions/gsd/auto-start.js +54 -31
  14. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +12 -2
  15. package/dist/resources/extensions/gsd/auto-timers.js +9 -2
  16. package/dist/resources/extensions/gsd/auto.js +97 -31
  17. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +1 -1
  18. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +67 -6
  19. package/dist/resources/extensions/gsd/codebase-generator.js +7 -1
  20. package/dist/resources/extensions/gsd/compat/compat-marker.js +44 -0
  21. package/dist/resources/extensions/gsd/config-overlay.js +7 -2
  22. package/dist/resources/extensions/gsd/db/engine.js +33 -0
  23. package/dist/resources/extensions/gsd/db/writers/cascades.js +20 -2
  24. package/dist/resources/extensions/gsd/db-adapter.js +30 -3
  25. package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  26. package/dist/resources/extensions/gsd/flat-phase-migration.js +18 -0
  27. package/dist/resources/extensions/gsd/gsd-db.js +12 -11
  28. package/dist/resources/extensions/gsd/guided-flow.js +16 -7
  29. package/dist/resources/extensions/gsd/journal.js +1 -1
  30. package/dist/resources/extensions/gsd/markdown-renderer.js +34 -91
  31. package/dist/resources/extensions/gsd/md-importer.js +89 -23
  32. package/dist/resources/extensions/gsd/metrics.js +2 -2
  33. package/dist/resources/extensions/gsd/migration-auto-check.js +30 -3
  34. package/dist/resources/extensions/gsd/paths.js +87 -52
  35. package/dist/resources/extensions/gsd/post-unit-hooks.js +27 -0
  36. package/dist/resources/extensions/gsd/preferences-models.js +66 -15
  37. package/dist/resources/extensions/gsd/preferences-skills.js +10 -16
  38. package/dist/resources/extensions/gsd/preferences-validation.js +66 -10
  39. package/dist/resources/extensions/gsd/preferences.js +20 -0
  40. package/dist/resources/extensions/gsd/rule-registry.js +106 -10
  41. package/dist/resources/extensions/gsd/skill-discovery.js +36 -23
  42. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +1 -10
  43. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +3 -24
  44. package/dist/resources/extensions/gsd/state-reconciliation/index.js +10 -0
  45. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -5
  46. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +16 -10
  47. package/dist/resources/extensions/gsd/tools/reopen-slice.js +5 -0
  48. package/dist/resources/extensions/gsd/tools/reopen-task.js +13 -5
  49. package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -8
  50. package/dist/resources/extensions/gsd/uat-dispatch.js +94 -0
  51. package/dist/resources/extensions/gsd/uok/audit-toggle.js +31 -2
  52. package/dist/resources/extensions/gsd/uok/audit.js +3 -0
  53. package/dist/resources/extensions/gsd/uok/gate-runner.js +22 -30
  54. package/dist/resources/extensions/gsd/uok/gitops.js +18 -15
  55. package/dist/resources/extensions/gsd/uok/kernel.js +120 -46
  56. package/dist/resources/extensions/gsd/uok/model-policy.js +17 -14
  57. package/dist/resources/extensions/gsd/workflow-event-ledger.js +1 -1
  58. package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
  59. package/dist/resources/extensions/gsd/workflow-projections.js +9 -26
  60. package/dist/resources/extensions/gsd/workspace.js +5 -14
  61. package/dist/resources/extensions/shared/gsd-browser-cli.js +29 -0
  62. package/dist/resources/extensions/shared/interview-ui.js +62 -9
  63. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  64. package/dist/web/standalone/.next/BUILD_ID +1 -1
  65. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  66. package/dist/web/standalone/.next/build-manifest.json +2 -2
  67. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  68. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  85. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  86. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  87. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  88. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  89. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  90. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  91. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  92. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  93. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  94. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  95. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  96. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  97. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  98. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  99. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  100. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
  101. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  102. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  104. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  105. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  106. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  107. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  108. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  109. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  110. package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  111. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  112. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  113. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  114. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  115. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  116. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  117. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  118. package/dist/web/standalone/.next/server/app/index.html +1 -1
  119. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  120. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  121. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  122. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  123. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  126. package/dist/web/standalone/.next/server/chunks/{1128.js → 1991.js} +1 -1
  127. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  128. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  130. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  131. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  132. package/dist/worktree-cli-create.d.ts +10 -0
  133. package/dist/worktree-cli-create.js +11 -0
  134. package/dist/worktree-cli-format.d.ts +12 -0
  135. package/dist/worktree-cli-format.js +38 -0
  136. package/dist/worktree-cli-plan.d.ts +16 -0
  137. package/dist/worktree-cli-plan.js +19 -0
  138. package/dist/worktree-cli-session.d.ts +13 -0
  139. package/dist/worktree-cli-session.js +18 -0
  140. package/dist/worktree-cli-status.d.ts +35 -0
  141. package/dist/worktree-cli-status.js +54 -0
  142. package/dist/worktree-cli.d.ts +1 -16
  143. package/dist/worktree-cli.js +28 -122
  144. package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
  145. package/integrations/hermes/pyproject.toml +1 -1
  146. package/package.json +1 -1
  147. package/packages/cloud-mcp-gateway/package.json +2 -2
  148. package/packages/contracts/package.json +1 -1
  149. package/packages/daemon/dist/orchestrator-agent.d.ts +21 -0
  150. package/packages/daemon/dist/orchestrator-agent.d.ts.map +1 -0
  151. package/packages/daemon/dist/orchestrator-agent.js +113 -0
  152. package/packages/daemon/dist/orchestrator-agent.js.map +1 -0
  153. package/packages/daemon/dist/orchestrator-tools.d.ts +12 -0
  154. package/packages/daemon/dist/orchestrator-tools.d.ts.map +1 -0
  155. package/packages/daemon/dist/orchestrator-tools.js +137 -0
  156. package/packages/daemon/dist/orchestrator-tools.js.map +1 -0
  157. package/packages/daemon/dist/orchestrator.d.ts +4 -56
  158. package/packages/daemon/dist/orchestrator.d.ts.map +1 -1
  159. package/packages/daemon/dist/orchestrator.js +13 -303
  160. package/packages/daemon/dist/orchestrator.js.map +1 -1
  161. package/packages/daemon/dist/orchestrator.test.js +11 -1
  162. package/packages/daemon/dist/orchestrator.test.js.map +1 -1
  163. package/packages/daemon/package.json +4 -4
  164. package/packages/gsd-agent-core/package.json +5 -5
  165. package/packages/gsd-agent-modes/package.json +7 -7
  166. package/packages/mcp-server/package.json +4 -4
  167. package/packages/native/package.json +1 -1
  168. package/packages/pi-agent-core/package.json +1 -1
  169. package/packages/pi-ai/package.json +1 -1
  170. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +9 -0
  171. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/package-manager.js +74 -53
  173. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +6 -0
  175. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  176. package/packages/pi-coding-agent/dist/core/resource-loader.js +30 -2
  177. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  178. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts +66 -0
  179. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts.map +1 -0
  180. package/packages/pi-coding-agent/dist/core/skill-directories.js +138 -0
  181. package/packages/pi-coding-agent/dist/core/skill-directories.js.map +1 -0
  182. package/packages/pi-coding-agent/dist/core/skills.d.ts +4 -1
  183. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/skills.js +4 -1
  185. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  186. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  187. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  188. package/packages/pi-coding-agent/dist/index.js +1 -0
  189. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  190. package/packages/pi-coding-agent/package.json +7 -7
  191. package/packages/pi-tui/package.json +2 -2
  192. package/packages/rpc-client/package.json +2 -2
  193. package/pkg/package.json +1 -1
  194. package/src/resources/extensions/ask-user-questions.ts +1 -1
  195. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +146 -33
  196. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +25 -0
  197. package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +93 -0
  198. package/src/resources/extensions/gsd/activity-log.ts +1 -1
  199. package/src/resources/extensions/gsd/auto/orchestrator.ts +17 -11
  200. package/src/resources/extensions/gsd/auto-artifact-paths.ts +14 -4
  201. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +9 -2
  202. package/src/resources/extensions/gsd/auto-dispatch.ts +77 -16
  203. package/src/resources/extensions/gsd/auto-model-selection.ts +33 -1
  204. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -2
  205. package/src/resources/extensions/gsd/auto-prompts.ts +53 -113
  206. package/src/resources/extensions/gsd/auto-recovery.ts +50 -6
  207. package/src/resources/extensions/gsd/auto-start.ts +58 -29
  208. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -2
  209. package/src/resources/extensions/gsd/auto-timers.ts +9 -2
  210. package/src/resources/extensions/gsd/auto.ts +103 -30
  211. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +1 -1
  212. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +66 -6
  213. package/src/resources/extensions/gsd/codebase-generator.ts +7 -1
  214. package/src/resources/extensions/gsd/compat/compat-marker.ts +47 -0
  215. package/src/resources/extensions/gsd/config-overlay.ts +7 -1
  216. package/src/resources/extensions/gsd/db/engine.ts +36 -0
  217. package/src/resources/extensions/gsd/db/writers/cascades.ts +23 -1
  218. package/src/resources/extensions/gsd/db-adapter.ts +29 -3
  219. package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  220. package/src/resources/extensions/gsd/flat-phase-migration.ts +20 -0
  221. package/src/resources/extensions/gsd/gsd-db.ts +20 -11
  222. package/src/resources/extensions/gsd/guided-flow.ts +19 -7
  223. package/src/resources/extensions/gsd/journal.ts +1 -1
  224. package/src/resources/extensions/gsd/markdown-renderer.ts +52 -103
  225. package/src/resources/extensions/gsd/md-importer.ts +93 -22
  226. package/src/resources/extensions/gsd/metrics.ts +2 -2
  227. package/src/resources/extensions/gsd/migration-auto-check.ts +32 -3
  228. package/src/resources/extensions/gsd/paths.ts +108 -51
  229. package/src/resources/extensions/gsd/post-unit-hooks.ts +32 -0
  230. package/src/resources/extensions/gsd/preferences-models.ts +74 -20
  231. package/src/resources/extensions/gsd/preferences-skills.ts +10 -16
  232. package/src/resources/extensions/gsd/preferences-types.ts +24 -1
  233. package/src/resources/extensions/gsd/preferences-validation.ts +77 -9
  234. package/src/resources/extensions/gsd/preferences.ts +20 -0
  235. package/src/resources/extensions/gsd/rule-registry.ts +111 -9
  236. package/src/resources/extensions/gsd/skill-discovery.ts +47 -23
  237. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +1 -9
  238. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +2 -22
  239. package/src/resources/extensions/gsd/state-reconciliation/index.ts +14 -0
  240. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +169 -1
  241. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +41 -2
  242. package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +48 -1
  243. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +57 -0
  244. package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +29 -1
  245. package/src/resources/extensions/gsd/tests/auto-timeout-recovery-supervisor-inflight-guard.test.ts +159 -0
  246. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
  247. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +3 -1
  248. package/src/resources/extensions/gsd/tests/compat-marker.test.ts +85 -0
  249. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +45 -0
  250. package/src/resources/extensions/gsd/tests/db-adapter.test.ts +39 -0
  251. package/src/resources/extensions/gsd/tests/db-writable-probe.test.ts +66 -0
  252. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +27 -0
  253. package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +22 -1
  254. package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +72 -32
  255. package/src/resources/extensions/gsd/tests/import-completion-lost-update.test.ts +196 -0
  256. package/src/resources/extensions/gsd/tests/integration/paths.test.ts +88 -1
  257. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -15
  258. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +91 -0
  259. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +2 -1
  260. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
  261. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +269 -0
  262. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +8 -1
  263. package/src/resources/extensions/gsd/tests/preferences.test.ts +19 -0
  264. package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +54 -0
  265. package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +49 -1
  266. package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +52 -0
  267. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +95 -1
  268. package/src/resources/extensions/gsd/tests/sibling-model-fallbacks.test.ts +189 -0
  269. package/src/resources/extensions/gsd/tests/single-field-model-thinking.test.ts +156 -0
  270. package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +46 -0
  271. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +6 -1
  272. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +32 -0
  273. package/src/resources/extensions/gsd/tests/supervisor-model-swap-inflight-guard.test.ts +181 -0
  274. package/src/resources/extensions/gsd/tests/supervisor-model-swap-thinking-clamp.test.ts +122 -0
  275. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +93 -0
  276. package/src/resources/extensions/gsd/tests/uat-dispatch.test.ts +211 -0
  277. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +39 -0
  278. package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +226 -2
  279. package/src/resources/extensions/gsd/tests/uok-model-policy.test.ts +6 -0
  280. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +213 -2
  281. package/src/resources/extensions/gsd/tools/complete-milestone.ts +9 -5
  282. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +19 -8
  283. package/src/resources/extensions/gsd/tools/reopen-slice.ts +5 -0
  284. package/src/resources/extensions/gsd/tools/reopen-task.ts +13 -5
  285. package/src/resources/extensions/gsd/tools/validate-milestone.ts +13 -9
  286. package/src/resources/extensions/gsd/types.ts +23 -4
  287. package/src/resources/extensions/gsd/uat-dispatch.ts +176 -0
  288. package/src/resources/extensions/gsd/uok/audit-toggle.ts +32 -2
  289. package/src/resources/extensions/gsd/uok/audit.ts +2 -0
  290. package/src/resources/extensions/gsd/uok/gate-runner.ts +26 -36
  291. package/src/resources/extensions/gsd/uok/gitops.ts +21 -18
  292. package/src/resources/extensions/gsd/uok/kernel.ts +169 -48
  293. package/src/resources/extensions/gsd/uok/model-policy.ts +20 -17
  294. package/src/resources/extensions/gsd/workflow-event-ledger.ts +1 -1
  295. package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
  296. package/src/resources/extensions/gsd/workflow-projections.ts +9 -24
  297. package/src/resources/extensions/gsd/workspace.ts +5 -21
  298. package/src/resources/extensions/shared/gsd-browser-cli.ts +29 -0
  299. package/src/resources/extensions/shared/interview-ui.ts +58 -11
  300. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +32 -0
  301. /package/dist/web/standalone/.next/static/{ZpFDazP-38PXBPiI_LdZf → NPMNi2HV8BuK3baQsEUhk}/_buildManifest.js +0 -0
  302. /package/dist/web/standalone/.next/static/{ZpFDazP-38PXBPiI_LdZf → NPMNi2HV8BuK3baQsEUhk}/_ssgManifest.js +0 -0
@@ -8,21 +8,53 @@
8
8
  import { logWarning } from "./workflow-logger.js";
9
9
  import { resolvePostUnitHooks, resolvePreDispatchHooks } from "./preferences.js";
10
10
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
11
- import { join } from "node:path";
11
+ import { dirname, join } from "node:path";
12
12
  import { parseUnitId } from "./unit-id.js";
13
+ import { resolveMilestonePath } from "./paths.js";
13
14
  import { queryJournal } from "./journal.js";
14
15
  import { readUnitRuntimeRecord } from "./unit-runtime.js";
15
16
  import { extractFrontmatterVerdict } from "./verdict-parser.js";
16
17
  // ─── Artifact Path Resolution ──────────────────────────────────────────────
17
18
  export function resolveHookArtifactPath(basePath, unitId, artifactName) {
18
19
  const { milestone, slice, task } = parseUnitId(unitId);
20
+ // Prefer the active phase directory (flat-phase layout: .gsd/phases/<NN>-.../).
21
+ // Configured gate artifacts are canonical phase-level files (e.g.
22
+ // BROWSER-RUNTIME-EVIDENCE.md), so resolve the exact declared name first,
23
+ // then a task-prefixed variant for retry sentinels / rework briefs.
24
+ const candidates = [];
25
+ const phaseDir = resolveMilestonePath(basePath, milestone);
26
+ const isFlatPhaseDir = phaseDir !== null
27
+ && !(dirname(phaseDir).endsWith("/milestones") || dirname(phaseDir).endsWith("\\milestones"));
28
+ if (isFlatPhaseDir) {
29
+ candidates.push(join(phaseDir, artifactName));
30
+ if (task !== undefined) {
31
+ candidates.push(join(phaseDir, `${task}-${artifactName}`));
32
+ }
33
+ }
34
+ // Legacy nested layout fallbacks (.gsd/milestones/<M>/slices/<S>/tasks/...).
35
+ const legacyBase = join(basePath, ".gsd", "milestones", milestone);
36
+ let legacyDefault;
19
37
  if (task !== undefined && slice !== undefined) {
20
- return join(basePath, ".gsd", "milestones", milestone, "slices", slice, "tasks", `${task}-${artifactName}`);
21
- }
22
- if (slice !== undefined) {
23
- return join(basePath, ".gsd", "milestones", milestone, "slices", slice, artifactName);
24
- }
25
- return join(basePath, ".gsd", "milestones", milestone, artifactName);
38
+ legacyDefault = join(legacyBase, "slices", slice, "tasks", `${task}-${artifactName}`);
39
+ candidates.push(legacyDefault);
40
+ candidates.push(join(legacyBase, "slices", slice, "tasks", artifactName));
41
+ candidates.push(join(legacyBase, "slices", slice, artifactName));
42
+ }
43
+ else if (slice !== undefined) {
44
+ legacyDefault = join(legacyBase, "slices", slice, artifactName);
45
+ candidates.push(legacyDefault);
46
+ }
47
+ else {
48
+ legacyDefault = join(legacyBase, artifactName);
49
+ }
50
+ candidates.push(join(legacyBase, artifactName));
51
+ const existing = candidates.find((candidate) => existsSync(candidate));
52
+ if (existing)
53
+ return existing;
54
+ // Nothing on disk yet: prefer the active phase path when the phase dir is
55
+ // known, otherwise fall back to the legacy default (preserves pre-flat-phase
56
+ // behavior for missing-artifact diagnostics).
57
+ return isFlatPhaseDir ? join(phaseDir, artifactName) : legacyDefault;
26
58
  }
27
59
  // ─── Dispatch Rule Conversion ──────────────────────────────────────────────
28
60
  /**
@@ -70,6 +102,13 @@ export class RuleRegistry {
70
102
  activeHook = null;
71
103
  hookQueue = [];
72
104
  cycleCounts = new Map();
105
+ /**
106
+ * Cycle keys that have already been granted a one-shot re-dispatch after a
107
+ * lost/interrupted dispatch (hook charged a cycle but never produced its own
108
+ * unit-end). Bounds the dispatch-cycle refund to exactly one per gate so a
109
+ * hook that repeatedly fails to complete cannot loop forever.
110
+ */
111
+ redispatchedGateKeys = new Set();
73
112
  retryPending = false;
74
113
  retryTrigger = null;
75
114
  hookFailure = null;
@@ -269,6 +308,10 @@ export class RuleRegistry {
269
308
  cycle: currentCycle,
270
309
  pendingRetry: false,
271
310
  };
311
+ return this._buildHookDispatch(config, triggerUnitId);
312
+ }
313
+ /** Construct the sidecar dispatch for a hook without mutating registry state. */
314
+ _buildHookDispatch(config, triggerUnitId) {
272
315
  const { milestone: mid, slice: sid, task: tid } = parseUnitId(triggerUnitId);
273
316
  let prompt = config.prompt
274
317
  .replace(/\{milestoneId\}/g, mid ?? "")
@@ -278,11 +321,27 @@ export class RuleRegistry {
278
321
  return {
279
322
  hookName: config.name,
280
323
  prompt,
281
- model: config.model,
324
+ // Model selection (including fallbacks[]) is handled by
325
+ // resolveModelWithFallbacksForUnit for the `hook/<name>` unit type (#1229).
282
326
  unitType: `hook/${config.name}`,
283
327
  unitId: triggerUnitId,
284
328
  };
285
329
  }
330
+ /**
331
+ * Reconstruct the in-flight hook's dispatch from the restored `activeHook`
332
+ * state, without mutating the registry. Used to reconcile a persisted
333
+ * `activeHook` whose session-local dispatch was lost across a pause/resume
334
+ * or crash-recovery, so the hook actually runs instead of being charged
335
+ * against the next unrelated unit's close-out (#1246).
336
+ */
337
+ getPendingHookDispatch(basePath) {
338
+ if (!this.activeHook)
339
+ return null;
340
+ const config = resolvePostUnitHooks(basePath).find(h => h.name === this.activeHook.hookName);
341
+ if (!config)
342
+ return null;
343
+ return this._buildHookDispatch(config, this.activeHook.triggerUnitId);
344
+ }
286
345
  _assessHookCompletion(basePath, hook) {
287
346
  return this._assessConfiguredHookCompletion(basePath, hook.hookName, hook.triggerUnitId);
288
347
  }
@@ -368,6 +427,30 @@ export class RuleRegistry {
368
427
  }
369
428
  _handleBlockingGateCompletion(config, hook, basePath, observedCleanExecution) {
370
429
  if (!observedCleanExecution) {
430
+ // The hook was charged a cycle at dispatch but never produced its own
431
+ // unit-end — its dispatch was lost/interrupted (pause/resume, crash
432
+ // recovery). Refund that dispatch-consumed cycle exactly once so the gate
433
+ // is re-dispatched at least once instead of hard-blocking on a hook that
434
+ // never actually ran.
435
+ //
436
+ // This refund is intentionally UNCONDITIONAL on the first lost dispatch —
437
+ // it must NOT be gated on `currentCycle >= max_cycles`. A lost dispatch is
438
+ // a non-run: charging its cycle at dispatch time must not consume the
439
+ // hook's real-run budget. Gating the refund on "budget already exhausted"
440
+ // lets a lost dispatch below max_cycles silently eat a genuine cycle, so
441
+ // the hook gets one fewer real run than configured — reintroducing #1244
442
+ // for max_cycles >= 2 (see the "does not steal a real-run cycle" test).
443
+ // The redispatchedGateKeys flag bounds this to a single refund per gate,
444
+ // so repeated lost dispatches still terminate (total dispatches never
445
+ // exceed max_cycles + 1) and a hook that keeps failing blocks normally.
446
+ const cycleKey = hookCycleKey(config, hook);
447
+ if (!this.redispatchedGateKeys.has(cycleKey)) {
448
+ this.redispatchedGateKeys.add(cycleKey);
449
+ const currentCycle = this.cycleCounts.get(cycleKey);
450
+ if (typeof currentCycle === "number" && currentCycle > 0) {
451
+ this.cycleCounts.set(cycleKey, currentCycle - 1);
452
+ }
453
+ }
371
454
  return this._rerunGateOrBlock(config, hook, basePath, {
372
455
  reason: `hook/${config.name} did not complete cleanly before the trigger unit resumed`,
373
456
  });
@@ -639,6 +722,7 @@ export class RuleRegistry {
639
722
  this.activeHook = null;
640
723
  this.hookQueue = [];
641
724
  this.cycleCounts.clear();
725
+ this.redispatchedGateKeys.clear();
642
726
  this.retryPending = false;
643
727
  this.retryTrigger = null;
644
728
  this.hookFailure = null;
@@ -652,6 +736,7 @@ export class RuleRegistry {
652
736
  persistState(basePath) {
653
737
  const state = {
654
738
  cycleCounts: Object.fromEntries(this.cycleCounts),
739
+ redispatchedGateKeys: Array.from(this.redispatchedGateKeys),
655
740
  activeHook: this.activeHook ? { ...this.activeHook } : null,
656
741
  hookQueue: this.hookQueue.map(entry => ({
657
742
  hookName: entry.config.name,
@@ -687,6 +772,14 @@ export class RuleRegistry {
687
772
  }
688
773
  }
689
774
  }
775
+ this.redispatchedGateKeys.clear();
776
+ if (Array.isArray(state.redispatchedGateKeys)) {
777
+ for (const key of state.redispatchedGateKeys) {
778
+ if (typeof key === "string") {
779
+ this.redispatchedGateKeys.add(key);
780
+ }
781
+ }
782
+ }
690
783
  this.activeHook = state.activeHook && typeof state.activeHook === "object"
691
784
  ? { ...state.activeHook }
692
785
  : null;
@@ -715,7 +808,7 @@ export class RuleRegistry {
715
808
  try {
716
809
  const filePath = this._hookStatePath(basePath);
717
810
  if (existsSync(filePath)) {
718
- writeFileSync(filePath, JSON.stringify({ cycleCounts: {}, activeHook: null, hookQueue: [], savedAt: new Date().toISOString() }, null, 2), "utf-8");
811
+ writeFileSync(filePath, JSON.stringify({ cycleCounts: {}, redispatchedGateKeys: [], activeHook: null, hookQueue: [], savedAt: new Date().toISOString() }, null, 2), "utf-8");
719
812
  }
720
813
  }
721
814
  catch (e) {
@@ -793,7 +886,10 @@ export class RuleRegistry {
793
886
  return {
794
887
  hookName: hook.name,
795
888
  prompt,
796
- model: hook.model,
889
+ // Model selection (including fallbacks[]) is resolved by dispatchHookUnit
890
+ // via resolveModelWithFallbacksForUnit for the `hook/<name>` unit type,
891
+ // matching the auto-mode path. Emitting the primary-only model here would
892
+ // discard the configured fallback chain (#1229).
797
893
  unitType: `hook/${hook.name}`,
798
894
  unitId,
799
895
  };
@@ -8,41 +8,49 @@
8
8
  * prompt block when reload fails before the next agent turn.
9
9
  */
10
10
  import { existsSync, readdirSync, readFileSync } from "node:fs";
11
- import { homedir } from "node:os";
12
11
  import { join } from "node:path";
12
+ import { getSkillDirectories } from "@gsd/pi-coding-agent";
13
13
  import { gsdHome } from "./gsd-home.js";
14
14
  import { getInstalledSkills, normalizeSkillName } from "./skills.js";
15
- /** Snapshot of normalized skill names at auto-mode start */
16
- let baselineSkills = null;
15
+ let snapshot = null;
17
16
  /**
18
17
  * Snapshot the current installed skill catalog. Call at auto-mode start.
18
+ * `cwd` is the project base path; skills dropped into `<cwd>/.agents/skills/`,
19
+ * `<cwd>/.claude/skills/`, or `<cwd>/.gsd/skills/` mid-session are detected.
19
20
  */
20
- export function snapshotSkills() {
21
- baselineSkills = snapshotCurrentSkillNames();
21
+ export function snapshotSkills(options) {
22
+ const searchDirs = getSkillDirectories({
23
+ cwd: options?.cwd ?? process.cwd(),
24
+ gsdHome: gsdHome(),
25
+ }).map((entry) => entry.path);
26
+ snapshot = {
27
+ baselineNames: snapshotCurrentSkillNames(searchDirs),
28
+ searchDirs,
29
+ };
22
30
  }
23
31
  /**
24
32
  * Clear the snapshot. Call when auto-mode stops.
25
33
  */
26
34
  export function clearSkillSnapshot() {
27
- baselineSkills = null;
35
+ snapshot = null;
28
36
  }
29
37
  /**
30
38
  * Check if a snapshot is active (auto-mode is running with discovery).
31
39
  */
32
40
  export function hasSkillSnapshot() {
33
- return baselineSkills !== null;
41
+ return snapshot !== null;
34
42
  }
35
43
  /**
36
44
  * Detect skills installed since the snapshot was taken.
37
45
  * Returns skill metadata for any new skills found in the loader catalog or on disk.
38
46
  */
39
47
  export function detectNewSkills() {
40
- if (!baselineSkills)
48
+ if (!snapshot)
41
49
  return [];
42
50
  const newSkills = [];
43
- for (const skill of getCurrentSkillsForDiscovery()) {
51
+ for (const skill of getCurrentSkillsForDiscovery(snapshot.searchDirs)) {
44
52
  const normalized = normalizeSkillName(skill.name);
45
- if (baselineSkills.has(normalized))
53
+ if (snapshot.baselineNames.has(normalized))
46
54
  continue;
47
55
  newSkills.push(skill);
48
56
  }
@@ -68,7 +76,13 @@ export async function refreshCatalogForNewSkills(options) {
68
76
  return newSkills;
69
77
  }
70
78
  }
71
- snapshotSkills();
79
+ // Re-snapshot, preserving the search dirs captured at auto-mode start.
80
+ if (snapshot) {
81
+ snapshot = {
82
+ baselineNames: snapshotCurrentSkillNames(),
83
+ searchDirs: snapshot.searchDirs,
84
+ };
85
+ }
72
86
  const names = newSkills.map((skill) => skill.name).join(", ");
73
87
  options?.notify?.(`GSD: loaded new skills: ${names}`, "info");
74
88
  return newSkills;
@@ -103,17 +117,16 @@ function escapeXml(value) {
103
117
  }
104
118
  });
105
119
  }
106
- function skillSearchDirs() {
107
- return [
108
- join(gsdHome(), "agent", "skills"),
109
- join(homedir(), ".agents", "skills"),
110
- join(homedir(), ".claude", "skills"),
111
- ];
120
+ function snapshotCurrentSkillNames(searchDirsOverride) {
121
+ // Uses explicit dirs, the snapshot's search dirs when active, or user-only dirs
122
+ // (no project context) when called before a snapshot is taken.
123
+ const searchDirs = searchDirsOverride ?? snapshot?.searchDirs ?? defaultSearchDirs();
124
+ return new Set(getCurrentSkillsForDiscovery(searchDirs).map(skill => normalizeSkillName(skill.name)));
112
125
  }
113
- function snapshotCurrentSkillNames() {
114
- return new Set(getCurrentSkillsForDiscovery().map(skill => normalizeSkillName(skill.name)));
126
+ function defaultSearchDirs() {
127
+ return getSkillDirectories({ cwd: process.cwd(), gsdHome: gsdHome() }).map((entry) => entry.path);
115
128
  }
116
- function getCurrentSkillsForDiscovery() {
129
+ function getCurrentSkillsForDiscovery(searchDirs) {
117
130
  const skills = new Map();
118
131
  for (const skill of getInstalledSkills()) {
119
132
  const normalized = normalizeSkillName(skill.name);
@@ -123,16 +136,16 @@ function getCurrentSkillsForDiscovery() {
123
136
  location: skill.filePath,
124
137
  });
125
138
  }
126
- for (const skill of getDiskSkills()) {
139
+ for (const skill of getDiskSkills(searchDirs)) {
127
140
  const normalized = normalizeSkillName(skill.name);
128
141
  if (!skills.has(normalized))
129
142
  skills.set(normalized, skill);
130
143
  }
131
144
  return [...skills.values()];
132
145
  }
133
- function getDiskSkills() {
146
+ function getDiskSkills(searchDirs) {
134
147
  const skills = new Map();
135
- for (const dir of skillSearchDirs()) {
148
+ for (const dir of searchDirs) {
136
149
  if (!existsSync(dir))
137
150
  continue;
138
151
  let entries;
@@ -140,16 +140,7 @@ function detectArtifactDbStatusDriftForMilestone(basePath, milestoneId) {
140
140
  for (const task of tasks) {
141
141
  if (isClosedStatus(task.status))
142
142
  continue;
143
- // Flat-phase: task summaries live in the phase dir as TID-SUMMARY.md
144
- let diskTaskSummary = resolveTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY");
145
- if (!diskTaskSummary) {
146
- const phaseDir = resolveMilestonePath(basePath, milestoneId);
147
- if (phaseDir) {
148
- const flatSummary = join(phaseDir, `${task.id}-SUMMARY.md`);
149
- if (existsSync(flatSummary))
150
- diskTaskSummary = flatSummary;
151
- }
152
- }
143
+ const diskTaskSummary = resolveTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY");
153
144
  if (!diskTaskSummary || !existsSync(diskTaskSummary))
154
145
  continue;
155
146
  addUniqueDrift(drifts, seen, {
@@ -7,8 +7,7 @@
7
7
  // same value).
8
8
  import { existsSync, statSync } from "node:fs";
9
9
  import { getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, isDbAvailable, updateMilestoneStatus, updateSliceStatus, updateTaskStatus, } from "../../gsd-db.js";
10
- import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveTaskFile, } from "../../paths.js";
11
- import { join } from "node:path";
10
+ import { resolveMilestoneFile, resolveSliceFile, resolveTaskFile, } from "../../paths.js";
12
11
  const COMPLETE_STATUSES = new Set(["complete", "done"]);
13
12
  function summaryMtimeIso(path) {
14
13
  try {
@@ -68,18 +67,7 @@ function collectMilestoneCompletionDrift(mid, ctx, drifts) {
68
67
  continue;
69
68
  if (task.completed_at !== null)
70
69
  continue;
71
- // Flat-phase: task summaries live in the phase dir as TID-SUMMARY.md.
72
- // Legacy: resolveTaskFile returns the tasks/TID/TID-SUMMARY.md path.
73
- let taskSummary = resolveTaskFile(ctx.basePath, mid, slice.id, task.id, "SUMMARY");
74
- // Flat-phase fallback: check the phase dir directly
75
- if (!taskSummary) {
76
- const phaseDir = resolveMilestonePath(ctx.basePath, mid);
77
- if (phaseDir) {
78
- const flatSummary = join(phaseDir, `${task.id}-SUMMARY.md`);
79
- if (existsSync(flatSummary))
80
- taskSummary = flatSummary;
81
- }
82
- }
70
+ const taskSummary = resolveTaskFile(ctx.basePath, mid, slice.id, task.id, "SUMMARY");
83
71
  if (taskSummary && existsSync(taskSummary)) {
84
72
  drifts.push({
85
73
  kind: "missing-completion-timestamp",
@@ -136,16 +124,7 @@ export function repairMissingCompletionTimestamp(record, ctx) {
136
124
  task.completed_at !== null ||
137
125
  !COMPLETE_STATUSES.has(task.status))
138
126
  return;
139
- // Flat-phase: task summaries live in the phase dir as TID-SUMMARY.md
140
- let summary = resolveTaskFile(ctx.basePath, mid, sid, tid, "SUMMARY");
141
- if (!summary) {
142
- const phaseDir = resolveMilestonePath(ctx.basePath, mid);
143
- if (phaseDir) {
144
- const flatSummary = join(phaseDir, `${tid}-SUMMARY.md`);
145
- if (existsSync(flatSummary))
146
- summary = flatSummary;
147
- }
148
- }
127
+ const summary = resolveTaskFile(ctx.basePath, mid, sid, tid, "SUMMARY");
149
128
  const ts = summary ? summaryMtimeIso(summary) : null;
150
129
  if (!ts)
151
130
  return;
@@ -41,6 +41,16 @@ export async function reconcileBeforeDispatch(basePath, partialDeps = {}) {
41
41
  if (!deps.dryRun) {
42
42
  const { capturePlanningCompatIfNeeded } = await import("../compat/planning-compat.js");
43
43
  await capturePlanningCompatIfNeeded(basePath);
44
+ // Self-heal phantom projection entries (#1257): a renamed/removed phase
45
+ // directory leaves stale `.compat.json` projection paths that no detector
46
+ // ever prunes (they all skip missing files), so the marker drifts from disk
47
+ // reality permanently. Drop entries whose backing file is gone before the
48
+ // detect passes run. Gated on !dryRun to keep detection read-only.
49
+ const { pruneOrphanedProjectionEntries } = await import("../compat/compat-marker.js");
50
+ const prunedCount = pruneOrphanedProjectionEntries(basePath);
51
+ if (prunedCount > 0) {
52
+ logWarning("reconcile", `pruned ${prunedCount} orphaned projection entr${prunedCount === 1 ? "y" : "ies"} from .compat.json (backing file no longer on disk)`);
53
+ }
44
54
  }
45
55
  for (let pass = 0; pass < MAX_PASSES; pass++) {
46
56
  deps.invalidateStateCache();
@@ -8,10 +8,9 @@
8
8
  * renders MILESTONE-SUMMARY.md to disk, stores rendered markdown in DB
9
9
  * for recovery, and invalidates caches.
10
10
  */
11
- import { join } from "node:path";
12
11
  import { existsSync } from "node:fs";
13
12
  import { transaction, getMilestone, getMilestoneSlices, getSliceTasks, getLatestAssessmentByScope, updateMilestoneStatus, } from "../gsd-db.js";
14
- import { clearPathCache, relMilestoneFile } from "../paths.js";
13
+ import { clearPathCache, resolveMilestoneFile, targetMilestoneFile } from "../paths.js";
15
14
  import { resolveCanonicalMilestoneRoot } from "../worktree-manager.js";
16
15
  import { isClosedStatus, isDeferredStatus } from "../status-guards.js";
17
16
  import { saveFile, clearParseCache } from "../files.js";
@@ -144,9 +143,8 @@ export async function handleCompleteMilestone(params, basePath) {
144
143
  }
145
144
  // ── Filesystem operations (outside transaction) ─────────────────────────
146
145
  const summaryMd = renderMilestoneSummaryMarkdown(params, completedAt);
147
- // Layout-aware: flat-phase phases/NN-slug/NN-SUMMARY.md;
148
- // legacy milestones/MID/MID-SUMMARY.md.
149
- const summaryPath = join(artifactBasePath, relMilestoneFile(artifactBasePath, params.milestoneId, "SUMMARY"));
146
+ const summaryPath = resolveMilestoneFile(artifactBasePath, params.milestoneId, "SUMMARY") ??
147
+ targetMilestoneFile(artifactBasePath, params.milestoneId, "SUMMARY", getMilestone(params.milestoneId)?.title);
150
148
  // Guard (#4598): if SUMMARY.md already exists on disk, do not overwrite it.
151
149
  // This handles re-dispatch scenarios (DB/disk state divergence) where a prior
152
150
  // completion already wrote the file. Overwriting would silently destroy the
@@ -1,6 +1,6 @@
1
- import { join } from "node:path";
2
1
  import { existsSync, unlinkSync } from "node:fs";
3
- import { relSliceFile, relMilestoneFile } from "../paths.js";
2
+ import { join } from "node:path";
3
+ import { relSliceFile, resolveMilestoneFile, resolveMilestonePath, targetMilestoneFile } from "../paths.js";
4
4
  import { clearParseCache } from "../files.js";
5
5
  import { isClosedStatus } from "../status-guards.js";
6
6
  import { isNonEmptyString } from "../validation.js";
@@ -225,14 +225,20 @@ export async function handleReassessRoadmap(rawParams, basePath) {
225
225
  params.sliceChanges.modified.length > 0 ||
226
226
  params.sliceChanges.removed.length > 0;
227
227
  if (hasStructuralChanges) {
228
- // Layout-aware: flat-phase phases/NN-slug/NN-VALIDATION.md; legacy → milestones/MID/MID-VALIDATION.md
229
- const validationFile = join(basePath, relMilestoneFile(basePath, params.milestoneId, "VALIDATION"));
230
- try {
231
- if (existsSync(validationFile))
232
- unlinkSync(validationFile);
233
- }
234
- catch (e) {
235
- logWarning("tool", `validation file cleanup failed: ${e.message}`);
228
+ const milestoneDir = resolveMilestonePath(basePath, params.milestoneId);
229
+ const validationFiles = new Set([
230
+ resolveMilestoneFile(basePath, params.milestoneId, "VALIDATION"),
231
+ targetMilestoneFile(basePath, params.milestoneId, "VALIDATION", getMilestone(params.milestoneId)?.title),
232
+ milestoneDir ? join(milestoneDir, `${params.milestoneId}-VALIDATION.md`) : null,
233
+ ].filter((file) => Boolean(file)));
234
+ for (const validationFile of validationFiles) {
235
+ try {
236
+ if (existsSync(validationFile))
237
+ unlinkSync(validationFile);
238
+ }
239
+ catch (e) {
240
+ logWarning("tool", `validation file cleanup failed: ${e.message}`);
241
+ }
236
242
  }
237
243
  }
238
244
  // ── Invalidate caches ─────────────────────────────────────────
@@ -5,6 +5,11 @@
5
5
  * tasks back to "pending". This is intentional — if you're reopening a
6
6
  * slice, you're re-doing the work. Partial resets create ambiguous state.
7
7
  *
8
+ * Also recovers a desynced slice (#1205): when a UAT→planning fallback leaves
9
+ * the slice open (e.g. "pending") but its tasks still "complete", this clears
10
+ * that state so the planner can re-plan without hitting "already complete"
11
+ * rejections.
12
+ *
8
13
  * The parent milestone must still be open (not complete).
9
14
  */
10
15
  // GSD — reopen-slice tool handler
@@ -16,8 +16,8 @@ import { writeManifest } from "../workflow-manifest.js";
16
16
  import { appendEvent } from "../workflow-events.js";
17
17
  import { logWarning } from "../workflow-logger.js";
18
18
  import { existsSync, unlinkSync } from "node:fs";
19
- import { join } from "node:path";
20
- import { resolveTasksDir, clearPathCache } from "../paths.js";
19
+ import { join, basename, dirname } from "node:path";
20
+ import { resolveTasksDir, resolveSlicePath, clearPathCache } from "../paths.js";
21
21
  export async function handleReopenTask(params, basePath) {
22
22
  // ── Validate required fields ────────────────────────────────────────────
23
23
  if (!params.taskId || typeof params.taskId !== "string" || params.taskId.trim() === "") {
@@ -69,10 +69,18 @@ export async function handleReopenTask(params, basePath) {
69
69
  // ── Clean up stale filesystem artifacts (M12 fix) ────────────────────────
70
70
  // Without this, the DB-filesystem reconciler sees the SUMMARY.md and
71
71
  // auto-corrects the task back to "complete", making reopen a no-op (#3161).
72
+ // Legacy layout keeps the summary under a tasks/ subdir; flat-phase writes
73
+ // TID-SUMMARY.md directly in the phase dir. A tasks/ subdir may still exist
74
+ // in flat-phase for auxiliary artifacts — its mere existence must NOT redirect
75
+ // summary cleanup into tasks/ (#1208).
72
76
  try {
73
- const tasksDir = resolveTasksDir(basePath, params.milestoneId, params.sliceId);
74
- if (tasksDir) {
75
- const summaryPath = join(tasksDir, `${params.taskId}-SUMMARY.md`);
77
+ const slicePath = resolveSlicePath(basePath, params.milestoneId, params.sliceId);
78
+ if (slicePath) {
79
+ const isLegacySlice = basename(dirname(slicePath)) === "slices";
80
+ const summaryDir = isLegacySlice
81
+ ? (resolveTasksDir(basePath, params.milestoneId, params.sliceId) ?? slicePath)
82
+ : slicePath;
83
+ const summaryPath = join(summaryDir, `${params.taskId}-SUMMARY.md`);
76
84
  if (existsSync(summaryPath))
77
85
  unlinkSync(summaryPath);
78
86
  }
@@ -10,9 +10,8 @@
10
10
  * were never persisted, causing M002+ milestones to have zero gate records
11
11
  * despite passing validation.
12
12
  */
13
- import { join } from "node:path";
14
13
  import { transaction, insertAssessment, getMilestoneSlices, getMilestone, } from "../gsd-db.js";
15
- import { clearPathCache, relMilestoneFile } from "../paths.js";
14
+ import { clearPathCache, targetMilestoneFile } from "../paths.js";
16
15
  import { resolveCanonicalMilestoneRoot } from "../worktree-manager.js";
17
16
  import { resolveWorktreeProjectRoot } from "../worktree-root.js";
18
17
  import { saveFile, clearParseCache } from "../files.js";
@@ -113,11 +112,7 @@ export async function handleValidateMilestone(params, basePath, opts) {
113
112
  // worktree exists for this milestone, validation reads/writes the
114
113
  // worktree's artifacts instead of stale project-root state.
115
114
  const validationMd = renderValidationMarkdown(effectiveParams);
116
- // Layout-aware: flat-phase phases/NN-slug/NN-VALIDATION.md;
117
- // legacy → milestones/MID/MID-VALIDATION.md.
118
- // join(artifactBasePath, relMilestoneFile) gives the absolute path; relMilestoneFile
119
- // returns ".gsd/..."-relative so joining with basePath (project/worktree root) is correct.
120
- const validationPath = join(artifactBasePath, relMilestoneFile(artifactBasePath, effectiveParams.milestoneId, "VALIDATION"));
115
+ const validationPath = targetMilestoneFile(artifactBasePath, effectiveParams.milestoneId, "VALIDATION", getMilestone(effectiveParams.milestoneId)?.title);
121
116
  // ── DB write first — matches complete-task/complete-slice pattern ───
122
117
  // Write DB before disk so a crash between the two leaves a recoverable
123
118
  // state: the DB row exists but the file is missing, which projection
@@ -148,7 +143,7 @@ export async function handleValidateMilestone(params, basePath, opts) {
148
143
  const projectRoot = resolveWorktreeProjectRoot(basePath);
149
144
  if (projectRoot !== artifactBasePath) {
150
145
  // Mirror to project root using the project root's layout (same logic as above).
151
- const projectValidationPath = join(projectRoot, relMilestoneFile(projectRoot, effectiveParams.milestoneId, "VALIDATION"));
146
+ const projectValidationPath = targetMilestoneFile(projectRoot, effectiveParams.milestoneId, "VALIDATION", getMilestone(effectiveParams.milestoneId)?.title);
152
147
  try {
153
148
  await saveFile(projectValidationPath, validationMd);
154
149
  }
@@ -0,0 +1,94 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Resolve whether a completed slice needs run-uat dispatch.
3
+ import { loadFile } from "./files.js";
4
+ import { resolveSliceFile } from "./paths.js";
5
+ import { classifyUatContentForRun, shouldDispatchUatForContent, } from "./uat-policy.js";
6
+ import { hasVerdict } from "./verdict-parser.js";
7
+ import { logWarning } from "./workflow-logger.js";
8
+ async function loadSliceFileContent(base, milestoneId, sliceId, kind) {
9
+ const filePath = resolveSliceFile(base, milestoneId, sliceId, kind);
10
+ return filePath ? ((await loadFile(filePath)) ?? "") : "";
11
+ }
12
+ async function resolveRunUatEffectiveType(base, milestoneId, sliceId, uatContent) {
13
+ let summaryContent = "";
14
+ try {
15
+ summaryContent = await loadSliceFileContent(base, milestoneId, sliceId, "SUMMARY");
16
+ }
17
+ catch (err) {
18
+ logWarning("prompt", `resolveRunUatEffectiveType SUMMARY load failed: ${err instanceof Error ? err.message : String(err)}`);
19
+ }
20
+ return classifyUatContentForRun(uatContent, summaryContent).effectiveType;
21
+ }
22
+ async function resolveCandidateRunUatDispatch(base, milestoneId, candidate, prefs) {
23
+ const uatContent = await loadSliceFileContent(base, milestoneId, candidate.sliceId, "UAT");
24
+ if (!uatContent)
25
+ return null;
26
+ if (hasVerdict(uatContent))
27
+ return null;
28
+ const assessmentContent = await loadSliceFileContent(base, milestoneId, candidate.sliceId, "ASSESSMENT");
29
+ if (assessmentContent && hasVerdict(assessmentContent))
30
+ return null;
31
+ if (!shouldDispatchUatForContent(uatContent, prefs))
32
+ return null;
33
+ return {
34
+ sliceId: candidate.sliceId,
35
+ uatType: await resolveRunUatEffectiveType(base, milestoneId, candidate.sliceId, uatContent),
36
+ };
37
+ }
38
+ async function getDbCompletedSliceCandidates(milestoneId) {
39
+ const { isDbAvailable, getMilestoneSlices } = await import("./gsd-db.js");
40
+ if (!isDbAvailable())
41
+ return null;
42
+ const slices = getMilestoneSlices(milestoneId);
43
+ // No DB slice rows for this milestone: the DB has no authoritative view, so
44
+ // return null to let the caller defer to the roadmap fallback.
45
+ if (slices.length === 0)
46
+ return null;
47
+ // The DB has slice rows and is therefore authoritative for this milestone.
48
+ // Return a (possibly empty) array rather than null: an empty result must NOT
49
+ // fall through to the roadmap fallback, because the DB, not stale roadmap
50
+ // checkboxes, is the source of truth for which slices are complete.
51
+ return slices
52
+ .filter((slice) => slice.status === "complete")
53
+ .map((slice) => ({ sliceId: slice.id }))
54
+ .reverse();
55
+ }
56
+ export async function findRunUatDispatchFromCandidates(base, milestoneId, candidates, prefs) {
57
+ for (const candidate of candidates) {
58
+ const dispatch = await resolveCandidateRunUatDispatch(base, milestoneId, candidate, prefs);
59
+ if (dispatch)
60
+ return dispatch;
61
+ }
62
+ return null;
63
+ }
64
+ /**
65
+ * Check if the most recently completed slice needs a UAT run.
66
+ * Returns { sliceId, uatType } if UAT should be dispatched, null otherwise.
67
+ *
68
+ * When the DB has slice rows for the milestone it is authoritative: only its
69
+ * completed slices are considered and the `fallbackCandidates` (roadmap-derived)
70
+ * are ignored, even when no DB slice is complete. The fallback candidates are
71
+ * only consulted when the DB has no slice rows for the milestone, is
72
+ * unavailable, or errors.
73
+ *
74
+ * Skips when:
75
+ * - No completed slices exist in DB or the caller-provided fallback candidates
76
+ * - uat_dispatch is not enabled and the UAT spec does not require runtime/browser evidence
77
+ * - No UAT file exists for the slice
78
+ * - UAT result already exists in the UAT or ASSESSMENT file
79
+ */
80
+ export async function checkNeedsRunUat(base, milestoneId, prefs, fallbackCandidates = []) {
81
+ try {
82
+ const dbCandidates = await getDbCompletedSliceCandidates(milestoneId);
83
+ // A non-null result (including an empty array) means the DB is authoritative
84
+ // for this milestone; only fall through to the roadmap fallback when the DB
85
+ // has no slice rows at all (null).
86
+ if (dbCandidates !== null) {
87
+ return findRunUatDispatchFromCandidates(base, milestoneId, dbCandidates, prefs);
88
+ }
89
+ }
90
+ catch (err) {
91
+ logWarning("prompt", `checkNeedsRunUat DB lookup failed: ${err instanceof Error ? err.message : String(err)}`);
92
+ }
93
+ return findRunUatDispatchFromCandidates(base, milestoneId, fallbackCandidates, prefs);
94
+ }