@opengsd/gsd-pi 1.5.0 → 1.6.0

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 (499) hide show
  1. package/README.md +10 -10
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/ask-user-questions.js +1 -1
  4. package/dist/resources/extensions/browser-tools/engine/managed-gsd-browser.js +114 -36
  5. package/dist/resources/extensions/gsd/activity-log.js +1 -1
  6. package/dist/resources/extensions/gsd/auto/closeout.js +97 -5
  7. package/dist/resources/extensions/gsd/auto/orchestrator.js +20 -13
  8. package/dist/resources/extensions/gsd/auto/pre-dispatch.js +6 -3
  9. package/dist/resources/extensions/gsd/auto/unit-phase.js +5 -9
  10. package/dist/resources/extensions/gsd/auto-artifact-paths.js +14 -5
  11. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -7
  12. package/dist/resources/extensions/gsd/auto-dispatch.js +69 -17
  13. package/dist/resources/extensions/gsd/auto-model-selection.js +28 -1
  14. package/dist/resources/extensions/gsd/auto-post-unit.js +4 -3
  15. package/dist/resources/extensions/gsd/auto-prompts.js +29 -117
  16. package/dist/resources/extensions/gsd/auto-recovery.js +50 -6
  17. package/dist/resources/extensions/gsd/auto-start.js +57 -33
  18. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +12 -2
  19. package/dist/resources/extensions/gsd/auto-timers.js +9 -2
  20. package/dist/resources/extensions/gsd/auto-worktree-branch-lifecycle.js +189 -0
  21. package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +133 -0
  22. package/dist/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.js +35 -0
  23. package/dist/resources/extensions/gsd/auto-worktree-creation.js +130 -0
  24. package/dist/resources/extensions/gsd/auto-worktree-entry.js +118 -0
  25. package/dist/resources/extensions/gsd/auto-worktree-merge-already-merged.js +98 -0
  26. package/dist/resources/extensions/gsd/auto-worktree-merge-branch-head.js +52 -0
  27. package/dist/resources/extensions/gsd/auto-worktree-merge-cleanup.js +87 -0
  28. package/dist/resources/extensions/gsd/auto-worktree-merge-code-changes.js +46 -0
  29. package/dist/resources/extensions/gsd/auto-worktree-merge-db-ready.js +68 -0
  30. package/dist/resources/extensions/gsd/auto-worktree-merge-integration-branch.js +51 -0
  31. package/dist/resources/extensions/gsd/auto-worktree-merge-message.js +106 -0
  32. package/dist/resources/extensions/gsd/auto-worktree-merge-pre-teardown.js +64 -0
  33. package/dist/resources/extensions/gsd/auto-worktree-merge-stash.js +168 -0
  34. package/dist/resources/extensions/gsd/auto-worktree-merge.js +302 -0
  35. package/dist/resources/extensions/gsd/auto-worktree-milestone-shelter.js +119 -0
  36. package/dist/resources/extensions/gsd/auto-worktree-path-resolution.js +5 -0
  37. package/dist/resources/extensions/gsd/auto-worktree-path-utils.js +5 -0
  38. package/dist/resources/extensions/gsd/auto-worktree-resource-version.js +41 -0
  39. package/dist/resources/extensions/gsd/auto-worktree-runtime-cleanup.js +99 -0
  40. package/dist/resources/extensions/gsd/auto-worktree-session-registry.js +57 -0
  41. package/dist/resources/extensions/gsd/auto-worktree-sync.js +249 -0
  42. package/dist/resources/extensions/gsd/auto-worktree-teardown.js +120 -0
  43. package/dist/resources/extensions/gsd/auto-worktree.js +20 -2073
  44. package/dist/resources/extensions/gsd/auto.js +106 -34
  45. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -1
  46. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +20 -2
  47. package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
  48. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +185 -27
  49. package/dist/resources/extensions/gsd/codebase-generator.js +7 -1
  50. package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
  51. package/dist/resources/extensions/gsd/commands-workflow-templates.js +3 -3
  52. package/dist/resources/extensions/gsd/compat/compat-marker.js +44 -0
  53. package/dist/resources/extensions/gsd/config-overlay.js +7 -2
  54. package/dist/resources/extensions/gsd/db/engine.js +33 -0
  55. package/dist/resources/extensions/gsd/db/queries.js +9 -9
  56. package/dist/resources/extensions/gsd/db/writers/cascades.js +20 -2
  57. package/dist/resources/extensions/gsd/db-adapter.js +30 -3
  58. package/dist/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  59. package/dist/resources/extensions/gsd/doctor-engine-checks.js +93 -11
  60. package/dist/resources/extensions/gsd/flat-phase-migration.js +32 -3
  61. package/dist/resources/extensions/gsd/forensics.js +1 -1
  62. package/dist/resources/extensions/gsd/git-conflict-resolve.js +2 -2
  63. package/dist/resources/extensions/gsd/git-service.js +45 -0
  64. package/dist/resources/extensions/gsd/gsd-db.js +23 -11
  65. package/dist/resources/extensions/gsd/guided-flow.js +22 -17
  66. package/dist/resources/extensions/gsd/journal.js +1 -1
  67. package/dist/resources/extensions/gsd/markdown-renderer.js +34 -79
  68. package/dist/resources/extensions/gsd/md-importer.js +89 -23
  69. package/dist/resources/extensions/gsd/metrics.js +2 -2
  70. package/dist/resources/extensions/gsd/migration-auto-check.js +30 -3
  71. package/dist/resources/extensions/gsd/milestone-merge-transaction.js +11 -0
  72. package/dist/resources/extensions/gsd/milestone-settlement.js +1 -1
  73. package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
  74. package/dist/resources/extensions/gsd/parallel-orchestrator.js +3 -1
  75. package/dist/resources/extensions/gsd/paths.js +87 -52
  76. package/dist/resources/extensions/gsd/post-unit-hooks.js +27 -0
  77. package/dist/resources/extensions/gsd/preferences-models.js +66 -15
  78. package/dist/resources/extensions/gsd/preferences-skills.js +10 -16
  79. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  80. package/dist/resources/extensions/gsd/preferences-validation.js +148 -10
  81. package/dist/resources/extensions/gsd/preferences.js +20 -0
  82. package/dist/resources/extensions/gsd/prompts/system.md +6 -6
  83. package/dist/resources/extensions/gsd/quick.js +12 -3
  84. package/dist/resources/extensions/gsd/rule-registry.js +106 -10
  85. package/dist/resources/extensions/gsd/skill-discovery.js +36 -23
  86. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +2 -1
  87. package/dist/resources/extensions/gsd/state/derive/from-db.js +88 -153
  88. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +1 -10
  89. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +3 -24
  90. package/dist/resources/extensions/gsd/state-reconciliation/index.js +10 -0
  91. package/dist/resources/extensions/gsd/tool-contract.js +14 -0
  92. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -5
  93. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +16 -10
  94. package/dist/resources/extensions/gsd/tools/reopen-slice.js +5 -0
  95. package/dist/resources/extensions/gsd/tools/reopen-task.js +13 -5
  96. package/dist/resources/extensions/gsd/tools/validate-milestone.js +3 -8
  97. package/dist/resources/extensions/gsd/uat-dispatch.js +94 -0
  98. package/dist/resources/extensions/gsd/unit-registry.js +12 -2
  99. package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +1 -1
  100. package/dist/resources/extensions/gsd/uok/audit-toggle.js +31 -2
  101. package/dist/resources/extensions/gsd/uok/audit.js +3 -0
  102. package/dist/resources/extensions/gsd/uok/gate-runner.js +22 -30
  103. package/dist/resources/extensions/gsd/uok/gitops.js +18 -15
  104. package/dist/resources/extensions/gsd/uok/kernel.js +120 -46
  105. package/dist/resources/extensions/gsd/uok/model-policy.js +17 -14
  106. package/dist/resources/extensions/gsd/validation-block-guard.js +2 -1
  107. package/dist/resources/extensions/gsd/workflow-event-ledger.js +1 -1
  108. package/dist/resources/extensions/gsd/workflow-logger.js +1 -1
  109. package/dist/resources/extensions/gsd/workflow-projections.js +9 -26
  110. package/dist/resources/extensions/gsd/workspace-git-preflight.js +2 -2
  111. package/dist/resources/extensions/gsd/workspace.js +5 -14
  112. package/dist/resources/extensions/gsd/worktree-command.js +1 -1
  113. package/dist/resources/extensions/gsd/worktree-lifecycle.js +14 -9
  114. package/dist/resources/extensions/gsd/worktree-reentry.js +2 -1
  115. package/dist/resources/extensions/shared/gsd-browser-cli.js +29 -0
  116. package/dist/resources/extensions/shared/interview-ui.js +62 -9
  117. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  118. package/dist/web/standalone/.next/BUILD_ID +1 -1
  119. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  120. package/dist/web/standalone/.next/build-manifest.json +2 -2
  121. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  122. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  123. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  124. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  125. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  126. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  127. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  128. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  129. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  130. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  131. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  132. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  133. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  134. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  135. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  136. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  137. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  138. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  140. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  142. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  144. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  146. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  148. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  150. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  152. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  154. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  156. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  157. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  158. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  159. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  160. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  161. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  162. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  163. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  164. package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  165. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  166. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  167. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  168. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  169. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  170. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  171. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  172. package/dist/web/standalone/.next/server/app/index.html +1 -1
  173. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  174. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  175. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  176. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  177. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  178. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  179. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  180. package/dist/web/standalone/.next/server/chunks/{1128.js → 1991.js} +1 -1
  181. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  182. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  183. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  184. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  185. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  186. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  187. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  188. package/dist/worktree-cli-create.d.ts +10 -0
  189. package/dist/worktree-cli-create.js +11 -0
  190. package/dist/worktree-cli-format.d.ts +12 -0
  191. package/dist/worktree-cli-format.js +38 -0
  192. package/dist/worktree-cli-plan.d.ts +16 -0
  193. package/dist/worktree-cli-plan.js +19 -0
  194. package/dist/worktree-cli-session.d.ts +13 -0
  195. package/dist/worktree-cli-session.js +18 -0
  196. package/dist/worktree-cli-status.d.ts +35 -0
  197. package/dist/worktree-cli-status.js +54 -0
  198. package/dist/worktree-cli.d.ts +1 -16
  199. package/dist/worktree-cli.js +28 -122
  200. package/integrations/hermes/open_gsd_hermes/gsd_client.py +25 -36
  201. package/integrations/hermes/pyproject.toml +1 -1
  202. package/integrations/hermes/tests/test_gsd_client_timeout.py +4 -4
  203. package/package.json +6 -6
  204. package/packages/cloud-mcp-gateway/package.json +2 -2
  205. package/packages/contracts/package.json +1 -1
  206. package/packages/daemon/dist/orchestrator-agent.d.ts +21 -0
  207. package/packages/daemon/dist/orchestrator-agent.d.ts.map +1 -0
  208. package/packages/daemon/dist/orchestrator-agent.js +113 -0
  209. package/packages/daemon/dist/orchestrator-agent.js.map +1 -0
  210. package/packages/daemon/dist/orchestrator-tools.d.ts +12 -0
  211. package/packages/daemon/dist/orchestrator-tools.d.ts.map +1 -0
  212. package/packages/daemon/dist/orchestrator-tools.js +137 -0
  213. package/packages/daemon/dist/orchestrator-tools.js.map +1 -0
  214. package/packages/daemon/dist/orchestrator.d.ts +4 -56
  215. package/packages/daemon/dist/orchestrator.d.ts.map +1 -1
  216. package/packages/daemon/dist/orchestrator.js +13 -303
  217. package/packages/daemon/dist/orchestrator.js.map +1 -1
  218. package/packages/daemon/dist/orchestrator.test.js +11 -1
  219. package/packages/daemon/dist/orchestrator.test.js.map +1 -1
  220. package/packages/daemon/package.json +4 -4
  221. package/packages/gsd-agent-core/dist/export-html/template.js +53 -4
  222. package/packages/gsd-agent-core/package.json +5 -5
  223. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  224. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +3 -2
  225. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  226. package/packages/gsd-agent-modes/package.json +7 -7
  227. package/packages/mcp-server/package.json +4 -4
  228. package/packages/native/package.json +1 -1
  229. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  230. package/packages/pi-agent-core/dist/agent-loop.js +14 -9
  231. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  232. package/packages/pi-agent-core/package.json +1 -1
  233. package/packages/pi-ai/dist/index.d.ts +1 -0
  234. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  235. package/packages/pi-ai/dist/index.js +1 -0
  236. package/packages/pi-ai/dist/index.js.map +1 -1
  237. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  238. package/packages/pi-ai/dist/providers/transform-messages.js +7 -3
  239. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  240. package/packages/pi-ai/dist/tool-result-content.d.ts +3 -0
  241. package/packages/pi-ai/dist/tool-result-content.d.ts.map +1 -0
  242. package/packages/pi-ai/dist/tool-result-content.js +62 -0
  243. package/packages/pi-ai/dist/tool-result-content.js.map +1 -0
  244. package/packages/pi-ai/dist/types.d.ts +8 -20
  245. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  246. package/packages/pi-ai/dist/types.js.map +1 -1
  247. package/packages/pi-ai/package.json +1 -1
  248. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +9 -0
  249. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  250. package/packages/pi-coding-agent/dist/core/package-manager.js +74 -53
  251. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  252. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +6 -0
  253. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  254. package/packages/pi-coding-agent/dist/core/resource-loader.js +30 -2
  255. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  256. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts +66 -0
  257. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts.map +1 -0
  258. package/packages/pi-coding-agent/dist/core/skill-directories.js +138 -0
  259. package/packages/pi-coding-agent/dist/core/skill-directories.js.map +1 -0
  260. package/packages/pi-coding-agent/dist/core/skills.d.ts +4 -1
  261. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  262. package/packages/pi-coding-agent/dist/core/skills.js +4 -1
  263. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  264. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  265. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  266. package/packages/pi-coding-agent/dist/index.js +1 -0
  267. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  268. package/packages/pi-coding-agent/package.json +7 -7
  269. package/packages/pi-tui/package.json +2 -2
  270. package/packages/rpc-client/package.json +2 -2
  271. package/pkg/dist/core/export-html/template.js +53 -4
  272. package/pkg/package.json +1 -1
  273. package/src/resources/extensions/ask-user-questions.ts +1 -1
  274. package/src/resources/extensions/browser-tools/engine/managed-gsd-browser.ts +146 -33
  275. package/src/resources/extensions/browser-tools/tests/gsd-browser-launch-config.test.mjs +25 -0
  276. package/src/resources/extensions/browser-tools/tests/managed-gsd-browser-tools.test.mjs +93 -0
  277. package/src/resources/extensions/gsd/activity-log.ts +1 -1
  278. package/src/resources/extensions/gsd/auto/closeout.ts +114 -7
  279. package/src/resources/extensions/gsd/auto/orchestrator.ts +21 -14
  280. package/src/resources/extensions/gsd/auto/pre-dispatch.ts +6 -3
  281. package/src/resources/extensions/gsd/auto/unit-phase.ts +5 -9
  282. package/src/resources/extensions/gsd/auto-artifact-paths.ts +14 -4
  283. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +13 -7
  284. package/src/resources/extensions/gsd/auto-dispatch.ts +77 -16
  285. package/src/resources/extensions/gsd/auto-model-selection.ts +33 -1
  286. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -2
  287. package/src/resources/extensions/gsd/auto-prompts.ts +53 -113
  288. package/src/resources/extensions/gsd/auto-recovery.ts +50 -6
  289. package/src/resources/extensions/gsd/auto-start.ts +62 -31
  290. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -2
  291. package/src/resources/extensions/gsd/auto-timers.ts +9 -2
  292. package/src/resources/extensions/gsd/auto-worktree-branch-lifecycle.ts +237 -0
  293. package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +166 -0
  294. package/src/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.ts +37 -0
  295. package/src/resources/extensions/gsd/auto-worktree-creation.ts +171 -0
  296. package/src/resources/extensions/gsd/auto-worktree-entry.ts +154 -0
  297. package/src/resources/extensions/gsd/auto-worktree-merge-already-merged.ts +161 -0
  298. package/src/resources/extensions/gsd/auto-worktree-merge-branch-head.ts +83 -0
  299. package/src/resources/extensions/gsd/auto-worktree-merge-cleanup.ts +132 -0
  300. package/src/resources/extensions/gsd/auto-worktree-merge-code-changes.ts +71 -0
  301. package/src/resources/extensions/gsd/auto-worktree-merge-db-ready.ts +112 -0
  302. package/src/resources/extensions/gsd/auto-worktree-merge-integration-branch.ts +99 -0
  303. package/src/resources/extensions/gsd/auto-worktree-merge-message.ts +166 -0
  304. package/src/resources/extensions/gsd/auto-worktree-merge-pre-teardown.ts +93 -0
  305. package/src/resources/extensions/gsd/auto-worktree-merge-stash.ts +213 -0
  306. package/src/resources/extensions/gsd/auto-worktree-merge.ts +391 -0
  307. package/src/resources/extensions/gsd/auto-worktree-milestone-shelter.ts +159 -0
  308. package/src/resources/extensions/gsd/auto-worktree-path-resolution.ts +6 -0
  309. package/src/resources/extensions/gsd/auto-worktree-path-utils.ts +10 -0
  310. package/src/resources/extensions/gsd/auto-worktree-resource-version.ts +44 -0
  311. package/src/resources/extensions/gsd/auto-worktree-runtime-cleanup.ts +119 -0
  312. package/src/resources/extensions/gsd/auto-worktree-session-registry.ts +71 -0
  313. package/src/resources/extensions/gsd/auto-worktree-sync.ts +370 -0
  314. package/src/resources/extensions/gsd/auto-worktree-teardown.ts +161 -0
  315. package/src/resources/extensions/gsd/auto-worktree.ts +69 -2429
  316. package/src/resources/extensions/gsd/auto.ts +114 -47
  317. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -1
  318. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +20 -2
  319. package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
  320. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +218 -27
  321. package/src/resources/extensions/gsd/codebase-generator.ts +7 -1
  322. package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
  323. package/src/resources/extensions/gsd/commands-workflow-templates.ts +3 -3
  324. package/src/resources/extensions/gsd/compat/compat-marker.ts +47 -0
  325. package/src/resources/extensions/gsd/config-overlay.ts +7 -1
  326. package/src/resources/extensions/gsd/db/engine.ts +36 -0
  327. package/src/resources/extensions/gsd/db/queries.ts +9 -9
  328. package/src/resources/extensions/gsd/db/writers/cascades.ts +23 -1
  329. package/src/resources/extensions/gsd/db-adapter.ts +29 -3
  330. package/src/resources/extensions/gsd/docs/preferences-reference.md +3 -3
  331. package/src/resources/extensions/gsd/doctor-engine-checks.ts +109 -10
  332. package/src/resources/extensions/gsd/flat-phase-migration.ts +34 -3
  333. package/src/resources/extensions/gsd/forensics.ts +1 -1
  334. package/src/resources/extensions/gsd/git-conflict-resolve.ts +2 -2
  335. package/src/resources/extensions/gsd/git-service.ts +53 -0
  336. package/src/resources/extensions/gsd/gsd-db.ts +33 -11
  337. package/src/resources/extensions/gsd/guided-flow.ts +26 -18
  338. package/src/resources/extensions/gsd/journal.ts +1 -1
  339. package/src/resources/extensions/gsd/markdown-renderer.ts +52 -91
  340. package/src/resources/extensions/gsd/md-importer.ts +93 -22
  341. package/src/resources/extensions/gsd/metrics.ts +2 -2
  342. package/src/resources/extensions/gsd/migration-auto-check.ts +32 -3
  343. package/src/resources/extensions/gsd/milestone-merge-transaction.ts +15 -0
  344. package/src/resources/extensions/gsd/milestone-settlement.ts +1 -1
  345. package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
  346. package/src/resources/extensions/gsd/parallel-orchestrator.ts +3 -1
  347. package/src/resources/extensions/gsd/paths.ts +108 -51
  348. package/src/resources/extensions/gsd/post-unit-hooks.ts +32 -0
  349. package/src/resources/extensions/gsd/preferences-models.ts +74 -20
  350. package/src/resources/extensions/gsd/preferences-skills.ts +10 -16
  351. package/src/resources/extensions/gsd/preferences-types.ts +54 -1
  352. package/src/resources/extensions/gsd/preferences-validation.ts +146 -9
  353. package/src/resources/extensions/gsd/preferences.ts +20 -0
  354. package/src/resources/extensions/gsd/prompts/system.md +6 -6
  355. package/src/resources/extensions/gsd/quick.ts +11 -3
  356. package/src/resources/extensions/gsd/rule-registry.ts +111 -9
  357. package/src/resources/extensions/gsd/skill-discovery.ts +47 -23
  358. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +3 -1
  359. package/src/resources/extensions/gsd/state/derive/from-db.ts +201 -157
  360. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +1 -9
  361. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +2 -22
  362. package/src/resources/extensions/gsd/state-reconciliation/index.ts +14 -0
  363. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +169 -1
  364. package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +2 -1
  365. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +41 -2
  366. package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +48 -1
  367. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +57 -0
  368. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +1 -1
  369. package/src/resources/extensions/gsd/tests/auto-start-project-milestone-reconcile.test.ts +29 -1
  370. package/src/resources/extensions/gsd/tests/auto-timeout-recovery-supervisor-inflight-guard.test.ts +159 -0
  371. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +2 -1
  372. package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +1 -1
  373. package/src/resources/extensions/gsd/tests/auto-worktree-merge-already-merged.test.ts +177 -0
  374. package/src/resources/extensions/gsd/tests/auto-worktree-merge-branch-head.test.ts +118 -0
  375. package/src/resources/extensions/gsd/tests/auto-worktree-merge-cleanup.test.ts +115 -0
  376. package/src/resources/extensions/gsd/tests/auto-worktree-merge-code-changes.test.ts +76 -0
  377. package/src/resources/extensions/gsd/tests/auto-worktree-merge-db-ready.test.ts +140 -0
  378. package/src/resources/extensions/gsd/tests/auto-worktree-merge-integration-branch.test.ts +123 -0
  379. package/src/resources/extensions/gsd/tests/auto-worktree-merge-message.test.ts +114 -0
  380. package/src/resources/extensions/gsd/tests/auto-worktree-merge-pre-teardown.test.ts +84 -0
  381. package/src/resources/extensions/gsd/tests/auto-worktree-merge-stash.test.ts +70 -0
  382. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-shelter.test.ts +52 -0
  383. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +6 -6
  384. package/src/resources/extensions/gsd/tests/auto-worktree-resource-version.test.ts +59 -0
  385. package/src/resources/extensions/gsd/tests/auto-worktree-untracked-content.test.ts +3 -1
  386. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
  387. package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +3 -1
  388. package/src/resources/extensions/gsd/tests/compat-marker.test.ts +85 -0
  389. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +45 -0
  390. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +1 -1
  391. package/src/resources/extensions/gsd/tests/db-adapter.test.ts +39 -0
  392. package/src/resources/extensions/gsd/tests/db-writable-probe.test.ts +66 -0
  393. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -0
  394. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +174 -0
  395. package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +1 -1
  396. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +71 -1
  397. package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +46 -1
  398. package/src/resources/extensions/gsd/tests/flat-phase-validation-path.test.ts +72 -32
  399. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +40 -0
  400. package/src/resources/extensions/gsd/tests/import-completion-lost-update.test.ts +196 -0
  401. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +4 -6
  402. package/src/resources/extensions/gsd/tests/integration/auto-stash-merge.test.ts +2 -1
  403. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +3 -3
  404. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +7 -8
  405. package/src/resources/extensions/gsd/tests/integration/feature-branch-lifecycle-integration.test.ts +2 -5
  406. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +76 -0
  407. package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +1 -1
  408. package/src/resources/extensions/gsd/tests/integration/merge-preserve-worktree-on-preteardown-error.test.ts +1 -1
  409. package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +2 -1
  410. package/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts +5 -7
  411. package/src/resources/extensions/gsd/tests/integration/paths.test.ts +88 -1
  412. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +2 -4
  413. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -15
  414. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +1 -1
  415. package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +1 -1
  416. package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +1 -1
  417. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +91 -0
  418. package/src/resources/extensions/gsd/tests/milestone-closeout.test.ts +2 -1
  419. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +439 -0
  420. package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +24 -0
  421. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +1 -1
  422. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -3
  423. package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +1 -1
  424. package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +0 -1
  425. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +269 -0
  426. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +8 -1
  427. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +1 -4
  428. package/src/resources/extensions/gsd/tests/preferences.test.ts +19 -0
  429. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +5 -0
  430. package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +54 -0
  431. package/src/resources/extensions/gsd/tests/reassess-handler.test.ts +49 -1
  432. package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +52 -0
  433. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +95 -1
  434. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +20 -0
  435. package/src/resources/extensions/gsd/tests/sibling-model-fallbacks.test.ts +189 -0
  436. package/src/resources/extensions/gsd/tests/single-field-model-thinking.test.ts +156 -0
  437. package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +46 -0
  438. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +6 -1
  439. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -5
  440. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +32 -0
  441. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +2 -1
  442. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +2 -1
  443. package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +1 -1
  444. package/src/resources/extensions/gsd/tests/supervisor-model-swap-inflight-guard.test.ts +181 -0
  445. package/src/resources/extensions/gsd/tests/supervisor-model-swap-thinking-clamp.test.ts +122 -0
  446. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +1 -1
  447. package/src/resources/extensions/gsd/tests/system-flat-phase-layout.test.ts +33 -0
  448. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +4 -4
  449. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +2 -1
  450. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +4 -4
  451. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +196 -0
  452. package/src/resources/extensions/gsd/tests/uat-dispatch.test.ts +211 -0
  453. package/src/resources/extensions/gsd/tests/uat-stuck-loop-orphaned-worktree.test.ts +1 -1
  454. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +26 -1
  455. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +39 -0
  456. package/src/resources/extensions/gsd/tests/uok-kernel-path.test.ts +226 -2
  457. package/src/resources/extensions/gsd/tests/uok-model-policy.test.ts +6 -0
  458. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +213 -2
  459. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -1
  460. package/src/resources/extensions/gsd/tests/worktree-db-respawn-truncation.test.ts +1 -1
  461. package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +1 -3
  462. package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +1 -1
  463. package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +1 -1
  464. package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +1 -1
  465. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +1 -1
  466. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +2 -1
  467. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +5 -2
  468. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +1 -1
  469. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +1 -1
  470. package/src/resources/extensions/gsd/tool-contract.ts +29 -0
  471. package/src/resources/extensions/gsd/tools/complete-milestone.ts +9 -5
  472. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +19 -8
  473. package/src/resources/extensions/gsd/tools/reopen-slice.ts +5 -0
  474. package/src/resources/extensions/gsd/tools/reopen-task.ts +13 -5
  475. package/src/resources/extensions/gsd/tools/validate-milestone.ts +13 -9
  476. package/src/resources/extensions/gsd/types.ts +23 -4
  477. package/src/resources/extensions/gsd/uat-dispatch.ts +176 -0
  478. package/src/resources/extensions/gsd/unit-registry.ts +16 -2
  479. package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +1 -1
  480. package/src/resources/extensions/gsd/uok/audit-toggle.ts +32 -2
  481. package/src/resources/extensions/gsd/uok/audit.ts +2 -0
  482. package/src/resources/extensions/gsd/uok/gate-runner.ts +26 -36
  483. package/src/resources/extensions/gsd/uok/gitops.ts +21 -18
  484. package/src/resources/extensions/gsd/uok/kernel.ts +169 -48
  485. package/src/resources/extensions/gsd/uok/model-policy.ts +20 -17
  486. package/src/resources/extensions/gsd/validation-block-guard.ts +2 -1
  487. package/src/resources/extensions/gsd/workflow-event-ledger.ts +1 -1
  488. package/src/resources/extensions/gsd/workflow-logger.ts +1 -1
  489. package/src/resources/extensions/gsd/workflow-projections.ts +9 -24
  490. package/src/resources/extensions/gsd/workspace-git-preflight.ts +2 -2
  491. package/src/resources/extensions/gsd/workspace.ts +5 -21
  492. package/src/resources/extensions/gsd/worktree-command.ts +1 -1
  493. package/src/resources/extensions/gsd/worktree-lifecycle.ts +19 -15
  494. package/src/resources/extensions/gsd/worktree-reentry.ts +2 -1
  495. package/src/resources/extensions/shared/gsd-browser-cli.ts +29 -0
  496. package/src/resources/extensions/shared/interview-ui.ts +58 -11
  497. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +32 -0
  498. /package/dist/web/standalone/.next/static/{bCg3aYrFN1DKZr2dqgE7L → 6xYMOzxVhsQgryneJ5KRc}/_buildManifest.js +0 -0
  499. /package/dist/web/standalone/.next/static/{bCg3aYrFN1DKZr2dqgE7L → 6xYMOzxVhsQgryneJ5KRc}/_ssgManifest.js +0 -0
