gsd-pi 2.41.0-dev.cac69f9 → 2.42.0-dev.1df898f

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 (263) hide show
  1. package/README.md +23 -0
  2. package/dist/cli.js +18 -3
  3. package/dist/loader.js +3 -1
  4. package/dist/resource-loader.js +39 -6
  5. package/dist/resources/extensions/async-jobs/async-bash-tool.js +52 -4
  6. package/dist/resources/extensions/async-jobs/await-tool.js +5 -0
  7. package/dist/resources/extensions/async-jobs/index.js +2 -0
  8. package/dist/resources/extensions/gsd/auto/loop.js +80 -0
  9. package/dist/resources/extensions/gsd/auto/phases.js +3 -5
  10. package/dist/resources/extensions/gsd/auto/session.js +6 -0
  11. package/dist/resources/extensions/gsd/auto-dashboard.js +2 -0
  12. package/dist/resources/extensions/gsd/auto-prompts.js +3 -16
  13. package/dist/resources/extensions/gsd/auto-start.js +8 -11
  14. package/dist/resources/extensions/gsd/auto.js +28 -1
  15. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -5
  16. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +7 -2
  17. package/dist/resources/extensions/gsd/commands/catalog.js +32 -0
  18. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +146 -0
  19. package/dist/resources/extensions/gsd/context-injector.js +74 -0
  20. package/dist/resources/extensions/gsd/custom-execution-policy.js +47 -0
  21. package/dist/resources/extensions/gsd/custom-verification.js +145 -0
  22. package/dist/resources/extensions/gsd/custom-workflow-engine.js +164 -0
  23. package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -0
  24. package/dist/resources/extensions/gsd/definition-loader.js +352 -0
  25. package/dist/resources/extensions/gsd/detection.js +19 -0
  26. package/dist/resources/extensions/gsd/dev-execution-policy.js +24 -0
  27. package/dist/resources/extensions/gsd/dev-workflow-engine.js +82 -0
  28. package/dist/resources/extensions/gsd/doctor-checks.js +31 -1
  29. package/dist/resources/extensions/gsd/doctor-providers.js +10 -0
  30. package/dist/resources/extensions/gsd/engine-resolver.js +40 -0
  31. package/dist/resources/extensions/gsd/engine-types.js +8 -0
  32. package/dist/resources/extensions/gsd/execution-policy.js +8 -0
  33. package/dist/resources/extensions/gsd/forensics.js +84 -0
  34. package/dist/resources/extensions/gsd/git-constants.js +1 -0
  35. package/dist/resources/extensions/gsd/git-service.js +1 -1
  36. package/dist/resources/extensions/gsd/graph.js +225 -0
  37. package/dist/resources/extensions/gsd/native-git-bridge.js +1 -0
  38. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  39. package/dist/resources/extensions/gsd/preferences.js +59 -8
  40. package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
  41. package/dist/resources/extensions/gsd/repo-identity.js +46 -5
  42. package/dist/resources/extensions/gsd/run-manager.js +134 -0
  43. package/dist/resources/extensions/gsd/service-tier.js +13 -4
  44. package/dist/resources/extensions/gsd/session-lock.js +2 -2
  45. package/dist/resources/extensions/gsd/workflow-engine.js +7 -0
  46. package/dist/resources/extensions/gsd/worktree-resolver.js +2 -2
  47. package/dist/resources/extensions/gsd/worktree.js +2 -2
  48. package/dist/resources/extensions/mcp-client/index.js +2 -1
  49. package/dist/resources/extensions/search-the-web/tool-search.js +3 -3
  50. package/dist/resources/skills/create-workflow/SKILL.md +103 -0
  51. package/dist/resources/skills/create-workflow/references/feature-patterns.md +128 -0
  52. package/dist/resources/skills/create-workflow/references/verification-policies.md +76 -0
  53. package/dist/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
  54. package/dist/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
  55. package/dist/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
  56. package/dist/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
  57. package/dist/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
  58. package/dist/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
  59. package/dist/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
  60. package/dist/web/standalone/.next/BUILD_ID +1 -1
  61. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  62. package/dist/web/standalone/.next/build-manifest.json +2 -2
  63. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  64. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  65. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/index.html +1 -1
  82. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  89. package/dist/web/standalone/.next/server/chunks/229.js +2 -2
  90. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  91. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  92. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  93. package/dist/web-mode.d.ts +2 -0
  94. package/dist/web-mode.js +40 -4
  95. package/package.json +1 -1
  96. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  97. package/packages/pi-agent-core/dist/agent.js +2 -0
  98. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  99. package/packages/pi-agent-core/dist/types.d.ts +6 -0
  100. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  101. package/packages/pi-agent-core/dist/types.js.map +1 -1
  102. package/packages/pi-agent-core/src/agent.test.ts +53 -0
  103. package/packages/pi-agent-core/src/agent.ts +3 -0
  104. package/packages/pi-agent-core/src/types.ts +6 -0
  105. package/packages/pi-agent-core/tsconfig.json +1 -1
  106. package/packages/pi-ai/dist/models.d.ts +5 -3
  107. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  108. package/packages/pi-ai/dist/models.generated.d.ts +801 -1468
  109. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  110. package/packages/pi-ai/dist/models.generated.js +1135 -1588
  111. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  112. package/packages/pi-ai/dist/models.js.map +1 -1
  113. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  114. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +60 -2
  115. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  116. package/packages/pi-ai/scripts/generate-models.ts +1543 -0
  117. package/packages/pi-ai/src/models.generated.ts +1140 -1593
  118. package/packages/pi-ai/src/models.ts +7 -4
  119. package/packages/pi-ai/src/utils/oauth/github-copilot.ts +74 -2
  120. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/agent-session.js +8 -1
  122. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +7 -0
  124. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/auth-storage.js +29 -2
  126. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +60 -0
  128. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
  131. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/core/lsp/client.js +23 -0
  134. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/model-registry.js +2 -0
  137. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +6 -0
  139. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/package-manager.js +63 -11
  141. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +9 -0
  143. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  144. package/packages/pi-coding-agent/dist/core/resource-loader.js +20 -6
  145. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  146. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -5
  148. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js +3 -0
  151. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  153. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +9 -6
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  155. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +30 -10
  157. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  158. package/packages/pi-coding-agent/package.json +1 -1
  159. package/packages/pi-coding-agent/src/core/agent-session.ts +7 -1
  160. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +68 -0
  161. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -2
  162. package/packages/pi-coding-agent/src/core/extensions/loader.ts +18 -0
  163. package/packages/pi-coding-agent/src/core/lsp/client.ts +29 -0
  164. package/packages/pi-coding-agent/src/core/model-registry.ts +3 -0
  165. package/packages/pi-coding-agent/src/core/package-manager.ts +99 -58
  166. package/packages/pi-coding-agent/src/core/resource-loader.ts +24 -6
  167. package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -5
  168. package/packages/pi-coding-agent/src/modes/interactive/components/extension-editor.ts +3 -0
  169. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +10 -6
  170. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +31 -11
  171. package/pkg/package.json +1 -1
  172. package/src/resources/extensions/async-jobs/async-bash-timeout.test.ts +122 -0
  173. package/src/resources/extensions/async-jobs/async-bash-tool.ts +40 -4
  174. package/src/resources/extensions/async-jobs/await-tool.test.ts +47 -0
  175. package/src/resources/extensions/async-jobs/await-tool.ts +5 -0
  176. package/src/resources/extensions/async-jobs/index.ts +1 -0
  177. package/src/resources/extensions/async-jobs/job-manager.ts +2 -0
  178. package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -1
  179. package/src/resources/extensions/gsd/auto/loop.ts +91 -0
  180. package/src/resources/extensions/gsd/auto/phases.ts +3 -5
  181. package/src/resources/extensions/gsd/auto/session.ts +6 -0
  182. package/src/resources/extensions/gsd/auto-dashboard.ts +2 -0
  183. package/src/resources/extensions/gsd/auto-prompts.ts +2 -18
  184. package/src/resources/extensions/gsd/auto-start.ts +7 -10
  185. package/src/resources/extensions/gsd/auto.ts +31 -1
  186. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -5
  187. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +9 -2
  188. package/src/resources/extensions/gsd/commands/catalog.ts +32 -0
  189. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +164 -0
  190. package/src/resources/extensions/gsd/context-injector.ts +100 -0
  191. package/src/resources/extensions/gsd/custom-execution-policy.ts +73 -0
  192. package/src/resources/extensions/gsd/custom-verification.ts +180 -0
  193. package/src/resources/extensions/gsd/custom-workflow-engine.ts +216 -0
  194. package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -0
  195. package/src/resources/extensions/gsd/definition-loader.ts +462 -0
  196. package/src/resources/extensions/gsd/detection.ts +19 -0
  197. package/src/resources/extensions/gsd/dev-execution-policy.ts +51 -0
  198. package/src/resources/extensions/gsd/dev-workflow-engine.ts +110 -0
  199. package/src/resources/extensions/gsd/doctor-checks.ts +32 -1
  200. package/src/resources/extensions/gsd/doctor-providers.ts +13 -0
  201. package/src/resources/extensions/gsd/doctor-types.ts +1 -0
  202. package/src/resources/extensions/gsd/engine-resolver.ts +57 -0
  203. package/src/resources/extensions/gsd/engine-types.ts +71 -0
  204. package/src/resources/extensions/gsd/execution-policy.ts +43 -0
  205. package/src/resources/extensions/gsd/forensics.ts +92 -0
  206. package/src/resources/extensions/gsd/git-constants.ts +1 -0
  207. package/src/resources/extensions/gsd/git-service.ts +0 -1
  208. package/src/resources/extensions/gsd/gitignore.ts +1 -1
  209. package/src/resources/extensions/gsd/graph.ts +312 -0
  210. package/src/resources/extensions/gsd/native-git-bridge.ts +1 -0
  211. package/src/resources/extensions/gsd/preferences-types.ts +3 -0
  212. package/src/resources/extensions/gsd/preferences.ts +62 -6
  213. package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
  214. package/src/resources/extensions/gsd/repo-identity.ts +48 -5
  215. package/src/resources/extensions/gsd/run-manager.ts +180 -0
  216. package/src/resources/extensions/gsd/service-tier.ts +17 -4
  217. package/src/resources/extensions/gsd/session-lock.ts +2 -2
  218. package/src/resources/extensions/gsd/tests/activity-log.test.ts +31 -69
  219. package/src/resources/extensions/gsd/tests/bundled-workflow-defs.test.ts +180 -0
  220. package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +283 -0
  221. package/src/resources/extensions/gsd/tests/context-injector.test.ts +313 -0
  222. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +540 -0
  223. package/src/resources/extensions/gsd/tests/custom-verification.test.ts +382 -0
  224. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +339 -0
  225. package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +87 -0
  226. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +778 -0
  227. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +318 -0
  228. package/src/resources/extensions/gsd/tests/e2e-workflow-pipeline-integration.test.ts +476 -0
  229. package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +271 -0
  230. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +48 -0
  231. package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +43 -0
  232. package/src/resources/extensions/gsd/tests/git-locale.test.ts +133 -0
  233. package/src/resources/extensions/gsd/tests/git-service.test.ts +44 -0
  234. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +599 -0
  235. package/src/resources/extensions/gsd/tests/iterate-engine-integration.test.ts +429 -0
  236. package/src/resources/extensions/gsd/tests/journal.test.ts +82 -127
  237. package/src/resources/extensions/gsd/tests/manifest-status.test.ts +73 -82
  238. package/src/resources/extensions/gsd/tests/run-manager.test.ts +229 -0
  239. package/src/resources/extensions/gsd/tests/service-tier.test.ts +30 -1
  240. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +56 -3
  241. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +151 -0
  242. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +45 -0
  243. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +156 -263
  244. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -78
  245. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +81 -74
  246. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +1 -2
  247. package/src/resources/extensions/gsd/workflow-engine.ts +38 -0
  248. package/src/resources/extensions/gsd/worktree-resolver.ts +2 -3
  249. package/src/resources/extensions/gsd/worktree.ts +2 -2
  250. package/src/resources/extensions/mcp-client/index.ts +5 -1
  251. package/src/resources/extensions/search-the-web/tool-search.ts +3 -3
  252. package/src/resources/skills/create-workflow/SKILL.md +103 -0
  253. package/src/resources/skills/create-workflow/references/feature-patterns.md +128 -0
  254. package/src/resources/skills/create-workflow/references/verification-policies.md +76 -0
  255. package/src/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
  256. package/src/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
  257. package/src/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
  258. package/src/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
  259. package/src/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
  260. package/src/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
  261. package/src/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
  262. /package/dist/web/standalone/.next/static/{EnGUNqHeGbE0tuuUkTJVA → qw8qDHXOTLUXBq1vEknSz}/_buildManifest.js +0 -0
  263. /package/dist/web/standalone/.next/static/{EnGUNqHeGbE0tuuUkTJVA → qw8qDHXOTLUXBq1vEknSz}/_ssgManifest.js +0 -0
