gsd-pi 2.82.0-dev.dfbc5f58f → 2.82.0-dev.e7a7f1ed5

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 (182) hide show
  1. package/README.md +1 -1
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +1 -1
  4. package/dist/resources/extensions/gsd/auto/phases.js +73 -30
  5. package/dist/resources/extensions/gsd/auto-dashboard.js +66 -1
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +1 -0
  7. package/dist/resources/extensions/gsd/auto-dispatch.js +10 -16
  8. package/dist/resources/extensions/gsd/auto-recovery.js +40 -13
  9. package/dist/resources/extensions/gsd/auto-start.js +3 -3
  10. package/dist/resources/extensions/gsd/auto-verification.js +17 -4
  11. package/dist/resources/extensions/gsd/auto-worktree.js +65 -9
  12. package/dist/resources/extensions/gsd/auto.js +7 -2
  13. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +27 -6
  14. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +4 -2
  15. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +7 -2
  16. package/dist/resources/extensions/gsd/crash-recovery.js +16 -4
  17. package/dist/resources/extensions/gsd/db/milestone-leases.js +24 -0
  18. package/dist/resources/extensions/gsd/doctor-git-checks.js +46 -1
  19. package/dist/resources/extensions/gsd/git-service.js +6 -2
  20. package/dist/resources/extensions/gsd/gsd-db.js +20 -6
  21. package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -3
  22. package/dist/resources/extensions/gsd/guided-flow.js +95 -116
  23. package/dist/resources/extensions/gsd/guided-unit-context.js +23 -0
  24. package/dist/resources/extensions/gsd/migration-auto-check.js +12 -17
  25. package/dist/resources/extensions/gsd/pending-auto-start.js +52 -0
  26. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  27. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  28. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +8 -8
  29. package/dist/resources/extensions/gsd/prompts/discuss.md +9 -9
  30. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +4 -4
  31. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +3 -3
  32. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  33. package/dist/resources/extensions/gsd/prompts/queue.md +4 -4
  34. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  35. package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  36. package/dist/resources/extensions/gsd/queue-reorder-ui.js +30 -13
  37. package/dist/resources/extensions/gsd/smart-entry-routing.js +36 -0
  38. package/dist/resources/extensions/gsd/state-reconciliation/drift/project-md.js +9 -14
  39. package/dist/resources/extensions/gsd/state-reconciliation/drift/roadmap.js +19 -24
  40. package/dist/resources/extensions/gsd/status-guards.js +7 -0
  41. package/dist/resources/extensions/gsd/workflow-mcp.js +17 -1
  42. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  43. package/dist/web/standalone/.next/BUILD_ID +1 -1
  44. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  45. package/dist/web/standalone/.next/build-manifest.json +2 -2
  46. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  47. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/index.html +1 -1
  65. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  72. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  74. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  75. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  76. package/package.json +1 -1
  77. package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
  78. package/packages/pi-ai/dist/providers/google-gemini-cli.js +5 -0
  79. package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
  80. package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts +2 -0
  81. package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts.map +1 -0
  82. package/packages/pi-ai/dist/providers/google-gemini-cli.test.js +41 -0
  83. package/packages/pi-ai/dist/providers/google-gemini-cli.test.js.map +1 -0
  84. package/packages/pi-ai/src/providers/google-gemini-cli.test.ts +49 -0
  85. package/packages/pi-ai/src/providers/google-gemini-cli.ts +7 -0
  86. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  87. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  88. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +24 -6
  89. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  90. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +23 -7
  91. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  92. package/packages/pi-tui/dist/__tests__/terminal.test.d.ts +2 -0
  93. package/packages/pi-tui/dist/__tests__/terminal.test.d.ts.map +1 -0
  94. package/packages/pi-tui/dist/__tests__/terminal.test.js +103 -0
  95. package/packages/pi-tui/dist/__tests__/terminal.test.js.map +1 -0
  96. package/packages/pi-tui/dist/terminal.d.ts +2 -0
  97. package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
  98. package/packages/pi-tui/dist/terminal.js +12 -0
  99. package/packages/pi-tui/dist/terminal.js.map +1 -1
  100. package/packages/pi-tui/src/__tests__/terminal.test.ts +121 -0
  101. package/packages/pi-tui/src/terminal.ts +11 -0
  102. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  103. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +1 -1
  104. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +9 -0
  105. package/src/resources/extensions/gsd/auto/phases.ts +83 -37
  106. package/src/resources/extensions/gsd/auto-dashboard.ts +72 -1
  107. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -0
  108. package/src/resources/extensions/gsd/auto-dispatch.ts +10 -16
  109. package/src/resources/extensions/gsd/auto-recovery.ts +45 -11
  110. package/src/resources/extensions/gsd/auto-start.ts +2 -3
  111. package/src/resources/extensions/gsd/auto-verification.ts +22 -2
  112. package/src/resources/extensions/gsd/auto-worktree.ts +74 -9
  113. package/src/resources/extensions/gsd/auto.ts +8 -2
  114. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +36 -6
  115. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +4 -2
  116. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +8 -3
  117. package/src/resources/extensions/gsd/crash-recovery.ts +16 -2
  118. package/src/resources/extensions/gsd/db/milestone-leases.ts +26 -0
  119. package/src/resources/extensions/gsd/doctor-git-checks.ts +45 -1
  120. package/src/resources/extensions/gsd/doctor-types.ts +1 -0
  121. package/src/resources/extensions/gsd/git-service.ts +6 -3
  122. package/src/resources/extensions/gsd/gsd-db.ts +18 -6
  123. package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -3
  124. package/src/resources/extensions/gsd/guided-flow.ts +128 -133
  125. package/src/resources/extensions/gsd/guided-unit-context.ts +30 -0
  126. package/src/resources/extensions/gsd/migration-auto-check.ts +15 -23
  127. package/src/resources/extensions/gsd/pending-auto-start.ts +79 -0
  128. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  129. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  130. package/src/resources/extensions/gsd/prompts/discuss-headless.md +8 -8
  131. package/src/resources/extensions/gsd/prompts/discuss.md +9 -9
  132. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +4 -4
  133. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +3 -3
  134. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  135. package/src/resources/extensions/gsd/prompts/queue.md +4 -4
  136. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  137. package/src/resources/extensions/gsd/prompts/rewrite-docs.md +1 -1
  138. package/src/resources/extensions/gsd/queue-reorder-ui.ts +31 -13
  139. package/src/resources/extensions/gsd/smart-entry-routing.ts +77 -0
  140. package/src/resources/extensions/gsd/state-reconciliation/drift/project-md.ts +12 -15
  141. package/src/resources/extensions/gsd/state-reconciliation/drift/roadmap.ts +17 -25
  142. package/src/resources/extensions/gsd/status-guards.ts +8 -0
  143. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +71 -0
  144. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
  145. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +29 -1
  146. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +53 -2
  147. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +76 -5
  148. package/src/resources/extensions/gsd/tests/auto-stop-notification.test.ts +20 -0
  149. package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +87 -0
  150. package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +11 -2
  151. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +5 -9
  152. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -0
  153. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
  154. package/src/resources/extensions/gsd/tests/db-authority-regression.test.ts +208 -0
  155. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +27 -0
  156. package/src/resources/extensions/gsd/tests/doctor-empty-worktree.test.ts +65 -0
  157. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +11 -0
  158. package/src/resources/extensions/gsd/tests/guided-discuss-project-prompt-rendering.test.ts +2 -0
  159. package/src/resources/extensions/gsd/tests/guided-dispatch-root.test.ts +106 -0
  160. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +59 -11
  161. package/src/resources/extensions/gsd/tests/guided-tool-contract.test.ts +65 -0
  162. package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +7 -7
  163. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +9 -0
  164. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +46 -0
  165. package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +179 -0
  166. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +26 -18
  167. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +29 -5
  168. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +2 -0
  169. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +59 -0
  170. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +37 -1
  171. package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +54 -0
  172. package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +43 -0
  173. package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +2 -3
  174. package/src/resources/extensions/gsd/tests/smart-entry-routing.test.ts +113 -0
  175. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +22 -1
  176. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +119 -23
  177. package/src/resources/extensions/gsd/tests/status-guards.test.ts +13 -1
  178. package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +29 -2
  179. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +18 -0
  180. package/src/resources/extensions/gsd/workflow-mcp.ts +18 -1
  181. /package/dist/web/standalone/.next/static/{q0WYuDVbHeFFYbdd-fei2 → 4dSwdrs__8NwCZggxP9KF}/_buildManifest.js +0 -0
  182. /package/dist/web/standalone/.next/static/{q0WYuDVbHeFFYbdd-fei2 → 4dSwdrs__8NwCZggxP9KF}/_ssgManifest.js +0 -0