@@ -8,7 +8,7 @@ import { existsSync, readdirSync } from "node:fs";
8
8
  import { spawnSync } from "node:child_process";
9
9
  import { resolveGsdPathContract } from "./paths.js";
10
10
  import { worktreePathFor, worktreesDirs } from "./worktree-placement.js";
11
- import { getAutoWorktreePath } from "./auto-worktree.js";
11
+ import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
12
12
  import { buildWorktreeLifecycleDeps } from "./auto.js";
13
13
  import { mergeMilestoneStandalone, } from "./worktree-lifecycle.js";
14
14
  import { MergeConflictError } from "./git-service.js";
@@ -12,7 +12,9 @@ import { join, dirname } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { gsdRoot } from "./paths.js";
14
14
  import { createWorktree, worktreePath } from "./worktree-manager.js";
15
- import { autoWorktreeBranch, fastForwardReusedMilestoneBranchIfSafe, runWorktreePostCreateHook, syncGsdStateToWorktreeByScope } from "./auto-worktree.js";
15
+ import { autoWorktreeBranch, fastForwardReusedMilestoneBranchIfSafe } from "./auto-worktree-branch-lifecycle.js";
16
+ import { syncGsdStateToWorktreeByScope } from "./auto-worktree-sync.js";
17
+ import { runWorktreePostCreateHook } from "./worktree-post-create-hook.js";
16
18
  import { nativeBranchExists } from "./native-git-bridge.js";
