@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
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { readdirSync, existsSync, realpathSync, statSync, Dirent } from "node:fs";
14
- import { join, dirname, normalize, resolve } from "node:path";
14
+ import { join, dirname, normalize, relative, resolve } from "node:path";
15
15
  import { homedir } from "node:os";
16
16
  import { spawnSync } from "node:child_process";
17
17
  import { nativeScanGsdTree, type GsdTreeEntry } from "./native-parser-bridge.js";
@@ -379,21 +379,26 @@ export function resolveGsdPathContract(
379
379
  const resolvedWorkRoot = resolve(workRoot || process.cwd());
380
380
  const isWorktree = isGsdWorktreePath(resolvedWorkRoot);
381
381
  if (isWorktree && !originalProjectRoot?.trim()) {
382
- const projectGsd = resolveExternalStateProjectGsdFromWorktreePath(resolvedWorkRoot);
383
- if (projectGsd) {
382
+ const rawProjectGsd = resolveExternalStateProjectGsdFromWorktreePath(resolvedWorkRoot);
383
+ if (rawProjectGsd) {
384
+ // Canonicalize the `.gsd` root so the DB path matches what every other
385
+ // accessor (gsdRoot/gsdProjectionRoot) returns — otherwise an unresolved
386
+ // symlink (e.g. `/mnt/c/.../.gsd` → native ext4 on WSL) selects the wrong
387
+ // journal mode and yields a fragile, move-prone handle. See issue #1239.
388
+ const projectGsd = normalizeRealPath(rawProjectGsd);
384
389
  return {
385
390
  projectRoot: dirname(dirname(projectGsd)),
386
391
  workRoot: resolvedWorkRoot,
387
392
  projectGsd,
388
- worktreeGsd: join(resolvedWorkRoot, ".gsd"),
393
+ worktreeGsd: normalizeRealPath(join(resolvedWorkRoot, ".gsd")),
389
394
  projectDb: join(projectGsd, "gsd.db"),
390
395
  isWorktree,
391
396
  };
392
397
  }
393
398
  }
394
399
  const projectRoot = resolve(resolveWorktreeProjectRoot(resolvedWorkRoot, originalProjectRoot));
395
- const projectGsd = join(projectRoot, ".gsd");
396
- const worktreeGsd = isWorktree ? join(resolvedWorkRoot, ".gsd") : null;
400
+ const projectGsd = normalizeRealPath(join(projectRoot, ".gsd"));
401
+ const worktreeGsd = isWorktree ? normalizeRealPath(join(resolvedWorkRoot, ".gsd")) : null;
397
402
 
398
403
  return {
399
404
  projectRoot,
@@ -696,7 +701,16 @@ function pickPreferredPhaseDir(phasesDir: string, matches: string[]): string {
696
701
  return best;
697
702
  }
698
703
 
699
- function phaseDirMatchesMilestoneId(dirName: string, milestoneId: string, phaseNum: number): boolean {
704
+ function slugLooksLikeTeamSuffixProjection(slugPart: string): boolean {
705
+ return /^[a-z0-9]{6}(-|$)/.test(slugPart);
706
+ }
707
+
708
+ function phaseDirMatchesMilestoneId(
709
+ dirName: string,
710
+ milestoneId: string,
711
+ phaseNum: number,
712
+ allowTeamSuffixSlugs = false,
713
+ ): boolean {
700
714
  const numMatch = dirName.match(/^(\d+)-(.*)$/);
701
715
  if (!numMatch || parseInt(numMatch[1]!, 10) !== phaseNum) return false;
702
716
  const slugPart = numMatch[2]!;
@@ -704,8 +718,8 @@ function phaseDirMatchesMilestoneId(dirName: string, milestoneId: string, phaseN
704
718
  if (suffix) {
705
719
  return slugPart === suffix || slugPart.startsWith(`${suffix}-`);
706
720
  }
707
- // Plain sequential IDs must not resolve to unique-suffix phase dirs.
708
- return !/^[a-z0-9]{6}(-|$)/.test(slugPart);
721
+ if (slugLooksLikeTeamSuffixProjection(slugPart) && !allowTeamSuffixSlugs) return false;
722
+ return true;
709
723
  }
710
724
 
711
725
  function resolvePhaseDir(basePath: string, milestoneId: string): string | null {
@@ -726,6 +740,15 @@ function resolvePhaseDir(basePath: string, milestoneId: string): string | null {
726
740
  matches.push(entry.name);
727
741
  }
728
742
  }
743
+ // Bare milestone ids may only have a suffixed team-mode projection on disk.
744
+ if (matches.length === 0 && !milestoneIdUniqueSuffix(milestoneId)) {
745
+ for (const entry of readdirSync(phasesDir, { withFileTypes: true })) {
746
+ if (!entry.isDirectory()) continue;
747
+ if (phaseDirMatchesMilestoneId(entry.name, milestoneId, phaseNum, true)) {
748
+ matches.push(entry.name);
749
+ }
750
+ }
751
+ }
729
752
  } catch {
730
753
  // unreadable — fall through
731
754
  }
@@ -898,10 +921,24 @@ export function resolveTaskFile(
898
921
  basePath: string, milestoneId: string, sliceId: string,
899
922
  taskId: string, suffix: string
900
923
  ): string | null {
924
+ const phaseDir = resolveMilestonePath(basePath, milestoneId);
925
+ if (!phaseDir) return null;
926
+
927
+ const legacyBase = legacyMilestonesDir(basePath);
928
+ const isLegacy = phaseDir.startsWith(legacyBase + "/") || phaseDir.startsWith(legacyBase + "\\");
929
+
930
+ if (suffix !== "PLAN" && !isLegacy) {
931
+ const flatPath = join(phaseDir, buildTaskFileName(taskId, suffix));
932
+ return existsSync(flatPath) ? flatPath : null;
933
+ }
934
+
901
935
  const tDir = resolveTasksDir(basePath, milestoneId, sliceId);
902
- if (!tDir) return null;
903
- const file = resolveFile(tDir, taskId, suffix);
904
- return file ? join(tDir, file) : null;
936
+ if (tDir) {
937
+ const file = resolveFile(tDir, taskId, suffix);
938
+ if (file) return join(tDir, file);
939
+ }
940
+
941
+ return null;
905
942
  }
906
943
 
907
944
  // ─── Relative Path Builders (for prompts — .gsd/milestones/...) ────────────
@@ -936,31 +973,41 @@ export function relMilestonePath(basePath: string, milestoneId: string, title?:
936
973
  }
937
974
 
938
975
  /**
939
- * Build relative .gsd/ path to a milestone file.
940
- * Layout-aware: uses resolveMilestoneFile to find NN-SUFFIX.md (flat-phase)
941
- * or M001-SUFFIX.md (legacy). Falls back to a layout-appropriate canonical
942
- * name when the file doesn't exist yet.
976
+ * Build the canonical absolute write target for a milestone file.
977
+ * Existing compatibility filenames are intentionally ignored; readers that need
978
+ * to preserve an existing file should call resolveMilestoneFile first.
943
979
  */
944
- export function relMilestoneFile(
945
- basePath: string, milestoneId: string, suffix: string
980
+ export function targetMilestoneFile(
981
+ basePath: string, milestoneId: string, suffix: string, title?: string
946
982
  ): string {
947
- const mRel = relMilestonePath(basePath, milestoneId);
948
- // resolveMilestoneFile checks NN-SUFFIX.md (flat-phase) and MID-SUFFIX.md (legacy).
949
- const absFile = resolveMilestoneFile(basePath, milestoneId, suffix);
950
- if (absFile) {
951
- const fileName = absFile.split(/[/\\]/).pop()!;
952
- return `${mRel}/${fileName}`;
953
- }
954
- // File doesn't exist yet — use layout-aware fallback filename.
955
983
  const mDir = resolveMilestonePath(basePath, milestoneId);
956
984
  const legacyBase = legacyMilestonesDir(basePath);
957
985
  const isLegacy = mDir
958
986
  ? mDir.startsWith(legacyBase + "/") || mDir.startsWith(legacyBase + "\\")
959
- : false;
987
+ : isLegacyMilestonesLayout(basePath);
988
+ const dir = mDir ?? join(
989
+ isLegacy ? legacyBase : milestonesDir(basePath),
990
+ isLegacy ? milestoneId : canonicalPhaseDirName(milestoneId, title),
991
+ );
960
992
  const fileName = isLegacy
961
993
  ? `${milestoneId}-${suffix}.md`
962
994
  : `${String(milestoneIdToPhaseNum(milestoneId)).padStart(2, "0")}-${suffix}.md`;
963
- return `${mRel}/${fileName}`;
995
+ return join(dir, fileName);
996
+ }
997
+
998
+ /**
999
+ * Build relative .gsd/ path to a milestone file.
1000
+ * Preserves an existing compatibility filename when present; otherwise falls
1001
+ * back to the canonical write target.
1002
+ */
1003
+ export function relMilestoneFile(
1004
+ basePath: string, milestoneId: string, suffix: string, title?: string
1005
+ ): string {
1006
+ const rel = relative(
1007
+ gsdProjectionRoot(basePath),
1008
+ resolveMilestoneFile(basePath, milestoneId, suffix) ?? targetMilestoneFile(basePath, milestoneId, suffix, title),
1009
+ ).replace(/\\/g, "/");
1010
+ return `.gsd/${rel}`;
964
1011
  }
965
1012
 
966
1013
  /**
@@ -992,34 +1039,44 @@ export function relSlicePath(
992
1039
  }
993
1040
 
994
1041
  /**
995
- * Build relative .gsd/ path to a slice file.
996
- * Layout-aware: legacy uses S01-SUFFIX.md; flat-phase uses NN-MM-SUFFIX.md.
997
- *
998
- * @param milestoneTitle - Optional milestone title forwarded to relSlicePath /
999
- * relMilestonePath for title-aware flat-phase fallback dir naming. Only used
1000
- * when the phase directory does not yet exist on disk.
1042
+ * Build the canonical absolute write target for a slice file.
1043
+ * Existing compatibility filenames are intentionally ignored; readers that need
1044
+ * to preserve an existing file should call resolveSliceFile first.
1001
1045
  */
1002
- export function relSliceFile(
1046
+ export function targetSliceFile(
1003
1047
  basePath: string, milestoneId: string, sliceId: string, suffix: string, milestoneTitle?: string
1004
1048
  ): string {
1005
- const sRel = relSlicePath(basePath, milestoneId, sliceId, milestoneTitle);
1006
- const absPath = resolveSliceFile(basePath, milestoneId, sliceId, suffix);
1007
- if (absPath) {
1008
- const fileName = absPath.split(/[/\\]/).pop()!;
1009
- return `${sRel}/${fileName}`;
1010
- }
1011
- // Fallback when file doesn't exist yet — use layout-aware filename.
1012
1049
  const mDir = resolveMilestonePath(basePath, milestoneId);
1013
1050
  const legacyBase = legacyMilestonesDir(basePath);
1014
1051
  const isLegacy = mDir
1015
1052
  ? mDir.startsWith(legacyBase + "/") || mDir.startsWith(legacyBase + "\\")
1016
- : false;
1017
- if (isLegacy) {
1018
- return `${sRel}/${sliceId}-${suffix}.md`;
1019
- }
1020
- const phaseNum = milestoneIdToPhaseNum(milestoneId);
1021
- const planNum = sliceIdToPlanNum(sliceId);
1022
- return `${sRel}/${planFileName(phaseNum, planNum, suffix)}`;
1053
+ : isLegacyMilestonesLayout(basePath);
1054
+ const milestoneDir = mDir
1055
+ ?? dirname(targetMilestoneFile(basePath, milestoneId, "ROADMAP", milestoneTitle));
1056
+ const slicesDir = join(milestoneDir, "slices");
1057
+ const dir = isLegacy
1058
+ ? join(slicesDir, resolveDir(slicesDir, sliceId) ?? sliceId)
1059
+ : milestoneDir;
1060
+ const fileName = isLegacy
1061
+ ? `${sliceId}-${suffix}.md`
1062
+ : planFileName(milestoneIdToPhaseNum(milestoneId), sliceIdToPlanNum(sliceId), suffix);
1063
+ return join(dir, fileName);
1064
+ }
1065
+
1066
+ /**
1067
+ * Build relative .gsd/ path to a slice file.
1068
+ * Preserves an existing compatibility filename when present; otherwise falls
1069
+ * back to the canonical write target.
1070
+ */
1071
+ export function relSliceFile(
1072
+ basePath: string, milestoneId: string, sliceId: string, suffix: string, milestoneTitle?: string
1073
+ ): string {
1074
+ const rel = relative(
1075
+ gsdProjectionRoot(basePath),
1076
+ resolveSliceFile(basePath, milestoneId, sliceId, suffix)
1077
+ ?? targetSliceFile(basePath, milestoneId, sliceId, suffix, milestoneTitle),
1078
+ ).replace(/\\/g, "/");
1079
+ return `.gsd/${rel}`;
1023
1080
  }
1024
1081
 
1025
1082
  /**
@@ -1031,7 +1088,7 @@ export function relSliceFile(
1031
1088
  */
1032
1089
  export function relTaskFile(
1033
1090
  basePath: string, milestoneId: string, sliceId: string,
1034
- taskId: string, suffix: string
1091
+ taskId: string, suffix: string, milestoneTitle?: string
1035
1092
  ): string {
1036
1093
  const sDir = resolveSlicePath(basePath, milestoneId, sliceId);
1037
1094
  const phaseDir = resolveMilestonePath(basePath, milestoneId);
@@ -1044,6 +1101,6 @@ export function relTaskFile(
1044
1101
  if (suffix === "PLAN") {
1045
1102
  return relSliceFile(basePath, milestoneId, sliceId, "PLAN");
1046
1103
  }
1047
- const relS = relSlicePath(basePath, milestoneId, sliceId);
1104
+ const relS = relSlicePath(basePath, milestoneId, sliceId, milestoneTitle);
1048
1105
  return `${relS}/${buildTaskFileName(taskId, suffix)}`;
1049
1106
  }
@@ -11,6 +11,7 @@ import type {
11
11
  HookStatusEntry,
12
12
  PostUnitGateBlock,
13
13
  } from "./types.js";
14
+ import type { SidecarItem } from "./auto/session.js";
14
15
  import { getOrCreateRegistry, resolveHookArtifactPath } from "./rule-registry.js";
15
16
 
16
17
  // Re-export resolveHookArtifactPath so existing importers still work.
@@ -75,6 +76,37 @@ export function restoreHookState(basePath: string): void {
75
76
  getOrCreateRegistry().restoreState(basePath);
76
77
  }
77
78
 
79
+ /**
80
+ * Reconcile a restored `activeHook` against the session's sidecar queue.
81
+ *
82
+ * The registry persists `activeHook` to disk but the pending hook *dispatch*
83
+ * lives only on the non-persisted `s.sidecarQueue`. After a pause/resume or
84
+ * crash-recovery, `restoreHookState` re-hydrates `activeHook` while the dispatch
85
+ * is gone, so the registry believes a hook is in-flight but nothing will ever
86
+ * complete it — and the next unrelated unit's close-out is falsely charged
87
+ * against the stale hook and blocked. Re-enqueue the missing dispatch so the
88
+ * hook actually runs, restoring the invariant that a persisted `activeHook`
89
+ * always has a live dispatch (#1246).
90
+ */
91
+ export function reconcileRestoredHookDispatch(
92
+ basePath: string,
93
+ sidecarQueue: SidecarItem[],
94
+ ): void {
95
+ const dispatch = getOrCreateRegistry().getPendingHookDispatch(basePath);
96
+ if (!dispatch) return;
97
+ const alreadyQueued = sidecarQueue.some(
98
+ item => item.kind === "hook" && item.unitType === dispatch.unitType,
99
+ );
100
+ if (alreadyQueued) return;
101
+ sidecarQueue.push({
102
+ kind: "hook",
103
+ unitType: dispatch.unitType,
104
+ unitId: dispatch.unitId,
105
+ prompt: dispatch.prompt,
106
+ model: dispatch.model,
107
+ });
108
+ }
109
+
78
110
  export function clearPersistedHookState(basePath: string): void {
79
111
  getOrCreateRegistry().clearPersistedState(basePath);
80
112
  }
@@ -23,7 +23,7 @@ import type {
23
23
  GSDModelPhaseKey,
24
24
  GSDThinkingConfig,
25
25
  ResolvedModelConfig,
26
- AutoSupervisorConfig,
26
+ ResolvedAutoSupervisorConfig,
27
27
  } from "./preferences-types.js";
28
28
  import { clearGSDPreferencesCache, loadEffectiveGSDPreferences, getGlobalGSDPreferencesPath } from "./preferences.js";
29
29
  import { getUnitPhaseChain } from "./unit-registry.js";
@@ -86,6 +86,40 @@ function resolveWinningPhase(
86
86
  return undefined;
87
87
  }
88
88
 
89
+ /**
90
+ * Normalize a single model field into a {@link ResolvedModelConfig}.
91
+ *
92
+ * Accepts either the legacy bare-string form or the extended object form
93
+ * (`{ model, provider?, fallbacks? }`). This is the shared normalization used
94
+ * for phase buckets so sibling single-model fields — `post_unit_hooks[].model`,
95
+ * `auto_supervisor.model`, and `reactive_execution.subagent_model` — honor
96
+ * `fallbacks[]` identically rather than accepting a bare string only (#1229).
97
+ *
98
+ * Returns undefined for an unset, blank, or model-less field.
99
+ */
100
+ export function normalizeModelFieldConfig(
101
+ field: string | GSDPhaseModelConfig | undefined,
102
+ ): ResolvedModelConfig | undefined {
103
+ if (!field) return undefined;
104
+ if (typeof field === "string") {
105
+ return field.trim() ? { primary: field, fallbacks: [] } : undefined;
106
+ }
107
+ if (!field.model) return undefined;
108
+ // When provider is explicitly set, prepend it to the model ID so the
109
+ // resolution code in auto.ts can do an explicit provider match.
110
+ const primary = field.provider && !field.model.includes("/")
111
+ ? `${field.provider}/${field.model}`
112
+ : field.model;
113
+ // Carry the per-field `thinking` sub-field through so the dispatch site can
114
+ // prefer it over the session/floor level, mirroring phase-inline thinking
115
+ // (#1269). `undefined` is preserved as absent so behavior is unchanged.
116
+ return {
117
+ primary,
118
+ fallbacks: field.fallbacks ?? [],
119
+ ...(field.thinking ? { thinking: field.thinking } : {}),
120
+ };
121
+ }
122
+
89
123
  /**
90
124
  * Resolve model and fallbacks for a given auto-mode unit type.
91
125
  * Returns the primary model and ordered fallbacks, or undefined if not configured.
@@ -93,6 +127,15 @@ function resolveWinningPhase(
93
127
  * Supports both legacy string format and extended object format:
94
128
  * - Legacy: `planning: claude-opus-4-6`
95
129
  * - Extended: `planning: { model: claude-opus-4-6, fallbacks: [glm-5, minimax-m2.5] }`
130
+ *
131
+ * Hook unit types (`hook/<name>`) resolve against the matching
132
+ * `post_unit_hooks[]` entry's `model` field so hook sessions honor the same
133
+ * `fallbacks[]` chain as phase buckets — the recovery path can then switch to
134
+ * a fallback provider instead of hard-failing (and potentially pausing) the
135
+ * run on a transient provider trip (#1229).
136
+ *
137
+ * The synthetic `supervisor` unit type resolves against `auto_supervisor.model`
138
+ * for supervisor interventions (wrap-up warnings, timeout recovery).
96
139
  */
97
140
  export function resolveModelWithFallbacksForUnit(
98
141
  unitType: string,
@@ -107,27 +150,26 @@ export function resolveModelWithFallbacksForUnit(
107
150
  ...(skipProfileDefaults ? { skipProfileDefaults: true } : {}),
108
151
  };
109
152
  const prefs = loadEffectiveGSDPreferences(basePath, loadOpts);
110
- const chain = phaseChainForUnit(unitType);
111
- if (!chain) return undefined;
112
- const winner = resolveWinningPhase(prefs?.preferences?.models as GSDModelConfigV2 | undefined, chain);
113
- if (!winner) return undefined;
114
- const phaseConfig = winner.config;
115
153
 
116
- // Normalize: string -> { model, fallbacks: [] }
117
- if (typeof phaseConfig === "string") {
118
- return { primary: phaseConfig, fallbacks: [] };
154
+ // Supervisor interventions resolve against `auto_supervisor.model` (#1229).
155
+ if (unitType === "supervisor") {
156
+ return normalizeModelFieldConfig(prefs?.preferences?.auto_supervisor?.model);
119
157
  }
120
158
 
121
- // When provider is explicitly set, prepend it to the model ID so the
122
- // resolution code in auto.ts can do an explicit provider match.
123
- const primary = phaseConfig.provider && !phaseConfig.model.includes("/")
124
- ? `${phaseConfig.provider}/${phaseConfig.model}`
125
- : phaseConfig.model;
159
+ // Hook sessions resolve against their own `post_unit_hooks[].model` field,
160
+ // which now shares the phase-bucket object form (#1229).
161
+ if (unitType.startsWith("hook/")) {
162
+ const hookName = unitType.slice("hook/".length);
163
+ const hooks = prefs?.preferences?.post_unit_hooks;
164
+ const hook = Array.isArray(hooks) ? hooks.find((h) => h.name === hookName) : undefined;
165
+ return normalizeModelFieldConfig(hook?.model);
166
+ }
126
167
 
127
- return {
128
- primary,
129
- fallbacks: phaseConfig.fallbacks ?? [],
130
- };
168
+ const chain = phaseChainForUnit(unitType);
169
+ if (!chain) return undefined;
170
+ const winner = resolveWinningPhase(prefs?.preferences?.models as GSDModelConfigV2 | undefined, chain);
171
+ if (!winner) return undefined;
172
+ return normalizeModelFieldConfig(winner.config);
131
173
  }
132
174
 
133
175
  /**
@@ -157,6 +199,14 @@ export function resolveThinkingLevelForUnit(
157
199
  basePath?: string,
158
200
  availableModelIds?: string[],
159
201
  ): GSDThinkingLevel | undefined {
202
+ // Single-field configs — `auto_supervisor.model` and `post_unit_hooks[].model`
203
+ // — carry their own inline `thinking` on the object model form and have no
204
+ // phase-bucket chain, so the walk below never covers them. Resolve their
205
+ // per-field level directly (#1269).
206
+ if (unitType === "supervisor" || unitType.startsWith("hook/")) {
207
+ return resolveModelWithFallbacksForUnit(unitType, basePath, availableModelIds)?.thinking;
208
+ }
209
+
160
210
  const loadOpts = availableModelIds !== undefined ? { availableModelIds } : undefined;
161
211
  const prefs = loadEffectiveGSDPreferences(basePath, loadOpts)?.preferences;
162
212
  if (!prefs) return undefined;
@@ -425,16 +475,20 @@ export function resolveDynamicRoutingConfig(): DynamicRoutingConfig {
425
475
  };
426
476
  }
427
477
 
428
- export function resolveAutoSupervisorConfig(): AutoSupervisorConfig {
478
+ export function resolveAutoSupervisorConfig(): ResolvedAutoSupervisorConfig {
429
479
  const prefs = loadEffectiveGSDPreferences();
430
480
  const configured = prefs?.preferences.auto_supervisor ?? {};
431
481
 
482
+ const modelConfig = normalizeModelFieldConfig(configured.model);
483
+
432
484
  return {
433
485
  soft_timeout_minutes: configured.soft_timeout_minutes ?? 20,
434
486
  idle_timeout_minutes: configured.idle_timeout_minutes ?? 10,
435
487
  hard_timeout_minutes: configured.hard_timeout_minutes ?? 30,
436
488
  stalled_tool_timeout_minutes: configured.stalled_tool_timeout_minutes ?? 5,
437
- ...(configured.model ? { model: configured.model } : {}),
489
+ ...(modelConfig
490
+ ? { model: modelConfig.primary, modelFallbacks: modelConfig.fallbacks }
491
+ : {}),
438
492
  };
439
493
  }
440
494
 
@@ -5,11 +5,11 @@
5
5
  * to absolute filesystem paths, plus skill discovery and staleness config.
6
6
  */
7
7
 
8
- import { existsSync, readdirSync } from "node:fs";
8
+ import { existsSync, readdirSync, statSync } from "node:fs";
9
9
  import { homedir } from "node:os";
10
10
  import { isAbsolute, join } from "node:path";
11
+ import { getSkillDirectories } from "@gsd/pi-coding-agent";
11
12
  import { gsdHome } from "./gsd-home.js";
12
- import { statSync } from "node:fs";
13
13
 
14
14
  import type {
15
15
  GSDPreferences,
@@ -25,26 +25,20 @@ export type { GSDSkillRule, SkillDiscoveryMode, SkillResolution, SkillResolution
25
25
  /**
26
26
  * Known skill directories for **preference reference resolution** (bare skill names).
27
27
  *
28
- * Search order (first match wins):
29
- * 1. ~/.gsd/agent/skills/ (GSD bundled)
30
- * 2. ~/.agents/skills/ (ecosystem global)
31
- * 3. .agents/skills/ (project ecosystem)
32
- * 4. ~/.claude/skills/ and .claude/skills/ (Claude Code compatibility)
28
+ * Sourced from the shared taxonomy (`@gsd/pi-coding-agent` `getSkillDirectories`)
29
+ * so preference resolution, mid-session discovery, and the PackageManager
30
+ * catalog all agree on which directories exist. The `method` is derived from
31
+ * each entry's scope.
33
32
  *
34
33
  * Note: the ResourceLoader catalog uses PackageManager precedence instead —
35
34
  * project `.gsd/skills` and `.agents/skills` can override bundled GSD skills
36
35
  * on name collision. See docs/dev/what-is-pi/09-the-customization-stack.md.
37
36
  */
38
37
  export function getSkillSearchDirs(cwd: string): Array<{ dir: string; method: SkillResolution["method"] }> {
39
- const dirs: Array<{ dir: string; method: SkillResolution["method"] }> = [
40
- { dir: join(gsdHome(), "agent", "skills"), method: "user-skill" },
41
- { dir: join(homedir(), ".agents", "skills"), method: "user-skill" },
42
- { dir: join(cwd, ".agents", "skills"), method: "project-skill" },
43
- // Claude Code official skill directories
44
- { dir: join(homedir(), ".claude", "skills"), method: "user-skill" },
45
- { dir: join(cwd, ".claude", "skills"), method: "project-skill" },
46
- ];
47
- return dirs;
38
+ return getSkillDirectories({ cwd, gsdHome: gsdHome() }).map((entry) => ({
39
+ dir: entry.path,
40
+ method: entry.scope === "project" ? "project-skill" : "user-skill",
41
+ }));
48
42
  }
49
43
 
50
44
  /**
@@ -312,12 +312,24 @@ export interface GSDModelConfigV2 {
312
312
  export interface ResolvedModelConfig {
313
313
  primary: string;
314
314
  fallbacks: string[];
315
+ /**
316
+ * Per-field reasoning level carried from the object model form's `thinking`
317
+ * sub-field (e.g. `{ model, thinking: high }` on `post_unit_hooks[].model`,
318
+ * `auto_supervisor.model`, or `reactive_execution.subagent_model`). Applied at
319
+ * the dispatch site in preference to the session/floor level (#1269).
320
+ */
321
+ thinking?: GSDThinkingLevel;
315
322
  }
316
323
 
317
324
  export type SkillDiscoveryMode = "auto" | "suggest" | "off";
318
325
 
319
326
  export interface AutoSupervisorConfig {
320
- model?: string;
327
+ /**
328
+ * Model ID for the supervisor process. Accepts a bare model ID or the
329
+ * extended `{ model, provider?, fallbacks? }` object form for parity with
330
+ * phase buckets (#1229). Resolution normalizes it to the primary model ID.
331
+ */
332
+ model?: string | GSDPhaseModelConfig;
321
333
  soft_timeout_minutes?: number;
322
334
  idle_timeout_minutes?: number;
323
335
  hard_timeout_minutes?: number;
@@ -331,6 +343,17 @@ export interface AutoSupervisorConfig {
331
343
  stalled_tool_timeout_minutes?: number;
332
344
  }
333
345
 
346
+ /**
347
+ * The supervisor config after resolution by `resolveAutoSupervisorConfig`.
348
+ * Identical to {@link AutoSupervisorConfig} except `model` is always the
349
+ * normalized primary model ID string and `modelFallbacks` carries the ordered
350
+ * fallback chain from the object form accepted in preferences (#1229).
351
+ */
352
+ export type ResolvedAutoSupervisorConfig = Omit<AutoSupervisorConfig, "model"> & {
353
+ model?: string;
354
+ modelFallbacks?: string[];
355
+ };
356
+
334
357
  export interface RemoteQuestionsConfig {
335
358
  channel: "slack" | "discord" | "telegram";
336
359
  channel_id: string | number;
@@ -22,6 +22,7 @@ import {
22
22
  SKILL_ACTIONS,
23
23
  type WorkflowMode,
24
24
  type GSDPreferences,
25
+ type GSDPhaseModelConfig,
25
26
  type GSDSkillRule,
26
27
  type GSDThinkingLevel,
27
28
  } from "./preferences-types.js";
@@ -41,6 +42,62 @@ const VALID_UOK_TURN_ACTIONS = new Set<"commit" | "snapshot" | "status-only">([
41
42
  "snapshot",
42
43
  "status-only",
43
44
  ]);
45
+ /**
46
+ * Sanitize a single model field that accepts either a bare model ID or the
47
+ * extended `{ model, provider?, fallbacks? }` object form used by phase buckets
48
+ * (#1229). Returns the sanitized value, or undefined when unset/invalid (in
49
+ * which case an error describing `label` is pushed onto `errors`).
50
+ */
51
+ function sanitizeModelField(
52
+ raw: unknown,
53
+ label: string,
54
+ errors: string[],
55
+ warnings?: string[],
56
+ ): string | GSDPhaseModelConfig | undefined {
57
+ if (raw === undefined) return undefined;
58
+ if (typeof raw === "string") {
59
+ const trimmed = raw.trim();
60
+ if (!trimmed) {
61
+ errors.push(`${label} must not be an empty string`);
62
+ return undefined;
63
+ }
64
+ return trimmed;
65
+ }
66
+ if (raw && typeof raw === "object") {
67
+ const obj = raw as Record<string, unknown>;
68
+ const model = typeof obj.model === "string" ? obj.model.trim() : "";
69
+ if (!model) {
70
+ errors.push(`${label} object form requires a non-empty "model"`);
71
+ return undefined;
72
+ }
73
+ const sanitized: GSDPhaseModelConfig = { model };
74
+ if (typeof obj.provider === "string" && obj.provider.trim()) {
75
+ sanitized.provider = obj.provider.trim();
76
+ }
77
+ if (obj.fallbacks !== undefined) {
78
+ const fallbacks = normalizeStringArray(obj.fallbacks);
79
+ if (fallbacks.length > 0) sanitized.fallbacks = fallbacks;
80
+ }
81
+ // Preserve the object form's `thinking` sub-field so the dispatch site can
82
+ // pin the hook/supervisor/subagent reasoning level (#1269). An illegal level
83
+ // is warned-and-stripped, mirroring inline per-phase thinking, so a typo can't
84
+ // reach the resolver and be treated as explicit configuration.
85
+ if (obj.thinking !== undefined) {
86
+ if (VALID_THINKING_LEVELS.has(obj.thinking as GSDThinkingLevel)) {
87
+ sanitized.thinking = obj.thinking as GSDThinkingLevel;
88
+ } else {
89
+ warnings?.push(
90
+ `${label} thinking "${String(obj.thinking)}" is not a valid thinking level ` +
91
+ `(off, minimal, low, medium, high, xhigh) — ignored`,
92
+ );
93
+ }
94
+ }
95
+ return sanitized;
96
+ }
97
+ errors.push(`${label} must be a string or an object with a "model" field`);
98
+ return undefined;
99
+ }
100
+
44
101
  const VALID_POST_UNIT_HOOK_CRITICALITIES = new Set(["advisory", "blocking"]);
45
102
  const VALID_POST_UNIT_HOOK_ON_BLOCK_ACTIONS = new Set([
46
103
  "retry-unit",
@@ -476,7 +533,15 @@ export function validatePreferences(preferences: GSDPreferences): {
476
533
  // ─── Auto Supervisor ────────────────────────────────────────────────
477
534
  if (preferences.auto_supervisor !== undefined) {
478
535
  if (preferences.auto_supervisor && typeof preferences.auto_supervisor === "object") {
479
- validated.auto_supervisor = preferences.auto_supervisor;
536
+ // Validate `model` through the shared sanitizer so the
537
+ // `{ model, provider?, fallbacks? }` object form is rejected on the same
538
+ // terms as its post_unit_hooks / reactive_execution siblings (#1229).
539
+ // Other supervisor fields pass through unchanged.
540
+ const supervisor = { ...preferences.auto_supervisor };
541
+ if (supervisor.model !== undefined) {
542
+ supervisor.model = sanitizeModelField(supervisor.model, "auto_supervisor.model", errors, warnings);
543
+ }
544
+ validated.auto_supervisor = supervisor;
480
545
  } else {
481
546
  errors.push("auto_supervisor must be an object");
482
547
  }
@@ -568,8 +633,9 @@ export function validatePreferences(preferences: GSDPreferences): {
568
633
  const mc = typeof hook.max_cycles === "number" ? hook.max_cycles : Number(hook.max_cycles);
569
634
  validHook.max_cycles = Number.isFinite(mc) ? Math.max(1, Math.min(10, Math.round(mc))) : 1;
570
635
  }
571
- if (typeof hook.model === "string" && hook.model.trim()) {
572
- validHook.model = hook.model.trim();
636
+ const hookModel = sanitizeModelField(hook.model, `post_unit_hooks "${name}" model`, errors, warnings);
637
+ if (hookModel !== undefined) {
638
+ validHook.model = hookModel;
573
639
  }
574
640
  if (typeof hook.artifact === "string" && hook.artifact.trim()) {
575
641
  validHook.artifact = hook.artifact.trim();
@@ -1021,12 +1087,14 @@ export function validatePreferences(preferences: GSDPreferences): {
1021
1087
  }
1022
1088
  }
1023
1089
 
1024
- if (re.subagent_model !== undefined) {
1025
- if (typeof re.subagent_model === "string" && re.subagent_model.length > 0) {
1026
- validRe.subagent_model = re.subagent_model;
1027
- } else {
1028
- errors.push("reactive_execution.subagent_model must be a non-empty string");
1029
- }
1090
+ const subagentModel = sanitizeModelField(
1091
+ re.subagent_model,
1092
+ "reactive_execution.subagent_model",
1093
+ errors,
1094
+ warnings,
1095
+ );
1096
+ if (subagentModel !== undefined) {
1097
+ validRe.subagent_model = subagentModel;
1030
1098
  }
1031
1099
 
1032
1100
  const knownReKeys = new Set(["enabled", "max_parallel", "isolation_mode", "subagent_model"]);