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
@@ -180,14 +180,6 @@ function clearProjectRootStateFiles(basePath, milestoneId) {
180
180
  }
181
181
  }
182
182
  }
183
- function isProjectGsdSymlink(basePath) {
184
- try {
185
- return lstatSyncFn(join(basePath, ".gsd")).isSymbolicLink();
186
- }
187
- catch {
188
- return false;
189
- }
190
- }
191
183
  // ─── Build Artifact Auto-Resolve ─────────────────────────────────────────────
192
184
  /** Patterns for machine-generated build artifacts that can be safely
193
185
  * auto-resolved by accepting --theirs during merge. These files are
@@ -1429,50 +1421,15 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
1429
1421
  });
1430
1422
  }
1431
1423
  }
1432
- // 7. Stash any pre-existing dirty files so the squash merge is not
1433
- // blocked by unrelated local changes (#2151). clearProjectRootStateFiles
1434
- // only removes untracked .gsd/ files; tracked dirty files elsewhere (e.g.
1435
- // .planning/work-state.json with stash conflict markers) are invisible to
1436
- // that cleanup but will cause `git merge --squash` to reject.
1437
- let stashed = false;
1438
- try {
1439
- const status = execFileSync("git", ["status", "--porcelain"], {
1440
- cwd: originalBasePath_,
1441
- stdio: ["ignore", "pipe", "pipe"],
1442
- encoding: "utf-8",
1443
- }).trim();
1444
- if (status) {
1445
- // Use --include-untracked to stash untracked files that would block
1446
- // the squash merge, but EXCLUDE .gsd/milestones/ (#2505).
1447
- // --include-untracked without exclusion sweeps queued milestone
1448
- // CONTEXT files into the stash. If stash pop later fails, those files
1449
- // are permanently trapped in the stash entry and lost on the next
1450
- // stash push or drop.
1451
- //
1452
- // When `.gsd` itself is a symlink, Git rejects pathspecs below it
1453
- // ("beyond a symbolic link"). In that layout, exclude the whole symlink
1454
- // and keep stashing real project files that could block the merge.
1455
- const stashPathspecs = isProjectGsdSymlink(originalBasePath_)
1456
- ? [".", ":(exclude).gsd"]
1457
- : [":(exclude).gsd/milestones"];
1458
- execFileSync("git", [
1459
- "stash", "push", "--include-untracked",
1460
- "-m", `gsd: pre-merge stash for ${milestoneId}`,
1461
- "--", ...stashPathspecs,
1462
- ], { cwd: originalBasePath_, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" });
1463
- stashed = true;
1464
- }
1465
- }
1466
- catch (err) {
1467
- // Stash failure is non-fatal — proceed without stash and let the merge
1468
- // report the dirty tree if it fails.
1469
- logWarning("worktree", `git stash failed: ${err instanceof Error ? err.message : String(err)}`);
1470
- }
1471
- // 7a. Shelter queued milestone directories before the squash merge (#2505).
1424
+ // 7. Shelter queued milestone directories before the squash merge (#2505).
1472
1425
  // The milestone branch may contain copies of queued milestone dirs (via
1473
1426
  // copyPlanningArtifacts), so `git merge --squash` rejects when those same
1474
1427
  // files exist as untracked in the working tree. Temporarily move them to
1475
1428
  // a backup location, then restore after the merge+commit.
1429
+ //
1430
+ // MUST run BEFORE the pre-merge stash (step 7a) so `--include-untracked`
1431
+ // does not sweep queued CONTEXT files into the stash. If stash pop later
1432
+ // fails, files trapped inside the stash are permanently lost (#2505).
1476
1433
  const milestonesDir = join(gsdRoot(originalBasePath_), "milestones");
1477
1434
  const shelterDir = join(gsdRoot(originalBasePath_), ".milestone-shelter");
1478
1435
  const shelteredDirs = [];
@@ -1526,6 +1483,31 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
1526
1483
  // Non-fatal — proceed with merge; untracked files may block it
1527
1484
  logWarning("worktree", `milestone shelter operation failed: ${err instanceof Error ? err.message : String(err)}`);
1528
1485
  }
1486
+ // 7a. Stash pre-existing dirty files so the squash merge is not blocked by
1487
+ // unrelated local changes (#2151). Includes untracked files to handle
1488
+ // locally-added files that conflict with tracked files on the milestone
1489
+ // branch. Passing NO pathspec lets git skip gitignored paths silently;
1490
+ // adding an explicit pathspec trips a `git add`-style fatal on ignored
1491
+ // entries (e.g. a gitignored `.gsd` symlink under ADR-002) (#4573).
1492
+ // Queued CONTEXT files under `.gsd/milestones/*` are already sheltered
1493
+ // in step 7 above, so they won't be swept into the stash.
1494
+ let stashed = false;
1495
+ try {
1496
+ const status = execFileSync("git", ["status", "--porcelain"], {
1497
+ cwd: originalBasePath_,
1498
+ stdio: ["ignore", "pipe", "pipe"],
1499
+ encoding: "utf-8",
1500
+ }).trim();
1501
+ if (status) {
1502
+ execFileSync("git", ["stash", "push", "--include-untracked", "-m", `gsd: pre-merge stash for ${milestoneId}`], { cwd: originalBasePath_, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" });
1503
+ stashed = true;
1504
+ }
1505
+ }
1506
+ catch (err) {
1507
+ // Stash failure is non-fatal — proceed without stash and let the merge
1508
+ // report the dirty tree if it fails.
1509
+ logWarning("worktree", `git stash failed: ${err instanceof Error ? err.message : String(err)}`);
1510
+ }
1529
1511
  // 7b. Clean up stale merge state before attempting squash merge (#2912).
1530
1512
  // A leftover MERGE_HEAD (from a previous failed merge, libgit2 native path,
1531
1513
  // or interrupted operation) causes `git merge --squash` to refuse with
@@ -55,7 +55,7 @@ import { getErrorMessage } from "./error-utils.js";
55
55
  import { recoverFailedMigration } from "./migrate-external.js";
56
56
  import { initRegistry, convertDispatchRules } from "./rule-registry.js";
57
57
  import { emitJournalEvent as _emitJournalEvent } from "./journal.js";
58
- import { updateProgressWidget as _updateProgressWidget, updateSliceProgressCache, clearSliceProgressCache, hideFooter, } from "./auto-dashboard.js";
58
+ import { updateProgressWidget as _updateProgressWidget, updateSliceProgressCache, clearSliceProgressCache, } from "./auto-dashboard.js";
59
59
  import { registerSigtermHandler as _registerSigtermHandler, deregisterSigtermHandler as _deregisterSigtermHandler, } from "./auto-supervisor.js";
60
60
  import { isDbAvailable, getMilestone } from "./gsd-db.js";
61
61
  import { countPendingCaptures } from "./captures.js";
@@ -145,8 +145,8 @@ export function startAutoDetached(ctx, pi, base, verboseMode, options) {
145
145
  });
146
146
  }
147
147
  /** Returns true if the project is configured for `isolation:worktree` mode. */
