gsd-pi 2.76.0-dev.82e249f7b → 2.76.0-dev.97807402

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 (236) hide show
  1. package/dist/claude-cli-check.js +32 -3
  2. package/dist/mcp-server.d.ts +7 -0
  3. package/dist/mcp-server.js +35 -1
  4. package/dist/resource-loader.d.ts +1 -1
  5. package/dist/resource-loader.js +2 -8
  6. package/dist/resources/extensions/claude-code-cli/readiness.js +4 -3
  7. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +77 -17
  8. package/dist/resources/extensions/gsd/auto/phases.js +14 -0
  9. package/dist/resources/extensions/gsd/auto/run-unit.js +27 -0
  10. package/dist/resources/extensions/gsd/auto-model-selection.js +1 -1
  11. package/dist/resources/extensions/gsd/auto-post-unit.js +1 -1
  12. package/dist/resources/extensions/gsd/auto-recovery.js +13 -0
  13. package/dist/resources/extensions/gsd/auto-start.js +27 -18
  14. package/dist/resources/extensions/gsd/auto-worktree.js +30 -48
  15. package/dist/resources/extensions/gsd/auto.js +13 -17
  16. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +17 -1
  17. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +39 -9
  18. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +93 -0
  19. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  20. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +40 -4
  21. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +12 -1
  22. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +968 -23
  23. package/dist/resources/extensions/gsd/compaction-snapshot.js +121 -0
  24. package/dist/resources/extensions/gsd/error-classifier.js +10 -3
  25. package/dist/resources/extensions/gsd/exec-history.js +120 -0
  26. package/dist/resources/extensions/gsd/exec-sandbox.js +258 -0
  27. package/dist/resources/extensions/gsd/gsd-db.js +115 -7
  28. package/dist/resources/extensions/gsd/guided-flow.js +189 -0
  29. package/dist/resources/extensions/gsd/health-widget.js +4 -1
  30. package/dist/resources/extensions/gsd/init-wizard.js +15 -1
  31. package/dist/resources/extensions/gsd/key-manager.js +6 -0
  32. package/dist/resources/extensions/gsd/model-router.js +36 -3
  33. package/dist/resources/extensions/gsd/pre-execution-checks.js +35 -9
  34. package/dist/resources/extensions/gsd/preferences-types.js +9 -0
  35. package/dist/resources/extensions/gsd/preferences-validation.js +83 -0
  36. package/dist/resources/extensions/gsd/preferences.js +17 -17
  37. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  38. package/dist/resources/extensions/gsd/prompts/discuss.md +29 -2
  39. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  40. package/dist/resources/extensions/gsd/safety/file-change-validator.js +10 -4
  41. package/dist/resources/extensions/gsd/safety/safety-harness.js +4 -0
  42. package/dist/resources/extensions/gsd/token-counter.js +22 -5
  43. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +59 -0
  44. package/dist/resources/extensions/gsd/tools/exec-tool.js +126 -0
  45. package/dist/resources/extensions/gsd/tools/resume-tool.js +23 -0
  46. package/dist/resources/extensions/gsd/workflow-mcp.js +3 -0
  47. package/dist/resources/skills/verify-before-complete/SKILL.md +2 -1
  48. package/dist/resources/skills/write-docs/SKILL.md +2 -1
  49. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  50. package/dist/web/standalone/.next/BUILD_ID +1 -1
  51. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  52. package/dist/web/standalone/.next/build-manifest.json +2 -2
  53. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  54. package/dist/web/standalone/.next/required-server-files.json +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.html +1 -1
  72. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  79. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  81. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  82. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  83. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  84. package/dist/web/standalone/server.js +1 -1
  85. package/package.json +1 -1
  86. package/packages/mcp-server/dist/remote-questions.d.ts +45 -0
  87. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -0
  88. package/packages/mcp-server/dist/remote-questions.js +732 -0
  89. package/packages/mcp-server/dist/remote-questions.js.map +1 -0
  90. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  91. package/packages/mcp-server/dist/server.js +18 -1
  92. package/packages/mcp-server/dist/server.js.map +1 -1
  93. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  94. package/packages/mcp-server/dist/workflow-tools.js +64 -25
  95. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  96. package/packages/mcp-server/package.json +2 -1
  97. package/packages/mcp-server/src/remote-questions.test.ts +294 -0
  98. package/packages/mcp-server/src/remote-questions.ts +916 -0
  99. package/packages/mcp-server/src/server.ts +19 -1
  100. package/packages/mcp-server/src/workflow-tools.test.ts +146 -1
  101. package/packages/mcp-server/src/workflow-tools.ts +84 -43
  102. package/packages/mcp-server/tsconfig.test.json +19 -0
  103. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  104. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/anthropic-shared.js +2 -0
  106. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/simple-options.d.ts +10 -0
  108. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  109. package/packages/pi-ai/dist/providers/simple-options.js +16 -1
  110. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  111. package/packages/pi-ai/src/providers/anthropic-shared.ts +3 -1
  112. package/packages/pi-ai/src/providers/simple-options.ts +17 -1
  113. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  114. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts +2 -0
  115. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.d.ts.map +1 -0
  116. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js +203 -0
  117. package/packages/pi-coding-agent/dist/core/model-registry-custom-caps.test.js.map +1 -0
  118. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/model-registry.js +14 -0
  120. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts +2 -0
  122. package/packages/pi-coding-agent/dist/core/redact-secrets.d.ts.map +1 -0
  123. package/packages/pi-coding-agent/dist/core/redact-secrets.js +49 -0
  124. package/packages/pi-coding-agent/dist/core/redact-secrets.js.map +1 -0
  125. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts +2 -0
  126. package/packages/pi-coding-agent/dist/core/redact-secrets.test.d.ts.map +1 -0
  127. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js +67 -0
  128. package/packages/pi-coding-agent/dist/core/redact-secrets.test.js.map +1 -0
  129. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/session-manager.js +9 -5
  131. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/session-manager.test.js +25 -1
  133. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
  141. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  142. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +13 -1
  144. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  145. package/packages/pi-coding-agent/src/core/model-registry-custom-caps.test.ts +245 -0
  146. package/packages/pi-coding-agent/src/core/model-registry.ts +16 -0
  147. package/packages/pi-coding-agent/src/core/redact-secrets.test.ts +86 -0
  148. package/packages/pi-coding-agent/src/core/redact-secrets.ts +58 -0
  149. package/packages/pi-coding-agent/src/core/session-manager.test.ts +36 -1
  150. package/packages/pi-coding-agent/src/core/session-manager.ts +9 -5
  151. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
  152. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
  153. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +13 -1
  154. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  155. package/src/resources/extensions/claude-code-cli/readiness.ts +4 -3
  156. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +78 -17
  157. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +149 -5
  158. package/src/resources/extensions/gsd/auto/phases.ts +14 -0
  159. package/src/resources/extensions/gsd/auto/run-unit.ts +29 -0
  160. package/src/resources/extensions/gsd/auto-model-selection.ts +1 -1
  161. package/src/resources/extensions/gsd/auto-post-unit.ts +1 -2
  162. package/src/resources/extensions/gsd/auto-recovery.ts +15 -0
  163. package/src/resources/extensions/gsd/auto-start.ts +29 -19
  164. package/src/resources/extensions/gsd/auto-worktree.ts +34 -52
  165. package/src/resources/extensions/gsd/auto.ts +12 -17
  166. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +23 -1
  167. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +40 -9
  168. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +109 -0
  169. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  170. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +42 -4
  171. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +13 -1
  172. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +898 -32
  173. package/src/resources/extensions/gsd/compaction-snapshot.ts +165 -0
  174. package/src/resources/extensions/gsd/error-classifier.ts +10 -3
  175. package/src/resources/extensions/gsd/exec-history.ts +153 -0
  176. package/src/resources/extensions/gsd/exec-sandbox.ts +326 -0
  177. package/src/resources/extensions/gsd/gsd-db.ts +122 -7
  178. package/src/resources/extensions/gsd/guided-flow.ts +221 -0
  179. package/src/resources/extensions/gsd/health-widget.ts +3 -1
  180. package/src/resources/extensions/gsd/init-wizard.ts +15 -1
  181. package/src/resources/extensions/gsd/journal.ts +2 -1
  182. package/src/resources/extensions/gsd/key-manager.ts +6 -0
  183. package/src/resources/extensions/gsd/model-router.ts +42 -1
  184. package/src/resources/extensions/gsd/pre-execution-checks.ts +36 -10
  185. package/src/resources/extensions/gsd/preferences-types.ts +46 -0
  186. package/src/resources/extensions/gsd/preferences-validation.ts +79 -0
  187. package/src/resources/extensions/gsd/preferences.ts +17 -17
  188. package/src/resources/extensions/gsd/prompts/discuss-headless.md +8 -0
  189. package/src/resources/extensions/gsd/prompts/discuss.md +29 -2
  190. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +5 -2
  191. package/src/resources/extensions/gsd/safety/file-change-validator.ts +14 -3
  192. package/src/resources/extensions/gsd/safety/safety-harness.ts +6 -0
  193. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +116 -0
  194. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +49 -0
  195. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +123 -0
  196. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  197. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  198. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +31 -0
  199. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +1 -1
  200. package/src/resources/extensions/gsd/tests/escalation.test.ts +1 -1
  201. package/src/resources/extensions/gsd/tests/exec-history.test.ts +124 -0
  202. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +210 -0
  203. package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +58 -0
  204. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +152 -1
  205. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
  206. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +1 -1
  207. package/src/resources/extensions/gsd/tests/issue-4540-regressions.test.ts +288 -0
  208. package/src/resources/extensions/gsd/tests/key-manager.test.ts +7 -0
  209. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  210. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  211. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +19 -0
  212. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +14 -0
  213. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +234 -0
  214. package/src/resources/extensions/gsd/tests/preferences.test.ts +110 -0
  215. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +44 -0
  216. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +48 -0
  217. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +388 -0
  218. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +9 -3
  219. package/src/resources/extensions/gsd/tests/save-gate-result-render.test.ts +95 -0
  220. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +32 -40
  221. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +56 -0
  222. package/src/resources/extensions/gsd/tests/token-counter.test.ts +105 -1
  223. package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +107 -0
  224. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +65 -2
  225. package/src/resources/extensions/gsd/tests/write-gate.test.ts +64 -0
  226. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -1
  227. package/src/resources/extensions/gsd/token-counter.ts +22 -5
  228. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +81 -0
  229. package/src/resources/extensions/gsd/tools/exec-tool.ts +183 -0
  230. package/src/resources/extensions/gsd/tools/resume-tool.ts +40 -0
  231. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  232. package/src/resources/extensions/gsd/workflow-mcp.ts +3 -0
  233. package/src/resources/skills/verify-before-complete/SKILL.md +2 -1
  234. package/src/resources/skills/write-docs/SKILL.md +2 -1
  235. /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → pI48IF3dgfs0CBrYi2bh_}/_buildManifest.js +0 -0
  236. /package/dist/web/standalone/.next/static/{ecSsu49rxxcpbNmVP4mLD → pI48IF3dgfs0CBrYi2bh_}/_ssgManifest.js +0 -0
