@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
@@ -0,0 +1,66 @@
1
+ // gsd-pi — DB writability probe regression tests (#1234).
2
+ //
3
+ // Auto-start's DB gate only checked that the provider opened, not that the
4
+ // handle can actually write. A schema-current DB does zero writes during open,
5
+ // so a read-only / DBMOVED handle passed the open-only check and failed much
6
+ // later at the first authoritative write with an opaque "readonly database"
7
+ // error. `probeDbWritable` forces a real page write so that failure class is
8
+ // caught at the natural checkpoint.
9
+
10
+ import { test } from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { tmpdir } from "node:os";
15
+
16
+ import { openDatabase, closeDatabase, probeDbWritable, _getAdapter } from "../gsd-db.ts";
17
+ import type { DbAdapter } from "../db-adapter.ts";
18
+
19
+ function makeBase(): string {
20
+ const dir = mkdtempSync(join(tmpdir(), "gsd-db-writable-probe-"));
21
+ mkdirSync(join(dir, ".gsd"), { recursive: true });
22
+ return dir;
23
+ }
24
+
25
+ test("probeDbWritable succeeds against a freshly opened writable database", () => {
26
+ const base = makeBase();
27
+ assert.equal(openDatabase(join(base, ".gsd", "gsd.db")), true);
28
+ try {
29
+ assert.deepEqual(probeDbWritable(), { ok: true });
30
+ } finally {
31
+ closeDatabase();
32
+ rmSync(base, { recursive: true, force: true });
33
+ }
34
+ });
35
+
36
+ test("probeDbWritable reports not-ok with detail when no database is open", () => {
37
+ closeDatabase();
38
+ const result = probeDbWritable();
39
+ assert.equal(result.ok, false);
40
+ assert.match(result.detail ?? "", /No database is open/u);
41
+ });
42
+
43
+ test("probeDbWritable reports not-ok when the page write fails (simulating a read-only handle)", () => {
44
+ const base = makeBase();
45
+ assert.equal(openDatabase(join(base, ".gsd", "gsd.db")), true);
46
+ const adapter = _getAdapter() as DbAdapter;
47
+ assert.ok(adapter);
48
+
49
+ // The read (prepare/get) still works; only the page-dirtying write throws,
50
+ // mirroring SQLITE_READONLY_DBMOVED which fails once a page is dirtied.
51
+ const origExec = adapter.exec.bind(adapter);
52
+ adapter.exec = (sql: string): void => {
53
+ if (sql.includes("user_version =")) throw new Error("attempt to write a readonly database");
54
+ return origExec(sql);
55
+ };
56
+
57
+ try {
58
+ const result = probeDbWritable();
59
+ assert.equal(result.ok, false);
60
+ assert.match(result.detail ?? "", /readonly database/u);
61
+ } finally {
62
+ adapter.exec = origExec;
63
+ closeDatabase();
64
+ rmSync(base, { recursive: true, force: true });
65
+ }
66
+ });
@@ -109,6 +109,33 @@ test("migrateToFlatPhase creates a backup", async () => {
109
109
  assert.ok(backups.length >= 1, "at least one migrate-* backup dir should exist");
110
110
  });
111
111
 
