@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
@@ -116,7 +116,8 @@ import {
116
116
  } from "./auto-tool-tracking.js";
117
117
  import { closeoutUnit } from "./auto-unit-closeout.js";
118
118
  import { recoverTimedOutUnit } from "./auto-timeout-recovery.js";
119
- import { selectAndApplyModel, resolveModelId, clearToolBaseline } from "./auto-model-selection.js";
119
+ import { selectAndApplyModel, resolveModelId, clearToolBaseline, isModelUnavailable, applyThinkingLevelForModel } from "./auto-model-selection.js";
120
+ import { resolveModelWithFallbacksForUnit } from "./preferences-models.js";
120
121
  import { resetRoutingHistory, recordOutcome } from "./routing-history.js";
121
122
  import {
122
123
  checkPostUnitHooks,
@@ -127,6 +128,7 @@ import {
127
128
  runPreDispatchHooks,
128
129
  persistHookState,
129
130
  restoreHookState,
131
+ reconcileRestoredHookDispatch,
130
132
  clearPersistedHookState,
131
133
  } from "./post-unit-hooks.js";
132
134
  import { runGSDDoctor, rebuildState } from "./doctor.js";
@@ -177,24 +179,18 @@ import {
177
179
  import { GitServiceImpl } from "./git-service.js";
178
180
  import { nativeCheckoutBranch } from "./native-git-bridge.js";
179
181
  import { getPriorSliceCompletionBlocker } from "./dispatch-guard.js";
180
- import {
181
- createAutoWorktree,
182
- enterAutoWorktree,
183
- enterBranchModeForMilestone,
184
- teardownAutoWorktree,
185
- isInAutoWorktree,
186
- getAutoWorktreePath,
187
- getAutoWorktreeOriginalBase,
188
- mergeMilestoneToMain,
189
- autoWorktreeBranch,
190
- syncWorktreeStateBack,
191
- readResourceVersion,
192
- checkResourcesStale,
193
- escapeStaleWorktree,
194
- } from "./auto-worktree.js";
182
+ import { autoWorktreeBranch, enterBranchModeForMilestone } from "./auto-worktree-branch-lifecycle.js";
183
+ import { createAutoWorktree } from "./auto-worktree-creation.js";
184
+ import { enterAutoWorktree, isInAutoWorktree } from "./auto-worktree-entry.js";
185
+ import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
186
+ import { checkResourcesStale, readResourceVersion } from "./auto-worktree-resource-version.js";
187
+ import { escapeStaleWorktree } from "./auto-worktree-runtime-cleanup.js";
188
+ import { getAutoWorktreeOriginalBase } from "./auto-worktree-session-registry.js";
189
+ import { syncWorktreeStateBack } from "./auto-worktree-sync.js";
190
+ import { teardownAutoWorktree } from "./auto-worktree-teardown.js";
195
191
  import { pruneQueueOrder } from "./queue-order.js";
196
192
  import { startCommandPolling as _startCommandPolling, isRemoteConfigured } from "../remote-questions/manager.js";
197
- import { createMilestoneMergeTransaction } from "./milestone-merge-transaction.js";
193
+ import { createDefaultMilestoneMergeTransaction } from "./milestone-merge-transaction.js";
198
194
 
199
195
  import { debugLog, isDebugEnabled, writeDebugSummary } from "./debug-logger.js";
200
196
  import {
@@ -621,11 +617,27 @@ export function startAutoDetached(
621
617
  milestoneLock?: string | null;
622
618
  },
623
619
  ): void {
624
- void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch((err) => {
620
+ void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch(async (err) => {
625
621
  const message = getErrorMessage(err);
626
622
  ctx.ui.notify(`Auto-start failed: ${message}`, "error");
627
623
  logWarning("engine", `auto start error: ${message}`, { file: "auto.ts" });
628
624
  debugLog("auto-start-failed", { error: message });
625
+ // Backstop cleanup (#1235): if startAuto threw after auto-mode was activated
626
+ // (e.g. an exception during bootstrap, before the loop's try/finally could
627
+ // run cleanupAfterLoopExit), s.active and the on-disk auto lock leak, so the
628
+ // re-entry guard silently no-ops every later /gsd auto until restart. Clear
629
+ // the leaked activation here so a failed start is always recoverable.
630
+ if (s.active) {
631
+ try {
632
+ await cleanupAfterLoopExit(ctx);
633
+ } catch (cleanupErr) {
634
+ logWarning(
635
+ "engine",
636
+ `auto start cleanup failed: ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}`,
637
+ { file: "auto.ts" },
638
+ );
639
+ }
640
+ }
629
641
  });
630
642
  }
631
643
 
@@ -2326,7 +2338,7 @@ export function buildWorktreeLifecycleDeps(): WorktreeLifecycleDeps {
2326
2338
  return new GitServiceImpl(basePath, gitConfig);
2327
2339
  },
2328
2340
  worktreeProjection: new WorktreeStateProjection(),
2329
- mergeMilestone: createMilestoneMergeTransaction(mergeMilestoneToMain),
2341
+ mergeMilestone: createDefaultMilestoneMergeTransaction(),
2330
2342
  };
2331
2343
  }
2332
2344
 
@@ -2833,6 +2845,10 @@ export async function startAuto(
2833
2845
  "info",
2834
2846
  );
2835
2847
  restoreHookState(s.basePath);
2848
+ // A restored activeHook has no live dispatch (the sidecar queue is not
2849
+ // persisted); re-enqueue it so the hook runs instead of blocking the next
2850
+ // unrelated unit's close-out (#1246).
2851
+ reconcileRestoredHookDispatch(s.basePath, s.sidecarQueue);
2836
2852
  // Re-sync managed resources on resume so long-lived auto sessions pick up
2837
2853
  // bundled extension updates before resume-time verification/state logic runs.
2838
2854
  // GSD_PKG_ROOT is set by loader.ts and points to the gsd-pi package root.
@@ -2901,15 +2917,18 @@ export async function startAuto(
2901
2917
  debugLog("resume-orchestration-resume", { error: err instanceof Error ? err.message : String(err) });
2902
2918
  }
2903
2919
  startAutoCommandPolling(s.basePath);
2904
- await runAutoLoopWithUok({
2905
- ctx,
2906
- pi,
2907
- s,
2908
- deps: loopDeps,
2909
- runKernelLoop: runUokKernelLoop,
2910
- runLegacyLoop: runLegacyAutoLoop,
2911
- });
2912
- await cleanupAfterLoopExit(ctx);
2920
+ try {
2921
+ await runAutoLoopWithUok({
2922
+ ctx,
2923
+ pi,
2924
+ s,
2925
+ deps: loopDeps,
2926
+ runKernelLoop: runUokKernelLoop,
2927
+ runLegacyLoop: runLegacyAutoLoop,
2928
+ });
2929
+ } finally {
2930
+ await cleanupAfterLoopExit(ctx);
2931
+ }
2913
2932
  return;
2914
2933
  }
2915
2934
 
@@ -2937,7 +2956,15 @@ export async function startAuto(
2937
2956
  bootstrapDeps,
2938
2957
  freshStartAssessment,
2939
2958
  );
2940
- if (!ready) return;
2959
+ if (!ready) {
2960
+ // bootstrapAutoSession sets s.active = true (auto-start.ts) before several
2961
+ // post-activation bail-outs return false without a loop ever running (e.g.
2962
+ // the SQLite-unavailable gate). Without this, s.active and the auto lock
2963
+ // leak and every later /gsd auto silently no-ops via the re-entry guard
2964
+ // (#1235). Run the canonical cleanup so a failed bootstrap stays recoverable.
2965
+ if (s.active) await cleanupAfterLoopExit(ctx);
2966
+ return;
2967
+ }
2941
2968
 
2942
2969
  // Build scope after bootstrap has populated s.basePath / s.originalBasePath /
2943
2970
  // s.currentMilestoneId (including worktree setup inside bootstrapAutoSession).
@@ -2963,15 +2990,18 @@ export async function startAuto(
2963
2990
  startAutoCommandPolling(s.basePath);
2964
2991
 
2965
2992
  // Dispatch the first unit
2966
- await runAutoLoopWithUok({
2967
- ctx,
2968
- pi,
2969
- s,
2970
- deps: loopDeps,
2971
- runKernelLoop: runUokKernelLoop,
2972
- runLegacyLoop: runLegacyAutoLoop,
2973
- });
2974
- await cleanupAfterLoopExit(ctx);
2993
+ try {
2994
+ await runAutoLoopWithUok({
2995
+ ctx,
2996
+ pi,
2997
+ s,
2998
+ deps: loopDeps,
2999
+ runKernelLoop: runUokKernelLoop,
3000
+ runLegacyLoop: runLegacyAutoLoop,
3001
+ });
3002
+ } finally {
3003
+ await cleanupAfterLoopExit(ctx);
3004
+ }
2975
3005
  }
2976
3006
 
2977
3007
  // describeNextUnit is imported from auto-dashboard.ts and re-exported
@@ -3129,19 +3159,56 @@ export async function dispatchHookUnit(
3129
3159
  workspaceRoot: s.basePath,
3130
3160
  });
