@opengsd/gsd-pi 1.4.0-dev.c18009cd → 1.5.0-dev.4fb2d460

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 (334) 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/gsd/auto/closeout.js +97 -5
  5. package/dist/resources/extensions/gsd/auto/orchestrator.js +4 -3
  6. package/dist/resources/extensions/gsd/auto/pre-dispatch.js +6 -3
  7. package/dist/resources/extensions/gsd/auto/unit-phase.js +5 -9
  8. package/dist/resources/extensions/gsd/auto-artifact-paths.js +12 -3
  9. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +11 -7
  10. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -2
  11. package/dist/resources/extensions/gsd/auto-recovery.js +50 -6
  12. package/dist/resources/extensions/gsd/auto-start.js +4 -3
  13. package/dist/resources/extensions/gsd/auto-worktree-branch-lifecycle.js +189 -0
  14. package/dist/resources/extensions/gsd/auto-worktree-cleanup.js +133 -0
  15. package/dist/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.js +35 -0
  16. package/dist/resources/extensions/gsd/auto-worktree-creation.js +130 -0
  17. package/dist/resources/extensions/gsd/auto-worktree-entry.js +118 -0
  18. package/dist/resources/extensions/gsd/auto-worktree-merge-already-merged.js +98 -0
  19. package/dist/resources/extensions/gsd/auto-worktree-merge-branch-head.js +52 -0
  20. package/dist/resources/extensions/gsd/auto-worktree-merge-cleanup.js +87 -0
  21. package/dist/resources/extensions/gsd/auto-worktree-merge-code-changes.js +46 -0
  22. package/dist/resources/extensions/gsd/auto-worktree-merge-db-ready.js +68 -0
  23. package/dist/resources/extensions/gsd/auto-worktree-merge-integration-branch.js +51 -0
  24. package/dist/resources/extensions/gsd/auto-worktree-merge-message.js +106 -0
  25. package/dist/resources/extensions/gsd/auto-worktree-merge-pre-teardown.js +64 -0
  26. package/dist/resources/extensions/gsd/auto-worktree-merge-stash.js +168 -0
  27. package/dist/resources/extensions/gsd/auto-worktree-merge.js +302 -0
  28. package/dist/resources/extensions/gsd/auto-worktree-milestone-shelter.js +119 -0
  29. package/dist/resources/extensions/gsd/auto-worktree-path-resolution.js +5 -0
  30. package/dist/resources/extensions/gsd/auto-worktree-path-utils.js +5 -0
  31. package/dist/resources/extensions/gsd/auto-worktree-resource-version.js +41 -0
  32. package/dist/resources/extensions/gsd/auto-worktree-runtime-cleanup.js +99 -0
  33. package/dist/resources/extensions/gsd/auto-worktree-session-registry.js +57 -0
  34. package/dist/resources/extensions/gsd/auto-worktree-sync.js +249 -0
  35. package/dist/resources/extensions/gsd/auto-worktree-teardown.js +120 -0
  36. package/dist/resources/extensions/gsd/auto-worktree.js +20 -2073
  37. package/dist/resources/extensions/gsd/auto.js +9 -3
  38. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -1
  39. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +20 -2
  40. package/dist/resources/extensions/gsd/bootstrap/system-context.js +1 -1
  41. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +185 -27
  42. package/dist/resources/extensions/gsd/codebase-generator.js +7 -1
  43. package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
  44. package/dist/resources/extensions/gsd/commands-workflow-templates.js +3 -3
  45. package/dist/resources/extensions/gsd/db/queries.js +9 -9
  46. package/dist/resources/extensions/gsd/db/writers/cascades.js +20 -2
  47. package/dist/resources/extensions/gsd/doctor-engine-checks.js +93 -11
  48. package/dist/resources/extensions/gsd/flat-phase-migration.js +32 -3
  49. package/dist/resources/extensions/gsd/forensics.js +1 -1
  50. package/dist/resources/extensions/gsd/git-conflict-resolve.js +2 -2
  51. package/dist/resources/extensions/gsd/git-service.js +45 -0
  52. package/dist/resources/extensions/gsd/gsd-db.js +23 -11
  53. package/dist/resources/extensions/gsd/guided-flow.js +22 -17
  54. package/dist/resources/extensions/gsd/markdown-renderer.js +15 -3
  55. package/dist/resources/extensions/gsd/md-importer.js +89 -23
  56. package/dist/resources/extensions/gsd/migration-auto-check.js +30 -3
  57. package/dist/resources/extensions/gsd/milestone-merge-transaction.js +11 -0
  58. package/dist/resources/extensions/gsd/milestone-settlement.js +1 -1
  59. package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
  60. package/dist/resources/extensions/gsd/parallel-orchestrator.js +3 -1
  61. package/dist/resources/extensions/gsd/paths.js +17 -3
  62. package/dist/resources/extensions/gsd/preferences-skills.js +10 -16
  63. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  64. package/dist/resources/extensions/gsd/preferences-validation.js +82 -0
  65. package/dist/resources/extensions/gsd/preferences.js +20 -0
  66. package/dist/resources/extensions/gsd/prompts/system.md +6 -6
  67. package/dist/resources/extensions/gsd/quick.js +12 -3
  68. package/dist/resources/extensions/gsd/skill-discovery.js +36 -23
  69. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +2 -1
  70. package/dist/resources/extensions/gsd/state/derive/from-db.js +88 -153
  71. package/dist/resources/extensions/gsd/tool-contract.js +14 -0
  72. package/dist/resources/extensions/gsd/tools/reopen-slice.js +5 -0
  73. package/dist/resources/extensions/gsd/tools/reopen-task.js +13 -5
  74. package/dist/resources/extensions/gsd/unit-registry.js +12 -2
  75. package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +1 -1
  76. package/dist/resources/extensions/gsd/validation-block-guard.js +2 -1
  77. package/dist/resources/extensions/gsd/workspace-git-preflight.js +2 -2
  78. package/dist/resources/extensions/gsd/worktree-command.js +1 -1
  79. package/dist/resources/extensions/gsd/worktree-lifecycle.js +14 -9
  80. package/dist/resources/extensions/gsd/worktree-reentry.js +2 -1
  81. package/dist/resources/extensions/shared/interview-ui.js +62 -9
  82. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  83. package/dist/web/standalone/.next/BUILD_ID +1 -1
  84. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  85. package/dist/web/standalone/.next/build-manifest.json +2 -2
  86. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  87. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  88. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  103. package/dist/web/standalone/.next/server/app/index.html +1 -1
  104. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  106. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  111. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  113. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  114. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  115. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  116. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  117. package/integrations/hermes/open_gsd_hermes/gsd_client.py +24 -35
  118. package/integrations/hermes/tests/test_gsd_client_timeout.py +4 -4
  119. package/package.json +6 -6
  120. package/packages/cloud-mcp-gateway/package.json +2 -2
  121. package/packages/contracts/package.json +1 -1
  122. package/packages/daemon/package.json +4 -4
  123. package/packages/gsd-agent-core/dist/export-html/template.js +53 -4
  124. package/packages/gsd-agent-core/package.json +5 -5
  125. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  126. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +3 -2
  127. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  128. package/packages/gsd-agent-modes/package.json +7 -7
  129. package/packages/mcp-server/package.json +4 -4
  130. package/packages/native/package.json +1 -1
  131. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  132. package/packages/pi-agent-core/dist/agent-loop.js +14 -9
  133. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  134. package/packages/pi-agent-core/package.json +1 -1
  135. package/packages/pi-ai/dist/index.d.ts +1 -0
  136. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  137. package/packages/pi-ai/dist/index.js +1 -0
  138. package/packages/pi-ai/dist/index.js.map +1 -1
  139. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  140. package/packages/pi-ai/dist/providers/transform-messages.js +7 -3
  141. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  142. package/packages/pi-ai/dist/tool-result-content.d.ts +3 -0
  143. package/packages/pi-ai/dist/tool-result-content.d.ts.map +1 -0
  144. package/packages/pi-ai/dist/tool-result-content.js +62 -0
  145. package/packages/pi-ai/dist/tool-result-content.js.map +1 -0
  146. package/packages/pi-ai/dist/types.d.ts +8 -20
  147. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  148. package/packages/pi-ai/dist/types.js.map +1 -1
  149. package/packages/pi-ai/package.json +1 -1
  150. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +9 -0
  151. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  152. package/packages/pi-coding-agent/dist/core/package-manager.js +74 -53
  153. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  154. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +6 -0
  155. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/core/resource-loader.js +30 -2
  157. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  158. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts +66 -0
  159. package/packages/pi-coding-agent/dist/core/skill-directories.d.ts.map +1 -0
  160. package/packages/pi-coding-agent/dist/core/skill-directories.js +138 -0
  161. package/packages/pi-coding-agent/dist/core/skill-directories.js.map +1 -0
  162. package/packages/pi-coding-agent/dist/core/skills.d.ts +4 -1
  163. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  164. package/packages/pi-coding-agent/dist/core/skills.js +4 -1
  165. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  166. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  167. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  168. package/packages/pi-coding-agent/dist/index.js +1 -0
  169. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  170. package/packages/pi-coding-agent/package.json +7 -7
  171. package/packages/pi-tui/package.json +2 -2
  172. package/packages/rpc-client/package.json +2 -2
  173. package/pkg/dist/core/export-html/template.js +53 -4
  174. package/pkg/package.json +1 -1
  175. package/src/resources/extensions/ask-user-questions.ts +1 -1
  176. package/src/resources/extensions/gsd/auto/closeout.ts +114 -7
  177. package/src/resources/extensions/gsd/auto/orchestrator.ts +4 -3
  178. package/src/resources/extensions/gsd/auto/pre-dispatch.ts +6 -3
  179. package/src/resources/extensions/gsd/auto/unit-phase.ts +5 -9
  180. package/src/resources/extensions/gsd/auto-artifact-paths.ts +12 -3
  181. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +13 -7
  182. package/src/resources/extensions/gsd/auto-dispatch.ts +18 -2
  183. package/src/resources/extensions/gsd/auto-recovery.ts +50 -6
  184. package/src/resources/extensions/gsd/auto-start.ts +5 -3
  185. package/src/resources/extensions/gsd/auto-worktree-branch-lifecycle.ts +237 -0
  186. package/src/resources/extensions/gsd/auto-worktree-cleanup.ts +166 -0
  187. package/src/resources/extensions/gsd/auto-worktree-conflict-auto-resolve.ts +37 -0
  188. package/src/resources/extensions/gsd/auto-worktree-creation.ts +171 -0
  189. package/src/resources/extensions/gsd/auto-worktree-entry.ts +154 -0
  190. package/src/resources/extensions/gsd/auto-worktree-merge-already-merged.ts +161 -0
  191. package/src/resources/extensions/gsd/auto-worktree-merge-branch-head.ts +83 -0
  192. package/src/resources/extensions/gsd/auto-worktree-merge-cleanup.ts +132 -0
  193. package/src/resources/extensions/gsd/auto-worktree-merge-code-changes.ts +71 -0
  194. package/src/resources/extensions/gsd/auto-worktree-merge-db-ready.ts +112 -0
  195. package/src/resources/extensions/gsd/auto-worktree-merge-integration-branch.ts +99 -0
  196. package/src/resources/extensions/gsd/auto-worktree-merge-message.ts +166 -0
  197. package/src/resources/extensions/gsd/auto-worktree-merge-pre-teardown.ts +93 -0
  198. package/src/resources/extensions/gsd/auto-worktree-merge-stash.ts +213 -0
  199. package/src/resources/extensions/gsd/auto-worktree-merge.ts +391 -0
  200. package/src/resources/extensions/gsd/auto-worktree-milestone-shelter.ts +159 -0
  201. package/src/resources/extensions/gsd/auto-worktree-path-resolution.ts +6 -0
  202. package/src/resources/extensions/gsd/auto-worktree-path-utils.ts +10 -0
  203. package/src/resources/extensions/gsd/auto-worktree-resource-version.ts +44 -0
  204. package/src/resources/extensions/gsd/auto-worktree-runtime-cleanup.ts +119 -0
  205. package/src/resources/extensions/gsd/auto-worktree-session-registry.ts +71 -0
  206. package/src/resources/extensions/gsd/auto-worktree-sync.ts +370 -0
  207. package/src/resources/extensions/gsd/auto-worktree-teardown.ts +161 -0
  208. package/src/resources/extensions/gsd/auto-worktree.ts +69 -2429
  209. package/src/resources/extensions/gsd/auto.ts +11 -17
  210. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -1
  211. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +20 -2
  212. package/src/resources/extensions/gsd/bootstrap/system-context.ts +1 -1
  213. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +218 -27
  214. package/src/resources/extensions/gsd/codebase-generator.ts +7 -1
  215. package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
  216. package/src/resources/extensions/gsd/commands-workflow-templates.ts +3 -3
  217. package/src/resources/extensions/gsd/db/queries.ts +9 -9
  218. package/src/resources/extensions/gsd/db/writers/cascades.ts +23 -1
  219. package/src/resources/extensions/gsd/doctor-engine-checks.ts +109 -10
  220. package/src/resources/extensions/gsd/flat-phase-migration.ts +34 -3
  221. package/src/resources/extensions/gsd/forensics.ts +1 -1
  222. package/src/resources/extensions/gsd/git-conflict-resolve.ts +2 -2
  223. package/src/resources/extensions/gsd/git-service.ts +53 -0
  224. package/src/resources/extensions/gsd/gsd-db.ts +33 -11
  225. package/src/resources/extensions/gsd/guided-flow.ts +26 -18
  226. package/src/resources/extensions/gsd/markdown-renderer.ts +15 -3
  227. package/src/resources/extensions/gsd/md-importer.ts +93 -22
  228. package/src/resources/extensions/gsd/migration-auto-check.ts +32 -3
  229. package/src/resources/extensions/gsd/milestone-merge-transaction.ts +15 -0
  230. package/src/resources/extensions/gsd/milestone-settlement.ts +1 -1
  231. package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
  232. package/src/resources/extensions/gsd/parallel-orchestrator.ts +3 -1
  233. package/src/resources/extensions/gsd/paths.ts +21 -3
  234. package/src/resources/extensions/gsd/preferences-skills.ts +10 -16
  235. package/src/resources/extensions/gsd/preferences-types.ts +30 -0
  236. package/src/resources/extensions/gsd/preferences-validation.ts +69 -0
  237. package/src/resources/extensions/gsd/preferences.ts +20 -0
  238. package/src/resources/extensions/gsd/prompts/system.md +6 -6
  239. package/src/resources/extensions/gsd/quick.ts +11 -3
  240. package/src/resources/extensions/gsd/skill-discovery.ts +47 -23
  241. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +3 -1
  242. package/src/resources/extensions/gsd/state/derive/from-db.ts +201 -157
  243. package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +169 -1
  244. package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +2 -1
  245. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +57 -0
  246. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +1 -1
  247. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +2 -1
  248. package/src/resources/extensions/gsd/tests/auto-worktree-auto-resolve.test.ts +1 -1
  249. package/src/resources/extensions/gsd/tests/auto-worktree-merge-already-merged.test.ts +177 -0
  250. package/src/resources/extensions/gsd/tests/auto-worktree-merge-branch-head.test.ts +118 -0
  251. package/src/resources/extensions/gsd/tests/auto-worktree-merge-cleanup.test.ts +115 -0
  252. package/src/resources/extensions/gsd/tests/auto-worktree-merge-code-changes.test.ts +76 -0
  253. package/src/resources/extensions/gsd/tests/auto-worktree-merge-db-ready.test.ts +140 -0
  254. package/src/resources/extensions/gsd/tests/auto-worktree-merge-integration-branch.test.ts +123 -0
  255. package/src/resources/extensions/gsd/tests/auto-worktree-merge-message.test.ts +114 -0
  256. package/src/resources/extensions/gsd/tests/auto-worktree-merge-pre-teardown.test.ts +84 -0
  257. package/src/resources/extensions/gsd/tests/auto-worktree-merge-stash.test.ts +70 -0
  258. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-shelter.test.ts +52 -0
  259. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +6 -6
  260. package/src/resources/extensions/gsd/tests/auto-worktree-resource-version.test.ts +59 -0
  261. package/src/resources/extensions/gsd/tests/auto-worktree-untracked-content.test.ts +3 -1
  262. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +22 -0
  263. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +1 -1
  264. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +4 -0
  265. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +174 -0
  266. package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +1 -1
  267. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +71 -1
  268. package/src/resources/extensions/gsd/tests/flat-phase-renderer.test.ts +25 -1
  269. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +40 -0
  270. package/src/resources/extensions/gsd/tests/import-completion-lost-update.test.ts +196 -0
  271. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +4 -6
  272. package/src/resources/extensions/gsd/tests/integration/auto-stash-merge.test.ts +2 -1
  273. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +3 -3
  274. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +7 -8
  275. package/src/resources/extensions/gsd/tests/integration/feature-branch-lifecycle-integration.test.ts +2 -5
  276. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +76 -0
  277. package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +1 -1
  278. package/src/resources/extensions/gsd/tests/integration/merge-preserve-worktree-on-preteardown-error.test.ts +1 -1
  279. package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +2 -1
  280. package/src/resources/extensions/gsd/tests/integration/milestone-transition-worktree.test.ts +5 -7
  281. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +2 -4
  282. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +16 -15
  283. package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +1 -1
  284. package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +1 -1
  285. package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +1 -1
  286. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +91 -0
  287. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +439 -0
  288. package/src/resources/extensions/gsd/tests/milestone-merge-transaction.test.ts +24 -0
  289. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +1 -1
  290. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -3
  291. package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +1 -1
  292. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +1 -4
  293. package/src/resources/extensions/gsd/tests/preferences.test.ts +19 -0
  294. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +5 -0
  295. package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +52 -0
  296. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +20 -0
  297. package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +46 -0
  298. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +3 -5
  299. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +2 -1
  300. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +2 -1
  301. package/src/resources/extensions/gsd/tests/steer-worktree-path.test.ts +1 -1
  302. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +1 -1
  303. package/src/resources/extensions/gsd/tests/system-flat-phase-layout.test.ts +33 -0
  304. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +4 -4
  305. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +2 -1
  306. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +4 -4
  307. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +196 -0
  308. package/src/resources/extensions/gsd/tests/uat-stuck-loop-orphaned-worktree.test.ts +1 -1
  309. package/src/resources/extensions/gsd/tests/unit-registry.test.ts +26 -1
  310. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -1
  311. package/src/resources/extensions/gsd/tests/worktree-db-respawn-truncation.test.ts +1 -1
  312. package/src/resources/extensions/gsd/tests/worktree-db-same-file.test.ts +1 -3
  313. package/src/resources/extensions/gsd/tests/worktree-expected-warnings.test.ts +1 -1
  314. package/src/resources/extensions/gsd/tests/worktree-git-pathspec.test.ts +1 -1
  315. package/src/resources/extensions/gsd/tests/worktree-main-branch.test.ts +1 -1
  316. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +1 -1
  317. package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +2 -1
  318. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +5 -2
  319. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +1 -1
  320. package/src/resources/extensions/gsd/tests/worktree-sync-tasks.test.ts +1 -1
  321. package/src/resources/extensions/gsd/tool-contract.ts +29 -0
  322. package/src/resources/extensions/gsd/tools/reopen-slice.ts +5 -0
  323. package/src/resources/extensions/gsd/tools/reopen-task.ts +13 -5
  324. package/src/resources/extensions/gsd/unit-registry.ts +16 -2
  325. package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +1 -1
  326. package/src/resources/extensions/gsd/validation-block-guard.ts +2 -1
  327. package/src/resources/extensions/gsd/workspace-git-preflight.ts +2 -2
  328. package/src/resources/extensions/gsd/worktree-command.ts +1 -1
  329. package/src/resources/extensions/gsd/worktree-lifecycle.ts +19 -15
  330. package/src/resources/extensions/gsd/worktree-reentry.ts +2 -1
  331. package/src/resources/extensions/shared/interview-ui.ts +58 -11
  332. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +32 -0
  333. /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → gXoWec4UNqcR0tkuHwYCr}/_buildManifest.js +0 -0
  334. /package/dist/web/standalone/.next/static/{L2UMqXELiDH3oz-7yguXp → gXoWec4UNqcR0tkuHwYCr}/_ssgManifest.js +0 -0