@@ -10,6 +10,7 @@ import { shouldIgnoreAgentEndForActiveUnit } from "../auto/unit-runner-events.js
10
10
  import { resolveModelId } from "../auto-model-selection.js";
11
11
  import { resolveProjectRoot } from "../worktree.js";
12
12
  import { clearDiscussionFlowState } from "./write-gate.js";
13
+ import { clearGuidedUnitContext } from "../guided-unit-context.js";
13
14
  import { resumeAutoAfterProviderDelay } from "./provider-error-resume.js";
14
15
  import { classifyError, createRetryState, resetRetryState, isTransient, } from "../error-classifier.js";
15
16
  import { blockModel, isModelBlocked } from "../blocked-models.js";
@@ -60,6 +61,11 @@ export function isUserInitiatedAbortMessage(message) {
60
61
  return false;
61
62
  return /\b(?:claude code process aborted by user|request aborted by user|process aborted by user)\b/i.test(message);
62
63
  }
64
+ export function shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg) {
65
+ if (!isTransient(cls) || cls.kind === "rate-limit")
66
+ return false;
67
+ return !/retry failed after \d+ attempts:/i.test(rawErrorMsg);
68
+ }
63
69
  function isBareClaudeCodeSessionSwitchAbortMarker(message) {
64
70
  if (!message)
65
71
  return false;
@@ -153,6 +159,13 @@ export function resolveAgentEndErrorDisplay(rawErrorMsg, content) {
153
159
  }
154
160
  return rawErrorMsg;
155
161
  }
