@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
@@ -1407,7 +1407,7 @@ export function registerHooks(pi, ecosystemHandlers) {
1407
1407
  }
1408
1408
  }
1409
1409
  if (!event.isError) {
1410
- recordToolCallLoopMutation(toolName);
1410
+ recordToolCallLoopMutation(toolName, event.details);
1411
1411
  refreshSourceObservationAfterMutation(toolName, event.input, ctx);
1412
1412
  clearSourceObservationsAfterShell(toolName);
1413
1413
  }
@@ -14,13 +14,16 @@
14
14
  * to stop tooling for the rest of that turn and answer in text.
15
15
  *
16
16
  * The per-tool-name check (#783 Brief C) tracks call counts within a
17
- * turn regardless of args, decaying after successful state-mutating tools.
17
+ * turn regardless of args, decaying after successful state-progressing tools.
18
18
  * This catches improvisation
19
19
  * loops where the model attempts the same missing workflow tool through
20
20
  * varied surfaces (bash → `node -e` → CLI), each with a different
21
21
  * signature, so the identical-args streak never trips, while allowing
22
- * tool-heavy turns that are making file-mutation progress. Whichever guard
23
- * trips first blocks.
22
+ * tool-heavy turns that are making progress. Progress means a successful file
23
+ * mutation (edit/write) or a successful shell/exec call (bg_shell,
24
+ * gsd_uat_exec, …) — so a local model running a legitimate iterative
25
+ * debugging loop (restart server, npm install, curl an endpoint) is not
26
+ * blocked as a false loop (#1206). Whichever guard trips first blocks.
24
27
  *
25
28
  * Thresholds, exempt tools, and enable flags are user-tunable (#1198) via the
26
29
  * `tool_call_loop_guard` key in `.gsd/PREFERENCES.md` and `GSD_TOOL_LOOP_*`
@@ -40,6 +43,52 @@ const DEFAULT_PER_TOOL_CAP_EXEMPT_TOOLS = ["find", "glob", "grep", "ls", "read",
40
43
  const STRICT_LOOP_TOOLS = new Set(["ask_user_questions"]);
41
44
  const MAX_CONSECUTIVE_STRICT = 1;
42
45
  const STATE_MUTATING_TOOL_SET = new Set(["edit", "write", "multi_edit", "notebook_edit"]);
46
+ /**
47
+ * Successful shell/exec calls are state progression too (#1206), not just file
48
+ * mutations. A local model debugging a UAT scenario legitimately restarts
49
+ * servers, runs `npm install`, and curls endpoints via bg_shell / gsd_uat_exec
50
+ * with varied args — productive work that never touches a file, so Guard 2's
51
+ * per-tool cap would otherwise misfire as a false loop. Decaying on these
52
+ * successful calls keeps the guard from aborting a progressing turn.
53
+ *
54
+ * Only successful calls reach {@link recordToolCallLoopMutation}: the
55
+ * tool_result hook skips `isError` results, and the bash tool throws on
56
+ * non-zero exit, so a genuinely stuck improvisation loop of failing commands
57
+ * (#783) never decays and still trips the cap. `async_bash` is excluded
58
+ * because it only registers a background job; command success is reported
59
+ * later via `await_job`. `bg_shell` may return `isError: false` for failed
60
+ * `run`/`start` outcomes, so the hook passes `details` for validation.
61
+ */
62
+ const STATE_PROGRESSING_EXEC_TOOL_SET = new Set([
63
+ "bash",
64
+ "bg_shell",
65
+ "shell",
66
+ "powershell",
67
+ "gsd_exec",
68
+ "gsd_uat_exec",
69
+ ]);
70
+ function isSuccessfulBgShellLoopProgress(details) {
71
+ if (!details || typeof details !== "object")
72
+ return false;
73
+ const record = details;
74
+ switch (record.action) {
75
+ case "run":
76
+ return record.exitCode === 0 && record.timedOut !== true;
77
+ case "start":
78
+ case "restart": {
79
+ const process = record.process;
80
+ if (!process || typeof process !== "object")
81
+ return false;
82
+ return process.alive === true;
83
+ }
84
+ case "wait_for_ready":
85
+ return record.ready === true;
86
+ case "send_and_wait":
87
+ return record.matched === true;
88
+ default:
89
+ return true;
90
+ }
91
+ }
43
92
  function defaultGuardConfig() {
44
93
  return {
45
94
  guardEnabled: true,
@@ -235,11 +284,23 @@ export function checkToolCallLoop(toolName, args) {
235
284
  }
236
285
  return { block: false, count: consecutiveCount };
237
286
  }
238
- /** Record successful mutation progress so Guard 2 can decay on the next count. */
239
- export function recordToolCallLoopMutation(toolName) {
287
+ /**
288
+ * Record successful state progress so Guard 2 can decay on the next count.
289
+ * File mutations (edit/write/…) and successful shell/exec calls (bash,
290
+ * bg_shell, gsd_uat_exec, …) both count as progress (#1092, #1206). The caller
291
+ * only invokes this for non-error results, so failing improvisation loops
292
+ * (#783) never decay and still trip the per-tool cap.
293
+ */
294
+ export function recordToolCallLoopMutation(toolName, details) {
240
295
  if (!enabled)
241
296
  return;
242
- if (!STATE_MUTATING_TOOL_SET.has(toolName))
297
+ if (STATE_MUTATING_TOOL_SET.has(toolName)) {
298
+ mutationEpoch++;
299
+ return;
300
+ }
301
+ if (!STATE_PROGRESSING_EXEC_TOOL_SET.has(toolName))
302
+ return;
303
+ if (toolName === "bg_shell" && details !== undefined && !isSuccessfulBgShellLoopProgress(details))
243
304
  return;
244
305
  mutationEpoch++;
245
306
  }
@@ -506,9 +506,15 @@ export function ensureCodebaseMapFresh(basePath, options, ensureOptions) {
506
506
  }
507
507
  const metadata = parseCodebaseMapMetadata(existing);
508
508
  const existingDescriptions = parseCodebaseMap(existing);
509
+ // Compare against the truncated count: when the repository has more tracked
510
+ // files than maxFiles, the map only lists the first maxFiles entries, so a
511
+ // stored fileCount above the cap (e.g. from an older map that recorded the
512
+ // full count) still matches the current truncated listing. Comparing raw
513
+ // values would flag "file-count-changed" on every turn, rewriting the
514
+ // timestamp and invalidating the provider KV cache prefix.
509
515
  const staleReason = !metadata ? "missing-metadata"
510
516
  : metadata.fingerprint !== fingerprint ? "files-changed"
511
- : metadata.fileCount !== listed.files.length ? "file-count-changed"
517
+ : Math.min(metadata.fileCount, resolved.maxFiles) !== listed.files.length ? "file-count-changed"
512
518
  : metadata.truncated !== listed.truncated ? "truncation-changed"
513
519
  : undefined;
514
520
  if (!staleReason) {
@@ -98,6 +98,50 @@ export function writeCompatMarker(basePath, marker) {
98
98
  writeFileSync(tmp, JSON.stringify(marker, null, 2), "utf-8");
99
99
  renameSync(tmp, path);
100
100
  }
101
+ /**
102
+ * Remove projection entries whose backing file no longer exists on disk.
103
+ *
104
+ * gsd-pi never deletes marker entries on its own: both drift detectors skip
105
+ * files missing from disk (`if (!existsSync(abs)) continue;`) and the write-time
106
+ * flush only ever adds or refreshes entries. So when a phase directory is
107
+ * renamed or removed (e.g. `phases/29-new-milestone-m029/` →
108
+ * `phases/29-frontend-code-debt-cleanup/`), the old projection paths linger in
109
+ * `.compat.json` forever as phantom entries pointing at directories that no
110
+ * longer exist (#1257). They never reconcile, and `.compat.json` keeps drifting
111
+ * from disk reality so `git status` stops being a reliable proxy for "did the
112
+ * engine touch my plans?".
113
+ *
114
+ * This prunes those orphaned entries across the `.gsd/` projections and the
115
+ * `.planning/` projections/passthrough maps. It is safe: a missing-file entry is
116
+ * inert (every detector already ignores it), and if the file is later
117
+ * re-projected the write-time flush / unseeded-file detection re-seeds an
118
+ * accurate baseline.
119
+ *
120
+ * Returns the number of entries removed; when nothing is orphaned the marker is
121
+ * left untouched (no needless write, no `lastProjectedAt` churn).
122
+ */
123
+ export function pruneOrphanedProjectionEntries(basePath) {
124
+ if (!existsSync(compatMarkerPath(basePath)))
125
+ return 0;
126
+ const marker = readCompatMarker(basePath);
127
+ let removed = 0;
128
+ const pruneMap = (map, root) => {
129
+ for (const relPath of Object.keys(map)) {
130
+ if (!existsSync(join(basePath, root, relPath))) {
131
+ delete map[relPath];
132
+ removed++;
133
+ }
134
+ }
135
+ };
136
+ pruneMap(marker.projections, ".gsd");
137
+ if (marker.planning) {
138
+ pruneMap(marker.planning.projections, ".planning");
139
+ pruneMap(marker.planning.passthrough, ".planning");
140
+ }
141
+ if (removed > 0)
142
+ writeCompatMarker(basePath, marker);
143
+ return removed;
144
+ }
101
145
  function quarantine(basePath, raw) {
102
146
  const path = compatMarkerPath(basePath);
103
147
  const badPath = `${path}.bad-${Date.now()}`;
@@ -121,8 +121,13 @@ function collectConfigSections(options) {
121
121
  if (prefs?.auto_supervisor) {
122
122
  const sup = resolveAutoSupervisorConfig();
123
123
  const supRows = [];
124
- if (sup.model)
125
- supRows.push({ label: "Model", value: sup.model });
124
+ if (sup.model) {
125
+ let modelVal = sup.model;
126
+ if (sup.modelFallbacks && sup.modelFallbacks.length > 0) {
127
+ modelVal += ` \u2192 ${sup.modelFallbacks.join(" \u2192 ")}`;
128
+ }
129
+ supRows.push({ label: "Model", value: modelVal });
130
+ }
126
131
  supRows.push({ label: "Soft timeout", value: `${sup.soft_timeout_minutes}m` });
127
132
  supRows.push({ label: "Idle timeout", value: `${sup.idle_timeout_minutes}m` });
128
133
  supRows.push({ label: "Stalled tool timeout", value: `${sup.stalled_tool_timeout_minutes}m` });
@@ -801,3 +801,36 @@ export function getDb() {
801
801
  export function getDbOrNull() {
802
802
  return currentDb;
803
803
  }
804
+ /**
805
+ * Confirm the open handle can actually write, not just that it opened.
806
+ *
807
+ * A schema-current database performs zero writes during open, so a handle that
808
+ * opened successfully but is not writable (read-only file, WAL/SHM permission
809
+ * mismatch, or a stale/moved handle → SQLITE_READONLY_DBMOVED) otherwise passes
810
+ * the open-only availability check and only fails much later at the first real
811
+ * write. The probe forces a genuine page write by re-writing the current
812
+ * `PRAGMA user_version` value back inside an IMMEDIATE transaction: a bare
813
+ * `BEGIN IMMEDIATE; ROLLBACK` is not sufficient because a moved handle does not
814
+ * fail until a page is actually dirtied. The value is unchanged, so the probe
815
+ * is idempotent, and the transaction is rolled back so nothing persists.
816
+ */
817
+ export function probeDbWritable() {
818
+ const db = currentDb;
819
+ if (!db)
820
+ return { ok: false, detail: "No database is open." };
821
+ try {
822
+ const row = db.prepare("PRAGMA user_version").get();
823
+ const current = typeof row?.user_version === "number" ? row.user_version : 0;
824
+ db.exec("BEGIN IMMEDIATE");
825
+ try {
826
+ db.exec(`PRAGMA user_version = ${current}`);
827
+ }
828
+ finally {
829
+ db.exec("ROLLBACK");
830
+ }
831
+ return { ok: true };
832
+ }
833
+ catch (err) {
834
+ return { ok: false, detail: err.message };
835
+ }
836
+ }
@@ -24,6 +24,17 @@ function requireDb() {
24
24
  * completion timestamps cleared, in one commit. Folds the hand-rolled
25
25
  * transaction-plus-cascade previously in tools/reopen-slice.ts. Guards run
26
26
  * inside the transaction (TOCTOU-safe).
27
+ *
28
+ * A closed slice is always reopenable. An open slice is only reopenable when it
29
+ * carries a completed-task state desync (#1205): a UAT→planning fallback can
30
+ * leave the slice "pending" while *all* its tasks stay "complete", which
31
+ * deadlocks every recovery path (plan-slice/replan-slice/complete-task all
32
+ * reject the still-closed tasks, and slice_reopen historically rejected the
33
+ * still-open slice). Resetting those tasks to pending gives the planner the
34
+ * clean slate it needs. An open slice that still has any unfinished
35
+ * (pending/in-progress) task is a normal in-flight slice, not the desync, and
36
+ * stays rejected as "slice-not-complete" so a full reset can't wipe its
37
+ * legitimate progress.
27
38
  */
28
39
  export function reopenSliceCascade(milestoneId, sliceId) {
29
40
  requireDb();
@@ -43,11 +54,18 @@ export function reopenSliceCascade(milestoneId, sliceId) {
43
54
  outcome = { ok: false, reason: "slice-not-found" };
44
55
  return;
45
56
  }
46
- if (!isClosedStatus(slice.status)) {
57
+ const tasks = getSliceTasks(milestoneId, sliceId);
58
+ // A closed slice is always reopenable. An open slice is only the #1205
59
+ // desync when EVERY task is already closed (a UAT→planning fallback left the
60
+ // slice open with all tasks still "complete"). Requiring *all* tasks closed
61
+ // (not merely one) means a normal in-flight slice — which keeps at least one
62
+ // pending/in-progress task — is rejected, so a full reset can't wipe its
63
+ // legitimate progress.
64
+ const allTasksClosed = tasks.length > 0 && tasks.every((t) => isClosedStatus(t.status));
65
+ if (!isClosedStatus(slice.status) && !allTasksClosed) {
47
66
  outcome = { ok: false, reason: "slice-not-complete", status: slice.status };
48
67
  return;
49
68
  }
50
- const tasks = getSliceTasks(milestoneId, sliceId);
51
69
  getDbOrNull().prepare(`UPDATE slices SET status = 'in_progress', completed_at = NULL WHERE milestone_id = :mid AND id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId });
52
70
  getDbOrNull().prepare(`UPDATE tasks SET status = 'pending', completed_at = NULL WHERE milestone_id = :mid AND slice_id = :sid`).run({ ":mid": milestoneId, ":sid": sliceId });
53
71
  outcome = { ok: true, tasksReset: tasks.length };
@@ -11,19 +11,46 @@ export function normalizeDbRow(row) {
11
11
  export function normalizeDbRows(rows) {
12
12
  return rows.map((row) => normalizeDbRow(row));
13
13
  }
14
+ function isPlainObject(value) {
15
+ if (value === null || typeof value !== "object")
16
+ return false;
17
+ const proto = Object.getPrototypeOf(value);
18
+ return proto === Object.prototype || proto === null;
19
+ }
20
+ // node:sqlite accepts binding-object keys with or without the ':'/'@'/'$'
21
+ // prefix, but better-sqlite3 accepts only bare keys — strip the prefix so
22
+ // call sites written against node:sqlite also work on the fallback provider.
23
+ function normalizeBindParams(params) {
24
+ return params.map((param) => {
25
+ if (!isPlainObject(param))
26
+ return param;
27
+ let changed = false;
28
+ const normalized = {};
29
+ for (const [key, value] of Object.entries(param)) {
30
+ if (key.length > 1 && (key[0] === ":" || key[0] === "@" || key[0] === "$")) {
31
+ normalized[key.slice(1)] = value;
32
+ changed = true;
33
+ }
34
+ else {
35
+ normalized[key] = value;
36
+ }
37
+ }
38
+ return changed ? normalized : param;
39
+ });
40
+ }
14
41
  export function createDbAdapter(rawDb) {
15
42
  const db = rawDb;
16
43
  const stmtCache = new Map();
17
44
  function wrapStmt(raw) {
18
45
  return {
19
46
  run(...params) {
20
- return raw.run(...params);
47
+ return raw.run(...normalizeBindParams(params));
21
48
  },
22
49
  get(...params) {
23
- return normalizeDbRow(raw.get(...params));
50
+ return normalizeDbRow(raw.get(...normalizeBindParams(params)));
24
51
  },
25
52
  all(...params) {
26
- return normalizeDbRows(raw.all(...params));
53
+ return normalizeDbRows(raw.all(...normalizeBindParams(params)));
27
54
  },
28
55
  };
29
56
  }
@@ -138,7 +138,7 @@ Diagnostics record the file path, scope (global/project), severity (error/warnin
138
138
  - `off` — skill discovery is disabled entirely.
139
139
 
140
140
  - `auto_supervisor`: configures the auto-mode supervisor that monitors agent progress and enforces timeouts. Keys:
141
- - `model`: model ID to use for the supervisor process (defaults to the currently active model).
141
+ - `model`: model to use for the supervisor process (defaults to the currently active model). Accepts a bare model ID **or** the same `{ model, provider?, fallbacks? }` object form as `models.<phase>`, so it honors `fallbacks[]` on a transient provider trip.
142
142
  - `soft_timeout_minutes`: minutes before the supervisor issues a soft warning (default: 20).
143
143
  - `idle_timeout_minutes`: minutes of inactivity before the supervisor intervenes (default: 10).
144
144
  - `hard_timeout_minutes`: minutes before the supervisor forces termination (default: 30).
@@ -228,7 +228,7 @@ In `"parent"` mode, slice/task `targetRepositories` default to the declared chil
228
228
  - `enabled`: boolean — set `false` to force sequential task execution. Default: `true`.
229
229
  - `max_parallel`: number — maximum tasks to dispatch in one batch, range `1`-`8`. Default: `2`.
230
230
  - `isolation_mode`: `"same-tree"` — currently the only supported value.
231
- - `subagent_model`: string — optional model override for reactive task subagents. Falls back to the `models.subagent` routing when omitted.
231
+ - `subagent_model`: optional model override for reactive task subagents. Accepts a bare model ID **or** the `{ model, provider?, fallbacks? }` object form (parity with `models.<phase>`). Falls back to the `models.subagent` routing when omitted.
232
232
 
233
233
  - `remote_questions`: route interactive questions to Slack/Discord for headless auto-mode. Keys:
234
234
  - `channel`: `"slack"` or `"discord"` — channel type.
@@ -326,7 +326,7 @@ In `"parent"` mode, slice/task `targetRepositories` default to the declared chil
326
326
  - `after`: string[] — unit types that trigger this hook (e.g., `["execute-task"]`).
327
327
  - `prompt`: string — prompt sent to the LLM. Supports `{milestoneId}`, `{sliceId}`, `{taskId}` substitutions.
328
328
  - `max_cycles`: number — max times this hook fires per trigger (default: 1, max: 10).
329
- - `model`: string — optional model override.
329
+ - `model`: optional model override. Accepts a bare model ID **or** the same `{ model, provider?, fallbacks? }` object form as `models.<phase>`. With `fallbacks[]`, a transient trip of the primary provider falls back to the next entry instead of hard-failing the hook — which, for a `blocking` hook with `on_block: { action: pause }`, would otherwise pause the whole run.
330
330
  - `artifact`: string — expected output file name (relative to task/slice dir). Hook is skipped if file already exists (idempotent).
331
331
  - `criticality`: `"advisory"` or `"blocking"` — advisory preserves current best-effort behavior; blocking requires clean hook completion plus a valid outcome verdict before auto-mode advances. Default: `"advisory"`.
332
332
  - `retry_on`: string — if this file is produced instead of the artifact, re-run the trigger unit then re-run hooks.
@@ -78,15 +78,33 @@ function rollbackPartialMigration(basePath, backupDir, migratingPath) {
78
78
  catch (removeErr) {
79
79
  logWarning("migration", `rollback: could not remove partial ${LAYOUT_SEGMENTS.level1}/: ${removeErr.message}`);
80
80
  }
81
+ // Only the standalone .gsd-backups/migrate-<ts>/ copy is disposable once the
82
+ // restore succeeds. When resuming an interrupted migration, backupDir IS the
83
+ // preserved migrating tree (the sole surviving data source) and must never be
84
+ // removed here. Deleting the backup after a successful rollback keeps the gate
85
+ // from leaking one .gsd-backups/migrate-<ts>/ per session_start.
86
+ const isDisposableBackup = Boolean(backupDir) && backupDir !== migratingPath;
87
+ const cleanupBackup = () => {
88
+ if (!isDisposableBackup || !existsSync(backupDir))
89
+ return;
90
+ try {
91
+ removePathWithRetries(backupDir);
92
+ }
93
+ catch (cleanupErr) {
94
+ logWarning("migration", `rollback: could not clean backup ${backupDir}: ${cleanupErr.message}`);
95
+ }
96
+ };
81
97
  // Restore milestones/ — prefer renaming the preserved migrating copy back.
82
98
  const milestonesPath = join(basePath, ".gsd", "milestones");
83
99
  try {
84
100
  if (migratingPath && existsSync(migratingPath) && !existsSync(milestonesPath)) {
85
101
  movePathWithCopyDeleteFallback(migratingPath, milestonesPath);
102
+ cleanupBackup();
86
103
  return;
87
104
  }
88
105
  if (existsSync(backupDir)) {
89
106
  cpSync(backupDir, milestonesPath, { recursive: true });
107
+ cleanupBackup();
90
108
  }
91
109
  if (migratingPath && existsSync(migratingPath)) {
92
110
  removePathWithRetries(migratingPath);
@@ -352,17 +352,17 @@ export function insertTask(t) {
352
352
  ON CONFLICT(milestone_id, slice_id, id) DO UPDATE SET
353
353
  title = CASE WHEN NULLIF(:title, '') IS NOT NULL THEN :title ELSE tasks.title END,
354
354
  status = :status,
355
- one_liner = :one_liner,
356
- narrative = :narrative,
357
- verification_result = :verification_result,
358
- duration = :duration,
359
- completed_at = :completed_at,
360
- blocker_discovered = :blocker_discovered,
361
- deviations = :deviations,
362
- known_issues = :known_issues,
363
- key_files = :key_files,
364
- key_decisions = :key_decisions,
365
- full_summary_md = :full_summary_md,
355
+ one_liner = CASE WHEN :preserve_completion = 1 AND NULLIF(:one_liner, '') IS NULL THEN tasks.one_liner ELSE :one_liner END,
356
+ narrative = CASE WHEN :preserve_completion = 1 AND NULLIF(:narrative, '') IS NULL THEN tasks.narrative ELSE :narrative END,
357
+ verification_result = CASE WHEN :preserve_completion = 1 AND NULLIF(:verification_result, '') IS NULL THEN tasks.verification_result ELSE :verification_result END,
358
+ duration = CASE WHEN :preserve_completion = 1 AND NULLIF(:duration, '') IS NULL THEN tasks.duration ELSE :duration END,
359
+ completed_at = CASE WHEN :preserve_completion = 1 AND tasks.completed_at IS NOT NULL THEN tasks.completed_at ELSE :completed_at END,
360
+ blocker_discovered = CASE WHEN :preserve_completion = 1 AND :blocker_discovered = 0 THEN tasks.blocker_discovered ELSE :blocker_discovered END,
361
+ deviations = CASE WHEN :preserve_completion = 1 AND NULLIF(:deviations, '') IS NULL THEN tasks.deviations ELSE :deviations END,
362
+ known_issues = CASE WHEN :preserve_completion = 1 AND NULLIF(:known_issues, '') IS NULL THEN tasks.known_issues ELSE :known_issues END,
363
+ key_files = CASE WHEN :preserve_completion = 1 AND NULLIF(:key_files, '[]') IS NULL THEN tasks.key_files ELSE :key_files END,
364
+ key_decisions = CASE WHEN :preserve_completion = 1 AND NULLIF(:key_decisions, '[]') IS NULL THEN tasks.key_decisions ELSE :key_decisions END,
365
+ full_summary_md = CASE WHEN :preserve_completion = 1 AND NULLIF(:full_summary_md, '') IS NULL THEN tasks.full_summary_md ELSE :full_summary_md END,
366
366
  description = CASE WHEN NULLIF(:description, '') IS NOT NULL THEN :description ELSE tasks.description END,
367
367
  estimate = CASE WHEN NULLIF(:estimate, '') IS NOT NULL THEN :estimate ELSE tasks.estimate END,
368
368
  files = CASE WHEN NULLIF(:files, '[]') IS NOT NULL THEN :files ELSE tasks.files END,
@@ -401,6 +401,7 @@ export function insertTask(t) {
401
401
  ":observability_impact": t.planning?.observabilityImpact ?? "",
402
402
  ":full_plan_md": t.planning?.fullPlanMd ?? "",
403
403
  ":sequence": t.sequence ?? 0,
404
+ ":preserve_completion": t.preserveCompletionMetadata && (t.status === "complete" || t.status === "done") ? 1 : 0,
404
405
  ":target_repositories": JSON.stringify(t.planning?.targetRepositories ?? []),
405
406
  ":raw_target_repositories": t.planning && "targetRepositories" in t.planning
406
407
  ? JSON.stringify(t.planning.targetRepositories ?? [])
@@ -27,7 +27,7 @@ import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.j
27
27
  import { nativeAddAll, nativeCommit, nativeHasCommittedHead, nativeIsRepo, nativeInit } from "./native-git-bridge.js";
28
28
  import { isInheritedRepo } from "./repo-identity.js";
29
29
  import { ensureGitignore, ensurePreferences, untrackRuntimeFiles } from "./gitignore.js";
30
- import { getIsolationMode, loadEffectiveGSDPreferences } from "./preferences.js";
30
+ import { getIsolationMode, loadEffectiveGSDPreferences, renderLanguageDirectiveForPrompt } from "./preferences.js";
31
31
  import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
32
32
  import { resolveUokFlags } from "./uok/flags.js";
33
33
  import { ensurePlanV2Graph, isMissingFinalizedContextResult } from "./uok/plan-v2.js";
@@ -642,7 +642,7 @@ function resolveAvailableModel(modelId, availableModels, currentProvider) {
642
642
  * Build the discuss-and-plan prompt for a new milestone.
643
643
  * Used by all three "new milestone" paths (first ever, no active, all complete).
644
644
  */
645
- function buildDiscussPrompt(nextId, preamble, _basePath, pi, ctx, preparationContext) {
645
+ function buildDiscussPrompt(nextId, preamble, basePath, pi, ctx, preparationContext) {
646
646
  const milestoneRel = `.gsd/milestones/${nextId}`;
647
647
  const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
648
648
  const inlinedTemplates = [
@@ -652,7 +652,7 @@ function buildDiscussPrompt(nextId, preamble, _basePath, pi, ctx, preparationCon
652
652
  inlineTemplate("roadmap", "Roadmap"),
653
653
  inlineTemplate("decisions", "Decisions"),
654
654
  ].join("\n\n---\n\n");
655
- return loadPrompt("discuss", {
655
+ return prependLanguageDirective(basePath, loadPrompt("discuss", {
656
656
  milestoneId: nextId,
657
657
  preamble,
658
658
  preparationContext: preparationContext ?? "",
@@ -662,13 +662,22 @@ function buildDiscussPrompt(nextId, preamble, _basePath, pi, ctx, preparationCon
662
662
  inlinedTemplates,
663
663
  commitInstruction: buildDocsCommitInstruction(`docs(${nextId}): context, requirements, and roadmap`),
664
664
  multiMilestoneCommitInstruction: buildDocsCommitInstruction("docs: project plan — N milestones"),
665
- });
665
+ }));
666
+ }
667
+ /**
668
+ * Prepend the configured response-language directive to a dispatched discuss
669
+ * prompt so the discuss/QA conversation is held in the language set in
670
+ * PREFERENCES.md (#1210). No-op when no language is configured.
671
+ */
672
+ function prependLanguageDirective(basePath, prompt) {
673
+ const directive = renderLanguageDirectiveForPrompt(loadEffectiveGSDPreferences(basePath)?.preferences);
674
+ return directive ? `${directive}\n\n${prompt}` : prompt;
666
675
  }
667
676
  /**
668
677
  * Build the discuss prompt for headless milestone creation.
669
678
  * Uses the discuss-headless prompt template with seed context injected.
670
679
  */
671
- function buildHeadlessDiscussPrompt(nextId, seedContext, _basePath) {
680
+ function buildHeadlessDiscussPrompt(nextId, seedContext, basePath) {
672
681
  const milestoneRel = `.gsd/milestones/${nextId}`;
673
682
  const inlinedTemplates = [
674
683
  inlineTemplate("project", "Project"),
@@ -677,7 +686,7 @@ function buildHeadlessDiscussPrompt(nextId, seedContext, _basePath) {
677
686
  inlineTemplate("roadmap", "Roadmap"),
678
687
  inlineTemplate("decisions", "Decisions"),
679
688
  ].join("\n\n---\n\n");
680
- return loadPrompt("discuss-headless", {
689
+ return prependLanguageDirective(basePath, loadPrompt("discuss-headless", {
681
690
  milestoneId: nextId,
682
691
  seedContext,
683
692
  contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
@@ -685,7 +694,7 @@ function buildHeadlessDiscussPrompt(nextId, seedContext, _basePath) {
685
694
  inlinedTemplates,
686
695
  commitInstruction: buildDocsCommitInstruction(`docs(${nextId}): context, requirements, and roadmap`),
687
696
  multiMilestoneCommitInstruction: buildDocsCommitInstruction("docs: project plan — N milestones"),
688
- });
697
+ }));
689
698
  }
690
699
  /**
691
700
  * Run preparation phase if enabled, then build the discuss prompt.
@@ -51,7 +51,7 @@ export function emitJournalEvent(basePath, entry) {
51
51
  catch {
52
52
  // Silent failure — journal must never break auto-mode
53
53
  }
54
- if (!isUnifiedAuditEnabled())
54
+ if (!isUnifiedAuditEnabled(basePath))
55
55
  return;
56
56
  try {
57
57
  const causedBy = entry.causedBy