@@ -238,14 +238,6 @@ function clearProjectRootStateFiles(basePath: string, milestoneId: string): void
238
238
  }
239
239
  }
240
240
 
241
- function isProjectGsdSymlink(basePath: string): boolean {
242
- try {
243
- return lstatSyncFn(join(basePath, ".gsd")).isSymbolicLink();
244
- } catch {
245
- return false;
246
- }
247
- }
248
-
249
241
  // ─── Build Artifact Auto-Resolve ─────────────────────────────────────────────
250
242
 
251
243
  /** Patterns for machine-generated build artifacts that can be safely
@@ -1664,54 +1656,15 @@ export function mergeMilestoneToMain(
1664
1656
  }
1665
1657
  }
1666
1658
 
1667
- // 7. Stash any pre-existing dirty files so the squash merge is not
1668
- // blocked by unrelated local changes (#2151). clearProjectRootStateFiles
1669
- // only removes untracked .gsd/ files; tracked dirty files elsewhere (e.g.
1670
- // .planning/work-state.json with stash conflict markers) are invisible to
1671
- // that cleanup but will cause `git merge --squash` to reject.
1672
- let stashed = false;
1673
- try {
1674
- const status = execFileSync("git", ["status", "--porcelain"], {
1675
- cwd: originalBasePath_,
1676
- stdio: ["ignore", "pipe", "pipe"],
1677
- encoding: "utf-8",
1678
- }).trim();
1679
- if (status) {
1680
- // Use --include-untracked to stash untracked files that would block
1681
- // the squash merge, but EXCLUDE .gsd/milestones/ (#2505).
1682
- // --include-untracked without exclusion sweeps queued milestone
1683
- // CONTEXT files into the stash. If stash pop later fails, those files
1684
- // are permanently trapped in the stash entry and lost on the next
1685
- // stash push or drop.
1686
- //
1687
- // When `.gsd` itself is a symlink, Git rejects pathspecs below it
1688
- // ("beyond a symbolic link"). In that layout, exclude the whole symlink
1689
- // and keep stashing real project files that could block the merge.
1690
- const stashPathspecs = isProjectGsdSymlink(originalBasePath_)
1691
- ? [".", ":(exclude).gsd"]
1692
- : [":(exclude).gsd/milestones"];
1693
- execFileSync(
1694
- "git",
1695
- [
1696
- "stash", "push", "--include-untracked",
1697
- "-m", `gsd: pre-merge stash for ${milestoneId}`,
1698
- "--", ...stashPathspecs,
1699
- ],
1700
- { cwd: originalBasePath_, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" },
1701
- );
1702
- stashed = true;
1703
- }
1704
- } catch (err) {
1705
- // Stash failure is non-fatal — proceed without stash and let the merge
1706
- // report the dirty tree if it fails.
1707
- logWarning("worktree", `git stash failed: ${err instanceof Error ? err.message : String(err)}`);
1708
- }
1709
-
1710
- // 7a. Shelter queued milestone directories before the squash merge (#2505).
1659
+ // 7. Shelter queued milestone directories before the squash merge (#2505).
1711
1660
  // The milestone branch may contain copies of queued milestone dirs (via
1712
1661
  // copyPlanningArtifacts), so `git merge --squash` rejects when those same
1713
1662
  // files exist as untracked in the working tree. Temporarily move them to
1714
1663
  // a backup location, then restore after the merge+commit.
1664
+ //
1665
+ // MUST run BEFORE the pre-merge stash (step 7a) so `--include-untracked`
1666
+ // does not sweep queued CONTEXT files into the stash. If stash pop later
1667
+ // fails, files trapped inside the stash are permanently lost (#2505).
1715
1668
  const milestonesDir = join(gsdRoot(originalBasePath_), "milestones");
1716
1669
  const shelterDir = join(gsdRoot(originalBasePath_), ".milestone-shelter");
1717
1670
  const shelteredDirs: string[] = [];
@@ -1759,6 +1712,35 @@ export function mergeMilestoneToMain(
1759
1712
  logWarning("worktree", `milestone shelter operation failed: ${err instanceof Error ? err.message : String(err)}`);
1760
1713
  }
1761
1714
 
1715
+ // 7a. Stash pre-existing dirty files so the squash merge is not blocked by
1716
+ // unrelated local changes (#2151). Includes untracked files to handle
1717
+ // locally-added files that conflict with tracked files on the milestone
1718
+ // branch. Passing NO pathspec lets git skip gitignored paths silently;
1719
+ // adding an explicit pathspec trips a `git add`-style fatal on ignored
1720
+ // entries (e.g. a gitignored `.gsd` symlink under ADR-002) (#4573).
1721
+ // Queued CONTEXT files under `.gsd/milestones/*` are already sheltered
1722
+ // in step 7 above, so they won't be swept into the stash.
1723
+ let stashed = false;
1724
+ try {
1725
+ const status = execFileSync("git", ["status", "--porcelain"], {
1726
+ cwd: originalBasePath_,
1727
+ stdio: ["ignore", "pipe", "pipe"],
1728
+ encoding: "utf-8",
1729
+ }).trim();
1730
+ if (status) {
1731
+ execFileSync(
1732
+ "git",
1733
+ ["stash", "push", "--include-untracked", "-m", `gsd: pre-merge stash for ${milestoneId}`],
1734
+ { cwd: originalBasePath_, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" },
1735
+ );
1736
+ stashed = true;
1737
+ }
1738
+ } catch (err) {
1739
+ // Stash failure is non-fatal — proceed without stash and let the merge
1740
+ // report the dirty tree if it fails.
1741
+ logWarning("worktree", `git stash failed: ${err instanceof Error ? err.message : String(err)}`);
1742
+ }
1743
+
1762
1744
  // 7b. Clean up stale merge state before attempting squash merge (#2912).
1763
1745
  // A leftover MERGE_HEAD (from a previous failed merge, libgit2 native path,
1764
1746
  // or interrupted operation) causes `git merge --squash` to refuse with
@@ -182,7 +182,6 @@ import {
182
182
  unitVerb,
183
183
  formatAutoElapsed as _formatAutoElapsed,
184
184
  formatWidgetTokens,
185
- hideFooter,
186
185
  type WidgetStateAccessors,
187
186
  } from "./auto-dashboard.js";
188
187
  import {
@@ -330,8 +329,8 @@ export function startAutoDetached(
330
329
  }
331
330
 
332
331
  /** Returns true if the project is configured for `isolation:worktree` mode. */