@@ -177,24 +177,18 @@ import {
177
177
  import { GitServiceImpl } from "./git-service.js";
178
178
  import { nativeCheckoutBranch } from "./native-git-bridge.js";
179
179
  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";
180
+ import { autoWorktreeBranch, enterBranchModeForMilestone } from "./auto-worktree-branch-lifecycle.js";
181
+ import { createAutoWorktree } from "./auto-worktree-creation.js";
182
+ import { enterAutoWorktree, isInAutoWorktree } from "./auto-worktree-entry.js";
183
+ import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
184
+ import { checkResourcesStale, readResourceVersion } from "./auto-worktree-resource-version.js";
185
+ import { escapeStaleWorktree } from "./auto-worktree-runtime-cleanup.js";
186
+ import { getAutoWorktreeOriginalBase } from "./auto-worktree-session-registry.js";
187
+ import { syncWorktreeStateBack } from "./auto-worktree-sync.js";
188
+ import { teardownAutoWorktree } from "./auto-worktree-teardown.js";
195
189
  import { pruneQueueOrder } from "./queue-order.js";
196
190
  import { startCommandPolling as _startCommandPolling, isRemoteConfigured } from "../remote-questions/manager.js";
197
- import { createMilestoneMergeTransaction } from "./milestone-merge-transaction.js";
191
+ import { createDefaultMilestoneMergeTransaction } from "./milestone-merge-transaction.js";
198
192
 
199
193
  import { debugLog, isDebugEnabled, writeDebugSummary } from "./debug-logger.js";
200
194
  import {
@@ -2326,7 +2320,7 @@ export function buildWorktreeLifecycleDeps(): WorktreeLifecycleDeps {
2326
2320
  return new GitServiceImpl(basePath, gitConfig);
2327
2321
  },
2328
2322
  worktreeProjection: new WorktreeStateProjection(),
2329
- mergeMilestone: createMilestoneMergeTransaction(mergeMilestoneToMain),
2323
+ mergeMilestone: createDefaultMilestoneMergeTransaction(),
2330
2324
  };
2331
2325
  }
