@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
@@ -797,9 +797,11 @@ test("stuck-loop: empty ring on a freshly constructed orchestrator advances norm
797
797
  assert.equal(result.kind, "advanced");
798
798
  });
799
799
 
800
- test("stuck-loop: partial fill of mixed units does not block", async (t) => {
800
+ test("stuck-loop: distinct units making forward progress do not block", async (t) => {
801
+ // Healthy forward progress visits new units every advance — no key repeats,
802
+ // so no oscillation/repeat rule fires even once the window is full.
801
803
  let i = 0;
802
- const sequence = ["M001/S01/A", "M001/S01/B", "M001/S01/A", "M001/S01/B", "M001/S01/A", "M001/S01/B"];
804
+ const sequence = ["M001/S01/A", "M001/S01/B", "M001/S01/C", "M001/S01/D", "M001/S01/E", "M001/S01/F"];
803
805
  const f = makeFixture({
804
806
  dispatch: () => ({ action: "dispatch", unitType: "execute-task", unitId: sequence[i++ % sequence.length], prompt: "p" }),
805
807
  });
@@ -811,6 +813,43 @@ test("stuck-loop: partial fill of mixed units does not block", async (t) => {
811
813
  }
812
814
  });
813
815
 
816
+ test("stuck-loop #1225: two-unit oscillation blocks once the window saturates", async (t) => {
817
+ // Reproduces the execute-task ↔ complete-slice loop: a slice gate keeps
818
+ // reopening the same task, so two keys alternate forever. Neither key ever
819
+ // saturates the window on its own, so the old matchingCount>=SIZE gate never
820
+ // consulted detect-stuck and the loop ran until an external kill. The window
821
+ // still fills, so detect-stuck's oscillation/repeat rules must now fire.
822
+ let i = 0;
823
+ const sequence: Array<{ unitType: string; unitId: string }> = [
824
+ { unitType: "execute-task", unitId: "M001/S01/T01" },
825
+ { unitType: "complete-slice", unitId: "M001/S01" },
826
+ ];
827
+ const f = makeFixture({
828
+ dispatch: () => {
829
+ const next = sequence[i++ % sequence.length];
830
+ return { action: "dispatch", unitType: next.unitType, unitId: next.unitId, prompt: "p" };
831
+ },
832
+ });
833
+ t.after(() => f.cleanup());
834
+
835
+ let blocked: Awaited<ReturnType<typeof f.orchestrator.advance>> | undefined;
836
+ for (let round = 0; round < STUCK_WINDOW_SIZE; round++) {
837
+ const result = await f.orchestrator.advance();
838
+ if (result.kind === "blocked") {
839
+ blocked = result;
840
+ break;
841
+ }
842
+ }
843
+
844
+ assert.ok(blocked, "oscillation must be detected once the window saturates");
845
+ if (!blocked || blocked.kind !== "blocked") return;
846
+ assert.equal(blocked.action, "stop");
847
+ assert.ok(
848
+ blocked.reason.startsWith("stuck-loop:"),
849
+ `expected stuck-loop verdict, got: ${blocked.reason}`,
850
+ );
851
+ });
852
+
814
853
  test("stuck-loop: ring saturated with same unit blocks with action 'stop' and stuck-loop reason", async (t) => {
815
854
  const f = makeFixture();
816
855
  t.after(() => f.cleanup());
@@ -1,6 +1,6 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
3
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import {
@@ -8,6 +8,7 @@ import {
8
8
  maybeWriteParallelResearchCostSpikeBlocker,
9
9
  } from "../auto-post-unit.ts";
10
10
  import { resolveExpectedArtifactPath } from "../auto-recovery.ts";
11
+ import { _clearGsdRootCache, clearPathCache } from "../paths.ts";
11
12
 
12
13
  test("missing execute-task artifact includes completion contract and completion-tool hint", () => {
13
14
  const base = mkdtempSync(join(tmpdir(), "gsd-artifact-diag-"));
@@ -56,3 +57,49 @@ test("parallel research cost spike writes durable PARALLEL-BLOCKER", () => {
56
57
  rmSync(base, { recursive: true, force: true });
57
58
  }
58
59
  });
60
+
61
+ // ── #1238: symlinked project root must not produce a ../ path walk ────────────
62
+ //
63
+ // On WSL (and any layout where the project root is reached through a symlink),
64
+ // artifact resolution canonicalizes through gsdRoot (realpathSync), so the
65
+ // resolved artifact path lives under the REAL root while `basePath` is still the
66
+ // symlink. Before the fix, relative(symlinkBase, realArtifactPath) walked up out
67
+ // of the symlink (../../…/home/…) and the diagnostic reported that broken path as
68
+ // "not found on disk". Normalizing basePath to its real path first keeps the
69
+ // displayed path anchored at the real .gsd root.
70
+
71
+ test("symlinked project root yields a clean .gsd-relative artifact path, not a ../ walk (#1238)", () => {
72
+ const realRoot = realpathSync(mkdtempSync(join(tmpdir(), "gsd-symlink-real-")));
73
+ const linkParent = realpathSync(mkdtempSync(join(tmpdir(), "gsd-symlink-link-")));
74
+ const linkRoot = join(linkParent, "project");
75
+ try {
76
+ // Content-bearing legacy milestone dir so plan-milestone resolves the
77
+ // canonical (realpath) ROADMAP path under realRoot/.gsd/milestones/M001/.
78
+ const milestoneDir = join(realRoot, ".gsd", "milestones", "M001");
79
+ mkdirSync(milestoneDir, { recursive: true });
80
+ writeFileSync(join(milestoneDir, "M001-CONTEXT.md"), "# context\n");
81
+ // The ROADMAP intentionally does not exist — this hits the "not found on
82
+ // disk" branch, the exact symptom in #1238.
83
+ symlinkSync(realRoot, linkRoot);
84
+
85
+ _clearGsdRootCache();
86
+ clearPathCache();
87
+
88
+ const msg = _describeArtifactVerificationFailureForTest("plan-milestone", "M001", linkRoot);
89
+
90
+ assert.match(msg, /was not found on disk after unit execution/);
91
+ // The displayed path must be anchored at the real .gsd root, not a broken
92
+ // parent-walk out of the symlinked base.
93
+ assert.doesNotMatch(msg, /\.\.[/\\]/, "must not contain a ../ walk out of the symlinked base");
94
+ assert.match(
95
+ msg,
96
+ /\.gsd[/\\]milestones[/\\]M001[/\\]M001-ROADMAP\.md/,
97
+ "must show the clean .gsd-relative ROADMAP path",
98
+ );
99
+ } finally {
100
+ _clearGsdRootCache();
101
+ clearPathCache();
102
+ rmSync(linkParent, { recursive: true, force: true });
103
+ rmSync(realRoot, { recursive: true, force: true });
104
+ }
105
+ });
@@ -299,6 +299,63 @@ test("refreshRecoveryDbForArtifact treats missing execute-task DB rows as fatal
299
299
  });
300
300
  });
301
301
 
302
+ test("refreshRecoveryDbForArtifact promotes an open execute-task DB row when artifacts are verified on disk (#1204)", () => {
303
+ const dir = makeTmpProject();
304
+ insertTask({
305
+ milestoneId: "M001",
306
+ sliceId: "S01",
307
+ id: "T01",
308
+ title: "Stuck Task",
309
+ status: "pending",
310
+ });
311
+ const sliceDir = join(dir, ".gsd", "milestones", "M001", "slices", "S01");
312
+ mkdirSync(sliceDir, { recursive: true });
313
+ writeFileSync(join(sliceDir, "S01-PLAN.md"), [
314
+ "# S01 Plan",
315
+ "",
316
+ "- [ ] **T01: Implement feature** `est:1h`",
317
+ "",
318
+ ].join("\n"));
319
+
320
+ const result = refreshRecoveryDbForArtifact("execute-task", "M001/S01/T01", dir);
321
+
322
+ assert.deepEqual(result, { ok: true });
323
+ assert.equal(getTask("M001", "S01", "T01")?.status, "complete");
324
+ const planContent = readFileSync(join(sliceDir, "S01-PLAN.md"), "utf-8");
325
+ assert.ok(planContent.includes("[x] **T01:"), "plan checkbox should be re-rendered to [x]");
326
+ const events = readEvents(join(dir, ".gsd", "event-log.jsonl"));
327
+ assert.ok(
328
+ events.some((e) => e.cmd === "complete-task" && e.trigger_reason === "stuck-artifact-recovery"),
329
+ "a complete-task recovery event should be appended",
330
+ );
331
+ });
332
+
333
+ test("refreshRecoveryDbForArtifact still appends the recovery event when the plan checkbox sync throws after DB promotion (#1221)", () => {
334
+ const dir = makeTmpProject();
335
+ insertTask({
336
+ milestoneId: "M001",
337
+ sliceId: "S01",
338
+ id: "T01",
339
+ title: "Stuck Task",
340
+ status: "pending",
341
+ });
342
+ const sliceDir = join(dir, ".gsd", "milestones", "M001", "slices", "S01");
343
+ mkdirSync(sliceDir, { recursive: true });
344
+ // Make the expected plan path a directory so the best-effort checkbox
345
+ // rewrite (readFileSync) throws EISDIR after the DB row is already promoted.
346
+ mkdirSync(join(sliceDir, "S01-PLAN.md"), { recursive: true });
347
+
348
+ const result = refreshRecoveryDbForArtifact("execute-task", "M001/S01/T01", dir);
349
+
350
+ assert.deepEqual(result, { ok: true });
351
+ assert.equal(getTask("M001", "S01", "T01")?.status, "complete");
352
+ const events = readEvents(join(dir, ".gsd", "event-log.jsonl"));
353
+ assert.ok(
354
+ events.some((e) => e.cmd === "complete-task" && e.trigger_reason === "stuck-artifact-recovery"),
355
+ "recovery event must still be appended even when the plan checkbox sync fails",
356
+ );
357
+ });
358
+
302
359
  test("refreshRecoveryDbForArtifact closes complete-milestone DB row when artifacts exist but DB is stale (#5568)", async () => {
303
360
  const base = mkdtempSync(join(tmpdir(), "auto-recovery-complete-ms-"));
304
361
  mkdirSync(join(base, ".gsd"), { recursive: true });
@@ -4,7 +4,7 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
 
7
- import { closeDatabase, getAllMilestones, getMilestone, insertMilestone, isDbAvailable, openDatabase } from "../gsd-db.ts";
7
+ import { _getAdapter, closeDatabase, getAllMilestones, getMilestone, insertMilestone, isDbAvailable, openDatabase } from "../gsd-db.ts";
8
8
  import { reconcileMergedMilestonesFromJournal, reconcileProjectMilestonesFromDisk } from "../auto-start.ts";
9
9
  import { emitWorktreeMerged } from "../worktree-telemetry.ts";
10
10
 
@@ -69,3 +69,31 @@ test("#5389: bootstrap reconciles PROJECT.md milestones that are missing from DB
69
69
  rmSync(base, { recursive: true, force: true });
70
70
  }
71
71
  });
72
+
73
+ test("#1236: bootstrap merged-milestone reconciliation degrades to a warning instead of aborting when the DB is degraded", () => {
74
+ const base = mkdtempSync(join(tmpdir(), "gsd-merged-reconcile-degraded-"));
75
+ try {
76
+ mkdirSync(join(base, ".gsd"), { recursive: true });
77
+ openDatabase(join(base, ".gsd", "gsd.db"));
78
+ insertMilestone({ id: "M001", title: "Merged Milestone", status: "active" });
79
+
80
+ emitWorktreeMerged(base, "M001", { reason: "milestone-complete", conflict: false });
81
+
82
+ // Simulate a degraded DB: the connection stays "available" (isDbAvailable()
83
+ // remains true because the handle is non-null), but the milestones table is
84
+ // gone, so the reconciler's DB access throws partway through bootstrap.
85
+ _getAdapter()!.exec("DROP TABLE milestones");
86
+ assert.equal(isDbAvailable(), true);
87
+
88
+ // Regression (#1236): this reconciler was previously unguarded, so a
89
+ // degraded-DB failure threw and aborted the rest of `/gsd auto` bootstrap.
90
+ // It must now catch, warn, and return 0, matching its sibling
91
+ // reconcileProjectMilestonesFromDisk. Reaching the assertion proves it did
92
+ // not throw.
93
+ const closed = reconcileMergedMilestonesFromJournal(base);
94
+ assert.equal(closed, 0);
95
+ } finally {
96
+ if (isDbAvailable()) closeDatabase();
97
+ rmSync(base, { recursive: true, force: true });
98
+ }
99
+ });
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Idle/hard timeout recovery honors the in-flight-tool guard (#1229 follow-up).
3
+ *
4
+ * `recoverTimedOutUnit` steers a still-running unit with `deliverAs: "steer"`
5
+ * and, before that, may swap the session model via
6
+ * `applySupervisorModelIfConfigured`. Swapping the model and triggering a turn
7
+ * while non-interactive tool calls are still open skips tool results and trips
8
+ * provider errors (#3512) — the same failure the soft wrap-up and continue-here
9
+ * timers guard against. This test pins that the idle and hard steering branches
10
+ * gate both the model swap and `triggerTurn` on `getInFlightToolCount() === 0`.
11
+ */
12
+
13
+ import test from "node:test";
14
+ import assert from "node:assert/strict";
15
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ import { tmpdir } from "node:os";
18
+
19
+ import { recoverTimedOutUnit, type RecoveryContext } from "../auto-timeout-recovery.ts";
20
+ import { clearGSDPreferencesCache } from "../preferences.ts";
21
+ import {
22
+ markToolStart,
23
+ markToolEnd,
24
+ clearInFlightTools,
25
+ } from "../auto-tool-tracking.ts";
26
+
27
+ const SUPERVISOR_PREFS = [
28
+ "auto_supervisor:",
29
+ " soft_timeout_minutes: 1",
30
+ " idle_timeout_minutes: 100",
31
+ " hard_timeout_minutes: 100",
32
+ " model:",
33
+ " model: supervisor-primary",
34
+ " fallbacks: [supervisor-fb]",
35
+ ];
36
+
37
+ interface Harness {
38
+ home: string;
39
+ base: string;
40
+ ctx: any;
41
+ pi: any;
42
+ rctx: RecoveryContext;
43
+ setModelCalls: unknown[][];
44
+ sendMessageCalls: unknown[][];
45
+ }
46
+
47
+ function makeHarness(): Harness {
48
+ const home = mkdtempSync(join(tmpdir(), "gsd-recovery-guard-home-"));
49
+ const base = mkdtempSync(join(tmpdir(), "gsd-recovery-guard-base-"));
50
+ process.env.GSD_HOME = home;
51
+ writeFileSync(join(home, "preferences.md"), ["---", ...SUPERVISOR_PREFS, "---", ""].join("\n"));
52
+ clearGSDPreferencesCache();
53
+
54
+ const setModelCalls: unknown[][] = [];
55
+ const sendMessageCalls: unknown[][] = [];
56
+
57
+ const ctx = {
58
+ ui: { notify: () => {} },
59
+ model: { provider: "anthropic" },
60
+ modelRegistry: {
61
+ getAvailable: () => [
62
+ { id: "supervisor-primary", provider: "anthropic" },
63
+ { id: "supervisor-fb", provider: "anthropic" },
64
+ ],
65
+ },
66
+ } as any;
67
+
68
+ const pi = {
69
+ sendMessage: (...args: unknown[]) => {
70
+ sendMessageCalls.push(args);
71
+ },
72
+ setModel: async (...args: unknown[]) => {
73
+ setModelCalls.push(args);
74
+ return true;
75
+ },
76
+ getThinkingLevel: () => "off",
77
+ setThinkingLevel: () => {},
78
+ } as any;
79
+
80
+ const rctx: RecoveryContext = {
81
+ basePath: base,
82
+ verbose: false,
83
+ currentUnitStartedAt: 1234,
84
+ unitRecoveryCount: new Map(),
85
+ };
86
+
87
+ return { home, base, ctx, pi, rctx, setModelCalls, sendMessageCalls };
88
+ }
89
+
90
+ function cleanup(h: Harness): void {
91
+ clearInFlightTools();
92
+ clearGSDPreferencesCache();
93
+ delete process.env.GSD_HOME;
94
+ rmSync(h.home, { recursive: true, force: true });
95
+ rmSync(h.base, { recursive: true, force: true });
96
+ }
97
+
98
+ // "discuss-project" resolves to a PROJECT.md that never exists in the temp base,
99
+ // so recovery skips the "already on disk" shortcut and reaches the steering
100
+ // branch with recoveryAttempts (0) < maxRecoveryAttempts.
101
+ for (const reason of ["idle", "hard"] as const) {
102
+ test(`${reason} recovery skips the supervisor model swap while a tool is in flight`, async () => {
103
+ const h = makeHarness();
104
+ try {
105
+ markToolStart("call-1", true, "some_tool");
106
+
107
+ const outcome = await recoverTimedOutUnit(
108
+ h.ctx,
109
+ h.pi,
110
+ "discuss-project",
111
+ "P01",
112
+ reason,
113
+ h.rctx,
114
+ );
115
+
116
+ assert.equal(outcome, "recovered");
117
+ assert.equal(
118
+ h.setModelCalls.length,
119
+ 0,
120
+ "model must NOT swap while a tool is in flight",
121
+ );
122
+ assert.equal(h.sendMessageCalls.length, 1, "steer message still sent (queued)");
123
+ const opts = h.sendMessageCalls[0][1] as { triggerTurn?: boolean; deliverAs?: string };
124
+ assert.equal(opts.triggerTurn, false, "turn is not triggered mid-tool-call");
125
+ assert.equal(opts.deliverAs, "steer");
126
+
127
+ markToolEnd("call-1");
128
+ } finally {
129
+ cleanup(h);
130
+ }
131
+ });
132
+
133
+ test(`${reason} recovery applies the supervisor model swap when no tools are in flight`, async () => {
134
+ const h = makeHarness();
135
+ try {
136
+ const outcome = await recoverTimedOutUnit(
137
+ h.ctx,
138
+ h.pi,
139
+ "discuss-project",
140
+ "P01",
141
+ reason,
142
+ h.rctx,
143
+ );
144
+
145
+ assert.equal(outcome, "recovered");
146
+ assert.equal(h.setModelCalls.length, 1, "model swaps to the supervisor model");
147
+ assert.deepEqual(
148
+ h.setModelCalls[0][0],
149
+ { id: "supervisor-primary", provider: "anthropic" },
150
+ "swaps to the configured supervisor primary",
151
+ );
152
+ assert.equal(h.sendMessageCalls.length, 1);
153
+ const opts = h.sendMessageCalls[0][1] as { triggerTurn?: boolean };
154
+ assert.equal(opts.triggerTurn, true, "turn is triggered when no tools are open");
155
+ } finally {
156
+ cleanup(h);
157
+ }
158
+ });
159
+ }
@@ -697,6 +697,28 @@ test("ensureCodebaseMapFresh: does not rewrite expired metadata when fingerprint
697
697
  }
698
698
  });
699
699
 
700
+ test("ensureCodebaseMapFresh: stays fresh when stored fileCount exceeds maxFiles", () => {
701
+ const base = makeTmpRepo();
702
+ try {
703
+ for (let i = 0; i < 10; i++) addFile(base, `file${i}.ts`);
704
+ ensureCodebaseMapFresh(base, { maxFiles: 5 }, { ttlMs: 0, force: true });
705
+
706
+ // Simulate a map written by an older version that recorded the full
707
+ // (untruncated) file count in its metadata rather than the capped count.
708
+ const original = readCodebaseMap(base);
709
+ assert.ok(original);
710
+ const fullCountContent = original.replace(/"fileCount":5\b/, '"fileCount":10');
711
+ writeCodebaseMap(base, fullCountContent);
712
+
713
+ const refreshed = ensureCodebaseMapFresh(base, { maxFiles: 5 }, { ttlMs: 0, force: true });
714
+ assert.equal(refreshed.status, "fresh");
715
+ // Timestamp must be preserved so the injected prompt stays byte-identical.
716
+ assert.equal(readCodebaseMap(base), fullCountContent);
717
+ } finally {
718
+ cleanup(base);
719
+ }
720
+ });
721
+
700
722
  test("ensureCodebaseMapFresh: uses TTL cache before enumerating files", () => {
701
723
  const base = makeTmpRepo();
702
724
  try {
@@ -18,6 +18,7 @@ import { randomUUID } from "node:crypto";
18
18
  import { handleVerdict, parseValidationFile } from "../commands-verdict.ts";
19
19
  import { openDatabase, closeDatabase, _getAdapter, insertAssessment } from "../gsd-db.ts";
20
20
  import { invalidateStateCache } from "../state.ts";
21
+ import { resolveMilestoneFile } from "../paths.ts";
21
22
 
22
23
  interface NotifyCall {
23
24
  message: string;
@@ -363,7 +364,8 @@ test("handleVerdict pass override works when validation only exists in DB", asyn
363
364
  const { ctx, calls } = makeMockCtx();
364
365
  await handleVerdict('pass --milestone M001 --rationale "reviewed and accepted"', ctx, base);
365
366
 
366
- const validationPath = join(base, ".gsd", "phases", "01-m001", "01-VALIDATION.md");
367
+ const validationPath = resolveMilestoneFile(base, "M001", "VALIDATION");
368
+ assert.ok(validationPath, "DB-backed validation should be materialized to disk");
367
369
  const rewritten = readFileSync(validationPath, "utf-8");
368
370
  assert.match(rewritten, /^verdict: pass$/m, "DB-backed validation should be rewritten as pass");
369
371
  assert.match(rewritten, /reviewed and accepted/, "explicit rationale should be persisted");
@@ -12,6 +12,7 @@ import {
12
12
  writeCompatMarker,
13
13
  normalizeForHash,
14
14
  computeProjectionSha,
15
+ pruneOrphanedProjectionEntries,
15
16
  EMPTY_MARKER,
16
17
  compatMarkerPath,
17
18
  } from "../compat/compat-marker.ts";
@@ -103,3 +104,87 @@ test("compatMarkerPath resolves under .gsd", () => {
103
104
  const base = makeTmpBase();
104
105
  assert.equal(compatMarkerPath(base), join(base, ".gsd", ".compat.json"));
105
106
  });
107
+
108
+ test("pruneOrphanedProjectionEntries drops entries whose backing file is gone (#1257)", () => {
109
+ const base = makeTmpBase();
110
+ // Live projection: file exists on disk.
111
+ const liveRel = "phases/29-frontend-code-debt-cleanup/29-ROADMAP.md";
112
+ mkdirSync(join(base, ".gsd", "phases", "29-frontend-code-debt-cleanup"), { recursive: true });
113
+ writeFileSync(join(base, ".gsd", liveRel), "# M029\n", "utf-8");
114
+ // Phantom projection: directory was renamed, file no longer exists.
115
+ const ghostRel = "phases/29-new-milestone-m029/29-ROADMAP.md";
116
+
117
+ writeCompatMarker(base, {
118
+ schema: 2,
119
+ lastWriter: "gsd-pi",
120
+ lastProjectedAt: "2026-07-05T05:35:19.379Z",
121
+ projections: {
122
+ [liveRel]: { sha: "live000000000000", entities: ["M029"] },
123
+ [ghostRel]: { sha: "ghost00000000000", entities: ["M029"] },
124
+ },
125
+ piVersion: "1.4.0",
126
+ });
127
+
128
+ const removed = pruneOrphanedProjectionEntries(base);
129
+ assert.equal(removed, 1);
130
+
131
+ const marker = readCompatMarker(base);
132
+ assert.ok(marker.projections[liveRel], "live projection must survive");
133
+ assert.equal(marker.projections[ghostRel], undefined, "phantom projection must be pruned");
134
+ });
135
+
136
+ test("pruneOrphanedProjectionEntries also prunes .planning projections/passthrough", () => {
137
+ const base = makeTmpBase();
138
+ mkdirSync(join(base, ".planning"), { recursive: true });
139
+ writeFileSync(join(base, ".planning", "ROADMAP.md"), "# roadmap\n", "utf-8");
140
+
141
+ writeCompatMarker(base, {
142
+ schema: 2,
143
+ lastWriter: "gsd-pi",
144
+ lastProjectedAt: "2026-07-05T05:35:19.379Z",
145
+ projections: {},
146
+ planning: {
147
+ active: true,
148
+ layout: "flat-phases",
149
+ projections: {
150
+ "ROADMAP.md": { sha: "live000000000000", entities: [] },
151
+ "gone/PLAN.md": { sha: "ghost00000000000", entities: [] },
152
+ },
153
+ passthrough: {
154
+ "gone/PATTERNS.md": { sha: "ghost11111111111", entities: [] },
155
+ },
156
+ },
157
+ piVersion: "1.4.0",
158
+ });
159
+
160
+ const removed = pruneOrphanedProjectionEntries(base);
161
+ assert.equal(removed, 2);
162
+
163
+ const marker = readCompatMarker(base);
164
+ assert.ok(marker.planning!.projections["ROADMAP.md"], "live planning projection must survive");
165
+ assert.equal(marker.planning!.projections["gone/PLAN.md"], undefined);
166
+ assert.equal(marker.planning!.passthrough["gone/PATTERNS.md"], undefined);
167
+ });
168
+
169
+ test("pruneOrphanedProjectionEntries is a no-op when nothing is orphaned", () => {
170
+ const base = makeTmpBase();
171
+ const rel = "phases/01-phase/01-01-PLAN.md";
172
+ mkdirSync(join(base, ".gsd", "phases", "01-phase"), { recursive: true });
173
+ writeFileSync(join(base, ".gsd", rel), "# plan\n", "utf-8");
174
+ writeCompatMarker(base, {
175
+ schema: 2,
176
+ lastWriter: "gsd-pi",
177
+ lastProjectedAt: "2026-07-05T05:35:19.379Z",
178
+ projections: { [rel]: { sha: "abc000000000000", entities: ["M001"] } },
179
+ piVersion: "1.4.0",
180
+ });
181
+
182
+ assert.equal(pruneOrphanedProjectionEntries(base), 0);
183
+ });
184
+
185
+ test("pruneOrphanedProjectionEntries returns 0 and writes nothing when marker is missing", () => {
186
+ const base = makeTmpBase();
187
+ assert.equal(pruneOrphanedProjectionEntries(base), 0);
188
+ const files = readdirSync(join(base, ".gsd"));
189
+ assert.ok(!files.includes(".compat.json"), "must not create a marker where none existed");
190
+ });
@@ -422,6 +422,51 @@ console.log('\n=== complete-milestone: existing SUMMARY.md preserved (#4598) ===
422
422
  cleanup(dbPath);
423
423
  }
424
424
 
425
+ // ═══════════════════════════════════════════════════════════════════════════
426
+ // complete-milestone: flat-phase compatibility SUMMARY.md is not hidden
427
+ // ═══════════════════════════════════════════════════════════════════════════
428
+
429
+ console.log('\n=== complete-milestone: flat-phase legacy-named SUMMARY.md preserved (#4598) ===');
430
+ {
431
+ const dbPath = tempDbPath();
432
+ openDatabase(dbPath);
433
+ const basePath = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-flat-complete-milestone-'));
434
+ const phaseDir = path.join(basePath, '.gsd', 'phases', '01-test-milestone');
435
+ fs.mkdirSync(path.join(phaseDir, 'slices', 'S01', 'tasks'), { recursive: true });
436
+ seedCompletedMilestone({ basePath, validationVerdict: 'pass' });
437
+
438
+ // Flat-phase projects may still contain legacy-named compatibility files.
439
+ // The no-overwrite guard must preserve those richer summaries instead of
440
+ // generating a canonical sibling that later readers would prefer.
441
+ const summaryPath = path.join(phaseDir, 'M001-SUMMARY.md');
442
+ const canonicalSummaryPath = path.join(phaseDir, '01-SUMMARY.md');
443
+ const sentinel = '# M001: Pre-existing flat-phase richer summary\n\nDO NOT OVERWRITE ME\n';
444
+ fs.writeFileSync(summaryPath, sentinel, 'utf-8');
445
+
446
+ const result = await handleCompleteMilestone(makeValidParams(), basePath);
447
+ assertTrue(!('error' in result), 'completion should still succeed when flat-phase compatibility SUMMARY.md exists');
448
+ if (!('error' in result)) {
449
+ assertEq(
450
+ result.summaryPath,
451
+ fs.realpathSync(summaryPath),
452
+ 'completion should report the preserved existing SUMMARY path',
453
+ );
454
+ assertEq(
455
+ fs.readFileSync(summaryPath, 'utf-8'),
456
+ sentinel,
457
+ 'flat-phase compatibility SUMMARY.md must be preserved, not hidden by a canonical rewrite',
458
+ );
459
+ assertTrue(
460
+ !fs.existsSync(canonicalSummaryPath),
461
+ 'completion must not create a canonical sibling over an existing compatibility summary',
462
+ );
463
+ assertEq(getMilestone('M001')!.status, 'complete', 'milestone should still be marked complete in DB');
464
+ }
465
+
466
+ cleanupDir(basePath);
467
+ cleanup(dbPath);
468
+ }
469
+
425
470
  // ═══════════════════════════════════════════════════════════════════════════
426
471
 
427
472
  report();
@@ -33,6 +33,45 @@ test("normalizeDbRows normalizes every row", () => {
33
33
  assert.deepEqual(normalizeDbRows([first, second]), [{ id: "S01" }, { id: "S02" }]);
34
34
  });
35
35
 
36
+ test("createDbAdapter strips named-parameter key prefixes for provider compatibility", () => {
37
+ const received: unknown[][] = [];
38
+ const rawStatement = {
39
+ run: (...params: unknown[]) => {
40
+ received.push(params);
41
+ return { changes: 1 };
42
+ },
43
+ get: (...params: unknown[]) => {
44
+ received.push(params);
45
+ return undefined;
46
+ },
47
+ all: (...params: unknown[]) => {
48
+ received.push(params);
49
+ return [];
50
+ },
51
+ };
52
+ const rawDb = {
53
+ exec: () => {},
54
+ prepare: () => rawStatement,
55
+ close: () => {},
56
+ };
57
+ const adapter = createDbAdapter(rawDb);
58
+ const stmt = adapter.prepare("INSERT INTO t (version, applied_at) VALUES (:version, :applied_at)");
59
+
60
+ stmt.run({ ":version": 1, "@name": "a", "$flag": true, bare: "kept" });
61
+ assert.deepEqual(received.pop(), [{ version: 1, name: "a", flag: true, bare: "kept" }]);
62
+
63
+ // Non-binding params pass through untouched: positional scalars, arrays,
64
+ // and binary blobs must not be treated as named-binding objects.
65
+ const blob = new Uint8Array([1, 2, 3]);
66
+ stmt.get("T01", blob);
67
+ const passthrough = received.pop() as unknown[];
68
+ assert.equal(passthrough[0], "T01");
69
+ assert.equal(passthrough[1], blob);
70
+
71
+ stmt.all({ plain: "unchanged" });
72
+ assert.deepEqual(received.pop(), [{ plain: "unchanged" }]);
73
+ });
74
+
36
75
  test("createDbAdapter caches prepared statements and clears cache on close", () => {
37
76
  const calls: unknown[] = [];
38
77
  const rawStatement = {