162
+ export function isTerminalDeletedWorktreeProviderError(message) {
163
+ if (!message)
164
+ return false;
165
+ if (!/\bdoes not exist\b/i.test(message))
166
+ return false;
167
+ return /[/\\]\.gsd[/\\](?:projects[/\\][^/\\]+[/\\])?worktrees[/\\][^/\\\s"']+/i.test(message);
168
+ }
156
169
  async function pauseTransientWithBackoff(cls, pi, ctx, errorDetail, isRateLimit) {
157
170
  retryState.consecutiveTransientCount += 1;
158
171
  const baseRetryAfterMs = "retryAfterMs" in cls ? cls.retryAfterMs : 15_000;
@@ -190,8 +203,10 @@ export async function handleAgentEnd(pi, event, ctx) {
190
203
  // falsely report files as missing — producing a spurious "ready signal
191
204
  // rejected" loop even though the files are on disk.
192
205
  clearPathCache();
206
+ const basePath = resolveAgentEndBasePath();
207
+ clearGuidedUnitContext(basePath);
193
208
  try {
194
- if (await checkDeepProjectSetupAfterTurn(event, ctx, resolveAgentEndBasePath())) {
209
+ if (await checkDeepProjectSetupAfterTurn(event, ctx, basePath)) {
195
210
  return;
196
211
  }
197
212
  }
@@ -199,22 +214,22 @@ export async function handleAgentEnd(pi, event, ctx) {
199
214
  const message = err instanceof Error ? err.message : String(err);
200
215
  logWarning("bootstrap", `checkDeepProjectSetupAfterTurn failed: ${message}`);
201
216
  }
202
- if (checkAutoStartAfterDiscuss()) {
203
- clearDiscussionFlowState(resolveAgentEndBasePath() ?? process.cwd());
217
+ if (checkAutoStartAfterDiscuss(basePath)) {
218
+ clearDiscussionFlowState(basePath ?? process.cwd());
204
219
  return;
205
220
  }
206
221
  // #4573 — When the LLM emits "Milestone X ready." but the required files
207
222
  // are missing, `checkAutoStartAfterDiscuss` returns false silently. Surface
208
223
  // that and nudge the LLM to complete the writes before the user hits the
209
224
  // downstream "All milestones complete" warning loop.
210
- if (maybeHandleReadyPhraseWithoutFiles(event))
225
+ if (maybeHandleReadyPhraseWithoutFiles(event, basePath))
211
226
  return;
212
227
  // #4573 — Empty-turn recovery: if the LLM announced intent in prose but
213
228
  // emitted no tool calls, nudge it to execute. Fires only when auto-mode is
214
229
  // active or a discussion autostart is pending (non-auto interactive discuss
215
230
  // is user-driven). Runs before `isAutoActive` early return so pending
216
231
  // discussions (where isAutoActive may be false) still get recovered.
217
- if (maybeHandleEmptyIntentTurn(event, isAutoActive()))
232
+ if (maybeHandleEmptyIntentTurn(event, isAutoActive(), basePath))
218
233
  return;
219
234
  if (!isAutoActive())
220
235
  return;
@@ -295,6 +310,12 @@ export async function handleAgentEnd(pi, event, ctx) {
295
310
  // the assistant message text content for display purposes only.
296
311
  // Classification still uses rawErrorMsg to avoid false positives from prose.
297
312
  const displayMsg = resolveAgentEndErrorDisplay(rawErrorMsg, "content" in lastMsg ? lastMsg.content : undefined);
313
+ if (isAutoCompletionStopInProgress() &&
314
+ isTerminalDeletedWorktreeProviderError(`${rawErrorMsg}\n${displayMsg}`)) {
315
+ resetRetryState(retryState);
316
+ logWarning("bootstrap", `Ignoring stale deleted-worktree provider error during terminal completion reroot: ${displayMsg || rawErrorMsg}`);
317
+ return;
318
+ }
298
319
  const errorDetail = displayMsg ? `: ${displayMsg}` : "";
299
320
  const explicitRetryAfterMs = ("retryAfterMs" in lastMsg && typeof lastMsg.retryAfterMs === "number") ? lastMsg.retryAfterMs : undefined;
300
321
  // ── 1. Classify using rawErrorMsg to avoid prose false-positives ────
@@ -378,7 +399,7 @@ export async function handleAgentEnd(pi, event, ctx) {
378
399
  // Core retries transient failures in-session after this handler.
379
400
  // Keep that behavior for non-rate-limit classes to avoid pause/retry races,
380
401
  // but let rate-limit continue into model fallback logic below (#4373).
381
- if (isTransient(cls) && cls.kind !== "rate-limit") {
402
+ if (shouldDeferTransientErrorToCoreRetry(cls, rawErrorMsg)) {
382
403
  return;
383
404
  }
384
405
  // Cap rate-limit backoff for CLI-style providers (openai-codex, google-gemini-cli)
@@ -24,6 +24,7 @@ import { resolveWorktreeProjectRoot } from "../worktree-root.js";
24
24
  import { extractSubagentAgentClasses } from "./subagent-input.js";
25
25
  import { approvalGateIdForUnit, isExplicitApprovalResponse, shouldPauseForUserApprovalQuestion } from "../user-input-boundary.js";
26
26
  import { resolveSkillManifest } from "../skill-manifest.js";
27
+ import { getGuidedUnitContext } from "../guided-unit-context.js";
27
28
  let approvalQuestionAbortInFlight = false;
28
29
  async function loadWelcomeScreenModule() {
29
30
  const candidates = [];
@@ -675,7 +676,8 @@ export function registerHooks(pi, ecosystemHandlers) {
675
676
  // subagent dispatch. Closes the b23 bug class where a discuss-milestone
676
677
  // turn used the host Edit tool to modify user source files.
677
678
  const dash = getAutoRuntimeSnapshot();
678
- const activeUnitType = dash.currentUnit?.type;
679
+ const guidedUnit = getGuidedUnitContext(discussionBasePath);
680
+ const activeUnitType = dash.currentUnit?.type ?? guidedUnit?.unitType;
679
681
  if (activeUnitType) {
680
682
  const manifest = resolveManifest(activeUnitType);
681
683
  if (manifest) {
@@ -694,7 +696,7 @@ export function registerHooks(pi, ecosystemHandlers) {
694
696
  // Subagent inputs use { agent }, { tasks: [{ agent }] }, or { chain: [{ agent }] }.
695
697
  agentClasses = extractSubagentAgentClasses(event.input);
696
698
  }
697
- const planningGuard = shouldBlockPlanningUnit(event.toolName, planningInput, dash.basePath || discussionBasePath, activeUnitType, manifest.tools, agentClasses);
699
+ const planningGuard = shouldBlockPlanningUnit(event.toolName, planningInput, dash.basePath || guidedUnit?.basePath || discussionBasePath, activeUnitType, manifest.tools, agentClasses);
698
700
  if (planningGuard.block)
699
701
  return planningGuard;
700
702
  }
@@ -571,10 +571,15 @@ async function configureModels(ctx, prefs) {
571
571
  ];
572
572
  const models = prefs.models ?? {};
573
573
  const availableModels = ctx.modelRegistry.getAvailable();
574
- if (availableModels.length > 0) {
574
+ const getAllWithDiscovered = ctx.modelRegistry.getAllWithDiscovered;
575
+ const availableProviders = new Set(availableModels.map((m) => m.provider));
576
+ const selectableModels = typeof getAllWithDiscovered === "function"
577
+ ? getAllWithDiscovered().filter((m) => availableProviders.has(m.provider))
578
+ : availableModels;
579
+ if (selectableModels.length > 0) {
575
580
  // Group models by provider, sorted alphabetically
576
581
  const byProvider = new Map();
577
- for (const m of availableModels) {
582
+ for (const m of selectableModels) {
578
583
  let group = byProvider.get(m.provider);
579
584
  if (!group) {
580
585
  group = [];
@@ -23,7 +23,8 @@
23
23
  import { emitJournalEvent, queryJournal, } from "./journal.js";
24
24
  import { readFileSync, unlinkSync, existsSync } from "node:fs";
25
25
  import { join } from "node:path";
26
- import { findStaleWorkerForProject, getAllAutoWorkers, markWorkerCrashed, } from "./db/auto-workers.js";
26
+ import { findStaleWorkerForProject, getAllAutoWorkers, markWorkerCrashed, markWorkerStopping, } from "./db/auto-workers.js";
27
+ import { forceReleaseLeasesForWorker } from "./db/milestone-leases.js";
27
28
  import { markLatestActiveForWorkerCanceled } from "./db/unit-dispatches.js";
28
29
  import { getRuntimeKv, setRuntimeKv, deleteRuntimeKv } from "./db/runtime-kv.js";
29
30
  import { _getAdapter, isDbAvailable } from "./gsd-db.js";
@@ -182,10 +183,21 @@ export function clearLock(basePath) {
182
183
  return;
183
184
  try {
184
185
  const projectRoot = normalizeRealPath(basePath);
185
- const worker = findActiveWorkerForCurrentProcess(projectRoot);
186
- if (!worker)
186
+ const staleWorker = findStaleWorkerForProject(projectRoot);
187
+ if (staleWorker) {
188
+ markWorkerCrashed(staleWorker.worker_id);
189
+ forceReleaseLeasesForWorker(staleWorker.worker_id);
190
+ deleteRuntimeKv("worker", staleWorker.worker_id, SESSION_FILE_KV_KEY);
187
191
  return;
188
- deleteRuntimeKv("worker", worker.worker_id, SESSION_FILE_KV_KEY);
192
+ }
193
+ const worker = findActiveWorkerForCurrentProcess(projectRoot);
194
+ if (worker)
195
+ deleteRuntimeKv("worker", worker.worker_id, SESSION_FILE_KV_KEY);
196
+ const stale = findStaleWorkerForProject(projectRoot);
197
+ if (stale) {
198
+ markWorkerStopping(stale.worker_id);
199
+ deleteRuntimeKv("worker", stale.worker_id, SESSION_FILE_KV_KEY);
200
+ }
189
201
  }
190
202
  catch {
191
203
  // Best-effort.
@@ -193,6 +193,30 @@ export function releaseMilestoneLease(workerId, milestoneId, fencingToken) {
193
193
  return changes === 1;
194
194
  });
195
195
  }
196
+ /**
197
+ * Force-release all held leases for a worker.
198
+ *
199
+ * Used by crash recovery once PID liveness has confirmed the worker is dead.
200
+ * No fencing token is required because this path is cleanup-only for a
201
+ * non-running process.
202
+ */
203
+ export function forceReleaseLeasesForWorker(workerId) {
204
+ if (!isDbAvailable())
205
+ return 0;
206
+ const db = _getAdapter();
207
+ let changes = 0;
208
+ transaction(() => {
209
+ const result = db.prepare(`UPDATE milestone_leases
210
+ SET status = 'released'
211
+ WHERE worker_id = :worker_id
212
+ AND status = 'held'`).run({ ":worker_id": workerId });
213
+ changes =
214
+ typeof result.changes === "number"
215
+ ? result.changes
216
+ : 0;
217
+ });
218
+ return changes;
219
+ }
196
220
  /**
197
221
  * Read current lease row for diagnostics. Returns null if no row exists.
198
222
  */
@@ -7,7 +7,7 @@ import { parseRoadmap as parseLegacyRoadmap } from "./parsers-legacy.js";
7
7
  import { isDbAvailable, getMilestone } from "./gsd-db.js";
8
8
  import { resolveMilestoneFile } from "./paths.js";
9
9
  import { deriveState, isMilestoneComplete } from "./state.js";
10
- import { listWorktrees, resolveGitDir, worktreesDir } from "./worktree-manager.js";
10
+ import { createWorktree, listWorktrees, resolveGitDir, worktreesDir } from "./worktree-manager.js";
11
11
  import { abortAndReset } from "./git-self-heal.js";
12
12
  import { RUNTIME_EXCLUSION_PATHS, resolveMilestoneIntegrationBranch, writeIntegrationBranch } from "./git-service.js";
13
13
  import { nativeIsRepo, nativeWorktreeList, nativeWorktreeRemove, nativeBranchList, nativeBranchDelete, nativeLsFiles, nativeRmCached, nativeHasChanges, nativeLastCommitEpoch, nativeGetCurrentBranch, nativeAddTracked, nativeCommit } from "./native-git-bridge.js";
@@ -50,6 +50,21 @@ function isSameOrNestedPath(candidate, container) {
50
50
  return normalizedCandidate === normalizedContainer ||
51
51
  normalizedCandidate.startsWith(`${normalizedContainer}/`);
52
52
  }
53
+ function hasProjectContentOnDisk(dirPath) {
54
+ try {
55
+ for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
56
+ if (entry.name === ".git" || entry.name === ".gsd")
57
+ continue;
58
+ if (entry.name === ".DS_Store")
59
+ continue;
60
+ return true;
61
+ }
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ return false;
67
+ }
53
68
  function getSnapshotDiffCheckFailure(basePath) {
54
69
  const failures = [];
55
70
  for (const args of [["--cached"], []]) {
@@ -104,6 +119,36 @@ export async function checkGitHealth(basePath, issues, fixesApplied, shouldFix,
104
119
  const isComplete = milestoneEntry
105
120
  ? await isCompletedMilestoneTerminal(basePath, milestoneId)
106
121
  : false;
122
+ if (!isComplete && !hasProjectContentOnDisk(wt.path) && hasProjectContentOnDisk(basePath)) {
123
+ issues.push({
124
+ severity: "error",
125
+ code: "worktree_empty_with_project_content",
126
+ scope: "milestone",
127
+ unitId: milestoneId,
128
+ message: `Worktree ${wt.path} has no project content, but project root ${basePath} does. Run doctor --fix to recreate the worktree.`,
129
+ fixable: true,
130
+ });
131
+ if (shouldFix("worktree_empty_with_project_content")) {
132
+ try {
133
+ nativeWorktreeRemove(basePath, wt.path, true);
134
+ const recreated = createWorktree(basePath, milestoneId, {
135
+ branch: wt.branch,
136
+ reuseExistingBranch: true,
137
+ });
138
+ const reset = spawnSync("git", ["reset", "--hard"], {
139
+ cwd: recreated.path,
140
+ encoding: "utf-8",
141
+ });
142
+ if (reset.status !== 0) {
143
+ throw new Error(reset.stderr || reset.error?.message || "git reset --hard failed");
144
+ }
145
+ fixesApplied.push(`recreated empty worktree ${wt.path}`);
146
+ }
147
+ catch {
148
+ fixesApplied.push(`failed to recreate empty worktree ${wt.path}`);
149
+ }
150
+ }
151
+ }
107
152
  if (isComplete) {
108
153
  issues.push({
109
154
  severity: "warning",
@@ -237,6 +237,8 @@ export function readIntegrationBranch(basePath, milestoneId) {
237
237
  return null;
238
238
  }
239
239
  }
240
+ /** Re-export for backward compatibility — canonical definitions in branch-patterns.ts */
241
+ export { QUICK_BRANCH_RE, WORKFLOW_BRANCH_RE } from "./branch-patterns.js";
240
242
  /**
241
243
  * Persist the integration branch for a milestone.
242
244
  *
@@ -247,9 +249,11 @@ export function readIntegrationBranch(basePath, milestoneId) {
247
249
  *
248
250
  * The file is committed immediately so the metadata is persisted in git.
249
251
  */
250
- /** Re-export for backward compatibility — canonical definitions in branch-patterns.ts */
251
- export { QUICK_BRANCH_RE, WORKFLOW_BRANCH_RE } from "./branch-patterns.js";
252
252
  export function writeIntegrationBranch(basePath, milestoneId, branch) {
253
+ // Never persist milestone branches as integration targets.
254
+ // They are ephemeral execution branches and can cause self-diff corruption.
255
+ if (branch.startsWith("milestone/"))
256
+ return;
253
257
  // Don't record slice branches as the integration target
254
258
  if (SLICE_BRANCH_RE.test(branch))
255
259
  return;
@@ -54,18 +54,19 @@ const providerLoader = createSqliteProviderLoader({
54
54
  writeStderr: (message) => process.stderr.write(message),
55
55
  });
56
56
  export const SCHEMA_VERSION = 28;
57
- function initSchema(db, fileBacked) {
57
+ function initSchema(db, fileBacked, dbPath) {
58
+ const conservativeFilePragmas = fileBacked && _isLikelyWslDrvFsPathForTest(dbPath);
58
59
  if (fileBacked)
59
- db.exec("PRAGMA journal_mode=WAL");
60
+ db.exec(conservativeFilePragmas ? "PRAGMA journal_mode=DELETE" : "PRAGMA journal_mode=WAL");
60
61
  if (fileBacked)
61
62
  db.exec("PRAGMA busy_timeout = 5000");
62
63
  if (fileBacked)
63
- db.exec("PRAGMA synchronous = NORMAL");
64
+ db.exec(conservativeFilePragmas ? "PRAGMA synchronous = FULL" : "PRAGMA synchronous = NORMAL");
64
65
  if (fileBacked)
65
66
  db.exec("PRAGMA auto_vacuum = INCREMENTAL");
66
67
  if (fileBacked)
67
68
  db.exec("PRAGMA cache_size = -8000"); // 8 MB page cache
68
- if (fileBacked && process.platform !== "darwin")
69
+ if (fileBacked && !conservativeFilePragmas && process.platform !== "darwin")
69
70
  db.exec("PRAGMA mmap_size = 67108864"); // 64 MB mmap
70
71
  db.exec("PRAGMA temp_store = MEMORY");
71
72
  db.exec("PRAGMA foreign_keys = ON");
@@ -99,6 +100,19 @@ function initSchema(db, fileBacked) {
99
100
  }
100
101
  migrateSchema(db);
101
102
  }
103
+ export function _isLikelyWslDrvFsPathForTest(dbPath) {
104
+ if (!dbPath || process.platform !== "linux")
105
+ return false;
106
+ const drvFsPathPattern = /^\/mnt\/[a-z](?:\/|$)/i;
107
+ if (drvFsPathPattern.test(dbPath))
108
+ return true;
109
+ try {
110
+ return drvFsPathPattern.test(realpathSync(dbPath));
111
+ }
112
+ catch {
113
+ return false;
114
+ }
115
+ }
102
116
  /**
103
117
  * Create the FTS5 virtual table for memories plus the triggers that keep it
104
118
  * in sync with the base table. FTS5 may be unavailable on stripped-down
@@ -504,7 +518,7 @@ export function openDatabase(path) {
504
518
  const adapter = createDbAdapter(rawDb);
505
519
  const fileBacked = path !== ":memory:";
506
520
  try {
507
- initSchema(adapter, fileBacked);
521
+ initSchema(adapter, fileBacked, path);
508
522
  }
509
523
  catch (err) {
510
524
  // Corrupt freelist: DDL fails with "malformed" but VACUUM can rebuild.
@@ -512,7 +526,7 @@ export function openDatabase(path) {
512
526
  if (fileBacked && err instanceof Error && err.message?.includes("malformed")) {
513
527
  try {
514
528
  adapter.exec("VACUUM");
515
- initSchema(adapter, fileBacked);
529
+ initSchema(adapter, fileBacked, path);
516
530
  process.stderr.write("gsd-db: recovered corrupt database via VACUUM\n");
517
531
  }
518
532
  catch (retryErr) {
@@ -18,6 +18,7 @@ import { nativeAddPaths, nativeCommit } from "./native-git-bridge.js";
18
18
  import { loadEffectiveGSDPreferences } from "./preferences.js";
19
19
  import { saveQueueOrder } from "./queue-order.js";
20
20
  import { findMilestoneIds, nextMilestoneId } from "./milestone-ids.js";
21
+ import { isFutureMilestoneStatus } from "./status-guards.js";
21
22
  // ─── Queue Entry Point ──────────────────────────────────────────────────────
22
23
  /**
23
24
  * Queue future milestones via conversational intake.
@@ -48,7 +49,7 @@ export async function showQueue(ctx, pi, basePath) {
48
49
  return;
49
50
  }
50
51
  // ── Count pending milestones ────────────────────────────────────────
51
- const pendingMilestones = state.registry.filter(m => m.status === "pending" || m.status === "active");
52
+ const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status) || m.status === "active");
52
53
  const completeCount = state.registry.filter(m => m.status === "complete").length;
53
54
  const parkedCount = state.registry.filter(m => m.status === "parked").length;
54
55
  // ── If multiple pending milestones, show queue management hub ──────
@@ -140,7 +141,7 @@ export async function showQueueAdd(ctx, pi, basePath, state) {
140
141
  const activePart = state.activeMilestone
141
142
  ? `Currently executing: ${state.activeMilestone.id} — ${state.activeMilestone.title} (phase: ${state.phase}).`
142
143
  : "No milestone currently active.";
143
- const pendingCount = state.registry.filter(m => m.status === "pending").length;
144
+ const pendingCount = state.registry.filter(m => isFutureMilestoneStatus(m.status)).length;
144
145
  const completeCount = state.registry.filter(m => m.status === "complete").length;
145
146
  const preamble = [
146
147
  `Queuing new work onto an existing GSD project.`,
@@ -223,7 +224,7 @@ export async function buildExistingMilestonesContext(basePath, milestoneIds, sta
223
224
  }
224
225
  // For active/pending/parked milestones, include the roadmap if it exists
225
226
  // (shows what's planned but not yet built)
226
- if (status === "active" || status === "pending" || status === "parked") {
227
+ if (status === "active" || isFutureMilestoneStatus(status) || status === "parked") {
227
228
  const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
228
229
  if (roadmapFile) {
229
230
  const content = await loadFile(roadmapFile);