2332
2326
 
@@ -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
 
@@ -25,7 +25,7 @@ import {
25
25
  filterDoctorIssues,
26
26
  } from "./doctor.js";
27
27
  import { isAutoActive, checkRemoteAutoSession } from "./auto.js";
28
- import { getAutoWorktreePath } from "./auto-worktree.js";
28
+ import { getAutoWorktreePath } from "./auto-worktree-path-resolution.js";
29
29
  import { currentDirectoryRoot, projectRoot } from "./commands/context.js";
30
30
  import { loadPrompt } from "./prompt-loader.js";
31
31
  import { buildClaudeRuntimeFloorAdvisory } from "../../shared/claude-runtime-floor.js";
@@ -23,7 +23,7 @@ import {
23
23
  } from "./workflow-templates.js";
24
24
  import { loadPrompt } from "./prompt-loader.js";
25
25
  import { gsdRoot } from "./paths.js";
26
- import { createGitService, runGit } from "./git-service.js";
26
+ import { createGitService, runGit, taskBranchArgs } from "./git-service.js";
27
27
  import { isAutoActive, isAutoPaused } from "./auto.js";
28
28
  import { getErrorMessage } from "./error-utils.js";
29
29
  import { resolvePlugin, type WorkflowPlugin } from "./workflow-plugins.js";