112
+ test("failed migration rolls back without leaking a .gsd-backups/migrate-* dir", async (t) => {
113
+ const base = makeTmp();
114
+ const milestonesPath = join(base, ".gsd", "milestones");
115
+ const phasesPath = join(base, ".gsd", "phases");
116
+
117
+ // Fail the pre-render clear of phases/ to drive the rollback path, which by
118
+ // this point has already created the .gsd-backups/migrate-<ts>/ backup.
119
+ const restoreFsOps = _setFlatPhaseMigrationFsOpsForTest({
120
+ rmSync(target, opts) {
121
+ if (target === phasesPath) {
122
+ throw Object.assign(new Error("simulated locked phases/"), { code: "EPERM" });
123
+ }
124
+ return rmSync(target, opts as never);
125
+ },
126
+ });
127
+ t.after(restoreFsOps);
128
+
129
+ await assert.rejects(migrateToFlatPhase(base), /simulated locked/);
130
+
131
+ assert.ok(existsSync(milestonesPath), "legacy milestones/ should be restored on rollback");
132
+ const backupRoot = join(base, ".gsd-backups");
133
+ const leaked = existsSync(backupRoot)
134
+ ? readdirSync(backupRoot).filter((d) => d.startsWith("migrate-"))
135
+ : [];
136
+ assert.equal(leaked.length, 0, "rollback must delete the migrate-* backup it created");
137
+ });
138
+
112
139
  test("migrateToFlatPhase preserves milestone/slice/task counts in DB", async () => {
113
140
  const base = makeTmp();
114
141
  const msBefore = getAllMilestones().length;
@@ -7,7 +7,7 @@ import { join } from "node:path";
7
7
  import { tmpdir } from "node:os";
8
8
  import { randomUUID } from "node:crypto";
9
9
 
10
- import { renderPlanFromDb, renderRoadmapFromDb, renderAssessmentFromDb } from "../markdown-renderer.ts";
10
+ import { renderPlanFromDb, renderRoadmapFromDb, renderAssessmentFromDb, renderReplanFromDb } from "../markdown-renderer.ts";
11
11
  import { clearPathCache } from "../paths.ts";
12
12
  import { openDatabase, closeDatabase, insertMilestone, insertSlice, insertTask } from "../gsd-db.ts";
13
13
 
@@ -83,6 +83,27 @@ test("renderPlanFromDb does NOT write per-task plan files", async () => {
83
83
  assert.equal(result.taskPlanPaths.length, 0);
84
84
  });
85
85
 
86
+
87
+ // Replan uses the same slice-level target rules as assessment: flat-phase
88
+ // writes must stay at the phase root even if an old slices/SID/ dir exists.
89
+ test("renderReplanFromDb writes to the phase root even when a stray slices/SID/ dir exists", async () => {
90
+ const base = makeTmp();
91
+ const plan = await renderPlanFromDb(base, "M001", "S01");
92
+ const phaseDir = plan.planPath.slice(0, plan.planPath.lastIndexOf("01-01-PLAN.md"));
93
+ mkdirSync(join(phaseDir, "slices", "S01"), { recursive: true });
94
+ clearPathCache();
95
+
96
+ const result = await renderReplanFromDb(base, "M001", "S01", {
97
+ blockerTaskId: "T01",
98
+ blockerDescription: "Need a better path target.",
99
+ whatChanged: "Centralized slice writer path resolution.",
100
+ });
101
+
102
+ assert.match(result.replanPath, /phases[/\\]01-[^/\\]+[/\\]01-01-REPLAN\.md$/);
103
+ assert.ok(!result.replanPath.includes(join("slices", "S01")), "must not write under slices/S01/");
104
+ assert.ok(existsSync(result.replanPath), "replan file should exist at the phase root");
105
+ });
106
+
86
107
  // Regression for #1190: a stray slices/SID/ subdir inside a flat-phase milestone
87
108
  // must not divert the ASSESSMENT write into slices/SID/ (a hybrid path that
88
109
  // auto-mode verification cannot find). It must land at the phase root.
@@ -9,7 +9,7 @@ import { join } from "node:path";
9
9
  import assert from "node:assert/strict";
10
10
  import test from "node:test";
11
11
 
12
- import { relMilestoneFile, resolveMilestoneFile } from "../paths.ts";
12
+ import { normalizeRealPath, relMilestoneFile, relSliceFile, resolveMilestoneFile, targetMilestoneFile } from "../paths.ts";
13
13
 
14
14
  function makeFlatPhaseFixture(): { basePath: string; cleanup: () => void } {
15
15
  const basePath = mkdtempSync(join(tmpdir(), "flat-phase-validation-"));
@@ -33,42 +33,82 @@ function makeFlatPhaseFixture(): { basePath: string; cleanup: () => void } {
33
33
  return { basePath, cleanup: () => rmSync(basePath, { recursive: true, force: true }) };
34
34
  }
35
35
 
36
- test("flat-phase: relMilestoneFile resolves to the layout-aware path", () => {
36
+ test("flat-phase: relMilestoneFile resolves to the layout-aware path", (t) => {
37
37
  const { basePath, cleanup } = makeFlatPhaseFixture();
38
- try {
39
- const rel = relMilestoneFile(basePath, "M001", "VALIDATION");
40
- assert.equal(rel, ".gsd/phases/01-foo/01-VALIDATION.md");
41
- const abs = join(basePath, rel);
42
- assert.equal(existsSync(abs), true);
43
- } finally {
44
- cleanup();
45
- }
38
+ t.after(cleanup);
39
+
40
+ const rel = relMilestoneFile(basePath, "M001", "VALIDATION");
41
+ assert.equal(rel, ".gsd/phases/01-foo/01-VALIDATION.md");
42
+ const abs = join(basePath, rel);
43
+ assert.equal(existsSync(abs), true);
44
+ });
45
+
46
+ test("flat-phase: relMilestoneFile fallback uses the milestone title when no directory exists", (t) => {
47
+ const basePath = mkdtempSync(join(tmpdir(), "flat-phase-missing-dir-"));
48
+ t.after(() => rmSync(basePath, { recursive: true, force: true }));
49
+
50
+ const rel = relMilestoneFile(basePath, "M001", "ROADMAP", "Milestone");
51
+ assert.equal(rel, ".gsd/phases/01-milestone/01-ROADMAP.md");
46
52
  });
47
53
 
48
- test("flat-phase: resolveMilestoneFile finds the on-disk file", () => {
54
+ test("legacy layout: relMilestoneFile fallback stays in milestones when the target dir is missing", (t) => {
55
+ const basePath = mkdtempSync(join(tmpdir(), "legacy-missing-dir-"));
56
+ t.after(() => rmSync(basePath, { recursive: true, force: true }));
57
+ const existingLegacyDir = join(basePath, ".gsd", "milestones", "M002");
58
+ mkdirSync(existingLegacyDir, { recursive: true });
59
+ writeFileSync(join(existingLegacyDir, "M002-ROADMAP.md"), "# existing legacy roadmap\n");
60
+
61
+ const rel = relMilestoneFile(basePath, "M001", "ROADMAP", "Milestone");
62
+
63
+ assert.equal(rel, ".gsd/milestones/M001/M001-ROADMAP.md");
64
+ });
65
+
66
+ test("legacy layout: relSliceFile fallback uses slices directory when target milestone is missing", (t) => {
67
+ const basePath = mkdtempSync(join(tmpdir(), "legacy-missing-slice-"));
68
+ t.after(() => rmSync(basePath, { recursive: true, force: true }));
69
+ const existingLegacyDir = join(basePath, ".gsd", "milestones", "M002");
70
+ mkdirSync(existingLegacyDir, { recursive: true });
71
+ writeFileSync(join(existingLegacyDir, "M002-ROADMAP.md"), "# existing legacy roadmap\n");
72
+
73
+ const rel = relSliceFile(basePath, "M001", "S01", "PLAN", "Milestone");
74
+
75
+ assert.equal(rel, ".gsd/milestones/M001/slices/S01/S01-PLAN.md");
76
+ });
77
+
78
+ test("flat-phase: targetMilestoneFile ignores legacy-named compatibility files when writing", (t) => {
79
+ const basePath = mkdtempSync(join(tmpdir(), "flat-phase-legacy-named-file-"));
80
+ t.after(() => rmSync(basePath, { recursive: true, force: true }));
81
+ const phaseDir = join(basePath, ".gsd", "phases", "01-foo");
82
+ mkdirSync(phaseDir, { recursive: true });
83
+ writeFileSync(join(phaseDir, "M001-ROADMAP.md"), "# legacy named roadmap\n");
84
+
85
+ assert.equal(
86
+ targetMilestoneFile(basePath, "M001", "ROADMAP", "Foo"),
87
+ join(normalizeRealPath(phaseDir), "01-ROADMAP.md"),
88
+ );
89
+ assert.equal(relMilestoneFile(basePath, "M001", "ROADMAP", "Foo"), ".gsd/phases/01-foo/M001-ROADMAP.md");
90
+ });
91
+
92
+ test("flat-phase: resolveMilestoneFile finds the on-disk file", (t) => {
49
93
  const { basePath, cleanup } = makeFlatPhaseFixture();
50
- try {
51
- const abs = resolveMilestoneFile(basePath, "M001", "VALIDATION");
52
- assert.ok(abs, "resolveMilestoneFile must return a path for the flat-phase fixture");
53
- assert.equal(abs!.endsWith("/.gsd/phases/01-foo/01-VALIDATION.md"), true);
54
- assert.equal(existsSync(abs!), true);
55
- } finally {
56
- cleanup();
57
- }
94
+ t.after(cleanup);
95
+
96
+ const abs = resolveMilestoneFile(basePath, "M001", "VALIDATION");
97
+ assert.ok(abs, "resolveMilestoneFile must return a path for the flat-phase fixture");
98
+ assert.equal(abs!.endsWith("/.gsd/phases/01-foo/01-VALIDATION.md"), true);
99
+ assert.equal(existsSync(abs!), true);
58
100
  });
59
101
 
60
- test("flat-phase: the legacy hardcoded path does NOT resolve (regression for #876)", () => {
102
+ test("flat-phase: the legacy hardcoded path does NOT resolve (regression for #876)", (t) => {
61
103
  const { basePath, cleanup } = makeFlatPhaseFixture();
62
- try {
63
- // This is the path that auto-verification.ts:264-270 used to construct.
64
- // It must NOT exist on a flat-phase project — that's the whole bug.
65
- const legacyHardcoded = join(basePath, ".gsd", "milestones", "M001", "M001-VALIDATION.md");
66
- assert.equal(
67
- existsSync(legacyHardcoded),
68
- false,
69
- "legacy hardcoded path must not exist in flat-phase fixture — if it does, the test fixture is wrong",
70
- );
71
- } finally {
72
- cleanup();
73
- }
104
+ t.after(cleanup);
105
+
106
+ // This is the path that auto-verification.ts:264-270 used to construct.
107
+ // It must NOT exist on a flat-phase project — that's the whole bug.
108
+ const legacyHardcoded = join(basePath, ".gsd", "milestones", "M001", "M001-VALIDATION.md");
109
+ assert.equal(
110
+ existsSync(legacyHardcoded),
111
+ false,
112
+ "legacy hardcoded path must not exist in flat-phase fixture — if it does, the test fixture is wrong",
113
+ );
74
114
  });
@@ -0,0 +1,196 @@
1
+ // Project/App: gsd-pi
2
+ // File Purpose: Regression for #1222 — a re-import must not silently downgrade
3
+ // a completed flat-phase task back to pending when its SUMMARY.md attests the
4
+ // completion. gsd_task_complete succeeds and writes TID-SUMMARY.md, but a stale
5
+ // plan checkbox re-imported into the DB would revert the task to pending,
6
+ // hard-stopping auto-mode on its "state did not advance" guard.
7
+
8
+ import test, { afterEach } from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { cpSync, mkdtempSync, rmSync, writeFileSync, unlinkSync, mkdirSync } from "node:fs";
11
+ import { join, dirname } from "node:path";
12
+ import { tmpdir } from "node:os";
13
+ import { randomUUID } from "node:crypto";
14
+ import { fileURLToPath } from "node:url";
15
+
16
+ import { openDatabase, closeDatabase, getAllMilestones, getMilestoneSlices, getSliceTasks, repairTaskCompletionFromSummary } from "../gsd-db.ts";
17
+ import { migrateHierarchyToDb } from "../md-importer.ts";
18
+ import { invalidateStateCache } from "../state.ts";
19
+
20
+ const __dirname = dirname(fileURLToPath(import.meta.url));
21
+ const FIXTURE_ROOT = join(__dirname, "__fixtures__", "flat-phase");
22
+ // The flat-phase fixture parses to M001/S01/T01 (phase 01, plan 01, task T01).
23
+ const PHASE_DIR = join(".gsd", "phases", "01-foundation");
24
+ const SUMMARY_REL = join(PHASE_DIR, "T01-SUMMARY.md");
25
+
26
+ const tmpDirs: string[] = [];
27
+ afterEach(() => {
28
+ closeDatabase();
29
+ for (const d of tmpDirs) { try { rmSync(d, { recursive: true, force: true }); } catch { /* */ } }
30
+ tmpDirs.length = 0;
31
+ });
32
+
33
+ function copyFixture(): string {
34
+ const base = mkdtempSync(join(tmpdir(), `gsd-lostupdate-${randomUUID()}`));
35
+ cpSync(FIXTURE_ROOT, base, { recursive: true });
36
+ tmpDirs.push(base);
37
+ return base;
38
+ }
39
+
40
+ function taskStatus(base: string): string | undefined {
41
+ return t01Row()?.status;
42
+ }
43
+
44
+ function t01Row() {
45
+ const ms = getAllMilestones();
46
+ if (ms.length === 0) return undefined;
47
+ const slices = getMilestoneSlices(ms[0]!.id);
48
+ if (slices.length === 0) return undefined;
49
+ const tasks = getSliceTasks(ms[0]!.id, slices[0]!.id);
50
+ return tasks.find((t) => t.id === "T01");
51
+ }
52
+
53
+ function t01Ids() {
54
+ const ms = getAllMilestones();
55
+ const slices = getMilestoneSlices(ms[0]!.id);
56
+ return { milestoneId: ms[0]!.id, sliceId: slices[0]!.id };
57
+ }
58
+
59
+ test("re-import keeps a flat-phase task complete when its SUMMARY.md is present (#1222)", () => {
60
+ const base = copyFixture();
61
+ // The fixture plan leaves T01 unchecked ([ ]). Simulate a completed task: the
62
+ // durable SUMMARY.md exists even though the plan checkbox is stale-unchecked.
63
+ writeFileSync(join(base, SUMMARY_REL), "# T01 SUMMARY\n\nDone.\n");
64
+
65
+ openDatabase(join(base, ".gsd", "gsd.db"));
66
+ migrateHierarchyToDb(base);
67
+ invalidateStateCache();
68
+
69
+ assert.equal(
70
+ taskStatus(base),
71
+ "complete",
72
+ "task with SUMMARY.md on disk must import as complete, not revert to pending",
73
+ );
74
+ });
75
+
76
+ test("re-import leaves an unchecked flat-phase task pending when no SUMMARY.md exists (#1222)", () => {
77
+ const base = copyFixture();
78
+ // No SUMMARY.md — the unchecked checkbox is the authoritative signal.
79
+
80
+ openDatabase(join(base, ".gsd", "gsd.db"));
81
+ migrateHierarchyToDb(base);
82
+ invalidateStateCache();
83
+
84
+ assert.equal(
85
+ taskStatus(base),
86
+ "pending",
87
+ "unchecked task without a SUMMARY.md must stay pending",
88
+ );
89
+ });
90
+
91
+ test("re-import keeps a flat-phase task complete even when an auxiliary tasks/ subdir exists (#1222)", () => {
92
+ const base = copyFixture();
93
+ // Flat-phase may keep a tasks/ subdir for gate artifacts (e.g. T01-VERIFY.json)
94
+ // while gsd_task_complete still writes TID-SUMMARY.md at the phase root. The
95
+ // tasks/ subdir must NOT shadow that real summary and downgrade the task.
96
+ mkdirSync(join(base, PHASE_DIR, "tasks"), { recursive: true });
97
+ writeFileSync(join(base, PHASE_DIR, "tasks", "T01-VERIFY.json"), "{}\n");
98
+ writeFileSync(join(base, SUMMARY_REL), "# T01 SUMMARY\n\nDone.\n");
99
+
100
+ openDatabase(join(base, ".gsd", "gsd.db"));
101
+ migrateHierarchyToDb(base);
102
+ invalidateStateCache();
103
+
104
+ assert.equal(
105
+ taskStatus(base),
106
+ "complete",
107
+ "a phase-root SUMMARY.md must win even when a tasks/ subdir exists (no shadowing)",
108
+ );
109
+ });
110
+
111
+ test("re-import preserves execution metadata and completed_at of an attested-complete task (#1222)", () => {
112
+ const base = copyFixture();
113
+ // T01 is completed on disk (SUMMARY present) but its plan checkbox is stale-unchecked.
114
+ writeFileSync(join(base, SUMMARY_REL), "# T01 SUMMARY\n\nDone.\n");
115
+
116
+ openDatabase(join(base, ".gsd", "gsd.db"));
117
+ migrateHierarchyToDb(base);
118
+ invalidateStateCache();
119
+
120
+ // Simulate the row gsd_task_complete leaves behind: real execution prose plus
121
+ // a completion timestamp from when the task actually finished.
122
+ const OLD_COMPLETED_AT = "2026-07-01T00:00:00.000Z";
123
+ const REAL_ONE_LINER = "Implemented the widget frobnicator end-to-end";
124
+ const REAL_SUMMARY = "# T01 full summary\n\nEverything about how T01 was done.";
125
+ const REAL_VERIFICATION = "all 12 checks passed";
126
+ const { milestoneId, sliceId } = t01Ids();
127
+ repairTaskCompletionFromSummary({
128
+ milestoneId,
129
+ sliceId,
130
+ taskId: "T01",
131
+ oneLiner: REAL_ONE_LINER,
132
+ verificationResult: REAL_VERIFICATION,
133
+ completedAt: OLD_COMPLETED_AT,
134
+ fullSummaryMd: REAL_SUMMARY,
135
+ });
136
+ invalidateStateCache();
137
+
138
+ const before = t01Row()!;
139
+ assert.equal(before.status, "complete", "precondition: T01 is complete");
140
+ assert.equal(before.one_liner, REAL_ONE_LINER, "precondition: one_liner set");
141
+ assert.equal(before.completed_at, OLD_COMPLETED_AT, "precondition: completed_at set");
142
+
143
+ // The pre-dispatch reconcile re-imports the whole tree (plan checkbox still [ ]).
144
+ migrateHierarchyToDb(base);
145
+ invalidateStateCache();
146
+
147
+ const after = t01Row()!;
148
+ assert.equal(after.status, "complete", "status must survive re-import");
149
+ assert.equal(after.one_liner, REAL_ONE_LINER, "one_liner must survive re-import");
150
+ assert.equal(after.full_summary_md, REAL_SUMMARY, "full_summary_md must survive re-import");
151
+ assert.equal(after.verification_result, REAL_VERIFICATION, "verification_result must survive re-import");
152
+ assert.equal(after.completed_at, OLD_COMPLETED_AT, "completed_at must not be refreshed by re-import");
153
+ });
154
+
155
+ test("removing the SUMMARY.md (reopen) lets a re-import return the task to pending and clears completion metadata (#1222)", () => {
156
+ const base = copyFixture();
157
+ writeFileSync(join(base, SUMMARY_REL), "# T01 SUMMARY\n\nDone.\n");
158
+
159
+ openDatabase(join(base, ".gsd", "gsd.db"));
160
+ migrateHierarchyToDb(base);
161
+ invalidateStateCache();
162
+ assert.equal(taskStatus(base), "complete", "precondition: task imports complete with SUMMARY present");
163
+
164
+ // Simulate the durable completion row gsd_task_complete leaves behind.
165
+ const { milestoneId, sliceId } = t01Ids();
166
+ repairTaskCompletionFromSummary({
167
+ milestoneId,
168
+ sliceId,
169
+ taskId: "T01",
170
+ oneLiner: "Implemented the widget frobnicator end-to-end",
171
+ verificationResult: "all 12 checks passed",
172
+ completedAt: "2026-07-01T00:00:00.000Z",
173
+ fullSummaryMd: "# T01 full summary\n\nEverything about how T01 was done.",
174
+ });
175
+ invalidateStateCache();
176
+ const before = t01Row()!;
177
+ assert.equal(before.status, "complete", "precondition: T01 is complete");
178
+ assert.equal(before.completed_at, "2026-07-01T00:00:00.000Z", "precondition: completed_at set");
179
+
180
+ // reopen-task deletes the flat-phase SUMMARY; the next re-import must respect it.
181
+ unlinkSync(join(base, SUMMARY_REL));
182
+ migrateHierarchyToDb(base);
183
+ invalidateStateCache();
184
+ const after = t01Row()!;
185
+ assert.equal(
186
+ after.status,
187
+ "pending",
188
+ "after the SUMMARY is removed, a re-import must return the unchecked task to pending",
189
+ );
190
+ // A pending re-import must not preserve stale completion metadata (#1228 thread):
191
+ // preserveCompletionMetadata only applies to complete/done imports.
192
+ assert.ok(!after.completed_at, "completed_at must be cleared when the task reverts to pending");
193
+ assert.ok(!after.one_liner, "one_liner must be cleared when the task reverts to pending");
194
+ assert.ok(!after.full_summary_md, "full_summary_md must be cleared when the task reverts to pending");
195
+ assert.ok(!after.verification_result, "verification_result must be cleared when the task reverts to pending");
196
+ });
@@ -1,6 +1,6 @@
1
1
  import { describe, test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { mkdtempSync, mkdirSync, rmSync, realpathSync, writeFileSync } from "node:fs";
3
+ import { mkdtempSync, mkdirSync, rmSync, realpathSync, symlinkSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import { spawnSync } from "node:child_process";
@@ -9,6 +9,7 @@ import {
9
9
  gsdProjectionRoot,
10
10
  gsdRoot,
11
11
  milestonesDir,
12
+ resolveGsdPathContract,
12
13
  resolveSliceFile,
13
14
  resolveTaskFile,
14
15
  _clearGsdRootCache,
@@ -166,4 +167,90 @@ describe('paths', () => {
166
167
  cleanup(root);
167
168
  }
168
169
  });
170
+
171
+ test('Case 9: flat-phase task SUMMARY resolves at phase root despite a stray slices/SID/ dir', () => {
172
+ const root = tmp();
173
+ try {
174
+ // Hybrid flat-phase milestone: phase dir holds flat TID-SUMMARY.md, but a
175
+ // stray slices/S01/ folder also exists on disk. resolveSlicePath then points
176
+ // under slices/, so layout (phases/ vs milestones/) — not slicePath===phaseDir
177
+ // — must decide the flat-phase task summary location.
178
+ const phaseDir = join(root, ".gsd", "phases", "01-foo");
179
+ mkdirSync(join(phaseDir, "slices", "S01"), { recursive: true });
180
+ const flatSummary = join(phaseDir, "T01-SUMMARY.md");
181
+ writeFileSync(flatSummary, "# task summary\n");
182
+
183
+ _clearGsdRootCache();
184
+
185
+ assert.deepStrictEqual(
186
+ resolveTaskFile(root, "M001", "S01", "T01", "SUMMARY"),
187
+ flatSummary,
188
+ "flat-phase TID-SUMMARY.md at phase root is found even when slices/S01/ exists",
189
+ );
190
+ } finally { cleanup(root); }
191
+ });
192
+
193
+ test('Case 10: resolveGsdPathContract canonicalizes a symlinked .gsd root (regression #1239)', () => {
194
+ // WSL / external-state shape: `<projectRoot>/.gsd` is a symlink pointing at a
195
+ // real directory on another volume. Before #1239 the DB path was built from
196
+ // the unresolved symlink, so the workflow SQLite handle opened through a
197
+ // move-prone path and could pick the wrong journal mode / SQLITE_READONLY_DBMOVED.
198
+ const root = tmp();
199
+ try {
200
+ const realState = join(root, "real-state"); // canonical symlink target
201
+ mkdirSync(join(realState, "milestones"), { recursive: true });
202
+ const projectRoot = join(root, "project");
203
+ mkdirSync(projectRoot, { recursive: true });
204
+ symlinkSync(realState, join(projectRoot, ".gsd"), "dir");
205
+
206
+ _clearGsdRootCache();
207
+ const contract = resolveGsdPathContract(projectRoot);
208
+
209
+ const canonicalGsd = realpathSync.native(join(projectRoot, ".gsd"));
210
+ assert.deepStrictEqual(contract.projectGsd, canonicalGsd,
211
+ "projectGsd is the realpath'd target, not the unresolved .gsd symlink");
212
+ assert.deepStrictEqual(contract.projectDb, join(canonicalGsd, "gsd.db"),
213
+ "projectDb is built from the canonical .gsd root");
214
+ assert.deepStrictEqual(contract.projectGsd, gsdProjectionRoot(projectRoot),
215
+ "projectGsd agrees with gsdProjectionRoot on the canonical root");
216
+ assert.notStrictEqual(contract.projectDb, join(projectRoot, ".gsd", "gsd.db"),
217
+ "regression #1239: DB must not open through the unresolved .gsd symlink");
218
+ } finally { cleanup(root); }
219
+ });
220
+
221
+ test('Case 11: resolveGsdPathContract canonicalizes the external-state worktree DB path through a symlinked store (regression #1239)', () => {
222
+ const root = tmp();
223
+ const originalStateDir = process.env.GSD_STATE_DIR;
224
+ try {
225
+ const stateDir = join(root, "state");
226
+ process.env.GSD_STATE_DIR = stateDir;
227
+
228
+ // The external-state project store `<state>/projects/<hash>` is itself a
229
+ // symlink to a real directory on another volume (the WSL `.gsd` shape).
230
+ const realProjectGsd = join(root, "real-projects", "abc123");
231
+ mkdirSync(realProjectGsd, { recursive: true });
232
+ mkdirSync(join(stateDir, "projects"), { recursive: true });
233
+ const storeSymlink = join(stateDir, "projects", "abc123");
234
+ symlinkSync(realProjectGsd, storeSymlink, "dir");
235
+
236
+ const wtRoot = join(storeSymlink, "worktrees", "M002");
237
+ mkdirSync(join(wtRoot, ".gsd"), { recursive: true });
238
+
239
+ _clearGsdRootCache();
240
+ const contract = resolveGsdPathContract(wtRoot);
241
+
242
+ const canonicalStore = realpathSync.native(storeSymlink);
243
+ assert.ok(contract.isWorktree, "recognized as an external-state worktree layout");
244
+ assert.deepStrictEqual(contract.projectGsd, canonicalStore,
245
+ "projectGsd is the realpath'd project store, not the unresolved symlink");
246
+ assert.deepStrictEqual(contract.projectDb, join(canonicalStore, "gsd.db"),
247
+ "projectDb is built from the canonical project store");
248
+ assert.notStrictEqual(contract.projectDb, join(storeSymlink, "gsd.db"),
249
+ "regression #1239: DB must not open through the unresolved external-state symlink");
250
+ } finally {
251
+ if (originalStateDir === undefined) delete process.env.GSD_STATE_DIR;
252
+ else process.env.GSD_STATE_DIR = originalStateDir;
253
+ cleanup(root);
254
+ }
255
+ });
169
256
  });
@@ -40,6 +40,7 @@ import { readUnitRuntimeRecord } from "../unit-runtime.js";
40
40
  import { ModelPolicyDispatchBlockedError } from "../auto-model-selection.js";
41
41
  import {
42
42
  closeDatabase,
43
+ getTask,
43
44
  insertMilestone,
44
45
  insertSlice,
45
46
  insertTask,
@@ -445,7 +446,7 @@ test("runDispatch retries when complete-milestone summary exists on disk and stu
445
446
  assert.equal(stopCalls, 0, "artifact-based recovery should not hard-stop the loop");
446
447
  });
447
448
 
448
- test("runDispatch pauses when execute-task artifacts exist but DB status is still open", async (t) => {
449
+ test("runDispatch promotes execute-task DB status when artifacts exist but DB status is still open (#1204)", async (t) => {
449
450
  const capture = createEventCapture();
450
451
  let pauseCalls = 0;
451
452
  let stopCalls = 0;
@@ -522,16 +523,16 @@ test("runDispatch pauses when execute-task artifacts exist but DB status is stil
522
523
 
523
524
  const result = await runDispatch(ic, preData, loopState);
524
525
 
525
- assert.equal(result.action, "break");
526
- assert.equal((result as any).reason, "execute-task-artifact-db-mismatch");
527
- assert.equal(pauseCalls, 1, "execute-task disk/db mismatch should pause auto-mode");
528
- assert.equal(stopCalls, 0, "execute-task disk/db mismatch should not hard-stop the loop");
529
- assert.equal(invalidateCalls, 0, "mismatch should not clear caches and continue toward redispatch");
530
- assert.equal(loopState.recentUnits.length, 3, "mismatch should keep the stuck window intact");
531
- assert.equal(loopState.stuckRecoveryAttempts, 1, "mismatch should not reset the recovery counter");
526
+ assert.equal(result.action, "continue");
527
+ assert.equal(pauseCalls, 0, "disk-verified promotion should not pause auto-mode");
528
+ assert.equal(stopCalls, 0, "disk-verified promotion should not hard-stop the loop");
529
+ assert.equal(invalidateCalls, 1, "successful promotion should invalidate caches before retrying");
530
+ assert.equal(loopState.recentUnits.length, 0, "successful promotion should clear the stuck window");
531
+ assert.equal(loopState.stuckRecoveryAttempts, 1, "Level 1 recovery increments the attempt counter");
532
+ assert.equal(getTask("M001", "S01", "T01")?.status, "complete", "the open DB task should be promoted to complete");
532
533
  });
533
534
 
534
- test("runDispatch pauses at Level 2 when execute-task artifacts exist but DB status is still open", async (t) => {
535
+ test("runDispatch promotes execute-task DB status at Level 2 when artifacts exist but DB status is still open (#1204)", async (t) => {
535
536
  const capture = createEventCapture();
536
537
  let pauseCalls = 0;
537
538
  let stopCalls = 0;
@@ -601,13 +602,13 @@ test("runDispatch pauses at Level 2 when execute-task artifacts exist but DB sta
601
602
 
602
603
  const result = await runDispatch(ic, preData, loopState);
603
604
 
604
- assert.equal(result.action, "break");
605
- assert.equal((result as any).reason, "execute-task-artifact-db-mismatch");
606
- assert.equal(pauseCalls, 1, "Level 2 execute-task disk/db mismatch should pause auto-mode");
607
- assert.equal(stopCalls, 0, "Level 2 execute-task disk/db mismatch should not hard-stop the loop");
605
+ assert.equal(result.action, "continue");
606
+ assert.equal(pauseCalls, 0, "Level 2 disk-verified promotion should not pause auto-mode");
607
+ assert.equal(stopCalls, 0, "Level 2 disk-verified promotion should not hard-stop the loop");
608
608
  assert.equal(invalidateCalls, 1, "Level 2 should invalidate caches before the final artifact recheck");
609
- assert.equal(loopState.recentUnits.length, 3, "Level 2 mismatch should keep the stuck window intact");
610
- assert.equal(loopState.stuckRecoveryAttempts, 1, "Level 2 mismatch should not reset the recovery counter");
609
+ assert.equal(loopState.recentUnits.length, 0, "Level 2 successful promotion should clear the stuck window");
610
+ assert.equal(loopState.stuckRecoveryAttempts, 1, "Level 2 does not reset the recovery counter");
611
+ assert.equal(getTask("M001", "S01", "T01")?.status, "complete", "the open DB task should be promoted to complete");
611
612
  });
612
613
 
613
614
  test("runDispatch clears execute-task stuck state when artifacts and DB status are complete", async (t) => {