3131
3161
 
3132
- if (hookModel) {
3133
- const availableModels = ctx.modelRegistry.getAvailable();
3134
- const match = resolveModelId(hookModel, availableModels, ctx.model?.provider);
3135
- if (match) {
3162
+ // Resolve the hook's model honoring the configured `fallbacks[]` chain
3163
+ // (#1229). The manual trigger path bypasses selectAndApplyModel, so resolve
3164
+ // the primary→fallbacks order here and apply the first candidate available in
3165
+ // the registry — a blocked or unconfigured primary then transparently drops
3166
+ // to a fallback instead of silently reverting to the session model.
3167
+ const availableModels = ctx.modelRegistry.getAvailable();
3168
+ const availableModelIds = availableModels.map(
3169
+ (m: { provider: string; id: string }) => `${m.provider}/${m.id}`,
3170
+ );
3171
+ const hookModelConfig = resolveModelWithFallbacksForUnit(
3172
+ hookUnitType,
3173
+ targetBasePath,
3174
+ availableModelIds,
3175
+ );
3176
+ const modelCandidates = hookModelConfig
3177
+ ? [hookModelConfig.primary, ...hookModelConfig.fallbacks]
3178
+ : hookModel
3179
+ ? [hookModel]
3180
+ : [];
3181
+ if (modelCandidates.length > 0) {
3182
+ let applied = false;
3183
+ for (const candidate of modelCandidates) {
3184
+ const match = resolveModelId(candidate, availableModels, ctx.model?.provider);
3185
+ if (!match) continue;
3186
+ // Skip models the runtime has marked blocked or temporarily unavailable
3187
+ // (e.g. a primary that just tripped a provider limit) so the configured
3188
+ // fallbacks[] chain actually engages — parity with auto-mode's
3189
+ // selectAndApplyModel, which consults the same blocked-models store (#1229).
3190
+ if (isModelUnavailable(targetBasePath, match.provider, match.id)) continue;
3136
3191
  try {
3137
- await pi.setModel(match);
3192
+ if (await pi.setModel(match)) {
3193
+ // The manual trigger path bypasses selectAndApplyModel, so apply the
3194
+ // hook's per-field `thinking` (from `post_unit_hooks[].model`'s object
3195
+ // form) here against the just-set model rather than leaving the hook at
3196
+ // the session level (#1269). Absent → session level, unchanged.
3197
+ if (hookModelConfig?.thinking) {
3198
+ applyThinkingLevelForModel(pi, hookModelConfig.thinking, match, ctx);
3199
+ }
3200
+ applied = true;
3201
+ break;
3202
+ }
3138
3203
  } catch (err) {
3139
- /* non-fatal */
3140
- logWarning("dispatch", `hook model set failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
3204
+ /* non-fatal — try the next fallback */
3205
+ logWarning("dispatch", `hook model set failed for ${candidate}: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
3141
3206
  }
3142
- } else {
3207
+ }
3208
+ if (!applied) {
3143
3209
  ctx.ui.notify(
3144
- `Hook model "${hookModel}" not found in available models. Falling back to current session model. ` +
3210
+ `Hook model${modelCandidates.length > 1 ? "s" : ""} "${modelCandidates.join(", ")}" not available. ` +
3211
+ `Falling back to current session model. ` +
3145
3212
  `Ensure the model is defined in models.json and has auth configured.`,
3146
3213
  "warning",
3147
3214
  );
@@ -14,7 +14,7 @@ import {
14
14
  type WorkflowDatabaseOpenResult,
15
15
  type WorkflowDatabaseStatus,
16
16
  } from "../db-workspace.js";
17
- import { getAutoWorktreePath } from "../auto-worktree.js";
17
+ import { getAutoWorktreePath } from "../auto-worktree-path-resolution.js";
18
18
  import { resolveWorktreeProjectRoot } from "../worktree-root.js";
19
19
  import { worktreesDirs } from "../worktree-placement.js";
20
20
 
@@ -40,7 +40,7 @@ import {
40
40
  } from "../auto-tool-tracking.js";
41
41
  import { applyProviderPayloadPolicy } from "../provider-payload-policy.js";
42
42
 
43
- import { checkToolCallLoop, recordToolCallLoopMutation, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
43
+ import { checkToolCallLoop, configureToolCallLoopGuard, recordToolCallLoopMutation, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
44
44
  import { MINIMAL_AUTO_BASE_TOOL_NAMES } from "./core-session-tools.js";
45
45
  import { maybePauseAutoForApprovalGate, resetPendingGatePauseGuard } from "./pending-gate-pause.js";
46
46
  import { saveActivityLog } from "../activity-log.js";
@@ -594,6 +594,22 @@ async function applyCompactionThresholdOverride(ctx: ExtensionContext): Promise<
594
594
  }
595
595
  }
596
596
 
597
+ /**
598
+ * Apply user-tunable tool-call loop guard thresholds (#1198) from GSD
599
+ * preferences plus GSD_TOOL_LOOP_* env overrides. Runs at session boundaries
600
+ * so both interactive sessions and `/gsd auto` pick up the configuration.
601
+ * Non-fatal: falls back to built-in defaults when preferences cannot be loaded.
602
+ */
603
+ async function applyToolCallLoopGuardConfig(basePath: string): Promise<void> {
604
+ try {
605
+ const { loadEffectiveGSDPreferences } = await import("../preferences.js");
606
+ const prefs = loadEffectiveGSDPreferences(basePath);
607
+ configureToolCallLoopGuard(prefs?.preferences.tool_call_loop_guard);
608
+ } catch {
609
+ configureToolCallLoopGuard(null);
610
+ }
611
+ }
612
+
597
613
  function clearDeferredApprovalGate(basePath?: string): void {
598
614
  if (!basePath) {
599
615
  deferredApprovalGates.clear();
@@ -996,6 +1012,7 @@ export function registerHooks(
996
1012
  }
997
1013
  resetWriteGateState(basePath);
998
1014
  resetToolCallLoopGuard();
1015
+ await applyToolCallLoopGuardConfig(basePath);
999
1016
  approvalQuestionAbortInFlight = false;
1000
1017
  clearDeferredApprovalGate();
1001
1018
  await resetAskUserQuestionsTurnCache();
@@ -1087,6 +1104,7 @@ export function registerHooks(
1087
1104
  initSessionNotifications(ctx);
1088
1105
  resetWriteGateState(basePath);
1089
1106
  resetToolCallLoopGuard();
1107
+ await applyToolCallLoopGuardConfig(basePath);
1090
1108
  clearDeferredApprovalGate();
1091
1109
  await resetAskUserQuestionsTurnCache();
1092
1110
  clearDiscussionFlowState(basePath);
@@ -1707,7 +1725,7 @@ export function registerHooks(
1707
1725
  }
1708
1726
  }
1709
1727
  if (!event.isError) {
1710
- recordToolCallLoopMutation(toolName);
1728
+ recordToolCallLoopMutation(toolName, event.details);
1711
1729
  refreshSourceObservationAfterMutation(toolName, event.input, ctx);
1712
1730
  clearSourceObservationsAfterShell(toolName);
1713
1731
  }
@@ -14,7 +14,7 @@ import { resolveModelWithFallbacksForUnit } from "../preferences-models.js";
14
14
  import { resolveGsdRootFile, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTaskFiles, resolveTasksDir, relSliceFile, relSlicePath, relTaskFile } from "../paths.js";
15
15
  import { extractIntroAndRules } from "../knowledge-parser.js";
16
16
  import { ensureCodebaseMapFresh, readCodebaseMap } from "../codebase-generator.js";
17
- import { getActiveAutoWorktreeContext } from "../auto-worktree.js";
17
+ import { getActiveAutoWorktreeContext } from "../auto-worktree-session-registry.js";
18
18
  import { getActiveWorktreeName, getWorktreeOriginalCwd } from "../worktree-session-state.js";
19
19
  import { deriveState } from "../state.js";
20
20
  import { formatOverridesSection, formatShortcut, loadActiveOverrides, loadFile, parseContinue, parseSummary } from "../files.js";
@@ -14,13 +14,21 @@
14
14
  * to stop tooling for the rest of that turn and answer in text.
15
15
  *
16
16
  * The per-tool-name check (#783 Brief C) tracks call counts within a
17
- * turn regardless of args, decaying after successful state-mutating tools.
17
+ * turn regardless of args, decaying after successful state-progressing tools.
18
18
  * This catches improvisation
19
19
  * loops where the model attempts the same missing workflow tool through
20
20
  * varied surfaces (bash → `node -e` → CLI), each with a different
21
21
  * signature, so the identical-args streak never trips, while allowing
22
- * tool-heavy turns that are making file-mutation progress. Whichever guard
23
- * trips first blocks.
22
+ * tool-heavy turns that are making progress. Progress means a successful file
23
+ * mutation (edit/write) or a successful shell/exec call (bg_shell,
24
+ * gsd_uat_exec, …) — so a local model running a legitimate iterative
25
+ * debugging loop (restart server, npm install, curl an endpoint) is not
26
+ * blocked as a false loop (#1206). Whichever guard trips first blocks.
27
+ *
28
+ * Thresholds, exempt tools, and enable flags are user-tunable (#1198) via the
29
+ * `tool_call_loop_guard` key in `.gsd/PREFERENCES.md` and `GSD_TOOL_LOOP_*`
30
+ * environment variables, applied through {@link configureToolCallLoopGuard}.
31
+ * Defaults preserve the original hardcoded behavior.
24
32
  */
25
33
 
26
34
  import { createHash } from "node:crypto";
@@ -28,26 +36,189 @@ import { INHERENTLY_REPEATABLE_TOOL_SET } from "./core-session-tools.js";
28
36
  import { hasBrowserContractPrefix } from "../../shared/browser-contract.js";
29
37
  import { canonicalToolName } from "../engine-hook-contract.js";
30
38
 
31
- const MAX_CONSECUTIVE_IDENTICAL_CALLS = 4;
39
+ /** Built-in defaults. Preserved when preferences/env do not override them. */
40
+ const DEFAULT_MAX_CONSECUTIVE_IDENTICAL_CALLS = 4;
41
+ const DEFAULT_PER_TOOL_DEFAULT_CAP = 6;
42
+ const DEFAULT_PER_TOOL_REPEATABLE_CAP = 15;
43
+ const DEFAULT_PER_TOOL_CAP_EXEMPT_TOOLS = ["find", "glob", "grep", "ls", "read", "search_and_read"] as const;
32
44
 
33
45
  /** Interactive/user-facing tools where even 1 duplicate is confusing. */
34
46
  const STRICT_LOOP_TOOLS = new Set(["ask_user_questions"]);
35
47
  const MAX_CONSECUTIVE_STRICT = 1;
36
48
 
49
+ const STATE_MUTATING_TOOL_SET = new Set(["edit", "write", "multi_edit", "notebook_edit"]);
50
+
37
51
  /**
38
- * Per-turn cap on calls to the SAME tool name, regardless of args (#783).
52
+ * Successful shell/exec calls are state progression too (#1206), not just file
53
+ * mutations. A local model debugging a UAT scenario legitimately restarts
54
+ * servers, runs `npm install`, and curls endpoints via bg_shell / gsd_uat_exec
55
+ * with varied args — productive work that never touches a file, so Guard 2's
56
+ * per-tool cap would otherwise misfire as a false loop. Decaying on these
57
+ * successful calls keeps the guard from aborting a progressing turn.
39
58
  *
40
- * General-purpose execution tools are routinely called many times per turn
41
- * (touching multiple files, running several commands), so they get a higher
42
- * ceiling. Everything else workflow one-shot tools (e.g. gsd_complete_milestone)
43
- * and any non-allowlisted tool gets the default cap. The default is generous
44
- * enough to absorb legitimate retries but catches the reported improvisation
45
- * loop (~51 calls) well before a cost spike.
59
+ * Only successful calls reach {@link recordToolCallLoopMutation}: the
60
+ * tool_result hook skips `isError` results, and the bash tool throws on
61
+ * non-zero exit, so a genuinely stuck improvisation loop of failing commands
62
+ * (#783) never decays and still trips the cap. `async_bash` is excluded
63
+ * because it only registers a background job; command success is reported
64
+ * later via `await_job`. `bg_shell` may return `isError: false` for failed
65
+ * `run`/`start` outcomes, so the hook passes `details` for validation.
46
66
  */
47
- const PER_TOOL_DEFAULT_CAP = 6;
48
- const PER_TOOL_REPEATABLE_CAP = 15;
49
- const PER_TOOL_CAP_EXEMPT_TOOLS = new Set(["find", "glob", "grep", "ls", "read", "search_and_read"]);
50
- const STATE_MUTATING_TOOL_SET = new Set(["edit", "write", "multi_edit", "notebook_edit"]);
67
+ const STATE_PROGRESSING_EXEC_TOOL_SET = new Set([
68
+ "bash",
69
+ "bg_shell",
70
+ "shell",
71
+ "powershell",
72
+ "gsd_exec",
73
+ "gsd_uat_exec",
74
+ ]);
75
+
76
+ function isSuccessfulBgShellLoopProgress(details: unknown): boolean {
77
+ if (!details || typeof details !== "object") return false;
78
+ const record = details as Record<string, unknown>;
79
+ switch (record.action) {
80
+ case "run":
81
+ return record.exitCode === 0 && record.timedOut !== true;
82
+ case "start":
83
+ case "restart": {
84
+ const process = record.process;
85
+ if (!process || typeof process !== "object") return false;
86
+ return (process as Record<string, unknown>).alive === true;
87
+ }
88
+ case "wait_for_ready":
89
+ return record.ready === true;
90
+ case "send_and_wait":
91
+ return record.matched === true;
92
+ default:
93
+ return true;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * User-tunable configuration shape for the loop guard (#1198).
99
+ *
100
+ * Mirrors the `tool_call_loop_guard` key in `.gsd/PREFERENCES.md`. All fields
101
+ * are optional; anything omitted falls back to the built-in defaults so that
102
+ * existing installs keep their current behavior.
103
+ */
104
+ export interface ToolCallLoopGuardConfig {
105
+ enabled?: boolean;
106
+ identical_args?: {
107
+ enabled?: boolean;
108
+ max_consecutive_calls?: number;
109
+ };
110
+ repeated_tool?: {
111
+ enabled?: boolean;
112
+ default_cap?: number;
113
+ repeatable_cap?: number;
114
+ exempt_tools?: string[];
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Active, resolved configuration. Set by {@link configureToolCallLoopGuard}
120
+ * from preferences + environment overrides, and deliberately NOT touched by
121
+ * {@link resetToolCallLoopGuard} so a session's tuning survives turn
122
+ * boundaries.
123
+ */
124
+ interface ResolvedGuardConfig {
125
+ /** Master switch for both guards. */
126
+ guardEnabled: boolean;
127
+ identicalEnabled: boolean;
128
+ maxConsecutiveIdentical: number;
129
+ repeatedEnabled: boolean;
130
+ perToolDefaultCap: number;
131
+ perToolRepeatableCap: number;
132
+ /** Tool names exempt from Guard 2 (per-tool-name cap). */
133
+ perToolExempt: Set<string>;
134
+ }
135
+
136
+ function defaultGuardConfig(): ResolvedGuardConfig {
137
+ return {
138
+ guardEnabled: true,
139
+ identicalEnabled: true,
140
+ maxConsecutiveIdentical: DEFAULT_MAX_CONSECUTIVE_IDENTICAL_CALLS,
141
+ repeatedEnabled: true,
142
+ perToolDefaultCap: DEFAULT_PER_TOOL_DEFAULT_CAP,
143
+ perToolRepeatableCap: DEFAULT_PER_TOOL_REPEATABLE_CAP,
144
+ perToolExempt: new Set<string>(DEFAULT_PER_TOOL_CAP_EXEMPT_TOOLS),
145
+ };
146
+ }
147
+
148
+ let config: ResolvedGuardConfig = defaultGuardConfig();
149
+
150
+ /** Parse a positive-integer env var, returning undefined when unset/invalid. */
151
+ function envPositiveInt(name: string): number | undefined {
152
+ const raw = process.env[name];
153
+ if (raw === undefined || raw.trim() === "") return undefined;
154
+ const n = Number(raw);
155
+ return Number.isFinite(n) && n >= 1 ? Math.floor(n) : undefined;
156
+ }
157
+
158
+ /** Parse a boolean env var (`true`/`1` vs `false`/`0`), undefined when unset. */
159
+ function envBool(name: string): boolean | undefined {
160
+ const raw = process.env[name];
161
+ if (raw === undefined || raw.trim() === "") return undefined;
162
+ const v = raw.trim().toLowerCase();
163
+ if (v === "true" || v === "1" || v === "yes" || v === "on") return true;
164
+ if (v === "false" || v === "0" || v === "no" || v === "off") return false;
165
+ return undefined;
166
+ }
167
+
168
+ /** Parse a comma-separated tool-name list env var. */
169
+ function envToolList(name: string): string[] {
170
+ const raw = process.env[name];
171
+ if (!raw) return [];
172
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
173
+ }
174
+
175
+ /**
176
+ * Resolve preferences + environment overrides into the active configuration.
177
+ * Environment variables win over preferences; preferences win over built-in
178
+ * defaults. User-supplied exempt tools are additive to the built-in exempt
179
+ * set so defaults are always preserved.
180
+ *
181
+ * Applied to both interactive sessions and `/gsd auto` (called from the
182
+ * session_start / session_switch hooks).
183
+ */
184
+ export function configureToolCallLoopGuard(prefs?: ToolCallLoopGuardConfig | null): void {
185
+ const next = defaultGuardConfig();
186
+
187
+ if (prefs) {
188
+ if (typeof prefs.enabled === "boolean") next.guardEnabled = prefs.enabled;
189
+ if (prefs.identical_args) {
190
+ if (typeof prefs.identical_args.enabled === "boolean") next.identicalEnabled = prefs.identical_args.enabled;
191
+ if (typeof prefs.identical_args.max_consecutive_calls === "number" && prefs.identical_args.max_consecutive_calls >= 1) {
192
+ next.maxConsecutiveIdentical = Math.floor(prefs.identical_args.max_consecutive_calls);
193
+ }
194
+ }
195
+ if (prefs.repeated_tool) {
196
+ if (typeof prefs.repeated_tool.enabled === "boolean") next.repeatedEnabled = prefs.repeated_tool.enabled;
197
+ if (typeof prefs.repeated_tool.default_cap === "number" && prefs.repeated_tool.default_cap >= 1) {
198
+ next.perToolDefaultCap = Math.floor(prefs.repeated_tool.default_cap);
199
+ }
200
+ if (typeof prefs.repeated_tool.repeatable_cap === "number" && prefs.repeated_tool.repeatable_cap >= 1) {
201
+ next.perToolRepeatableCap = Math.floor(prefs.repeated_tool.repeatable_cap);
202
+ }
203
+ for (const tool of prefs.repeated_tool.exempt_tools ?? []) {
204
+ if (typeof tool === "string" && tool.trim()) next.perToolExempt.add(tool.trim());
205
+ }
206
+ }
207
+ }
208
+
209
+ // Environment overrides (win over preferences).
210
+ const envEnabled = envBool("GSD_TOOL_LOOP_GUARD_ENABLED");
211
+ if (envEnabled !== undefined) next.guardEnabled = envEnabled;
212
+ const envIdenticalMax = envPositiveInt("GSD_TOOL_LOOP_IDENTICAL_MAX");
213
+ if (envIdenticalMax !== undefined) next.maxConsecutiveIdentical = envIdenticalMax;
214
+ const envDefaultCap = envPositiveInt("GSD_TOOL_LOOP_REPEATED_DEFAULT_CAP");
215
+ if (envDefaultCap !== undefined) next.perToolDefaultCap = envDefaultCap;
216
+ const envRepeatableCap = envPositiveInt("GSD_TOOL_LOOP_REPEATED_REPEATABLE_CAP");
217
+ if (envRepeatableCap !== undefined) next.perToolRepeatableCap = envRepeatableCap;
218
+ for (const tool of envToolList("GSD_TOOL_LOOP_EXEMPT_TOOLS")) next.perToolExempt.add(tool);
219
+
220
+ config = next;
221
+ }
51
222
 
52
223
  let consecutiveCount = 0;
53
224
  let lastSignature = "";
@@ -82,17 +253,20 @@ function hashToolCall(toolName: string, args: Record<string, unknown>): string {
82
253
  * Returns `{ block: true, reason }` when the loop threshold is exceeded.
83
254
  *
84
255
  * Two independent guards run; whichever trips first blocks:
85
- * 1. Identical-signature streak (MAX_CONSECUTIVE_IDENTICAL_CALLS, strict for
256
+ * 1. Identical-signature streak (config.maxConsecutiveIdentical, strict for
86
257
  * ask_user_questions).
87
- * 2. Per-tool-name cap (PER_TOOL_DEFAULT_CAP / PER_TOOL_REPEATABLE_CAP),
258
+ * 2. Per-tool-name cap (config.perToolDefaultCap / config.perToolRepeatableCap),
88
259
  * independent of args, reset after file-mutation progress — catches
89
260
  * improvisation loops (#783).
261
+ *
262
+ * Both guards, their thresholds, and the per-tool exempt set are user-tunable
263
+ * via {@link configureToolCallLoopGuard} (#1198).
90
264
  */
91
265
  export function checkToolCallLoop(
92
266
  toolName: string,
93
267
  args: Record<string, unknown>,
94
268
  ): { block: boolean; reason?: string; count?: number } {
95
- if (!enabled) return { block: false, count: 0 };
269
+ if (!enabled || !config.guardEnabled) return { block: false, count: 0 };
96
270
 
97
271
  const sig = hashToolCall(toolName, args);
98
272
 
@@ -107,14 +281,16 @@ export function checkToolCallLoop(
107
281
  // ── Guard 1: identical-signature streak ──
108
282
  const threshold = STRICT_LOOP_TOOLS.has(toolName)
109
283
  ? MAX_CONSECUTIVE_STRICT
110
- : MAX_CONSECUTIVE_IDENTICAL_CALLS;
284
+ : config.maxConsecutiveIdentical;
111
285
 
112
- if (consecutiveCount > threshold) {
286
+ if (config.identicalEnabled && consecutiveCount > threshold) {
113
287
  return {
114
288
  block: true,
115
289
  reason:
116
290
  `Tool loop detected (identical args): ${toolName} called ${consecutiveCount} times ` +
117
- `with identical arguments. Blocking to prevent infinite loop. ` +
291
+ `with identical arguments (max ${threshold}). Blocking to prevent infinite loop. ` +
292
+ `Raise tool_call_loop_guard.identical_args.max_consecutive_calls in .gsd/PREFERENCES.md ` +
293
+ `(or GSD_TOOL_LOOP_IDENTICAL_MAX) if this is expected. ` +
118
294
  `Do not retry this tool or call other tools this turn — stop and respond to the user in text.`,
119
295
  count: consecutiveCount,
120
296
  };
@@ -139,15 +315,16 @@ export function checkToolCallLoop(
139
315
  // Guard 1's identical-signature streak still catches a genuinely stuck
140
316
  // browser loop.
141
317
  if (
142
- PER_TOOL_CAP_EXEMPT_TOOLS.has(toolName) ||
318
+ !config.repeatedEnabled ||
319
+ config.perToolExempt.has(toolName) ||
143
320
  hasBrowserContractPrefix(canonicalToolName(toolName))
144
321
  ) {
145
322
  return { block: false, count: consecutiveCount };
146
323
  }
147
324
 
148
325
  const perToolCap = INHERENTLY_REPEATABLE_TOOL_SET.has(toolName)
149
- ? PER_TOOL_REPEATABLE_CAP
150
- : PER_TOOL_DEFAULT_CAP;
326
+ ? config.perToolRepeatableCap
327
+ : config.perToolDefaultCap;
151
328
 
152
329
  if (perToolCount > perToolCap) {
153
330
  return {
@@ -156,6 +333,9 @@ export function checkToolCallLoop(
156
333
  `Tool loop detected (repeated tool): ${toolName} called ${perToolCount} times ` +
157
334
  `this turn (cap ${perToolCap}). Blocking to prevent infinite loop. ` +
158
335
  `The tool may be unavailable or failing repeatedly. ` +
336
+ `Raise tool_call_loop_guard.repeated_tool.default_cap/repeatable_cap or add "${toolName}" ` +
337
+ `to tool_call_loop_guard.repeated_tool.exempt_tools in .gsd/PREFERENCES.md ` +
338
+ `(or GSD_TOOL_LOOP_REPEATED_* / GSD_TOOL_LOOP_EXEMPT_TOOLS) if this is expected. ` +
159
339
  `Do not retry this tool or pivot to other tools this turn — stop and respond to the user in text.`,
160
340
  count: perToolCount,
161
341
  };
@@ -164,10 +344,21 @@ export function checkToolCallLoop(
164
344
  return { block: false, count: consecutiveCount };
165
345
  }
166
346
 
167
- /** Record successful mutation progress so Guard 2 can decay on the next count. */
168
- export function recordToolCallLoopMutation(toolName: string): void {
347
+ /**
348
+ * Record successful state progress so Guard 2 can decay on the next count.
349
+ * File mutations (edit/write/…) and successful shell/exec calls (bash,
350
+ * bg_shell, gsd_uat_exec, …) both count as progress (#1092, #1206). The caller
351
+ * only invokes this for non-error results, so failing improvisation loops
352
+ * (#783) never decay and still trip the per-tool cap.
353
+ */
354
+ export function recordToolCallLoopMutation(toolName: string, details?: unknown): void {
169
355
  if (!enabled) return;
170
- if (!STATE_MUTATING_TOOL_SET.has(toolName)) return;
356
+ if (STATE_MUTATING_TOOL_SET.has(toolName)) {
357
+ mutationEpoch++;
358
+ return;
359
+ }
360
+ if (!STATE_PROGRESSING_EXEC_TOOL_SET.has(toolName)) return;
361
+ if (toolName === "bg_shell" && details !== undefined && !isSuccessfulBgShellLoopProgress(details)) return;
171
362
  mutationEpoch++;
172
363
  }
173
364
 
@@ -675,10 +675,16 @@ export function ensureCodebaseMapFresh(
675
675
 
676
676
  const metadata = parseCodebaseMapMetadata(existing);
677
677
  const existingDescriptions = parseCodebaseMap(existing);
678
+ // Compare against the truncated count: when the repository has more tracked
679
+ // files than maxFiles, the map only lists the first maxFiles entries, so a
680
+ // stored fileCount above the cap (e.g. from an older map that recorded the
681
+ // full count) still matches the current truncated listing. Comparing raw
682
+ // values would flag "file-count-changed" on every turn, rewriting the
683
+ // timestamp and invalidating the provider KV cache prefix.
678
684
  const staleReason =
679
685
  !metadata ? "missing-metadata"
680
686
  : metadata.fingerprint !== fingerprint ? "files-changed"
681
- : metadata.fileCount !== listed.files.length ? "file-count-changed"
687
+ : Math.min(metadata.fileCount, resolved.maxFiles) !== listed.files.length ? "file-count-changed"
682
688
  : metadata.truncated !== listed.truncated ? "truncation-changed"
683
689
  : undefined;
684
690