333
- export function shouldUseWorktreeIsolation(): boolean {
334
- const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
332
+ export function shouldUseWorktreeIsolation(basePath?: string): boolean {
333
+ const prefs = loadEffectiveGSDPreferences(basePath)?.preferences?.git;
335
334
  if (prefs?.isolation === "worktree") return true;
336
335
  // Default is false — worktree isolation requires explicit opt-in
337
336
  return false;
@@ -424,7 +423,7 @@ export function getAutoDashboardData(): AutoDashboardData {
424
423
  const rtkSavings = sessionId && s.basePath
425
424
  ? getRtkSessionSavings(s.basePath, sessionId)
426
425
  : null;
427
- const rtkEnabled = loadEffectiveGSDPreferences()?.preferences.experimental?.rtk === true;
426
+ const rtkEnabled = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences.experimental?.rtk === true;
428
427
  // Pending capture count — lazy check, non-fatal
429
428
  let pendingCaptureCount = 0;
430
429
  try {
@@ -648,7 +647,7 @@ function buildSnapshotOpts(
648
647
  gitStatus?: "ok" | "failed";
649
648
  gitError?: string;
650
649
  } & Record<string, unknown> {
651
- const prefs = loadEffectiveGSDPreferences()?.preferences;
650
+ const prefs = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences;
652
651
  const uokFlags = resolveUokFlags(prefs);
653
652
  return {
654
653
  ...(s.autoStartTime > 0 ? { autoSessionKey: String(s.autoStartTime) } : {}),
@@ -686,7 +685,7 @@ function handleLostSessionLock(
686
685
  restoreProjectRootEnv();
687
686
  restoreMilestoneLockEnv();
688
687
  deregisterSigtermHandler();
689
- clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
688
+ clearCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences);
690
689
  const base = lockBase();
691
690
  const lockFilePath = base ? join(gsdRoot(base), "auto.lock") : "unknown";
692
691
  const recoverySuggestion = "\nTo recover, run: gsd doctor --fix";
@@ -706,7 +705,6 @@ function handleLostSessionLock(
706
705
  );
707
706
  ctx?.ui.setStatus("gsd-auto", undefined);
708
707
  ctx?.ui.setWidget("gsd-progress", undefined);
709
- ctx?.ui.setFooter(undefined);
710
708
  if (ctx) initHealthWidget(ctx);
711
709
  }
712
710
 
@@ -742,7 +740,6 @@ function cleanupAfterLoopExit(ctx: ExtensionContext): void {
742
740
  if (!s.paused) {
743
741
  ctx.ui.setStatus("gsd-auto", undefined);
744
742
  ctx.ui.setWidget("gsd-progress", undefined);
745
- ctx.ui.setFooter(undefined);
746
743
  initHealthWidget(ctx);
747
744
  }
748
745
 
@@ -764,7 +761,7 @@ export async function stopAuto(
764
761
  reason?: string,
765
762
  ): Promise<void> {
766
763
  if (!s.active && !s.paused) return;
767
- const loadedPreferences = loadEffectiveGSDPreferences()?.preferences;
764
+ const loadedPreferences = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences;
768
765
  const reasonSuffix = reason ? ` — ${reason}` : "";
769
766
 
770
767
  try {
@@ -1018,7 +1015,6 @@ export async function stopAuto(
1018
1015
  // UI cleanup
1019
1016
  ctx?.ui.setStatus("gsd-auto", undefined);
1020
1017
  ctx?.ui.setWidget("gsd-progress", undefined);
1021
- ctx?.ui.setFooter(undefined);
1022
1018
  if (ctx) initHealthWidget(ctx);
1023
1019
  restoreProjectRootEnv();
1024
1020
  restoreMilestoneLockEnv();
@@ -1121,7 +1117,6 @@ export async function pauseAuto(
1121
1117
  s.verificationRetryCount.clear();
1122
1118
  ctx?.ui.setStatus("gsd-auto", "paused");
1123
1119
  ctx?.ui.setWidget("gsd-progress", undefined);
1124
- ctx?.ui.setFooter(undefined);
1125
1120
  if (ctx) initHealthWidget(ctx);
1126
1121
  const resumeCmd = s.stepMode ? "/gsd next" : "/gsd auto";
1127
1122
  ctx?.ui.notify(
@@ -1495,7 +1490,7 @@ export async function startAuto(
1495
1490
  // ── Auto-worktree / branch-mode: re-enter on resume ──
1496
1491
  if (
1497
1492
  s.currentMilestoneId &&
1498
- getIsolationMode() !== "none" &&
1493
+ getIsolationMode(s.originalBasePath || s.basePath) !== "none" &&
1499
1494
  s.originalBasePath &&
1500
1495
  !isInAutoWorktree(s.basePath) &&
1501
1496
  !detectWorktreeName(s.basePath) &&
@@ -1509,7 +1504,7 @@ export async function startAuto(
1509
1504
  registerSigtermHandler(lockBase());
1510
1505
 
1511
1506
  ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
1512
- ctx.ui.setFooter(hideFooter);
1507
+ ctx.ui.setWidget("gsd-health", undefined);
1513
1508
  ctx.ui.notify(
1514
1509
  s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.",
1515
1510
  "info",
@@ -1534,7 +1529,7 @@ export async function startAuto(
1534
1529
  await openProjectDbIfPresent(s.basePath);
1535
1530
  try {
1536
1531
  await rebuildState(s.basePath);
1537
- syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
1532
+ syncCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, await deriveState(s.basePath));
1538
1533
  } catch (e) {
1539
1534
  debugLog("resume-rebuild-state-failed", {
1540
1535
  error: e instanceof Error ? e.message : String(e),
@@ -1584,7 +1579,7 @@ export async function startAuto(
1584
1579
  "resuming",
1585
1580
  s.currentMilestoneId ?? "unknown",
1586
1581
  );
1587
- logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
1582
+ logCmuxEvent(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
1588
1583
 
1589
1584
  captureProjectRootEnv(s.originalBasePath || s.basePath);
1590
1585
  startAutoCommandPolling(s.basePath);
@@ -1622,12 +1617,12 @@ export async function startAuto(
1622
1617
 
1623
1618
  captureProjectRootEnv(s.originalBasePath || s.basePath);
1624
1619
  try {
1625
- syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
1620
+ syncCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, await deriveState(s.basePath));
1626
1621
  } catch (err) {
1627
1622
  // Best-effort only — sidebar sync must never block auto-mode startup
1628
1623
  logWarning("engine", `cmux sync failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
1629
1624
  }
1630
- logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
1625
+ logCmuxEvent(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
1631
1626
 
1632
1627
  startAutoCommandPolling(s.basePath);
1633
1628
 
@@ -1,7 +1,12 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
2
2
 
3
3
  import { logWarning } from "../workflow-logger.js";
4
- import { checkAutoStartAfterDiscuss } from "../guided-flow.js";
4
+ import {
5
+ checkAutoStartAfterDiscuss,
6
+ maybeHandleReadyPhraseWithoutFiles,
7
+ maybeHandleEmptyIntentTurn,
8
+ resetEmptyTurnCounter,
9
+ } from "../guided-flow.js";
5
10
  import { getAutoDashboardData, getAutoModeStartModel, isAutoActive, pauseAuto, setCurrentDispatchedModelId } from "../auto.js";
6
11
  import { getNextFallbackModel, resolveModelWithFallbacksForUnit } from "../preferences.js";
7
12
  import { pauseAutoForProviderError } from "../provider-error-pause.js";
@@ -75,6 +80,20 @@ export async function handleAgentEnd(
75
80
  clearDiscussionFlowState();
76
81
  return;
77
82
  }
83
+
84
+ // #4573 — When the LLM emits "Milestone X ready." but the required files
85
+ // are missing, `checkAutoStartAfterDiscuss` returns false silently. Surface
86
+ // that and nudge the LLM to complete the writes before the user hits the
87
+ // downstream "All milestones complete" warning loop.
88
+ if (maybeHandleReadyPhraseWithoutFiles(event)) return;
89
+
90
+ // #4573 — Empty-turn recovery: if the LLM announced intent in prose but
91
+ // emitted no tool calls, nudge it to execute. Fires only when auto-mode is
92
+ // active or a discussion autostart is pending (non-auto interactive discuss
93
+ // is user-driven). Runs before `isAutoActive` early return so pending
94
+ // discussions (where isAutoActive may be false) still get recovered.
95
+ if (maybeHandleEmptyIntentTurn(event, isAutoActive())) return;
96
+
78
97
  if (!isAutoActive()) return;
79
98
  if (isSessionSwitchInFlight()) return;
80
99
 
@@ -336,6 +355,9 @@ export async function handleAgentEnd(
336
355
  // ── Success path ─────────────────────────────────────────────────────────
337
356
  try {
338
357
  resetRetryState(retryState);
358
+ // #4573 — Reset the empty-turn counter on any successful agent turn so
359
+ // transient stalls don't accumulate across independent units.
360
+ resetEmptyTurnCounter();
339
361
  resolveAgentEnd(event);
340
362
  } catch (err) {
341
363
  const message = err instanceof Error ? err.message : String(err);
@@ -35,6 +35,19 @@ function registerAlias(pi: ExtensionAPI, toolDef: any, aliasName: string, canoni
35
35
  });
36
36
  }
37
37
 
38
+ /**
39
+ * Read a tool result's structured payload, accommodating MCP's `details` →
40
+ * `structuredContent` rename (#4472, #4477). In-process executions still
41
+ * deliver the payload on `result.details`; MCP-routed executions deliver it
42
+ * on `result.structuredContent` (post `adaptExecutorResult` transform). All
43
+ * `renderResult` callbacks in this file route through this helper so a future
44
+ * field rename only needs to be applied in one place.
45
+ */
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- result shape varies by tool
47
+ function readDetails(result: any): any {
48
+ return result?.details ?? result?.structuredContent;
49
+ }
50
+
38
51
  export function registerDbTools(pi: ExtensionAPI): void {
39
52
  // ─── gsd_decision_save (formerly gsd_save_decision) ─────────────────────
40
53
 
@@ -110,7 +123,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
110
123
  return new Text(text, 0, 0);
111
124
  },
112
125
  renderResult(result: any, _options: any, theme: any) {
113
- const d = result.details;
126
+ const d = readDetails(result);
114
127
  if (result.isError || d?.error) {
115
128
  return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
116
129
  }
@@ -188,7 +201,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
188
201
  return new Text(text, 0, 0);
189
202
  },
190
203
  renderResult(result: any, _options: any, theme: any) {
191
- const d = result.details;
204
+ const d = readDetails(result);
192
205
  if (result.isError || d?.error) {
193
206
  return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
194
207
  }
@@ -273,7 +286,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
273
286
  return new Text(text, 0, 0);
274
287
  },
275
288
  renderResult(result: any, _options: any, theme: any) {
276
- const d = result.details;
289
+ const d = readDetails(result);
277
290
  if (result.isError || d?.error) {
278
291
  return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
279
292
  }
@@ -322,7 +335,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
322
335
  return new Text(text, 0, 0);
323
336
  },
324
337
  renderResult(result: any, _options: any, theme: any) {
325
- const d = result.details;
338
+ const d = readDetails(result);
326
339
  if (result.isError || d?.error) {
327
340
  return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
328
341
  }
@@ -406,7 +419,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
406
419
  return new Text(theme.fg("toolTitle", theme.bold("milestone_generate_id")), 0, 0);
407
420
  },
408
421
  renderResult(result: any, _options: any, theme: any) {
409
- const d = result.details;
422
+ const d = readDetails(result);
410
423
  if (result.isError || d?.error) {
411
424
  return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
412
425
  }
@@ -1074,13 +1087,31 @@ export function registerDbTools(pi: ExtensionAPI): void {
1074
1087
  text += theme.fg("dim", ` → ${args.verdict ?? ""}`);
1075
1088
  return new Text(text, 0, 0);
1076
1089
  },
1090
+ /**
1091
+ * Render the save_gate_result tool output for the TUI.
1092
+ *
1093
+ * Prefers structured fields, but falls back to `content[0].text` when the
1094
+ * structured payload is empty. Defensive: the structural fix on this
1095
+ * branch plumbs `details` through MCP via `structuredContent`, but older
1096
+ * hosts, a future handler that forgets `structuredContent`, or any drop
1097
+ * of non-standard return fields would otherwise render as
1098
+ * "undefined: undefined". Same fallback applies to error rendering, and
1099
+ * we strip a leading `Error:` from the fallback text to avoid producing
1100
+ * `Error: Error: ...`.
1101
+ */
1077
1102
  renderResult(result: any, _options: any, theme: any) {
1078
- const d = result.details;
1103
+ const d = readDetails(result);
1079
1104
  if (result.isError || d?.error) {
1080
- return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
1105
+ const rawMsg = d?.error ?? result.content?.[0]?.text ?? "unknown";
1106
+ const msg = rawMsg.replace(/^\s*Error:\s*/i, "");
1107
+ return new Text(theme.fg("error", `Error: ${msg}`), 0, 0);
1108
+ }
1109
+ if (!d?.gateId || !d?.verdict) {
1110
+ const text = result.content?.[0]?.text ?? "Gate result saved";
1111
+ return new Text(theme.fg("success", text), 0, 0);
1081
1112
  }
1082
- const color = d?.verdict === "flag" ? "warning" : "success";
1083
- return new Text(theme.fg(color, `${d?.gateId}: ${d?.verdict}`), 0, 0);
1113
+ const color = d.verdict === "flag" ? "warning" : "success";
1114
+ return new Text(theme.fg(color, `${d.gateId}: ${d.verdict}`), 0, 0);
1084
1115
  },
1085
1116
  };
1086
1117
 
@@ -0,0 +1,109 @@
1
+ // GSD2 — Exec (context-mode) tool registration.
2
+ //
3
+ // Exposes the `gsd_exec` tool over MCP. Opt-in: disabled unless
4
+ // `context_mode.enabled: true` is set in preferences.
5
+
6
+ import { Type } from "@sinclair/typebox";
7
+ import type { ExtensionAPI } from "@gsd/pi-coding-agent";
8
+
9
+ import { executeGsdExec } from "../tools/exec-tool.js";
10
+ import { executeExecSearch } from "../tools/exec-search-tool.js";
11
+ import { executeResume } from "../tools/resume-tool.js";
12
+ import { loadEffectiveGSDPreferences } from "../preferences.js";
13
+ import { logWarning } from "../workflow-logger.js";
14
+
15
+ export function registerExecTools(pi: ExtensionAPI): void {
16
+ pi.registerTool({
17
+ name: "gsd_exec",
18
+ label: "Exec (Sandboxed)",
19
+ description:
20
+ "Run a short script (bash/node/python) in a subprocess. Full stdout/stderr persist to " +
21
+ ".gsd/exec/<id>.{stdout,stderr,meta.json}; only a short digest returns in context. Use " +
22
+ "this instead of reading many files or emitting large tool outputs — e.g. have the script " +
23
+ "count/grep/summarize and log the finding. Enabled by default; opt out via " +
24
+ "preferences.context_mode.enabled=false.",
25
+ promptSnippet:
26
+ "Run a bash/node/python script in a sandbox; full output is saved to disk and only a digest returns",
27
+ promptGuidelines: [
28
+ "Prefer gsd_exec for analyses that would otherwise read >3 files or produce large tool output.",
29
+ "Write scripts that log the finding (counts, matches, summaries) rather than raw dumps.",
30
+ "The digest is the last ~300 chars of stdout — size your log output accordingly.",
31
+ "Need the full output? Read the stdout_path returned in details (file on local disk).",
32
+ ],
33
+ parameters: Type.Object({
34
+ runtime: Type.Union(
35
+ [Type.Literal("bash"), Type.Literal("node"), Type.Literal("python")],
36
+ { description: "Interpreter: bash (-c), node (-e), or python3 (-c)." },
37
+ ),
38
+ script: Type.String({ description: "Script body. Keep output small (log the finding, not the data)." }),
39
+ purpose: Type.Optional(Type.String({ description: "Short label recorded in meta.json for later review." })),
40
+ timeout_ms: Type.Optional(
41
+ Type.Number({
42
+ description: "Per-invocation timeout (ms). Capped at 600000. Default from preferences.",
43
+ minimum: 1_000,
44
+ maximum: 600_000,
45
+ }),
46
+ ),
47
+ }),
48
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
49
+ let prefs: Awaited<ReturnType<typeof loadEffectiveGSDPreferences>> | null = null;
50
+ try {
51
+ prefs = loadEffectiveGSDPreferences();
52
+ } catch (err) {
53
+ logWarning("tool", `gsd_exec could not load preferences: ${err instanceof Error ? err.message : String(err)}`);
54
+ }
55
+ return executeGsdExec(params as Parameters<typeof executeGsdExec>[0], {
56
+ baseDir: process.cwd(),
57
+ preferences: prefs?.preferences ?? null,
58
+ });
59
+ },
60
+ });
61
+
62
+ pi.registerTool({
63
+ name: "gsd_exec_search",
64
+ label: "Search gsd_exec History",
65
+ description:
66
+ "List prior gsd_exec runs (most recent first) from .gsd/exec/*.meta.json. Useful for " +
67
+ "rediscovering the stdout_path of an earlier run without re-executing it. Read-only.",
68
+ promptSnippet: "Search prior gsd_exec runs by substring, runtime, or failing-only filter",
69
+ promptGuidelines: [
70
+ "Use this before re-running an expensive analysis — the prior run's stdout file may still answer.",
71
+ "The preview shows the trailing ~300 chars of stdout; read stdout_path for the full transcript.",
72
+ ],
73
+ parameters: Type.Object({
74
+ query: Type.Optional(Type.String({ description: "Substring matched against id and purpose (case-insensitive)." })),
75
+ runtime: Type.Optional(
76
+ Type.Union([Type.Literal("bash"), Type.Literal("node"), Type.Literal("python")], {
77
+ description: "Restrict to one runtime.",
78
+ }),
79
+ ),
80
+ failing_only: Type.Optional(Type.Boolean({ description: "Only non-zero exit codes and timeouts." })),
81
+ limit: Type.Optional(Type.Number({ description: "Max results (default 20, cap 200)", minimum: 1, maximum: 200 })),
82
+ }),
83
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
84
+ return executeExecSearch(params as Parameters<typeof executeExecSearch>[0], {
85
+ baseDir: process.cwd(),
86
+ });
87
+ },
88
+ });
89
+
90
+ pi.registerTool({
91
+ name: "gsd_resume",
92
+ label: "Resume (Read Snapshot)",
93
+ description:
94
+ "Return the contents of .gsd/last-snapshot.md — a ≤2 KB digest of top memories, recent " +
95
+ "gsd_exec runs, and active context, written automatically on session_before_compact. Use " +
96
+ "this after compaction or session resume to re-orient quickly.",
97
+ promptSnippet: "Read the pre-compaction snapshot to re-orient after context loss",
98
+ promptGuidelines: [
99
+ "Call this right after a session resumes if you feel you've lost durable context.",
100
+ "The snapshot is a summary — use memory_query or gsd_exec_search for detail.",
101
+ ],
102
+ parameters: Type.Object({}),
103
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
104
+ return executeResume(params as Parameters<typeof executeResume>[0], {
105
+ baseDir: process.cwd(),
106
+ });
107
+ },
108
+ });
109
+ }
@@ -8,6 +8,7 @@ import type { GSDEcosystemBeforeAgentStartHandler } from "../ecosystem/gsd-exten
8
8
  import { loadEcosystemExtensions } from "../ecosystem/loader.js";
9
9
  import { registerDbTools } from "./db-tools.js";
10
10
  import { registerDynamicTools } from "./dynamic-tools.js";
11
+ import { registerExecTools } from "./exec-tools.js";
11
12
  import { registerJournalTools } from "./journal-tools.js";
12
13
  import { registerMemoryTools } from "./memory-tools.js";
13
14
  import { registerQueryTools } from "./query-tools.js";
@@ -100,6 +101,7 @@ export function registerGsdExtension(pi: ExtensionAPI): void {
100
101
  ["journal-tools", () => registerJournalTools(pi)],
101
102
  ["query-tools", () => registerQueryTools(pi)],
102
103
  ["memory-tools", () => registerMemoryTools(pi)],
104
+ ["exec-tools", () => registerExecTools(pi)],
103
105
  ["shortcuts", () => registerShortcuts(pi)],
104
106
  ["hooks", () => registerHooks(pi, ecosystemHandlers)],
105
107
  ["ecosystem", () => {
@@ -18,7 +18,7 @@ import { loadToolApiKeys } from "../commands-config.js";
18
18
  import { loadFile, saveFile, formatContinue } from "../files.js";
19
19
  import { deriveState } from "../state.js";
20
20
  import { getAutoDashboardData, isAutoActive, isAutoPaused, markToolEnd, markToolStart, recordToolInvocationError } from "../auto.js";
21
- import { hideFooter } from "../auto-dashboard.js";
21
+
22
22
  import { isParallelActive, shutdownParallel } from "../parallel-orchestrator.js";
23
23
  import { checkToolCallLoop, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
24
24
  import { saveActivityLog } from "../activity-log.js";
@@ -48,7 +48,9 @@ export function registerHooks(
48
48
  initNotificationStore(process.cwd());
49
49
  installNotifyInterceptor(ctx);
50
50
  initNotificationWidget(ctx);
51
- initHealthWidget(ctx);
51
+ if (!isAutoActive()) {
52
+ initHealthWidget(ctx);
53
+ }
52
54
  resetWriteGateState();
53
55
  resetToolCallLoopGuard();
54
56
  resetAskUserQuestionsCache();
@@ -90,7 +92,7 @@ export function registerHooks(
90
92
  }
91
93
  loadToolApiKeys();
92
94
  if (isAutoActive()) {
93
- ctx.ui.setFooter(hideFooter);
95
+ ctx.ui.setWidget("gsd-health", undefined);
94
96
  }
95
97
  });
96
98
 
@@ -113,7 +115,7 @@ export function registerHooks(
113
115
  }
114
116
  loadToolApiKeys();
115
117
  if (isAutoActive()) {
116
- ctx.ui.setFooter(hideFooter);
118
+ ctx.ui.setWidget("gsd-health", undefined);
117
119
  }
118
120
  });
119
121
 
@@ -225,6 +227,42 @@ export function registerHooks(
225
227
  }));
226
228
  });
227
229
 
230
+ // Context-mode snapshot: write .gsd/last-snapshot.md before compaction so
231
+ // agents can call gsd_resume (or Read the file) to re-orient. Opt-in via
232
+ // preferences.context_mode.enabled. Runs after the auto-cancel handler
233
+ // above — if that one returned cancel:true, pi still fires us but the
234
+ // compaction won't actually happen; the snapshot is still useful then,
235
+ // since auto may pause and resume later.
236
+ pi.on("session_before_compact", async () => {
237
+ try {
238
+ const { loadEffectiveGSDPreferences } = await import("../preferences.js");
239
+ const { isContextModeEnabled } = await import("../preferences-types.js");
240
+ const prefs = loadEffectiveGSDPreferences();
241
+ if (!isContextModeEnabled(prefs?.preferences)) return;
242
+ const { writeCompactionSnapshot } = await import("../compaction-snapshot.js");
243
+ const { ensureDbOpen } = await import("./dynamic-tools.js");
244
+ await ensureDbOpen();
245
+ const basePath = process.cwd();
246
+ let activeContext: string | null = null;
247
+ try {
248
+ const state = await deriveState(basePath);
249
+ if (state.activeMilestone && state.activeSlice && state.activeTask) {
250
+ activeContext =
251
+ `Active: ${state.activeMilestone.id} / ${state.activeSlice.id} / ${state.activeTask.id}` +
252
+ (state.activeTask.title ? ` — ${state.activeTask.title}` : "");
253
+ }
254
+ } catch {
255
+ /* non-fatal */
256
+ }
257
+ writeCompactionSnapshot(basePath, { activeContext });
258
+ } catch (err) {
259
+ safetyLogWarning(
260
+ "context-mode",
261
+ `failed to write compaction snapshot: ${err instanceof Error ? err.message : String(err)}`,
262
+ );
263
+ }
264
+ });
265
+
228
266
  pi.on("session_shutdown", async (_event, ctx: ExtensionContext) => {
229
267
  if (isParallelActive()) {
230
268
  try {
@@ -117,9 +117,21 @@ function normalizeWriteGateSnapshot(value: unknown): WriteGateSnapshot {
117
117
  };
118
118
  }
119
119
 
120
+ const EMPTY_SNAPSHOT: WriteGateSnapshot = {
121
+ verifiedDepthMilestones: [],
122
+ activeQueuePhase: false,
123
+ pendingGateId: null,
124
+ };
125
+
120
126
  export function loadWriteGateSnapshot(basePath: string = process.cwd()): WriteGateSnapshot {
121
127
  const path = writeGateSnapshotPath(basePath);
122
- if (!existsSync(path)) return currentWriteGateSnapshot();
128
+ if (!existsSync(path)) {
129
+ // When persist mode is active and the file has been deleted, treat it as a
130
+ // full state reset so deleting the file clears the HARD BLOCK gate.
131
+ // In non-persist mode the file is never written, so fall back to in-memory.
132
+ if (shouldPersistWriteGateSnapshot()) return EMPTY_SNAPSHOT;
133
+ return currentWriteGateSnapshot();
134
+ }
123
135
  try {
124
136
  return normalizeWriteGateSnapshot(JSON.parse(readFileSync(path, "utf-8")));
125
137
  } catch {