@@ -464,7 +464,7 @@ export async function handleStart(
464
464
  try {
465
465
  git.autoCommit("workflow-template", templateId, []);
466
466
  } catch { /* nothing to commit */ }
467
- runGit(basePath, ["checkout", "-b", branchName]);
467
+ runGit(basePath, taskBranchArgs(basePath, branchName, current, git.getMainBranch()));
468
468
  branchCreated = true;
469
469
  }
470
470
  } catch (err) {
@@ -636,7 +636,7 @@ export function dispatchMarkdownPhasePlugin(
636
636
  const current = git.getCurrentBranch();
637
637
  if (current !== branchName) {
638
638
  try { git.autoCommit("workflow-template", templateId, []); } catch { /* nothing to commit */ }
639
- runGit(basePath, ["checkout", "-b", branchName]);
639
+ runGit(basePath, taskBranchArgs(basePath, branchName, current, git.getMainBranch()));
640
640
  branchCreated = true;
641
641
  }
642
642
  } catch (err) {
@@ -281,7 +281,7 @@ export function getMilestone(id: string): MilestoneRow | null {
281
281
  export function getActiveMilestoneFromDb(): MilestoneRow | null {
282
282
  if (!getDbOrNull()!) return null;
283
283
  const row = getDbOrNull()!.prepare(
284
- "SELECT * FROM milestones WHERE status NOT IN ('complete', 'done', 'skipped', 'closed', 'parked') ORDER BY id LIMIT 1",
284
+ `SELECT * FROM milestones WHERE status NOT IN (${TERMINAL_STATUS_SQL}, 'parked') ORDER BY id LIMIT 1`,
285
285
  ).get();
286
286
  if (!row) return null;
287
287
  return rowToMilestone(row);
@@ -295,11 +295,11 @@ export function getActiveSliceFromDb(milestoneId: string): SliceRow | null {
295
295
  const row = getDbOrNull()!.prepare(
296
296
  `SELECT s.* FROM slices s
297
297
  WHERE s.milestone_id = :mid
298
- AND s.status NOT IN ('complete', 'done', 'skipped')
298
+ AND s.status NOT IN (${TERMINAL_STATUS_SQL})
299
299
  AND NOT EXISTS (
300
300
  SELECT 1 FROM json_each(s.depends) AS dep
301
301
  WHERE dep.value NOT IN (
302
- SELECT id FROM slices WHERE milestone_id = :mid AND status IN ('complete', 'done', 'skipped')
302
+ SELECT id FROM slices WHERE milestone_id = :mid AND status IN (${TERMINAL_STATUS_SQL})
303
303
  )
304
304
  )
305
305
  ORDER BY s.sequence, s.id
@@ -312,7 +312,7 @@ export function getActiveSliceFromDb(milestoneId: string): SliceRow | null {
312
312
  export function getActiveTaskFromDb(milestoneId: string, sliceId: string): TaskRow | null {
313
313
  if (!getDbOrNull()!) return null;
314
314
  const row = getDbOrNull()!.prepare(
315
- "SELECT * FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND status NOT IN ('complete', 'done') ORDER BY sequence, id LIMIT 1",
315
+ `SELECT * FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND status NOT IN (${TERMINAL_STATUS_SQL}) ORDER BY sequence, id LIMIT 1`,
316
316
  ).get({ ":mid": milestoneId, ":sid": sliceId });
317
317
  if (!row) return null;
318
318
  return rowToTask(row);
@@ -339,7 +339,7 @@ export function getParallelMonitorSliceProgress(milestoneId: string): ParallelMo
339
339
  s.id AS id,
340
340
  s.status AS status,
341
341
  COUNT(t.id) AS total,
342
- COALESCE(SUM(CASE WHEN t.status='complete' THEN 1 ELSE 0 END), 0) AS done
342
+ COALESCE(SUM(CASE WHEN t.status IN (${TERMINAL_STATUS_SQL}) THEN 1 ELSE 0 END), 0) AS done
343
343
  FROM slices s
344
344
  LEFT JOIN tasks t ON s.milestone_id=t.milestone_id AND s.id=t.slice_id
345
345
  WHERE s.milestone_id=:mid
@@ -518,7 +518,7 @@ export function getSliceScopedArtifacts(milestoneId: string, sliceId: string): A
518
518
  export function getActiveMilestoneIdFromDb(): IdStatusSummary | null {
519
519
  if (!getDbOrNull()!) return null;
520
520
  const row = getDbOrNull()!.prepare(
521
- "SELECT id, status FROM milestones WHERE status NOT IN ('complete', 'done', 'skipped', 'closed', 'parked') ORDER BY id LIMIT 1",
521
+ `SELECT id, status FROM milestones WHERE status NOT IN (${TERMINAL_STATUS_SQL}, 'parked') ORDER BY id LIMIT 1`,
522
522
  ).get();
523
523
  if (!row) return null;
524
524
  return rowToIdStatusSummary(row);
@@ -536,7 +536,7 @@ export function getSliceStatusSummary(milestoneId: string): IdStatusSummary[] {
536
536
  export function getActiveTaskIdFromDb(milestoneId: string, sliceId: string): ActiveTaskSummary | null {
537
537
  if (!getDbOrNull()!) return null;
538
538
  const row = getDbOrNull()!.prepare(
539
- "SELECT id, status, title FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND status NOT IN ('complete', 'done') ORDER BY sequence, id LIMIT 1",
539
+ `SELECT id, status, title FROM tasks WHERE milestone_id = :mid AND slice_id = :sid AND status NOT IN (${TERMINAL_STATUS_SQL}) ORDER BY sequence, id LIMIT 1`,
540
540
  ).get({ ":mid": milestoneId, ":sid": sliceId });
541
541
  if (!row) return null;
542
542
  return rowToActiveTaskSummary(row);
@@ -548,8 +548,8 @@ export function getSliceTaskCounts(milestoneId: string, sliceId: string): TaskSt
548
548
  const row = getDbOrNull()!.prepare(
549
549
  `SELECT
550
550
  COUNT(*) as total,
551
- SUM(CASE WHEN status IN ('complete', 'done') THEN 1 ELSE 0 END) as done,
552
- SUM(CASE WHEN status NOT IN ('complete', 'done') THEN 1 ELSE 0 END) as pending
551
+ SUM(CASE WHEN status IN (${TERMINAL_STATUS_SQL}) THEN 1 ELSE 0 END) as done,
552
+ SUM(CASE WHEN status NOT IN (${TERMINAL_STATUS_SQL}) THEN 1 ELSE 0 END) as pending
553
553
  FROM tasks WHERE milestone_id = :mid AND slice_id = :sid`,
554
554
  ).get({ ":mid": milestoneId, ":sid": sliceId });
555
555
  return rowToTaskStatusCounts(row);
@@ -38,6 +38,17 @@ export type ReopenSliceOutcome =
38
38
  * completion timestamps cleared, in one commit. Folds the hand-rolled
39
39
  * transaction-plus-cascade previously in tools/reopen-slice.ts. Guards run
40
40
  * inside the transaction (TOCTOU-safe).
41
+ *
42
+ * A closed slice is always reopenable. An open slice is only reopenable when it
43
+ * carries a completed-task state desync (#1205): a UAT→planning fallback can
44
+ * leave the slice "pending" while *all* its tasks stay "complete", which
45
+ * deadlocks every recovery path (plan-slice/replan-slice/complete-task all
46
+ * reject the still-closed tasks, and slice_reopen historically rejected the
47
+ * still-open slice). Resetting those tasks to pending gives the planner the
48
+ * clean slate it needs. An open slice that still has any unfinished
49
+ * (pending/in-progress) task is a normal in-flight slice, not the desync, and
50
+ * stays rejected as "slice-not-complete" so a full reset can't wipe its
51
+ * legitimate progress.
41
52
  */
42
53
  export function reopenSliceCascade(milestoneId: string, sliceId: string): ReopenSliceOutcome {
43
54
  requireDb();
@@ -48,9 +59,20 @@ export function reopenSliceCascade(milestoneId: string, sliceId: string): Reopen
48
59
  if (isClosedStatus(milestone.status)) { outcome = { ok: false, reason: "milestone-closed", status: milestone.status }; return; }
49
60
  const slice = getSlice(milestoneId, sliceId);
50
61
  if (!slice) { outcome = { ok: false, reason: "slice-not-found" }; return; }
51
- if (!isClosedStatus(slice.status)) { outcome = { ok: false, reason: "slice-not-complete", status: slice.status }; return; }
52
62
 
53
63
  const tasks = getSliceTasks(milestoneId, sliceId);
64
+ // A closed slice is always reopenable. An open slice is only the #1205
65
+ // desync when EVERY task is already closed (a UAT→planning fallback left the
66
+ // slice open with all tasks still "complete"). Requiring *all* tasks closed
67
+ // (not merely one) means a normal in-flight slice — which keeps at least one
68
+ // pending/in-progress task — is rejected, so a full reset can't wipe its
69
+ // legitimate progress.
70
+ const allTasksClosed = tasks.length > 0 && tasks.every((t) => isClosedStatus(t.status));
71
+ if (!isClosedStatus(slice.status) && !allTasksClosed) {
72
+ outcome = { ok: false, reason: "slice-not-complete", status: slice.status };
73
+ return;
74
+ }
75
+
54
76
  getDbOrNull()!.prepare(
55
77
  `UPDATE slices SET status = 'in_progress', completed_at = NULL WHERE milestone_id = :mid AND id = :sid`,
56
78
  ).run({ ":mid": milestoneId, ":sid": sliceId });