@@ -1519,6 +1519,13 @@ export class InteractiveMode {
1519
1519
  options: string[],
1520
1520
  opts?: ExtensionUIDialogOptions,
1521
1521
  ): Promise<string | undefined> {
1522
+ // If a previous selector is still active, dispose it before creating a
1523
+ // new one. This avoids leaking the previous promise and DOM state when
1524
+ // showExtensionSelector is called rapidly.
1525
+ if (this.extensionSelector) {
1526
+ this.hideExtensionSelector();
1527
+ }
1528
+
1522
1529
  return new Promise((resolve) => {
1523
1530
  if (opts?.signal?.aborted) {
1524
1531
  resolve(undefined);
@@ -2331,18 +2338,24 @@ export class InteractiveMode {
2331
2338
  const ignoreSigint = () => {};
2332
2339
  process.on("SIGINT", ignoreSigint);
2333
2340
 
2334
- // Set up handler to restore TUI when resumed
2335
- process.once("SIGCONT", () => {
2336
- process.removeListener("SIGINT", ignoreSigint);
2337
- this.ui.start();
2338
- this.ui.requestRender(true);
2339
- });
2341
+ try {
2342
+ // Set up handler to restore TUI when resumed
2343
+ process.once("SIGCONT", () => {
2344
+ process.removeListener("SIGINT", ignoreSigint);
2345
+ this.ui.start();
2346
+ this.ui.requestRender(true);
2347
+ });
2340
2348
 
2341
- // Stop the TUI (restore terminal to normal mode)
2342
- this.ui.stop();
2349
+ // Stop the TUI (restore terminal to normal mode)
2350
+ this.ui.stop();
2343
2351
 
2344
- // Send SIGTSTP to process group (pid=0 means all processes in group)
2345
- process.kill(0, "SIGTSTP");
2352
+ // Send SIGTSTP to process group (pid=0 means all processes in group)
2353
+ process.kill(0, "SIGTSTP");
2354
+ } catch {
2355
+ // If suspend fails (e.g. SIGTSTP not supported), ensure the
2356
+ // SIGINT listener doesn't leak.
2357
+ process.removeListener("SIGINT", ignoreSigint);
2358
+ }
2346
2359
  }
2347
2360
 
2348
2361
  private async handleFollowUp(): Promise<void> {
@@ -2460,7 +2473,14 @@ export class InteractiveMode {
2460
2473
  // Determine editor (respect $VISUAL, then $EDITOR)
2461
2474
  const editorCmd = process.env.VISUAL || process.env.EDITOR;
2462
2475
  if (!editorCmd) {
2463
- this.showWarning("No editor configured. Set $VISUAL or $EDITOR environment variable.");
2476
+ let msg = "No editor configured. Set $VISUAL or $EDITOR environment variable.";
2477
+ if (process.env.TERM_PROGRAM === "iTerm.app") {
2478
+ msg +=
2479
+ "\n\nTip: If you meant to open the GSD dashboard (Ctrl+Alt+G), set Left Option Key to" +
2480
+ " \"Esc+\" in iTerm2 → Profiles → Keys. With the default \"Normal\" setting," +
2481
+ " Ctrl+Alt+G sends Ctrl+G instead.";
2482
+ }
2483
+ this.showWarning(msg);
2464
2484
  return;
2465
2485
  }
2466
2486
 
package/pkg/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glittercowboy/gsd",
3
- "version": "2.41.0",
3
+ "version": "2.42.0",
4
4
  "piConfig": {
5
5
  "name": "gsd",
6
6
  "configDir": ".gsd"
@@ -0,0 +1,122 @@
1
+ /**
2
+ * async-bash-timeout.test.ts — Tests for async_bash timeout behavior.
3
+ *
4
+ * Reproduces issue #2186: when an async bash job exceeds its timeout and
5
+ * the child process ignores SIGTERM, the promise hangs indefinitely.
6
+ * The fix adds a SIGKILL fallback and a hard deadline that force-resolves
7
+ * the promise so execution can continue.
8
+ */
9
+
10
+ import test from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import { createAsyncBashTool } from "./async-bash-tool.ts";
13
+ import { AsyncJobManager } from "./job-manager.ts";
14
+
15
+ function getTextFromResult(result: { content: Array<{ type: string; text?: string }> }): string {
16
+ return result.content.map((c) => c.text ?? "").join("\n");
17
+ }
18
+
19
+ const noopSignal = new AbortController().signal;
20
+
21
+ test("async_bash with timeout resolves even if process ignores SIGTERM", async () => {
22
+ const manager = new AsyncJobManager();
23
+ const tool = createAsyncBashTool(() => manager, () => process.cwd());
24
+
25
+ // Start a job that traps SIGTERM (ignores it), with a 2s timeout.
26
+ // The process installs a SIGTERM trap and sleeps for 60s.
27
+ // Before the fix, this would hang forever because SIGTERM is ignored
28
+ // and the close event never fires.
29
+ const result = await tool.execute(
30
+ "tc-timeout",
31
+ {
32
+ command: "trap '' TERM; sleep 60",
33
+ timeout: 2,
34
+ label: "sigterm-resistant",
35
+ },
36
+ noopSignal,
37
+ () => {},
38
+ undefined as never,
39
+ );
40
+
41
+ const text = getTextFromResult(result);
42
+ assert.match(text, /sigterm-resistant/);
43
+
44
+ const jobId = text.match(/\*\*(bg_[a-f0-9]+)\*\*/)?.[1];
45
+ assert.ok(jobId, "Should have returned a job ID");
46
+
47
+ // Now await the job — it should resolve within a reasonable time
48
+ // (timeout 2s + SIGKILL grace 5s + buffer = well under 15s)
49
+ const start = Date.now();
50
+ const job = manager.getJob(jobId)!;
51
+ assert.ok(job, "Job should exist");
52
+
53
+ await Promise.race([
54
+ job.promise,
55
+ new Promise<never>((_, reject) => {
56
+ const t = setTimeout(() => reject(new Error(
57
+ `Job promise hung for ${Date.now() - start}ms — ` +
58
+ `this is the bug from issue #2186: timeout hangs indefinitely`,
59
+ )), 15_000);
60
+ if (typeof t === "object" && "unref" in t) t.unref();
61
+ }),
62
+ ]);
63
+
64
+ const elapsed = Date.now() - start;
65
+ // Should have resolved well within 15s (timeout 2s + kill grace ~5s)
66
+ assert.ok(elapsed < 15_000, `Job took ${elapsed}ms — expected <15s`);
67
+
68
+ // Job should have completed (resolved, not rejected) with timeout message
69
+ assert.ok(
70
+ job.status === "completed" || job.status === "failed",
71
+ `Job status should be completed or failed, got: ${job.status}`,
72
+ );
73
+
74
+ if (job.status === "completed") {
75
+ assert.ok(
76
+ job.resultText?.includes("timed out") || job.resultText?.includes("Timed out"),
77
+ `Result should mention timeout, got: ${job.resultText}`,
78
+ );
79
+ }
80
+
81
+ manager.shutdown();
82
+ });
83
+
84
+ test("async_bash with timeout resolves normally when process exits on SIGTERM", async () => {
85
+ const manager = new AsyncJobManager();
86
+ const tool = createAsyncBashTool(() => manager, () => process.cwd());
87
+
88
+ // Start a normal sleep that will die on SIGTERM, with a 1s timeout
89
+ const result = await tool.execute(
90
+ "tc-normal-timeout",
91
+ {
92
+ command: "sleep 60",
93
+ timeout: 1,
94
+ label: "normal-timeout",
95
+ },
96
+ noopSignal,
97
+ () => {},
98
+ undefined as never,
99
+ );
100
+
101
+ const text = getTextFromResult(result);
102
+ const jobId = text.match(/\*\*(bg_[a-f0-9]+)\*\*/)?.[1];
103
+ assert.ok(jobId, "Should have returned a job ID");
104
+
105
+ const job = manager.getJob(jobId)!;
106
+ const start = Date.now();
107
+
108
+ await Promise.race([
109
+ job.promise,
110
+ new Promise<never>((_, reject) => {
111
+ const t = setTimeout(() => reject(new Error("Job hung")), 10_000);
112
+ if (typeof t === "object" && "unref" in t) t.unref();
113
+ }),
114
+ ]);
115
+
116
+ const elapsed = Date.now() - start;
117
+ assert.ok(elapsed < 5_000, `Expected quick resolution after SIGTERM, took ${elapsed}ms`);
118
+ assert.equal(job.status, "completed");
119
+ assert.ok(job.resultText?.includes("timed out"), `Should mention timeout: ${job.resultText}`);
120
+
121
+ manager.shutdown();
122
+ });
@@ -109,6 +109,10 @@ function executeBashInBackground(
109
109
  timeout?: number,
110
110
  ): Promise<string> {
111
111
  return new Promise<string>((resolve, reject) => {
112
+ let settled = false;
113
+ const safeResolve = (value: string) => { if (!settled) { settled = true; resolve(value); } };
114
+ const safeReject = (err: unknown) => { if (!settled) { settled = true; reject(err); } };
115
+
112
116
  const { shell, args } = getShellConfig();
113
117
  const resolvedCommand = sanitizeCommand(command);
114
118
 
@@ -121,11 +125,39 @@ function executeBashInBackground(
121
125
 
122
126
  let timedOut = false;
123
127
  let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
128
+ let sigkillHandle: ReturnType<typeof setTimeout> | undefined;
129
+ let hardDeadlineHandle: ReturnType<typeof setTimeout> | undefined;
130
+
131
+ /** Grace period (ms) between SIGTERM and SIGKILL. */
132
+ const SIGKILL_GRACE_MS = 5_000;
133
+ /** Hard deadline (ms) after SIGKILL to force-resolve the promise. */
134
+ const HARD_DEADLINE_MS = 3_000;
124
135
 
125
136
  if (timeout !== undefined && timeout > 0) {
126
137
  timeoutHandle = setTimeout(() => {
127
138
  timedOut = true;
128
139
  if (child.pid) killTree(child.pid);
140
+
141
+ // If the process ignores SIGTERM, escalate to SIGKILL
142
+ sigkillHandle = setTimeout(() => {
143
+ if (child.pid) {
144
+ try { process.kill(-child.pid, "SIGKILL"); } catch { /* ignore */ }
145
+ try { process.kill(child.pid, "SIGKILL"); } catch { /* ignore */ }
146
+ }
147
+
148
+ // Hard deadline: if even SIGKILL doesn't trigger 'close',
149
+ // force-resolve so the job doesn't hang forever (#2186).
150
+ hardDeadlineHandle = setTimeout(() => {
151
+ const output = Buffer.concat(chunks).toString("utf-8");
152
+ safeResolve(
153
+ output
154
+ ? `${output}\n\nCommand timed out after ${timeout} seconds (force-killed)`
155
+ : `Command timed out after ${timeout} seconds (force-killed)`,
156
+ );
157
+ }, HARD_DEADLINE_MS);
158
+ if (typeof hardDeadlineHandle === "object" && "unref" in hardDeadlineHandle) hardDeadlineHandle.unref();
159
+ }, SIGKILL_GRACE_MS);
160
+ if (typeof sigkillHandle === "object" && "unref" in sigkillHandle) sigkillHandle.unref();
129
161
  }, timeout * 1000);
130
162
  }
131
163
 
@@ -168,24 +200,28 @@ function executeBashInBackground(
168
200
 
169
201
  child.on("error", (err) => {
170
202
  if (timeoutHandle) clearTimeout(timeoutHandle);
203
+ if (sigkillHandle) clearTimeout(sigkillHandle);
204
+ if (hardDeadlineHandle) clearTimeout(hardDeadlineHandle);
171
205
  signal.removeEventListener("abort", onAbort);
172
- reject(err);
206
+ safeReject(err);
173
207
  });
174
208
 
175
209
  child.on("close", (code) => {
176
210
  if (timeoutHandle) clearTimeout(timeoutHandle);
211
+ if (sigkillHandle) clearTimeout(sigkillHandle);
212
+ if (hardDeadlineHandle) clearTimeout(hardDeadlineHandle);
177
213
  signal.removeEventListener("abort", onAbort);
178
214
  if (spillStream) spillStream.end();
179
215
 
180
216
  if (signal.aborted) {
181
217
  const output = Buffer.concat(chunks).toString("utf-8");
182
- resolve(output ? `${output}\n\nCommand aborted` : "Command aborted");
218
+ safeResolve(output ? `${output}\n\nCommand aborted` : "Command aborted");
183
219
  return;
184
220
  }
185
221
 
186
222
  if (timedOut) {
187
223
  const output = Buffer.concat(chunks).toString("utf-8");
188
- resolve(output ? `${output}\n\nCommand timed out after ${timeout} seconds` : `Command timed out after ${timeout} seconds`);
224
+ safeResolve(output ? `${output}\n\nCommand timed out after ${timeout} seconds` : `Command timed out after ${timeout} seconds`);
189
225
  return;
190
226
  }
191
227
 
@@ -208,7 +244,7 @@ function executeBashInBackground(
208
244
  text += `\n\nCommand exited with code ${code}`;
209
245
  }
210
246
 
211
- resolve(text);
247
+ safeResolve(text);
212
248
  });
213
249
  });
214
250
  }
@@ -118,3 +118,50 @@ test("await_job returns not-found message for invalid job IDs", async () => {
118
118
 
119
119
  manager.shutdown();
120
120
  });
121
+
122
+ test("await_job marks jobs as awaited to suppress follow-up delivery (#2248)", async () => {
123
+ const followUps: string[] = [];
124
+ const manager = new AsyncJobManager({
125
+ onJobComplete: (job) => {
126
+ if (!job.awaited) followUps.push(job.id);
127
+ },
128
+ });
129
+ const tool = createAwaitTool(() => manager);
130
+
131
+ // Register a job that completes in 50ms
132
+ const jobId = manager.register("bash", "awaited-job", async () => {
133
+ return new Promise<string>((resolve) => setTimeout(() => resolve("result"), 50));
134
+ });
135
+
136
+ // await_job consumes the result — should mark as awaited before promise resolves
137
+ await tool.execute("tc7", { jobs: [jobId] }, noopSignal, () => {}, undefined as never);
138
+
139
+ // Give the onJobComplete callback a tick to fire
140
+ await new Promise((r) => setTimeout(r, 50));
141
+
142
+ assert.equal(followUps.length, 0, "onJobComplete should not deliver follow-up for awaited jobs");
143
+
144
+ manager.shutdown();
145
+ });
146
+
147
+ test("unawaited jobs still get follow-up delivery (#2248)", async () => {
148
+ const followUps: string[] = [];
149
+ const manager = new AsyncJobManager({
150
+ onJobComplete: (job) => {
151
+ if (!job.awaited) followUps.push(job.id);
152
+ },
153
+ });
154
+
155
+ // Register a fire-and-forget job
156
+ const jobId = manager.register("bash", "fire-and-forget", async () => "done");
157
+ const job = manager.getJob(jobId)!;
158
+ await job.promise;
159
+
160
+ // Give the callback a tick
161
+ await new Promise((r) => setTimeout(r, 50));
162
+
163
+ assert.equal(followUps.length, 1, "onJobComplete should deliver follow-up for unawaited jobs");
164
+ assert.equal(followUps[0], jobId);
165
+
166
+ manager.shutdown();
167
+ });
@@ -66,6 +66,11 @@ export function createAwaitTool(getManager: () => AsyncJobManager): ToolDefiniti
66
66
  }
67
67
  }
68
68
 
69
+ // Mark all watched jobs as awaited upfront so the onJobComplete
70
+ // callback (which fires synchronously in the promise .then()) knows
71
+ // to suppress the follow-up message.
72
+ for (const j of watched) j.awaited = true;
73
+
69
74
  // If all watched jobs are already done, return immediately
70
75
  const running = watched.filter((j) => j.status === "running");
71
76
  if (running.length === 0) {
@@ -42,6 +42,7 @@ export default function AsyncJobs(pi: ExtensionAPI) {
42
42
 
43
43
  manager = new AsyncJobManager({
44
44
  onJobComplete: (job) => {
45
+ if (job.awaited) return;
45
46
  const statusEmoji = job.status === "completed" ? "done" : "error";
46
47
  const elapsed = ((Date.now() - job.startTime) / 1000).toFixed(1);
47
48
  const output = job.status === "completed"
@@ -22,6 +22,8 @@ export interface Job {
22
22
  promise: Promise<void>;
23
23
  resultText?: string;
24
24
  errorText?: string;
25
+ /** Set by await_job when results are consumed. Suppresses follow-up delivery. */
26
+ awaited?: boolean;
25
27
  }
26
28
 
27
29
  export interface JobManagerOptions {
@@ -109,7 +109,6 @@ export interface LoopDeps {
109
109
  captureIntegrationBranch: (
110
110
  basePath: string,
111
111
  mid: string,
112
- opts?: { commitDocs?: boolean },
113
112
  ) => void;
114
113
  getIsolationMode: () => string;
115
114
  getCurrentBranch: (basePath: string) => string;
@@ -28,6 +28,7 @@ import {
28
28
  } from "./phases.js";
29
29
  import { debugLog } from "../debug-logger.js";
30
30
  import { isInfrastructureError } from "./infra-errors.js";
31
+ import { resolveEngine } from "../engine-resolver.js";
31
32
 
32
33
  /**
33
34
  * Main auto-mode execution loop. Iterates: derive → dispatch → guards →
@@ -117,6 +118,96 @@ export async function autoLoop(
117
118
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-start", data: { iteration } });
118
119
  let iterData: IterationData;
119
120
 
121
+ // ── Custom engine path ──────────────────────────────────────────────
122
+ // When activeEngineId is a non-dev value, bypass runPreDispatch and
123
+ // runDispatch entirely — the custom engine drives its own state via
124
+ // GRAPH.yaml. Shares runGuards and runUnitPhase with the dev path.
125
+ // After unit execution, verifies then reconciles via the engine layer.
126
+ //
127
+ // GSD_ENGINE_BYPASS=1 skips the engine layer entirely — falls through
128
+ // to the dev path below.
129
+ if (s.activeEngineId != null && s.activeEngineId !== "dev" && !sidecarItem && process.env.GSD_ENGINE_BYPASS !== "1") {
130
+ debugLog("autoLoop", { phase: "custom-engine-derive", iteration, engineId: s.activeEngineId });
131
+
132
+ const { engine, policy } = resolveEngine({
133
+ activeEngineId: s.activeEngineId,
134
+ activeRunDir: s.activeRunDir,
135
+ });
136
+
137
+ const engineState = await engine.deriveState(s.basePath);
138
+ if (engineState.isComplete) {
139
+ await deps.stopAuto(ctx, pi, "Workflow complete");
140
+ break;
141
+ }
142
+
143
+ debugLog("autoLoop", { phase: "custom-engine-dispatch", iteration });
144
+ const dispatch = await engine.resolveDispatch(engineState, { basePath: s.basePath });
145
+
146
+ if (dispatch.action === "stop") {
147
+ await deps.stopAuto(ctx, pi, dispatch.reason ?? "Engine stopped");
148
+ break;
149
+ }
150
+ if (dispatch.action === "skip") {
151
+ continue;
152
+ }
153
+
154
+ // dispatch.action === "dispatch"
155
+ const step = dispatch.step!;
156
+ const gsdState = await deps.deriveState(s.basePath);
157
+
158
+ iterData = {
159
+ unitType: step.unitType,
160
+ unitId: step.unitId,
161
+ prompt: step.prompt,
162
+ finalPrompt: step.prompt,
163
+ pauseAfterUatDispatch: false,
164
+ observabilityIssues: [],
165
+ state: gsdState,
166
+ mid: s.currentMilestoneId ?? "workflow",
167
+ midTitle: "Workflow",
168
+ isRetry: false,
169
+ previousTier: undefined,
170
+ };
171
+
172
+ // ── Progress widget (mirrors dev path in runDispatch) ──
173
+ deps.updateProgressWidget(ctx, iterData.unitType, iterData.unitId, iterData.state);
174
+
175
+ // ── Guards (shared with dev path) ──
176
+ const guardsResult = await runGuards(ic, s.currentMilestoneId ?? "workflow");
177
+ if (guardsResult.action === "break") break;
178
+
179
+ // ── Unit execution (shared with dev path) ──
180
+ const unitPhaseResult = await runUnitPhase(ic, iterData, loopState);
181
+ if (unitPhaseResult.action === "break") break;
182
+
183
+ // ── Verify first, then reconcile (only mark complete on pass) ──
184
+ debugLog("autoLoop", { phase: "custom-engine-verify", iteration, unitId: iterData.unitId });
185
+ const verifyResult = await policy.verify(iterData.unitType, iterData.unitId, { basePath: s.basePath });
186
+ if (verifyResult === "pause") {
187
+ await deps.pauseAuto(ctx, pi);
188
+ break;
189
+ }
190
+ if (verifyResult === "retry") {
191
+ debugLog("autoLoop", { phase: "custom-engine-verify-retry", iteration, unitId: iterData.unitId });
192
+ continue;
193
+ }
194
+
195
+ // Verification passed — mark step complete
196
+ debugLog("autoLoop", { phase: "custom-engine-reconcile", iteration, unitId: iterData.unitId });
197
+ await engine.reconcile(engineState, {
198
+ unitType: iterData.unitType,
199
+ unitId: iterData.unitId,
200
+ startedAt: s.currentUnit?.startedAt ?? Date.now(),
201
+ finishedAt: Date.now(),
202
+ });
203
+
204
+ deps.clearUnitTimeout();
205
+ consecutiveErrors = 0;
206
+ deps.emitJournalEvent({ ts: new Date().toISOString(), flowId, seq: nextSeq(), eventType: "iteration-end", data: { iteration } });
207
+ debugLog("autoLoop", { phase: "iteration-complete", iteration });
208
+ continue;
209
+ }
210
+
120
211
  if (!sidecarItem) {
121
212
  // ── Phase 1: Pre-dispatch ─────────────────────────────────────────
122
213
  const preDispatchResult = await runPreDispatch(ic, loopState);
@@ -261,9 +261,7 @@ export async function runPreDispatch(
261
261
 
262
262
  if (mid) {
263
263
  if (deps.getIsolationMode() !== "none") {
264
- deps.captureIntegrationBranch(s.basePath, mid, {
265
- commitDocs: prefs?.git?.commit_docs,
266
- });
264
+ deps.captureIntegrationBranch(s.basePath, mid);
267
265
  }
268
266
  deps.resolver.enterMilestone(mid, ctx.ui);
269
267
  } else {
@@ -1133,9 +1131,9 @@ export async function runUnitPhase(
1133
1131
  );
1134
1132
  }
1135
1133
 
1136
- const isHookUnit = unitType.startsWith("hook/");
1134
+ const skipArtifactVerification = unitType.startsWith("hook/") || unitType === "custom-step";
1137
1135
  const artifactVerified =
1138
- isHookUnit ||
1136
+ skipArtifactVerification ||
1139
1137
  deps.verifyExpectedArtifact(unitType, unitId, s.basePath);
1140
1138
  if (artifactVerified) {
1141
1139
  s.completedUnits.push({
@@ -83,6 +83,8 @@ export class AutoSession {
83
83
  paused = false;
84
84
  stepMode = false;
85
85
  verbose = false;
86
+ activeEngineId: string | null = null;
87
+ activeRunDir: string | null = null;
86
88
  cmdCtx: ExtensionCommandContext | null = null;
87
89
 
88
90
  // ── Paths ────────────────────────────────────────────────────────────────
@@ -174,6 +176,8 @@ export class AutoSession {
174
176
  this.paused = false;
175
177
  this.stepMode = false;
176
178
  this.verbose = false;
179
+ this.activeEngineId = null;
180
+ this.activeRunDir = null;
177
181
  this.cmdCtx = null;
178
182
 
179
183
  // Paths
@@ -226,6 +230,8 @@ export class AutoSession {
226
230
  paused: this.paused,
227
231
  stepMode: this.stepMode,
228
232
  basePath: this.basePath,
233
+ activeEngineId: this.activeEngineId,
234
+ activeRunDir: this.activeRunDir,
229
235
  currentMilestoneId: this.currentMilestoneId,
230
236
  currentUnit: this.currentUnit,
231
237
  completedUnits: this.completedUnits.length,
@@ -79,6 +79,7 @@ export function unitVerb(unitType: string): string {
79
79
  case "rewrite-docs": return "rewriting";
80
80
  case "reassess-roadmap": return "reassessing";
81
81
  case "run-uat": return "running UAT";
82
+ case "custom-step": return "executing workflow step";
82
83
  default: return unitType;
83
84
  }
84
85
  }
@@ -97,6 +98,7 @@ export function unitPhaseLabel(unitType: string): string {
97
98
  case "rewrite-docs": return "REWRITE";
98
99
  case "reassess-roadmap": return "REASSESS";
99
100
  case "run-uat": return "UAT";
101
+ case "custom-step": return "WORKFLOW";
100
102
  default: return unitType.toUpperCase();
101
103
  }
102
104
  }
@@ -420,8 +420,6 @@ export function buildSkillActivationBlock(params: {
420
420
  params.sliceTitle,
421
421
  params.taskId,
422
422
  params.taskTitle,
423
- ...(params.extraContext ?? []),
424
- params.taskPlanContent ?? undefined,
425
423
  );
426
424
 
427
425
  const visibleSkills = (typeof getLoadedSkills === 'function' ? getLoadedSkills() : []).filter(skill => !skill.disableModelInvocation);
@@ -452,12 +450,6 @@ export function buildSkillActivationBlock(params: {
452
450
  }
453
451
  }
454
452
 
455
- for (const skill of visibleSkills) {
456
- if (skillMatchesContext(skill, contextTokens)) {
457
- matched.add(normalizeSkillReference(skill.name));
458
- }
459
- }
460
-
461
453
  const ordered = [...matched]
462
454
  .filter(name => installedNames.has(name) && !avoided.has(name))
463
455
  .sort();
@@ -983,11 +975,7 @@ export async function buildPlanSlicePrompt(
983
975
  const executorContextConstraints = formatExecutorConstraints();
984
976
 
985
977
  const outputRelPath = relSliceFile(base, mid, sid, "PLAN");
986
- const prefs = loadEffectiveGSDPreferences();
987
- const commitDocsEnabled = prefs?.preferences?.git?.commit_docs !== false;
988
- const commitInstruction = commitDocsEnabled
989
- ? `Commit the plan files only: \`git add ${relSlicePath(base, mid, sid)}/ .gsd/DECISIONS.md .gitignore && git commit -m "docs(${sid}): add slice plan"\`. Do not stage .gsd/STATE.md or other runtime files — the system manages those.`
990
- : "Do not commit — planning docs are not tracked in git for this project.";
978
+ const commitInstruction = "Do not commit — .gsd/ planning docs are managed externally and not tracked in git.";
991
979
  return loadPrompt("plan-slice", {
992
980
  workingDirectory: base,
993
981
  milestoneId: mid, sliceId: sid, sliceTitle: sTitle,
@@ -1485,11 +1473,7 @@ export async function buildReassessRoadmapPrompt(
1485
1473
  // Non-fatal — captures module may not be available
1486
1474
  }
1487
1475
 
1488
- const reassessPrefs = loadEffectiveGSDPreferences();
1489
- const reassessCommitDocsEnabled = reassessPrefs?.preferences?.git?.commit_docs !== false;
1490
- const reassessCommitInstruction = reassessCommitDocsEnabled
1491
- ? `Commit: \`docs(${mid}): reassess roadmap after ${completedSliceId}\`. Stage only the .gsd/milestones/ files you changed — do not stage .gsd/STATE.md or other runtime files.`
1492
- : "Do not commit — planning docs are not tracked in git for this project.";
1476
+ const reassessCommitInstruction = "Do not commit — .gsd/ planning docs are managed externally and not tracked in git.";
1493
1477
 
1494
1478
  return loadPrompt("reassess-roadmap", {
1495
1479
  workingDirectory: base,
@@ -167,22 +167,19 @@ export async function bootstrapAutoSession(
167
167
  // ensureGitignore checks for git-tracked .gsd/ files and skips the
168
168
  // ".gsd" pattern if the project intentionally tracks .gsd/ in git.
169
169
  const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
170
- const commitDocs = gitPrefs?.commit_docs;
171
170
  const manageGitignore = gitPrefs?.manage_gitignore;
172
- ensureGitignore(base, { commitDocs, manageGitignore });
171
+ ensureGitignore(base, { manageGitignore });
173
172
  if (manageGitignore !== false) untrackRuntimeFiles(base);
174
173
 
175
174
  // Bootstrap .gsd/ if it doesn't exist
176
175
  const gsdDir = join(base, ".gsd");
177
176
  if (!existsSync(gsdDir)) {
178
177
  mkdirSync(join(gsdDir, "milestones"), { recursive: true });
179
- if (commitDocs !== false) {
180
- try {
181
- nativeAddAll(base);
182
- nativeCommit(base, "chore: init gsd");
183
- } catch {
184
- /* nothing to commit */
185
- }
178
+ try {
179
+ nativeAddAll(base);
180
+ nativeCommit(base, "chore: init gsd");
181
+ } catch {
182
+ /* nothing to commit */
186
183
  }
187
184
  }
188
185
 
@@ -487,7 +484,7 @@ export async function bootstrapAutoSession(
487
484
  // Capture integration branch
488
485
  if (s.currentMilestoneId) {
489
486
  if (getIsolationMode() !== "none") {
490
- captureIntegrationBranch(base, s.currentMilestoneId, { commitDocs });
487
+ captureIntegrationBranch(base, s.currentMilestoneId);
491
488
  }
492
489
  setActiveMilestoneId(base, s.currentMilestoneId);
493
490
  }