17
19
  import { readIntegrationBranch } from "./git-service.js";
18
20
  import { createWorkspace, scopeMilestone } from "./workspace.js";
@@ -10,7 +10,7 @@
10
10
  * via prefix matching, so existing projects work without migration.
11
11
  */
12
12
  import { readdirSync, existsSync, realpathSync, statSync, Dirent } from "node:fs";
13
- import { join, dirname, normalize, resolve } from "node:path";
13
+ import { join, dirname, normalize, relative, resolve } from "node:path";
14
14
  import { homedir } from "node:os";
15
15
  import { spawnSync } from "node:child_process";
16
16
  import { nativeScanGsdTree } from "./native-parser-bridge.js";
@@ -331,21 +331,26 @@ export function resolveGsdPathContract(workRoot, originalProjectRoot) {
331
331
  const resolvedWorkRoot = resolve(workRoot || process.cwd());
332
332
  const isWorktree = isGsdWorktreePath(resolvedWorkRoot);
333
333
  if (isWorktree && !originalProjectRoot?.trim()) {
334
- const projectGsd = resolveExternalStateProjectGsdFromWorktreePath(resolvedWorkRoot);
335
- if (projectGsd) {
334
+ const rawProjectGsd = resolveExternalStateProjectGsdFromWorktreePath(resolvedWorkRoot);
335
+ if (rawProjectGsd) {
336
+ // Canonicalize the `.gsd` root so the DB path matches what every other
337
+ // accessor (gsdRoot/gsdProjectionRoot) returns — otherwise an unresolved
338
+ // symlink (e.g. `/mnt/c/.../.gsd` → native ext4 on WSL) selects the wrong
339
+ // journal mode and yields a fragile, move-prone handle. See issue #1239.
340
+ const projectGsd = normalizeRealPath(rawProjectGsd);
336
341
  return {
337
342
  projectRoot: dirname(dirname(projectGsd)),
338
343
  workRoot: resolvedWorkRoot,
339
344
  projectGsd,
340
- worktreeGsd: join(resolvedWorkRoot, ".gsd"),
345
+ worktreeGsd: normalizeRealPath(join(resolvedWorkRoot, ".gsd")),
341
346
  projectDb: join(projectGsd, "gsd.db"),
342
347
  isWorktree,
343
348
  };
344
349
  }
345
350
  }
346
351
  const projectRoot = resolve(resolveWorktreeProjectRoot(resolvedWorkRoot, originalProjectRoot));
347
- const projectGsd = join(projectRoot, ".gsd");
348
- const worktreeGsd = isWorktree ? join(resolvedWorkRoot, ".gsd") : null;
352
+ const projectGsd = normalizeRealPath(join(projectRoot, ".gsd"));
353
+ const worktreeGsd = isWorktree ? normalizeRealPath(join(resolvedWorkRoot, ".gsd")) : null;
349
354
  return {
350
355
  projectRoot,
351
356
  workRoot: resolvedWorkRoot,
@@ -669,7 +674,10 @@ function pickPreferredPhaseDir(phasesDir, matches) {
669
674
  }
670
675
  return best;
671
676
  }
672
- function phaseDirMatchesMilestoneId(dirName, milestoneId, phaseNum) {
677
+ function slugLooksLikeTeamSuffixProjection(slugPart) {
678
+ return /^[a-z0-9]{6}(-|$)/.test(slugPart);
679
+ }
680
+ function phaseDirMatchesMilestoneId(dirName, milestoneId, phaseNum, allowTeamSuffixSlugs = false) {
673
681
  const numMatch = dirName.match(/^(\d+)-(.*)$/);
674
682
  if (!numMatch || parseInt(numMatch[1], 10) !== phaseNum)
675
683
  return false;
@@ -678,8 +686,9 @@ function phaseDirMatchesMilestoneId(dirName, milestoneId, phaseNum) {
678
686
  if (suffix) {
679
687
  return slugPart === suffix || slugPart.startsWith(`${suffix}-`);
680
688
  }
681
- // Plain sequential IDs must not resolve to unique-suffix phase dirs.
682
- return !/^[a-z0-9]{6}(-|$)/.test(slugPart);
689
+ if (slugLooksLikeTeamSuffixProjection(slugPart) && !allowTeamSuffixSlugs)
690
+ return false;
691
+ return true;
683
692
  }
684
693
  function resolvePhaseDir(basePath, milestoneId) {
685
694
  // Try flat-phase layout first: phases/NN-slug/ (always scan phases/, even when
@@ -700,6 +709,16 @@ function resolvePhaseDir(basePath, milestoneId) {
700
709
  matches.push(entry.name);
701
710
  }
702
711
  }
712
+ // Bare milestone ids may only have a suffixed team-mode projection on disk.
713
+ if (matches.length === 0 && !milestoneIdUniqueSuffix(milestoneId)) {
714
+ for (const entry of readdirSync(phasesDir, { withFileTypes: true })) {
715
+ if (!entry.isDirectory())
716
+ continue;
717
+ if (phaseDirMatchesMilestoneId(entry.name, milestoneId, phaseNum, true)) {
718
+ matches.push(entry.name);
719
+ }
720
+ }
721
+ }
703
722
  }
704
723
  catch {
705
724
  // unreadable — fall through
@@ -865,11 +884,22 @@ export function resolveTasksDir(basePath, milestoneId, sliceId) {
865
884
  * Resolve a specific task file.
866
885
  */
867
886
  export function resolveTaskFile(basePath, milestoneId, sliceId, taskId, suffix) {
868
- const tDir = resolveTasksDir(basePath, milestoneId, sliceId);
869
- if (!tDir)
887
+ const phaseDir = resolveMilestonePath(basePath, milestoneId);
888
+ if (!phaseDir)
870
889
  return null;
871
- const file = resolveFile(tDir, taskId, suffix);
872
- return file ? join(tDir, file) : null;
890
+ const legacyBase = legacyMilestonesDir(basePath);
891
+ const isLegacy = phaseDir.startsWith(legacyBase + "/") || phaseDir.startsWith(legacyBase + "\\");
892
+ if (suffix !== "PLAN" && !isLegacy) {
893
+ const flatPath = join(phaseDir, buildTaskFileName(taskId, suffix));
894
+ return existsSync(flatPath) ? flatPath : null;
895
+ }
896
+ const tDir = resolveTasksDir(basePath, milestoneId, sliceId);
897
+ if (tDir) {
898
+ const file = resolveFile(tDir, taskId, suffix);
899
+ if (file)
900
+ return join(tDir, file);
901
+ }
902
+ return null;
873
903
  }
874
904
  // ─── Relative Path Builders (for prompts — .gsd/milestones/...) ────────────
875
905
  /**
@@ -901,29 +931,30 @@ export function relMilestonePath(basePath, milestoneId, title) {
901
931
  return `.gsd/${LAYOUT_SEGMENTS.level1}/${canonicalPhaseDirName(milestoneId, title)}`;
902
932
  }
903
933
  /**
904
- * Build relative .gsd/ path to a milestone file.
905
- * Layout-aware: uses resolveMilestoneFile to find NN-SUFFIX.md (flat-phase)
906
- * or M001-SUFFIX.md (legacy). Falls back to a layout-appropriate canonical
907
- * name when the file doesn't exist yet.
908
- */
909
- export function relMilestoneFile(basePath, milestoneId, suffix) {
910
- const mRel = relMilestonePath(basePath, milestoneId);
911
- // resolveMilestoneFile checks NN-SUFFIX.md (flat-phase) and MID-SUFFIX.md (legacy).
912
- const absFile = resolveMilestoneFile(basePath, milestoneId, suffix);
913
- if (absFile) {
914
- const fileName = absFile.split(/[/\\]/).pop();
915
- return `${mRel}/${fileName}`;
916
- }
917
- // File doesn't exist yet — use layout-aware fallback filename.
934
+ * Build the canonical absolute write target for a milestone file.
935
+ * Existing compatibility filenames are intentionally ignored; readers that need
936
+ * to preserve an existing file should call resolveMilestoneFile first.
937
+ */
938
+ export function targetMilestoneFile(basePath, milestoneId, suffix, title) {
918
939
  const mDir = resolveMilestonePath(basePath, milestoneId);
919
940
  const legacyBase = legacyMilestonesDir(basePath);
920
941
  const isLegacy = mDir
921
942
  ? mDir.startsWith(legacyBase + "/") || mDir.startsWith(legacyBase + "\\")
922
- : false;
943
+ : isLegacyMilestonesLayout(basePath);
944
+ const dir = mDir ?? join(isLegacy ? legacyBase : milestonesDir(basePath), isLegacy ? milestoneId : canonicalPhaseDirName(milestoneId, title));
923
945
  const fileName = isLegacy
924
946
  ? `${milestoneId}-${suffix}.md`
925
947
  : `${String(milestoneIdToPhaseNum(milestoneId)).padStart(2, "0")}-${suffix}.md`;
926
- return `${mRel}/${fileName}`;
948
+ return join(dir, fileName);
949
+ }
950
+ /**
951
+ * Build relative .gsd/ path to a milestone file.
952
+ * Preserves an existing compatibility filename when present; otherwise falls
953
+ * back to the canonical write target.
954
+ */
955
+ export function relMilestoneFile(basePath, milestoneId, suffix, title) {
956
+ const rel = relative(gsdProjectionRoot(basePath), resolveMilestoneFile(basePath, milestoneId, suffix) ?? targetMilestoneFile(basePath, milestoneId, suffix, title)).replace(/\\/g, "/");
957
+ return `.gsd/${rel}`;
927
958
  }
928
959
  /**
929
960
  * Build relative .gsd/ path to a slice directory.
@@ -951,32 +982,36 @@ export function relSlicePath(basePath, milestoneId, sliceId, milestoneTitle) {
951
982
  return relMilestonePath(basePath, milestoneId, milestoneTitle);
952
983
  }
953
984
  /**
954
- * Build relative .gsd/ path to a slice file.
955
- * Layout-aware: legacy uses S01-SUFFIX.md; flat-phase uses NN-MM-SUFFIX.md.
956
- *
957
- * @param milestoneTitle - Optional milestone title forwarded to relSlicePath /
958
- * relMilestonePath for title-aware flat-phase fallback dir naming. Only used
959
- * when the phase directory does not yet exist on disk.
985
+ * Build the canonical absolute write target for a slice file.
986
+ * Existing compatibility filenames are intentionally ignored; readers that need
987
+ * to preserve an existing file should call resolveSliceFile first.
960
988
  */
961
- export function relSliceFile(basePath, milestoneId, sliceId, suffix, milestoneTitle) {
962
- const sRel = relSlicePath(basePath, milestoneId, sliceId, milestoneTitle);
963
- const absPath = resolveSliceFile(basePath, milestoneId, sliceId, suffix);
964
- if (absPath) {
965
- const fileName = absPath.split(/[/\\]/).pop();
966
- return `${sRel}/${fileName}`;
967
- }
968
- // Fallback when file doesn't exist yet — use layout-aware filename.
989
+ export function targetSliceFile(basePath, milestoneId, sliceId, suffix, milestoneTitle) {
969
990
  const mDir = resolveMilestonePath(basePath, milestoneId);
970
991
  const legacyBase = legacyMilestonesDir(basePath);
971
992
  const isLegacy = mDir
972
993
  ? mDir.startsWith(legacyBase + "/") || mDir.startsWith(legacyBase + "\\")
973
- : false;
974
- if (isLegacy) {
975
- return `${sRel}/${sliceId}-${suffix}.md`;
976
- }
977
- const phaseNum = milestoneIdToPhaseNum(milestoneId);
978
- const planNum = sliceIdToPlanNum(sliceId);
979
- return `${sRel}/${planFileName(phaseNum, planNum, suffix)}`;
994
+ : isLegacyMilestonesLayout(basePath);
995
+ const milestoneDir = mDir
996
+ ?? dirname(targetMilestoneFile(basePath, milestoneId, "ROADMAP", milestoneTitle));
997
+ const slicesDir = join(milestoneDir, "slices");
998
+ const dir = isLegacy
999
+ ? join(slicesDir, resolveDir(slicesDir, sliceId) ?? sliceId)
1000
+ : milestoneDir;
1001
+ const fileName = isLegacy
1002
+ ? `${sliceId}-${suffix}.md`
1003
+ : planFileName(milestoneIdToPhaseNum(milestoneId), sliceIdToPlanNum(sliceId), suffix);
1004
+ return join(dir, fileName);
1005
+ }
1006
+ /**
1007
+ * Build relative .gsd/ path to a slice file.
1008
+ * Preserves an existing compatibility filename when present; otherwise falls
1009
+ * back to the canonical write target.
1010
+ */
1011
+ export function relSliceFile(basePath, milestoneId, sliceId, suffix, milestoneTitle) {
1012
+ const rel = relative(gsdProjectionRoot(basePath), resolveSliceFile(basePath, milestoneId, sliceId, suffix)
1013
+ ?? targetSliceFile(basePath, milestoneId, sliceId, suffix, milestoneTitle)).replace(/\\/g, "/");
1014
+ return `.gsd/${rel}`;
980
1015
  }
981
1016
  /**
982
1017
  * Build relative .gsd/ path to a task file.
@@ -985,7 +1020,7 @@ export function relSliceFile(basePath, milestoneId, sliceId, suffix, milestoneTi
985
1020
  * Flat-phase: PLAN → slice plan path (tasks as checkboxes); other suffixes
986
1021
  * (e.g. SUMMARY) → phase dir / TID-SUFFIX.md
987
1022
  */
988
- export function relTaskFile(basePath, milestoneId, sliceId, taskId, suffix) {
1023
+ export function relTaskFile(basePath, milestoneId, sliceId, taskId, suffix, milestoneTitle) {
989
1024
  const sDir = resolveSlicePath(basePath, milestoneId, sliceId);
990
1025
  const phaseDir = resolveMilestonePath(basePath, milestoneId);
991
1026
  // Legacy: slice path is a slices/SID/ subdir inside the milestone dir
@@ -997,6 +1032,6 @@ export function relTaskFile(basePath, milestoneId, sliceId, taskId, suffix) {
997
1032
  if (suffix === "PLAN") {
998
1033
  return relSliceFile(basePath, milestoneId, sliceId, "PLAN");
999
1034
  }
1000
- const relS = relSlicePath(basePath, milestoneId, sliceId);
1035
+ const relS = relSlicePath(basePath, milestoneId, sliceId, milestoneTitle);
1001
1036
  return `${relS}/${buildTaskFileName(taskId, suffix)}`;
1002
1037
  }
@@ -42,6 +42,33 @@ export function persistHookState(basePath) {
42
42
  export function restoreHookState(basePath) {
43
43
  getOrCreateRegistry().restoreState(basePath);
44
44
  }
45
+ /**
46
+ * Reconcile a restored `activeHook` against the session's sidecar queue.
47
+ *
48
+ * The registry persists `activeHook` to disk but the pending hook *dispatch*
49
+ * lives only on the non-persisted `s.sidecarQueue`. After a pause/resume or
50
+ * crash-recovery, `restoreHookState` re-hydrates `activeHook` while the dispatch
51
+ * is gone, so the registry believes a hook is in-flight but nothing will ever
52
+ * complete it — and the next unrelated unit's close-out is falsely charged
53
+ * against the stale hook and blocked. Re-enqueue the missing dispatch so the
54
+ * hook actually runs, restoring the invariant that a persisted `activeHook`
55
+ * always has a live dispatch (#1246).
56
+ */
57
+ export function reconcileRestoredHookDispatch(basePath, sidecarQueue) {
58
+ const dispatch = getOrCreateRegistry().getPendingHookDispatch(basePath);
59
+ if (!dispatch)
60
+ return;
61
+ const alreadyQueued = sidecarQueue.some(item => item.kind === "hook" && item.unitType === dispatch.unitType);
62
+ if (alreadyQueued)
63
+ return;
64
+ sidecarQueue.push({
65
+ kind: "hook",
66
+ unitType: dispatch.unitType,
67
+ unitId: dispatch.unitId,
68
+ prompt: dispatch.prompt,
69
+ model: dispatch.model,
70
+ });
71
+ }
45
72
  export function clearPersistedHookState(basePath) {
46
73
  getOrCreateRegistry().clearPersistedState(basePath);
47
74
  }
@@ -66,6 +66,39 @@ function resolveWinningPhase(models, chain) {
66
66
  }
67
67
  return undefined;
68
68
  }
69
+ /**
70
+ * Normalize a single model field into a {@link ResolvedModelConfig}.
71
+ *
72
+ * Accepts either the legacy bare-string form or the extended object form
73
+ * (`{ model, provider?, fallbacks? }`). This is the shared normalization used
74
+ * for phase buckets so sibling single-model fields — `post_unit_hooks[].model`,
75
+ * `auto_supervisor.model`, and `reactive_execution.subagent_model` — honor
76
+ * `fallbacks[]` identically rather than accepting a bare string only (#1229).
77
+ *
78
+ * Returns undefined for an unset, blank, or model-less field.
79
+ */
80
+ export function normalizeModelFieldConfig(field) {
81
+ if (!field)
82
+ return undefined;
83
+ if (typeof field === "string") {
84
+ return field.trim() ? { primary: field, fallbacks: [] } : undefined;
85
+ }
86
+ if (!field.model)
87
+ return undefined;
88
+ // When provider is explicitly set, prepend it to the model ID so the
89
+ // resolution code in auto.ts can do an explicit provider match.
90
+ const primary = field.provider && !field.model.includes("/")
91
+ ? `${field.provider}/${field.model}`
92
+ : field.model;
93
+ // Carry the per-field `thinking` sub-field through so the dispatch site can
94
+ // prefer it over the session/floor level, mirroring phase-inline thinking
95
+ // (#1269). `undefined` is preserved as absent so behavior is unchanged.
96
+ return {
97
+ primary,
98
+ fallbacks: field.fallbacks ?? [],
99
+ ...(field.thinking ? { thinking: field.thinking } : {}),
100
+ };
101
+ }
69
102
  /**
70
103
  * Resolve model and fallbacks for a given auto-mode unit type.
71
104
  * Returns the primary model and ordered fallbacks, or undefined if not configured.
@@ -73,6 +106,15 @@ function resolveWinningPhase(models, chain) {
73
106
  * Supports both legacy string format and extended object format:
74
107
  * - Legacy: `planning: claude-opus-4-6`
75
108
  * - Extended: `planning: { model: claude-opus-4-6, fallbacks: [glm-5, minimax-m2.5] }`
109
+ *
110
+ * Hook unit types (`hook/<name>`) resolve against the matching
111
+ * `post_unit_hooks[]` entry's `model` field so hook sessions honor the same
112
+ * `fallbacks[]` chain as phase buckets — the recovery path can then switch to
113
+ * a fallback provider instead of hard-failing (and potentially pausing) the
114
+ * run on a transient provider trip (#1229).
115
+ *
116
+ * The synthetic `supervisor` unit type resolves against `auto_supervisor.model`
117
+ * for supervisor interventions (wrap-up warnings, timeout recovery).
76
118
  */
77
119
  export function resolveModelWithFallbacksForUnit(unitType, basePath, availableModelIds, preferredModelId, skipProfileDefaults = false) {
78
120
  const loadOpts = {
@@ -81,26 +123,25 @@ export function resolveModelWithFallbacksForUnit(unitType, basePath, availableMo
81
123
  ...(skipProfileDefaults ? { skipProfileDefaults: true } : {}),
82
124
  };
83
125
  const prefs = loadEffectiveGSDPreferences(basePath, loadOpts);
126
+ // Supervisor interventions resolve against `auto_supervisor.model` (#1229).
127
+ if (unitType === "supervisor") {
128
+ return normalizeModelFieldConfig(prefs?.preferences?.auto_supervisor?.model);
129
+ }
130
+ // Hook sessions resolve against their own `post_unit_hooks[].model` field,
131
+ // which now shares the phase-bucket object form (#1229).
132
+ if (unitType.startsWith("hook/")) {
133
+ const hookName = unitType.slice("hook/".length);
134
+ const hooks = prefs?.preferences?.post_unit_hooks;
135
+ const hook = Array.isArray(hooks) ? hooks.find((h) => h.name === hookName) : undefined;
136
+ return normalizeModelFieldConfig(hook?.model);
137
+ }
84
138
  const chain = phaseChainForUnit(unitType);
85
139
  if (!chain)
86
140
  return undefined;
87
141
  const winner = resolveWinningPhase(prefs?.preferences?.models, chain);
88
142
  if (!winner)
89
143
  return undefined;
90
- const phaseConfig = winner.config;
91
- // Normalize: string -> { model, fallbacks: [] }
92
- if (typeof phaseConfig === "string") {
93
- return { primary: phaseConfig, fallbacks: [] };
94
- }
95
- // When provider is explicitly set, prepend it to the model ID so the
96
- // resolution code in auto.ts can do an explicit provider match.
97
- const primary = phaseConfig.provider && !phaseConfig.model.includes("/")
98
- ? `${phaseConfig.provider}/${phaseConfig.model}`
99
- : phaseConfig.model;
100
- return {
101
- primary,
102
- fallbacks: phaseConfig.fallbacks ?? [],
103
- };
144
+ return normalizeModelFieldConfig(winner.config);
104
145
  }
105
146
  /**
106
147
  * Resolve the explicitly configured reasoning effort for a unit type (ADR-026).
@@ -125,6 +166,13 @@ export function resolveModelWithFallbacksForUnit(unitType, basePath, availableMo
125
166
  * applied here.
126
167
  */
127
168
  export function resolveThinkingLevelForUnit(unitType, basePath, availableModelIds) {
169
+ // Single-field configs — `auto_supervisor.model` and `post_unit_hooks[].model`
170
+ // — carry their own inline `thinking` on the object model form and have no
171
+ // phase-bucket chain, so the walk below never covers them. Resolve their
172
+ // per-field level directly (#1269).
173
+ if (unitType === "supervisor" || unitType.startsWith("hook/")) {
174
+ return resolveModelWithFallbacksForUnit(unitType, basePath, availableModelIds)?.thinking;
175
+ }
128
176
  const loadOpts = availableModelIds !== undefined ? { availableModelIds } : undefined;
129
177
  const prefs = loadEffectiveGSDPreferences(basePath, loadOpts)?.preferences;
130
178
  if (!prefs)
@@ -383,12 +431,15 @@ export function resolveDynamicRoutingConfig() {
383
431
  export function resolveAutoSupervisorConfig() {
384
432
  const prefs = loadEffectiveGSDPreferences();
385
433
  const configured = prefs?.preferences.auto_supervisor ?? {};
434
+ const modelConfig = normalizeModelFieldConfig(configured.model);
386
435
  return {
387
436
  soft_timeout_minutes: configured.soft_timeout_minutes ?? 20,
388
437
  idle_timeout_minutes: configured.idle_timeout_minutes ?? 10,
389
438
  hard_timeout_minutes: configured.hard_timeout_minutes ?? 30,
390
439
  stalled_tool_timeout_minutes: configured.stalled_tool_timeout_minutes ?? 5,
391
- ...(configured.model ? { model: configured.model } : {}),
440
+ ...(modelConfig
441
+ ? { model: modelConfig.primary, modelFallbacks: modelConfig.fallbacks }
442
+ : {}),
392
443
  };
393
444
  }
394
445
  // ─── Token Profile Resolution ─────────────────────────────────────────────
@@ -4,35 +4,29 @@
4
4
  * Contains all logic for resolving skill references from preferences
5
5
  * to absolute filesystem paths, plus skill discovery and staleness config.
6
6
  */
7
- import { existsSync, readdirSync } from "node:fs";
7
+ import { existsSync, readdirSync, statSync } from "node:fs";
8
8
  import { homedir } from "node:os";
9
9
  import { isAbsolute, join } from "node:path";
10
+ import { getSkillDirectories } from "@gsd/pi-coding-agent";
10
11
  import { gsdHome } from "./gsd-home.js";
11
- import { statSync } from "node:fs";
12
12
  import { validatePreferences } from "./preferences-validation.js";
13
13
  /**
14
14
  * Known skill directories for **preference reference resolution** (bare skill names).
15
15
  *
16
- * Search order (first match wins):
17
- * 1. ~/.gsd/agent/skills/ (GSD bundled)
18
- * 2. ~/.agents/skills/ (ecosystem global)
19
- * 3. .agents/skills/ (project ecosystem)
20
- * 4. ~/.claude/skills/ and .claude/skills/ (Claude Code compatibility)
16
+ * Sourced from the shared taxonomy (`@gsd/pi-coding-agent` `getSkillDirectories`)
17
+ * so preference resolution, mid-session discovery, and the PackageManager
18
+ * catalog all agree on which directories exist. The `method` is derived from
19
+ * each entry's scope.
21
20
  *
22
21
  * Note: the ResourceLoader catalog uses PackageManager precedence instead —
23
22
  * project `.gsd/skills` and `.agents/skills` can override bundled GSD skills
24
23
  * on name collision. See docs/dev/what-is-pi/09-the-customization-stack.md.
25
24
  */
26
25
  export function getSkillSearchDirs(cwd) {
27
- const dirs = [
28
- { dir: join(gsdHome(), "agent", "skills"), method: "user-skill" },
29
- { dir: join(homedir(), ".agents", "skills"), method: "user-skill" },
30
- { dir: join(cwd, ".agents", "skills"), method: "project-skill" },
31
- // Claude Code official skill directories
32
- { dir: join(homedir(), ".claude", "skills"), method: "user-skill" },
33
- { dir: join(cwd, ".claude", "skills"), method: "project-skill" },
34
- ];
35
- return dirs;
26
+ return getSkillDirectories({ cwd, gsdHome: gsdHome() }).map((entry) => ({
27
+ dir: entry.path,
28
+ method: entry.scope === "project" ? "project-skill" : "user-skill",
29
+ }));
36
30
  }
37
31
  /**
38
32
  * Resolve a single skill reference to an absolute path.
@@ -86,6 +86,7 @@ export const KNOWN_PREFERENCE_KEYS = new Set([
86
86
  "min_request_interval_ms",
87
87
  "stale_commit_threshold_minutes",
88
88
  "context_management",
89
+ "tool_call_loop_guard",
89
90
  "experimental",
90
91
  "codebase",
91
92
  "slice_parallel",
@@ -25,6 +25,57 @@ const VALID_UOK_TURN_ACTIONS = new Set([
25
25
  "snapshot",
26
26
  "status-only",
27
27
  ]);
28
+ /**
29
+ * Sanitize a single model field that accepts either a bare model ID or the
30
+ * extended `{ model, provider?, fallbacks? }` object form used by phase buckets
31
+ * (#1229). Returns the sanitized value, or undefined when unset/invalid (in
32
+ * which case an error describing `label` is pushed onto `errors`).
33
+ */
34
+ function sanitizeModelField(raw, label, errors, warnings) {
35
+ if (raw === undefined)
36
+ return undefined;
37
+ if (typeof raw === "string") {
38
+ const trimmed = raw.trim();
39
+ if (!trimmed) {
40
+ errors.push(`${label} must not be an empty string`);
41
+ return undefined;
42
+ }
43
+ return trimmed;
44
+ }
45
+ if (raw && typeof raw === "object") {
46
+ const obj = raw;
47
+ const model = typeof obj.model === "string" ? obj.model.trim() : "";
48
+ if (!model) {
49
+ errors.push(`${label} object form requires a non-empty "model"`);
50
+ return undefined;
51
+ }
52
+ const sanitized = { model };
53
+ if (typeof obj.provider === "string" && obj.provider.trim()) {
54
+ sanitized.provider = obj.provider.trim();
55
+ }
56
+ if (obj.fallbacks !== undefined) {
57
+ const fallbacks = normalizeStringArray(obj.fallbacks);
58
+ if (fallbacks.length > 0)
59
+ sanitized.fallbacks = fallbacks;
60
+ }
61
+ // Preserve the object form's `thinking` sub-field so the dispatch site can
62
+ // pin the hook/supervisor/subagent reasoning level (#1269). An illegal level
63
+ // is warned-and-stripped, mirroring inline per-phase thinking, so a typo can't
64
+ // reach the resolver and be treated as explicit configuration.
65
+ if (obj.thinking !== undefined) {
66
+ if (VALID_THINKING_LEVELS.has(obj.thinking)) {
67
+ sanitized.thinking = obj.thinking;
68
+ }
69
+ else {
70
+ warnings?.push(`${label} thinking "${String(obj.thinking)}" is not a valid thinking level ` +
71
+ `(off, minimal, low, medium, high, xhigh) — ignored`);
72
+ }
73
+ }
74
+ return sanitized;
75
+ }
76
+ errors.push(`${label} must be a string or an object with a "model" field`);
77
+ return undefined;
78
+ }
28
79
  const VALID_POST_UNIT_HOOK_CRITICALITIES = new Set(["advisory", "blocking"]);
29
80
  const VALID_POST_UNIT_HOOK_ON_BLOCK_ACTIONS = new Set([
30
81
  "retry-unit",
@@ -452,7 +503,15 @@ export function validatePreferences(preferences) {
452
503
  // ─── Auto Supervisor ────────────────────────────────────────────────
453
504
  if (preferences.auto_supervisor !== undefined) {
454
505
  if (preferences.auto_supervisor && typeof preferences.auto_supervisor === "object") {
455
- validated.auto_supervisor = preferences.auto_supervisor;
506
+ // Validate `model` through the shared sanitizer so the
507
+ // `{ model, provider?, fallbacks? }` object form is rejected on the same
508
+ // terms as its post_unit_hooks / reactive_execution siblings (#1229).
509
+ // Other supervisor fields pass through unchanged.
510
+ const supervisor = { ...preferences.auto_supervisor };
511
+ if (supervisor.model !== undefined) {
512
+ supervisor.model = sanitizeModelField(supervisor.model, "auto_supervisor.model", errors, warnings);
513
+ }
514
+ validated.auto_supervisor = supervisor;
456
515
  }
457
516
  else {
458
517
  errors.push("auto_supervisor must be an object");
@@ -548,8 +607,9 @@ export function validatePreferences(preferences) {
548
607
  const mc = typeof hook.max_cycles === "number" ? hook.max_cycles : Number(hook.max_cycles);
549
608
  validHook.max_cycles = Number.isFinite(mc) ? Math.max(1, Math.min(10, Math.round(mc))) : 1;
550
609
  }
551
- if (typeof hook.model === "string" && hook.model.trim()) {
552
- validHook.model = hook.model.trim();
610
+ const hookModel = sanitizeModelField(hook.model, `post_unit_hooks "${name}" model`, errors, warnings);
611
+ if (hookModel !== undefined) {
612
+ validHook.model = hookModel;
553
613
  }
554
614
  if (typeof hook.artifact === "string" && hook.artifact.trim()) {
555
615
  validHook.artifact = hook.artifact.trim();
@@ -801,6 +861,88 @@ export function validatePreferences(preferences) {
801
861
  errors.push("context_management must be an object");
802
862
  }
803
863
  }
864
+ // ─── Tool-call loop guard (#1198) ────────────────────────────────────────
865
+ if (preferences.tool_call_loop_guard !== undefined) {
866
+ if (typeof preferences.tool_call_loop_guard === "object" && preferences.tool_call_loop_guard !== null) {
867
+ const g = preferences.tool_call_loop_guard;
868
+ const validGuard = {};
869
+ if (g.enabled !== undefined) {
870
+ if (typeof g.enabled === "boolean")
871
+ validGuard.enabled = g.enabled;
872
+ else
873
+ errors.push("tool_call_loop_guard.enabled must be a boolean");
874
+ }
875
+ if (g.identical_args !== undefined) {
876
+ if (typeof g.identical_args === "object" && g.identical_args !== null) {
877
+ const ia = g.identical_args;
878
+ const validIa = {};
879
+ if (ia.enabled !== undefined) {
880
+ if (typeof ia.enabled === "boolean")
881
+ validIa.enabled = ia.enabled;
882
+ else
883
+ errors.push("tool_call_loop_guard.identical_args.enabled must be a boolean");
884
+ }
885
+ if (ia.max_consecutive_calls !== undefined) {
886
+ const n = ia.max_consecutive_calls;
887
+ if (typeof n === "number" && Number.isInteger(n) && n >= 1)
888
+ validIa.max_consecutive_calls = n;
889
+ else
890
+ errors.push("tool_call_loop_guard.identical_args.max_consecutive_calls must be an integer >= 1");
891
+ }
892
+ if (Object.keys(validIa).length > 0)
893
+ validGuard.identical_args = validIa;
894
+ }
895
+ else {
896
+ errors.push("tool_call_loop_guard.identical_args must be an object");
897
+ }
898
+ }
899
+ if (g.repeated_tool !== undefined) {
900
+ if (typeof g.repeated_tool === "object" && g.repeated_tool !== null) {
901
+ const rt = g.repeated_tool;
902
+ const validRt = {};
903
+ if (rt.enabled !== undefined) {
904
+ if (typeof rt.enabled === "boolean")
905
+ validRt.enabled = rt.enabled;
906
+ else
907
+ errors.push("tool_call_loop_guard.repeated_tool.enabled must be a boolean");
908
+ }
909
+ if (rt.default_cap !== undefined) {
910
+ const n = rt.default_cap;
911
+ if (typeof n === "number" && Number.isInteger(n) && n >= 1)
912
+ validRt.default_cap = n;
913
+ else
914
+ errors.push("tool_call_loop_guard.repeated_tool.default_cap must be an integer >= 1");
915
+ }
916
+ if (rt.repeatable_cap !== undefined) {
917
+ const n = rt.repeatable_cap;
918
+ if (typeof n === "number" && Number.isInteger(n) && n >= 1)
919
+ validRt.repeatable_cap = n;
920
+ else
921
+ errors.push("tool_call_loop_guard.repeated_tool.repeatable_cap must be an integer >= 1");
922
+ }
923
+ if (rt.exempt_tools !== undefined) {
924
+ if (Array.isArray(rt.exempt_tools) && rt.exempt_tools.every((t) => typeof t === "string")) {
925
+ validRt.exempt_tools = rt.exempt_tools;
926
+ }
927
+ else {
928
+ errors.push("tool_call_loop_guard.repeated_tool.exempt_tools must be an array of strings");
929
+ }
930
+ }
931
+ if (Object.keys(validRt).length > 0)
932
+ validGuard.repeated_tool = validRt;
933
+ }
934
+ else {
935
+ errors.push("tool_call_loop_guard.repeated_tool must be an object");
936
+ }
937
+ }
938
+ if (Object.keys(validGuard).length > 0) {
939
+ validated.tool_call_loop_guard = validGuard;
940
+ }
941
+ }
942
+ else {
943
+ errors.push("tool_call_loop_guard must be an object");
944
+ }
945
+ }
804
946
  // ─── Context Mode (gsd_exec sandbox) ────────────────────────────────────
805
947
  if (preferences.context_mode !== undefined) {
806
948
  if (typeof preferences.context_mode === "object" && preferences.context_mode !== null) {
@@ -968,13 +1110,9 @@ export function validatePreferences(preferences) {
968
1110
  errors.push('reactive_execution.isolation_mode must be "same-tree"');
969
1111
  }
970
1112
  }
971
- if (re.subagent_model !== undefined) {
972
- if (typeof re.subagent_model === "string" && re.subagent_model.length > 0) {
973
- validRe.subagent_model = re.subagent_model;
974
- }
975
- else {
976
- errors.push("reactive_execution.subagent_model must be a non-empty string");
977
- }
1113
+ const subagentModel = sanitizeModelField(re.subagent_model, "reactive_execution.subagent_model", errors, warnings);
1114
+ if (subagentModel !== undefined) {
1115
+ validRe.subagent_model = subagentModel;
978
1116
  }
979
1117
  const knownReKeys = new Set(["enabled", "max_parallel", "isolation_mode", "subagent_model"]);
980
1118
  for (const key of Object.keys(re)) {