148
- export function shouldUseWorktreeIsolation() {
149
- const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
148
+ export function shouldUseWorktreeIsolation(basePath) {
149
+ const prefs = loadEffectiveGSDPreferences(basePath)?.preferences?.git;
150
150
  if (prefs?.isolation === "worktree")
151
151
  return true;
152
152
  // Default is false — worktree isolation requires explicit opt-in
@@ -215,7 +215,7 @@ export function getAutoDashboardData() {
215
215
  const rtkSavings = sessionId && s.basePath
216
216
  ? getRtkSessionSavings(s.basePath, sessionId)
217
217
  : null;
218
- const rtkEnabled = loadEffectiveGSDPreferences()?.preferences.experimental?.rtk === true;
218
+ const rtkEnabled = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences.experimental?.rtk === true;
219
219
  // Pending capture count — lazy check, non-fatal
220
220
  let pendingCaptureCount = 0;
221
221
  try {
@@ -393,7 +393,7 @@ function clearUnitTimeout() {
393
393
  }
394
394
  /** Build snapshot metric opts. */
395
395
  function buildSnapshotOpts(_unitType, _unitId) {
396
- const prefs = loadEffectiveGSDPreferences()?.preferences;
396
+ const prefs = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences;
397
397
  const uokFlags = resolveUokFlags(prefs);
398
398
  return {
399
399
  ...(s.autoStartTime > 0 ? { autoSessionKey: String(s.autoStartTime) } : {}),
@@ -427,7 +427,7 @@ function handleLostSessionLock(ctx, lockStatus) {
427
427
  restoreProjectRootEnv();
428
428
  restoreMilestoneLockEnv();
429
429
  deregisterSigtermHandler();
430
- clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
430
+ clearCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences);
431
431
  const base = lockBase();
432
432
  const lockFilePath = base ? join(gsdRoot(base), "auto.lock") : "unknown";
433
433
  const recoverySuggestion = "\nTo recover, run: gsd doctor --fix";
@@ -443,7 +443,6 @@ function handleLostSessionLock(ctx, lockStatus) {
443
443
  ctx?.ui.notify(message, "error");
444
444
  ctx?.ui.setStatus("gsd-auto", undefined);
445
445
  ctx?.ui.setWidget("gsd-progress", undefined);
446
- ctx?.ui.setFooter(undefined);
447
446
  if (ctx)
448
447
  initHealthWidget(ctx);
449
448
  }
@@ -480,7 +479,6 @@ function cleanupAfterLoopExit(ctx) {
480
479
  if (!s.paused) {
481
480
  ctx.ui.setStatus("gsd-auto", undefined);
482
481
  ctx.ui.setWidget("gsd-progress", undefined);
483
- ctx.ui.setFooter(undefined);
484
482
  initHealthWidget(ctx);
485
483
  }
486
484
  // Restore CWD out of worktree back to original project root
@@ -498,7 +496,7 @@ function cleanupAfterLoopExit(ctx) {
498
496
  export async function stopAuto(ctx, pi, reason) {
499
497
  if (!s.active && !s.paused)
500
498
  return;
501
- const loadedPreferences = loadEffectiveGSDPreferences()?.preferences;
499
+ const loadedPreferences = loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences;
502
500
  const reasonSuffix = reason ? ` — ${reason}` : "";
503
501
  try {
504
502
  // ── Step 1: Timers and locks ──
@@ -743,7 +741,6 @@ export async function stopAuto(ctx, pi, reason) {
743
741
  // UI cleanup
744
742
  ctx?.ui.setStatus("gsd-auto", undefined);
745
743
  ctx?.ui.setWidget("gsd-progress", undefined);
746
- ctx?.ui.setFooter(undefined);
747
744
  if (ctx)
748
745
  initHealthWidget(ctx);
749
746
  restoreProjectRootEnv();
@@ -832,7 +829,6 @@ export async function pauseAuto(ctx, _pi, _errorContext) {
832
829
  s.verificationRetryCount.clear();
833
830
  ctx?.ui.setStatus("gsd-auto", "paused");
834
831
  ctx?.ui.setWidget("gsd-progress", undefined);
835
- ctx?.ui.setFooter(undefined);
836
832
  if (ctx)
837
833
  initHealthWidget(ctx);
838
834
  const resumeCmd = s.stepMode ? "/gsd next" : "/gsd auto";
@@ -1164,7 +1160,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1164
1160
  });
1165
1161
  // ── Auto-worktree / branch-mode: re-enter on resume ──
1166
1162
  if (s.currentMilestoneId &&
1167
- getIsolationMode() !== "none" &&
1163
+ getIsolationMode(s.originalBasePath || s.basePath) !== "none" &&
1168
1164
  s.originalBasePath &&
1169
1165
  !isInAutoWorktree(s.basePath) &&
1170
1166
  !detectWorktreeName(s.basePath) &&
@@ -1175,7 +1171,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1175
1171
  }
1176
1172
  registerSigtermHandler(lockBase());
1177
1173
  ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
1178
- ctx.ui.setFooter(hideFooter);
1174
+ ctx.ui.setWidget("gsd-health", undefined);
1179
1175
  ctx.ui.notify(s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "info");
1180
1176
  restoreHookState(s.basePath);
1181
1177
  // Re-sync managed resources on resume so long-lived auto sessions pick up
@@ -1197,7 +1193,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1197
1193
  await openProjectDbIfPresent(s.basePath);
1198
1194
  try {
1199
1195
  await rebuildState(s.basePath);
1200
- syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
1196
+ syncCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, await deriveState(s.basePath));
1201
1197
  }
1202
1198
  catch (e) {
1203
1199
  debugLog("resume-rebuild-state-failed", {
@@ -1227,7 +1223,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1227
1223
  }
1228
1224
  updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown");
1229
1225
  writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown");
1230
- logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
1226
+ logCmuxEvent(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
1231
1227
  captureProjectRootEnv(s.originalBasePath || s.basePath);
1232
1228
  startAutoCommandPolling(s.basePath);
1233
1229
  await runAutoLoopWithUok({
@@ -1253,13 +1249,13 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1253
1249
  return;
1254
1250
  captureProjectRootEnv(s.originalBasePath || s.basePath);
1255
1251
  try {
1256
- syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
1252
+ syncCmuxSidebar(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, await deriveState(s.basePath));
1257
1253
  }
1258
1254
  catch (err) {
1259
1255
  // Best-effort only — sidebar sync must never block auto-mode startup
1260
1256
  logWarning("engine", `cmux sync failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
1261
1257
  }
1262
- logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
1258
+ logCmuxEvent(loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
1263
1259
  startAutoCommandPolling(s.basePath);
1264
1260
  // Dispatch the first unit
1265
1261
  await runAutoLoopWithUok({
@@ -1,5 +1,5 @@
1
1
  import { logWarning } from "../workflow-logger.js";
2
- import { checkAutoStartAfterDiscuss } from "../guided-flow.js";
2
+ import { checkAutoStartAfterDiscuss, maybeHandleReadyPhraseWithoutFiles, maybeHandleEmptyIntentTurn, resetEmptyTurnCounter, } from "../guided-flow.js";
3
3
  import { getAutoDashboardData, getAutoModeStartModel, isAutoActive, pauseAuto, setCurrentDispatchedModelId } from "../auto.js";
4
4
  import { getNextFallbackModel, resolveModelWithFallbacksForUnit } from "../preferences.js";
5
5
  import { pauseAutoForProviderError } from "../provider-error-pause.js";
@@ -53,6 +53,19 @@ export async function handleAgentEnd(pi, event, ctx) {
53
53
  clearDiscussionFlowState();
54
54
  return;
55
55
  }
56
+ // #4573 — When the LLM emits "Milestone X ready." but the required files
57
+ // are missing, `checkAutoStartAfterDiscuss` returns false silently. Surface
58
+ // that and nudge the LLM to complete the writes before the user hits the
59
+ // downstream "All milestones complete" warning loop.
60
+ if (maybeHandleReadyPhraseWithoutFiles(event))
61
+ return;
62
+ // #4573 — Empty-turn recovery: if the LLM announced intent in prose but
63
+ // emitted no tool calls, nudge it to execute. Fires only when auto-mode is
64
+ // active or a discussion autostart is pending (non-auto interactive discuss
65
+ // is user-driven). Runs before `isAutoActive` early return so pending
66
+ // discussions (where isAutoActive may be false) still get recovered.
67
+ if (maybeHandleEmptyIntentTurn(event, isAutoActive()))
68
+ return;
56
69
  if (!isAutoActive())
57
70
  return;
58
71
  if (isSessionSwitchInFlight())
@@ -286,6 +299,9 @@ export async function handleAgentEnd(pi, event, ctx) {
286
299
  // ── Success path ─────────────────────────────────────────────────────────
287
300
  try {
288
301
  resetRetryState(retryState);
302
+ // #4573 — Reset the empty-turn counter on any successful agent turn so
303
+ // transient stalls don't accumulate across independent units.
304
+ resetEmptyTurnCounter();
289
305
  resolveAgentEnd(event);
290
306
  }
291
307
  catch (err) {
@@ -19,6 +19,18 @@ function registerAlias(pi, toolDef, aliasName, canonicalName) {
19
19
  promptGuidelines: [`Alias for ${canonicalName} — prefer the canonical name.`],
20
20
  });
21
21
  }
22
+ /**
23
+ * Read a tool result's structured payload, accommodating MCP's `details` →
24
+ * `structuredContent` rename (#4472, #4477). In-process executions still
25
+ * deliver the payload on `result.details`; MCP-routed executions deliver it
26
+ * on `result.structuredContent` (post `adaptExecutorResult` transform). All
27
+ * `renderResult` callbacks in this file route through this helper so a future
28
+ * field rename only needs to be applied in one place.
29
+ */
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- result shape varies by tool
31
+ function readDetails(result) {
32
+ return result?.details ?? result?.structuredContent;
33
+ }
22
34
  export function registerDbTools(pi) {
23
35
  // ─── gsd_decision_save (formerly gsd_save_decision) ─────────────────────
24
36
  const decisionSaveExecute = async (_toolCallId, params, _signal, _onUpdate, _ctx) => {
@@ -92,7 +104,7 @@ export function registerDbTools(pi) {
92
104
  return new Text(text, 0, 0);
93
105
  },
94
106
  renderResult(result, _options, theme) {
95
- const d = result.details;
107
+ const d = readDetails(result);
96
108
  if (result.isError || d?.error) {
97
109
  return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
98
110
  }
@@ -175,7 +187,7 @@ export function registerDbTools(pi) {
175
187
  return new Text(text, 0, 0);
176
188
  },
177
189
  renderResult(result, _options, theme) {
178
- const d = result.details;
190
+ const d = readDetails(result);
179
191
  if (result.isError || d?.error) {
180
192
  return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
181
193
  }
@@ -255,7 +267,7 @@ export function registerDbTools(pi) {
255
267
  return new Text(text, 0, 0);
256
268
  },
257
269
  renderResult(result, _options, theme) {
258
- const d = result.details;
270
+ const d = readDetails(result);
259
271
  if (result.isError || d?.error) {
260
272
  return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
261
273
  }
@@ -301,7 +313,7 @@ export function registerDbTools(pi) {
301
313
  return new Text(text, 0, 0);
302
314
  },
303
315
  renderResult(result, _options, theme) {
304
- const d = result.details;
316
+ const d = readDetails(result);
305
317
  if (result.isError || d?.error) {
306
318
  return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
307
319
  }
@@ -382,7 +394,7 @@ export function registerDbTools(pi) {
382
394
  return new Text(theme.fg("toolTitle", theme.bold("milestone_generate_id")), 0, 0);
383
395
  },
384
396
  renderResult(result, _options, theme) {
385
- const d = result.details;
397
+ const d = readDetails(result);
386
398
  if (result.isError || d?.error) {
387
399
  return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
388
400
  }
@@ -967,13 +979,31 @@ export function registerDbTools(pi) {
967
979
  text += theme.fg("dim", ` → ${args.verdict ?? ""}`);
968
980
  return new Text(text, 0, 0);
969
981
  },
982
+ /**
983
+ * Render the save_gate_result tool output for the TUI.
984
+ *
985
+ * Prefers structured fields, but falls back to `content[0].text` when the
986
+ * structured payload is empty. Defensive: the structural fix on this
987
+ * branch plumbs `details` through MCP via `structuredContent`, but older
988
+ * hosts, a future handler that forgets `structuredContent`, or any drop
989
+ * of non-standard return fields would otherwise render as
990
+ * "undefined: undefined". Same fallback applies to error rendering, and
991
+ * we strip a leading `Error:` from the fallback text to avoid producing
992
+ * `Error: Error: ...`.
993
+ */
970
994
  renderResult(result, _options, theme) {
971
- const d = result.details;
995
+ const d = readDetails(result);
972
996
  if (result.isError || d?.error) {
973
- return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
997
+ const rawMsg = d?.error ?? result.content?.[0]?.text ?? "unknown";
998
+ const msg = rawMsg.replace(/^\s*Error:\s*/i, "");
999
+ return new Text(theme.fg("error", `Error: ${msg}`), 0, 0);
1000
+ }
1001
+ if (!d?.gateId || !d?.verdict) {
1002
+ const text = result.content?.[0]?.text ?? "Gate result saved";
1003
+ return new Text(theme.fg("success", text), 0, 0);
974
1004
  }
975
- const color = d?.verdict === "flag" ? "warning" : "success";
976
- return new Text(theme.fg(color, `${d?.gateId}: ${d?.verdict}`), 0, 0);
1005
+ const color = d.verdict === "flag" ? "warning" : "success";
1006
+ return new Text(theme.fg(color, `${d.gateId}: ${d.verdict}`), 0, 0);
977
1007
  },
978
1008
  };
979
1009
  pi.registerTool(saveGateResultTool);
@@ -0,0 +1,93 @@
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
+ import { Type } from "@sinclair/typebox";
6
+ import { executeGsdExec } from "../tools/exec-tool.js";
7
+ import { executeExecSearch } from "../tools/exec-search-tool.js";
8
+ import { executeResume } from "../tools/resume-tool.js";
9
+ import { loadEffectiveGSDPreferences } from "../preferences.js";
10
+ import { logWarning } from "../workflow-logger.js";
11
+ export function registerExecTools(pi) {
12
+ pi.registerTool({
13
+ name: "gsd_exec",
14
+ label: "Exec (Sandboxed)",
15
+ description: "Run a short script (bash/node/python) in a subprocess. Full stdout/stderr persist to " +
16
+ ".gsd/exec/<id>.{stdout,stderr,meta.json}; only a short digest returns in context. Use " +
17
+ "this instead of reading many files or emitting large tool outputs — e.g. have the script " +
18
+ "count/grep/summarize and log the finding. Enabled by default; opt out via " +
19
+ "preferences.context_mode.enabled=false.",
20
+ promptSnippet: "Run a bash/node/python script in a sandbox; full output is saved to disk and only a digest returns",
21
+ promptGuidelines: [
22
+ "Prefer gsd_exec for analyses that would otherwise read >3 files or produce large tool output.",
23
+ "Write scripts that log the finding (counts, matches, summaries) rather than raw dumps.",
24
+ "The digest is the last ~300 chars of stdout — size your log output accordingly.",
25
+ "Need the full output? Read the stdout_path returned in details (file on local disk).",
26
+ ],
27
+ parameters: Type.Object({
28
+ runtime: Type.Union([Type.Literal("bash"), Type.Literal("node"), Type.Literal("python")], { description: "Interpreter: bash (-c), node (-e), or python3 (-c)." }),
29
+ script: Type.String({ description: "Script body. Keep output small (log the finding, not the data)." }),
30
+ purpose: Type.Optional(Type.String({ description: "Short label recorded in meta.json for later review." })),
31
+ timeout_ms: Type.Optional(Type.Number({
32
+ description: "Per-invocation timeout (ms). Capped at 600000. Default from preferences.",
33
+ minimum: 1_000,
34
+ maximum: 600_000,
35
+ })),
36
+ }),
37
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
38
+ let prefs = null;
39
+ try {
40
+ prefs = loadEffectiveGSDPreferences();
41
+ }
42
+ catch (err) {
43
+ logWarning("tool", `gsd_exec could not load preferences: ${err instanceof Error ? err.message : String(err)}`);
44
+ }
45
+ return executeGsdExec(params, {
46
+ baseDir: process.cwd(),
47
+ preferences: prefs?.preferences ?? null,
48
+ });
49
+ },
50
+ });
51
+ pi.registerTool({
52
+ name: "gsd_exec_search",
53
+ label: "Search gsd_exec History",
54
+ description: "List prior gsd_exec runs (most recent first) from .gsd/exec/*.meta.json. Useful for " +
55
+ "rediscovering the stdout_path of an earlier run without re-executing it. Read-only.",
56
+ promptSnippet: "Search prior gsd_exec runs by substring, runtime, or failing-only filter",
57
+ promptGuidelines: [
58
+ "Use this before re-running an expensive analysis — the prior run's stdout file may still answer.",
59
+ "The preview shows the trailing ~300 chars of stdout; read stdout_path for the full transcript.",
60
+ ],
61
+ parameters: Type.Object({
62
+ query: Type.Optional(Type.String({ description: "Substring matched against id and purpose (case-insensitive)." })),
63
+ runtime: Type.Optional(Type.Union([Type.Literal("bash"), Type.Literal("node"), Type.Literal("python")], {
64
+ description: "Restrict to one runtime.",
65
+ })),
66
+ failing_only: Type.Optional(Type.Boolean({ description: "Only non-zero exit codes and timeouts." })),
67
+ limit: Type.Optional(Type.Number({ description: "Max results (default 20, cap 200)", minimum: 1, maximum: 200 })),
68
+ }),
69
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
70
+ return executeExecSearch(params, {
71
+ baseDir: process.cwd(),
72
+ });
73
+ },
74
+ });
75
+ pi.registerTool({
76
+ name: "gsd_resume",
77
+ label: "Resume (Read Snapshot)",
78
+ description: "Return the contents of .gsd/last-snapshot.md — a ≤2 KB digest of top memories, recent " +
79
+ "gsd_exec runs, and active context, written automatically on session_before_compact. Use " +
80
+ "this after compaction or session resume to re-orient quickly.",
81
+ promptSnippet: "Read the pre-compaction snapshot to re-orient after context loss",
82
+ promptGuidelines: [
83
+ "Call this right after a session resumes if you feel you've lost durable context.",
84
+ "The snapshot is a summary — use memory_query or gsd_exec_search for detail.",
85
+ ],
86
+ parameters: Type.Object({}),
87
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
88
+ return executeResume(params, {
89
+ baseDir: process.cwd(),
90
+ });
91
+ },
92
+ });
93
+ }
@@ -4,6 +4,7 @@ import { registerWorktreeCommand } from "../worktree-command.js";
4
4
  import { loadEcosystemExtensions } from "../ecosystem/loader.js";
5
5
  import { registerDbTools } from "./db-tools.js";
6
6
  import { registerDynamicTools } from "./dynamic-tools.js";
7
+ import { registerExecTools } from "./exec-tools.js";
7
8
  import { registerJournalTools } from "./journal-tools.js";
8
9
  import { registerMemoryTools } from "./memory-tools.js";
9
10
  import { registerQueryTools } from "./query-tools.js";
@@ -86,6 +87,7 @@ export function registerGsdExtension(pi) {
86
87
  ["journal-tools", () => registerJournalTools(pi)],
87
88
  ["query-tools", () => registerQueryTools(pi)],
88
89
  ["memory-tools", () => registerMemoryTools(pi)],
90
+ ["exec-tools", () => registerExecTools(pi)],
89
91
  ["shortcuts", () => registerShortcuts(pi)],
90
92
  ["hooks", () => registerHooks(pi, ecosystemHandlers)],
91
93
  ["ecosystem", () => {
@@ -13,7 +13,6 @@ import { loadToolApiKeys } from "../commands-config.js";
13
13
  import { loadFile, saveFile, formatContinue } from "../files.js";
14
14
  import { deriveState } from "../state.js";
15
15
  import { getAutoDashboardData, isAutoActive, isAutoPaused, markToolEnd, markToolStart, recordToolInvocationError } from "../auto.js";
16
- import { hideFooter } from "../auto-dashboard.js";
17
16
  import { isParallelActive, shutdownParallel } from "../parallel-orchestrator.js";
18
17
  import { checkToolCallLoop, resetToolCallLoopGuard } from "./tool-call-loop-guard.js";
19
18
  import { saveActivityLog } from "../activity-log.js";
@@ -37,7 +36,9 @@ export function registerHooks(pi, ecosystemHandlers) {
37
36
  initNotificationStore(process.cwd());
38
37
  installNotifyInterceptor(ctx);
39
38
  initNotificationWidget(ctx);
40
- initHealthWidget(ctx);
39
+ if (!isAutoActive()) {
40
+ initHealthWidget(ctx);
41
+ }
41
42
  resetWriteGateState();
42
43
  resetToolCallLoopGuard();
43
44
  resetAskUserQuestionsCache();
@@ -79,7 +80,7 @@ export function registerHooks(pi, ecosystemHandlers) {
79
80
  }
80
81
  loadToolApiKeys();
81
82
  if (isAutoActive()) {
82
- ctx.ui.setFooter(hideFooter);
83
+ ctx.ui.setWidget("gsd-health", undefined);
83
84
  }
84
85
  });
85
86
  pi.on("session_switch", async (_event, ctx) => {
@@ -101,7 +102,7 @@ export function registerHooks(pi, ecosystemHandlers) {
101
102
  }
102
103
  loadToolApiKeys();
103
104
  if (isAutoActive()) {
104
- ctx.ui.setFooter(hideFooter);
105
+ ctx.ui.setWidget("gsd-health", undefined);
105
106
  }
106
107
  });
107
108
  pi.on("before_agent_start", async (event, ctx) => {
@@ -204,6 +205,41 @@ export function registerHooks(pi, ecosystemHandlers) {
204
205
  nextAction: `Resume task ${state.activeTask.id}: ${state.activeTask.title}.`,
205
206
  }));
206
207
  });
208
+ // Context-mode snapshot: write .gsd/last-snapshot.md before compaction so
209
+ // agents can call gsd_resume (or Read the file) to re-orient. Opt-in via
210
+ // preferences.context_mode.enabled. Runs after the auto-cancel handler
211
+ // above — if that one returned cancel:true, pi still fires us but the
212
+ // compaction won't actually happen; the snapshot is still useful then,
213
+ // since auto may pause and resume later.
214
+ pi.on("session_before_compact", async () => {
215
+ try {
216
+ const { loadEffectiveGSDPreferences } = await import("../preferences.js");
217
+ const { isContextModeEnabled } = await import("../preferences-types.js");
218
+ const prefs = loadEffectiveGSDPreferences();
219
+ if (!isContextModeEnabled(prefs?.preferences))
220
+ return;
221
+ const { writeCompactionSnapshot } = await import("../compaction-snapshot.js");
222
+ const { ensureDbOpen } = await import("./dynamic-tools.js");
223
+ await ensureDbOpen();
224
+ const basePath = process.cwd();
225
+ let activeContext = null;
226
+ try {
227
+ const state = await deriveState(basePath);
228
+ if (state.activeMilestone && state.activeSlice && state.activeTask) {
229
+ activeContext =
230
+ `Active: ${state.activeMilestone.id} / ${state.activeSlice.id} / ${state.activeTask.id}` +
231
+ (state.activeTask.title ? ` — ${state.activeTask.title}` : "");
232
+ }
233
+ }
234
+ catch {
235
+ /* non-fatal */
236
+ }
237
+ writeCompactionSnapshot(basePath, { activeContext });
238
+ }
239
+ catch (err) {
240
+ safetyLogWarning("context-mode", `failed to write compaction snapshot: ${err instanceof Error ? err.message : String(err)}`);
241
+ }
242
+ });
207
243
  pi.on("session_shutdown", async (_event, ctx) => {
208
244
  if (isParallelActive()) {
209
245
  try {
@@ -99,10 +99,21 @@ function normalizeWriteGateSnapshot(value) {
99
99
  pendingGateId: typeof record.pendingGateId === "string" ? record.pendingGateId : null,
100
100
  };
101
101
  }
102
+ const EMPTY_SNAPSHOT = {
103
+ verifiedDepthMilestones: [],
104
+ activeQueuePhase: false,
105
+ pendingGateId: null,
106
+ };
102
107
  export function loadWriteGateSnapshot(basePath = process.cwd()) {
103
108
  const path = writeGateSnapshotPath(basePath);
104
- if (!existsSync(path))
109
+ if (!existsSync(path)) {
110
+ // When persist mode is active and the file has been deleted, treat it as a
111
+ // full state reset so deleting the file clears the HARD BLOCK gate.
112
+ // In non-persist mode the file is never written, so fall back to in-memory.
113
+ if (shouldPersistWriteGateSnapshot())
114
+ return EMPTY_SNAPSHOT;
105
115
  return currentWriteGateSnapshot();
116
+ }
106
117
  try {
107
118
  return normalizeWriteGateSnapshot(JSON.parse(readFileSync(path, "utf-8")));
108
119
  }