gsd-pi 2.80.0-dev.8b3a59f5c → 2.80.0-dev.b2a374d5b

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 (222) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/GSD-WORKFLOW.md +2 -2
  3. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  4. package/dist/resources/extensions/gsd/auto/phases.js +37 -30
  5. package/dist/resources/extensions/gsd/auto-dispatch.js +10 -0
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +10 -10
  7. package/dist/resources/extensions/gsd/auto-prompts.js +111 -1
  8. package/dist/resources/extensions/gsd/auto-start.js +2 -3
  9. package/dist/resources/extensions/gsd/auto.js +16 -3
  10. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +21 -0
  11. package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
  12. package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
  13. package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
  14. package/dist/resources/extensions/gsd/detection.js +106 -0
  15. package/dist/resources/extensions/gsd/guided-flow.js +63 -12
  16. package/dist/resources/extensions/gsd/migrate/command.js +40 -1
  17. package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
  18. package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
  19. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  20. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  21. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  22. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  23. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
  24. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  25. package/dist/resources/extensions/gsd/quick.js +34 -2
  26. package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
  27. package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
  28. package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
  29. package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
  30. package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
  31. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  32. package/dist/web/standalone/.next/BUILD_ID +1 -1
  33. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  34. package/dist/web/standalone/.next/build-manifest.json +2 -2
  35. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  36. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/index.html +1 -1
  53. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  60. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  62. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  63. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  64. package/package.json +4 -2
  65. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  66. package/packages/pi-coding-agent/dist/core/agent-session.js +3 -0
  67. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  68. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +32 -0
  69. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
  71. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  72. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
  73. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  74. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
  75. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
  76. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
  77. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
  78. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
  79. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
  80. package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
  81. package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
  82. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
  83. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
  84. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
  85. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
  86. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
  87. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  88. package/packages/pi-coding-agent/dist/core/extensions/runner.js +17 -1
  89. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  90. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +99 -0
  91. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
  93. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  95. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
  96. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
  98. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js +6 -4
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +25 -20
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +2 -2
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +2 -1
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +7 -6
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +26 -13
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  117. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +15 -5
  118. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
  120. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
  123. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +36 -27
  126. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +21 -0
  128. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
  130. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
  132. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
  133. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
  134. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
  135. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
  136. package/packages/pi-coding-agent/src/core/agent-session.ts +3 -0
  137. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +38 -0
  138. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
  139. package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
  140. package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
  141. package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
  142. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +110 -0
  143. package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -1
  144. package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
  145. package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
  146. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.ts +7 -5
  147. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +25 -20
  148. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +2 -2
  149. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +1 -0
  150. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +2 -1
  151. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +10 -9
  152. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +33 -13
  153. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
  154. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +24 -7
  155. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
  156. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +36 -27
  157. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +25 -0
  158. package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
  159. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
  160. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  161. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  162. package/packages/pi-tui/dist/tui.js +17 -7
  163. package/packages/pi-tui/dist/tui.js.map +1 -1
  164. package/packages/pi-tui/src/tui.ts +19 -7
  165. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  166. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  167. package/pkg/dist/modes/interactive/theme/themes.js +36 -27
  168. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  169. package/src/resources/GSD-WORKFLOW.md +2 -2
  170. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  171. package/src/resources/extensions/gsd/auto/loop.ts +50 -8
  172. package/src/resources/extensions/gsd/auto/phases.ts +42 -28
  173. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -0
  174. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
  175. package/src/resources/extensions/gsd/auto-prompts.ts +116 -1
  176. package/src/resources/extensions/gsd/auto-start.ts +7 -6
  177. package/src/resources/extensions/gsd/auto.ts +23 -3
  178. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
  179. package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
  180. package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
  181. package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
  182. package/src/resources/extensions/gsd/detection.ts +128 -0
  183. package/src/resources/extensions/gsd/guided-flow.ts +66 -12
  184. package/src/resources/extensions/gsd/migrate/command.ts +47 -1
  185. package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
  186. package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
  187. package/src/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  188. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  189. package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  190. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  191. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
  192. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  193. package/src/resources/extensions/gsd/quick.ts +37 -2
  194. package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
  195. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -1
  196. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
  197. package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
  198. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
  199. package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
  200. package/src/resources/extensions/gsd/tests/has-pending-deep-stage.test.ts +33 -1
  201. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
  202. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
  203. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
  204. package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
  205. package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
  206. package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
  207. package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
  208. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
  209. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -0
  210. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +63 -2
  211. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
  212. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +7 -0
  213. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
  214. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
  215. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
  216. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
  217. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
  218. package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
  219. package/src/resources/extensions/gsd/unit-runtime.ts +14 -0
  220. package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
  221. /package/dist/web/standalone/.next/static/{k-MDjzPMF62Rg1-FR437h → FU0iL9dhdc2IBzEYBDOlY}/_buildManifest.js +0 -0
  222. /package/dist/web/standalone/.next/static/{k-MDjzPMF62Rg1-FR437h → FU0iL9dhdc2IBzEYBDOlY}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- 2b428bb01a7f0322
1
+ 8b59c93905f68a66
@@ -28,7 +28,7 @@ Then do the thing `STATE.md` says to do next.
28
28
  ## The Hierarchy
29
29
 
30
30
  ```
31
- Milestone → a shippable version (4-10 slices)
31
+ Milestone → a shippable version (1-10 slices, sized to the work)
32
32
  Slice → one demoable vertical capability (1-7 tasks)
33
33
  Task → one context-window-sized unit of work (fits in one session)
34
34
  ```
@@ -331,7 +331,7 @@ The **Don't Hand-Roll** and **Common Pitfalls** sections prevent the most expens
331
331
 
332
332
  **For a milestone (roadmap):**
333
333
  1. Read `M###-CONTEXT.md`, `M###-RESEARCH.md`, and `.gsd/DECISIONS.md` if they exist.
334
- 2. Decompose the vision into 4-10 demoable vertical slices.
334
+ 2. Decompose the vision into 1-10 demoable vertical slices. Prefer one slice for tiny, single-file, or static work unless the request clearly spans independent capabilities.
335
335
  3. Order by risk (high-risk first to validate feasibility early).
336
336
  4. Write `M###-ROADMAP.md` with checkboxes, risk levels, dependencies, demo sentences.
337
337
  5. **Write the boundary map** — for each slice, specify what it produces (functions, types, interfaces, endpoints) and what it consumes from upstream slices. This forces interface thinking before implementation and enables deterministic verification that slices actually connect.
@@ -25,6 +25,8 @@ import { normalizeRealPath } from "../paths.js";
25
25
  import { decideCooldownRecovery, decideDispatchClaim, decideEngineDispatch, decideFinalizeResult, decideInfrastructureError, decideIterationErrorRecovery, decideMemoryPressure, decideModelPolicyBlocked, decideMinRequestInterval, decideWorkflowLoop, formatDispatchExceptionSummary, formatUnhandledDispatchErrorSummary, resolveUnitRequestTimestamp, shouldUseCustomEnginePath, } from "./workflow-kernel.js";
26
26
  import { hydrateCustomVerifyRetryCounts, saveCustomVerifyRetryCounts, } from "./custom-verify-retry-store.js";
27
27
  import { settleDispatchCompleted, settleDispatchFailed, } from "./workflow-dispatch-ledger.js";
28
+ import { emitOpenUnitEndForUnit } from "../crash-recovery.js";
29
+ import { writeUnitRuntimeRecord } from "../unit-runtime.js";
28
30
  import { openDispatchClaim } from "./workflow-dispatch-claim.js";
29
31
  import { completeWorkflowIteration } from "./workflow-iteration-completion.js";
30
32
  import { createWorkflowJournalReporter } from "./workflow-journal-reporter.js";
@@ -133,6 +135,24 @@ async function enforceMinRequestInterval(s, prefs) {
133
135
  await new Promise(r => setTimeout(r, decision.waitMs));
134
136
  }
135
137
  }
138
+ function closeOutCrashedUnit(s, iterData, err) {
139
+ const summary = formatDispatchExceptionSummary({ error: err });
140
+ try {
141
+ emitOpenUnitEndForUnit(s.basePath, iterData.unitType, iterData.unitId, "cancelled", {
142
+ message: summary,
143
+ category: "unit-exception",
144
+ isTransient: false,
145
+ });
146
+ writeUnitRuntimeRecord(s.basePath, iterData.unitType, iterData.unitId, s.currentUnit?.startedAt ?? Date.now(), {
147
+ phase: "crashed",
148
+ lastProgressAt: Date.now(),
149
+ lastProgressKind: "unit-exception",
150
+ });
151
+ }
152
+ catch (closeoutErr) {
153
+ logWarning("dispatch", `unit crash closeout failed: ${closeoutErr instanceof Error ? closeoutErr.message : String(closeoutErr)}`);
154
+ }
155
+ }
136
156
  /**
137
157
  * Main auto-mode execution loop. Iterates: derive → dispatch → guards →
138
158
  * runUnit → finalize → repeat. Exits when s.active becomes false or a
@@ -392,7 +412,17 @@ export async function autoLoop(ctx, pi, s, deps, options) {
392
412
  }
393
413
  // ── Unit execution (shared with dev path) ──
394
414
  await enforceMinRequestInterval(s, prefs);
395
- const unitPhaseResult = await runUnitPhaseViaContract(dispatchContract, ic, iterData, loopState, undefined, unitDispatchDeps);
415
+ let unitPhaseResult;
416
+ try {
417
+ unitPhaseResult = await runUnitPhaseViaContract(dispatchContract, ic, iterData, loopState, undefined, unitDispatchDeps);
418
+ }
419
+ catch (err) {
420
+ if (err instanceof ModelPolicyDispatchBlockedError) {
421
+ throw err;
422
+ }
423
+ closeOutCrashedUnit(s, iterData, err);
424
+ throw err;
425
+ }
396
426
  if (unitPhaseResult.action === "next") {
397
427
  const requestTimestamp = resolveUnitRequestTimestamp(unitPhaseResult.data);
398
428
  if (requestTimestamp !== undefined)
@@ -580,6 +610,7 @@ export async function autoLoop(ctx, pi, s, deps, options) {
580
610
  if (err instanceof ModelPolicyDispatchBlockedError) {
581
611
  throw err;
582
612
  }
613
+ closeOutCrashedUnit(s, iterData, err);
583
614
  dispatchSettled = settleDispatchFailed(dispatchId, formatDispatchExceptionSummary({ error: err }), {
584
615
  markFailed: markDispatchFailed,
585
616
  logWriteFailure: logDispatchLedgerWriteFailure,
@@ -13,13 +13,13 @@ import { detectStuck } from "./detect-stuck.js";
13
13
  import { runUnit } from "./run-unit.js";
14
14
  import { debugLog } from "../debug-logger.js";
15
15
  import { resolveWorktreeProjectRoot, normalizeWorktreePathForCompare } from "../worktree-root.js";
16
- import { PROJECT_FILES, hasProjectFileInAncestor } from "../detection.js";
16
+ import { classifyProject } from "../detection.js";
17
17
  import { MergeConflictError } from "../git-service.js";
18
18
  import { setCurrentPhase, clearCurrentPhase } from "../../shared/gsd-phase-state.js";
19
19
  import { pauseAutoForProviderError } from "../provider-error-pause.js";
20
20
  import { resumeAutoAfterProviderDelay } from "../bootstrap/provider-error-resume.js";
21
21
  import { join, basename } from "node:path";
22
- import { existsSync, cpSync, readdirSync } from "node:fs";
22
+ import { existsSync, cpSync } from "node:fs";
23
23
  import { logWarning, logError, _resetLogs, drainLogs, drainAndSummarize, formatForNotification, hasAnyIssues, } from "../workflow-logger.js";
24
24
  import { gsdRoot } from "../paths.js";
25
25
  import { atomicWriteSync } from "../atomic-write.js";
@@ -498,7 +498,7 @@ export async function runPreDispatch(ic, loopState) {
498
498
  }
499
499
  // #2909: postflight — restore stashed changes after successful merge
500
500
  if (preflightTransition.stashPushed) {
501
- deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
501
+ deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, preflightTransition.stashMarker, ctx.ui.notify.bind(ctx.ui));
502
502
  }
503
503
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
504
504
  deps.invalidateAllCaches();
@@ -574,7 +574,7 @@ export async function runPreDispatch(ic, loopState) {
574
574
  }
575
575
  // #2909: postflight — restore stashed changes after successful merge
576
576
  if (preflightAllComplete.stashPushed) {
577
- deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
577
+ deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, preflightAllComplete.stashMarker, ctx.ui.notify.bind(ctx.ui));
578
578
  }
579
579
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
580
580
  }
@@ -660,7 +660,7 @@ export async function runPreDispatch(ic, loopState) {
660
660
  }
661
661
  // #2909: postflight — restore stashed changes after successful merge
662
662
  if (preflightComplete.stashPushed) {
663
- deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, ctx.ui.notify.bind(ctx.ui));
663
+ deps.postflightPopStash(s.originalBasePath || s.basePath, s.currentMilestoneId, preflightComplete.stashMarker, ctx.ui.notify.bind(ctx.ui));
664
664
  }
665
665
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
666
666
  }
@@ -1084,8 +1084,9 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
1084
1084
  // Verify the working directory is a valid git checkout with project
1085
1085
  // files before dispatching work. A broken worktree causes agents to
1086
1086
  // hallucinate summaries since they cannot read or write any files.
1087
- // Uses the shared PROJECT_FILES list from detection.ts to support all
1088
- // ecosystems (Rust, Go, Python, Java, etc.), not just JS.
1087
+ // Uses project classification so project presence is not conflated with
1088
+ // ecosystem marker detection. Static/minimal repos become untyped-existing.
1089
+ let projectClassification = null;
1089
1090
  if (s.basePath && unitType === "execute-task") {
1090
1091
  const gitMarker = join(s.basePath, ".git");
1091
1092
  const hasGit = deps.existsSync(gitMarker);
@@ -1096,30 +1097,26 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
1096
1097
  await deps.stopAuto(ctx, pi, msg);
1097
1098
  return { action: "break", reason: "worktree-invalid" };
1098
1099
  }
1099
- const hasProjectFile = PROJECT_FILES.some((f) => deps.existsSync(join(s.basePath, f)));
1100
- const hasSrcDir = deps.existsSync(join(s.basePath, "src"));
1101
- // Xcode bundles have project-specific names (*.xcodeproj, *.xcworkspace)
1102
- // that cannot be matched by exact filename — scan the directory by suffix.
1103
- let hasXcodeBundle = false;
1104
- try {
1105
- const entries = deps.existsSync(s.basePath) ? readdirSync(s.basePath) : [];
1106
- hasXcodeBundle = entries.some((e) => e.endsWith(".xcodeproj") || e.endsWith(".xcworkspace"));
1100
+ projectClassification = classifyProject(s.basePath);
1101
+ if (projectClassification.kind === "invalid-repo") {
1102
+ const msg = `Worktree health check failed: ${s.basePath} classified as invalid-repo (${projectClassification.reason}) — refusing to dispatch ${unitType} ${unitId}`;
1103
+ debugLog("runUnitPhase", { phase: "worktree-health-invalid-repo", basePath: s.basePath, classification: projectClassification });
1104
+ if (projectClassification.reason === "missing .git" && hasGit) {
1105
+ ctx.ui.notify(`Warning: ${s.basePath} project classification could not confirm .git; assuming it has no project content yet — proceeding as greenfield project because worktree health reported .git present`, "warning");
1106
+ }
1107
+ else {
1108
+ ctx.ui.notify(msg, "error");
1109
+ await deps.stopAuto(ctx, pi, msg);
1110
+ return { action: "break", reason: "worktree-invalid" };
1111
+ }
1107
1112
  }
1108
- catch (err) {
1109
- debugLog("runUnitPhase", { phase: "xcode-bundle-scan-failed", basePath: s.basePath, error: String(err) });
1110
- }
1111
- // Monorepo support (#2347): if no project files in the worktree directory,
1112
- // walk parent directories up to the filesystem root. In monorepos,
1113
- // package.json / Cargo.toml etc. live in a parent directory.
1114
- const hasProjectFileInParent = !hasProjectFile && !hasSrcDir && !hasXcodeBundle
1115
- ? hasProjectFileInAncestor(s.basePath, deps.existsSync)
1116
- : false;
1117
- if (!hasProjectFile && !hasSrcDir && !hasXcodeBundle && !hasProjectFileInParent) {
1118
- // Greenfield projects won't have project files yet — the first task creates them.
1119
- // Log a warning but allow execution to proceed. The .git check above is sufficient
1120
- // to ensure we're in a valid working directory.
1121
- debugLog("runUnitPhase", { phase: "worktree-health-warn-greenfield", basePath: s.basePath, hasProjectFile, hasSrcDir, hasXcodeBundle });
1122
- ctx.ui.notify(`Warning: ${s.basePath} has no recognized project files — proceeding as greenfield project`, "warning");
1113
+ else if (projectClassification.kind === "greenfield") {
1114
+ debugLog("runUnitPhase", { phase: "worktree-health-greenfield", basePath: s.basePath, classification: projectClassification });
1115
+ ctx.ui.notify(`Warning: ${s.basePath} has no project content yet — proceeding as greenfield project`, "warning");
1116
+ }
1117
+ else if (projectClassification.kind === "untyped-existing") {
1118
+ debugLog("runUnitPhase", { phase: "worktree-health-untyped-existing", basePath: s.basePath, classification: projectClassification });
1119
+ ctx.ui.notify(`Notice: ${s.basePath} has existing project content but no recognized tooling markers — using generic file-level workflow guidance`, "info");
1123
1120
  }
1124
1121
  }
1125
1122
  // Detect retry and capture previous tier for escalation
@@ -1182,6 +1179,16 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
1182
1179
  }
1183
1180
  // Prompt injection
1184
1181
  let finalPrompt = prompt;
1182
+ if (unitType === "execute-task") {
1183
+ projectClassification ??= classifyProject(s.basePath);
1184
+ if (projectClassification.kind === "untyped-existing") {
1185
+ const samples = projectClassification.contentFiles.slice(0, 8).join(", ") || "project files";
1186
+ finalPrompt +=
1187
+ "\n\n**Project classification:** Existing untyped project. No recognized build/tooling markers were detected, " +
1188
+ "so use generic file-level workflow guidance. Task plans and completion summaries must list every concrete " +
1189
+ `project file changed in \`files\` or \`expected_output\`. Detected content sample: ${samples}.`;
1190
+ }
1191
+ }
1185
1192
  if (s.pendingVerificationRetry) {
1186
1193
  const retryCtx = s.pendingVerificationRetry;
1187
1194
  s.pendingVerificationRetry = null;
@@ -93,6 +93,16 @@ export function hasPendingDeepStage(prefs, basePath) {
93
93
  const gate = getDeepStageGate(prefs, basePath);
94
94
  return gate.status === "pending" || gate.status === "blocked";
95
95
  }
96
+ export function shouldRunDeepProjectSetup(state, prefs, basePath, options = {}) {
97
+ if (options.hasSurvivorBranch === true)
98
+ return false;
99
+ if (state.phase !== "pre-planning" &&
100
+ state.phase !== "needs-discussion" &&
101
+ state.phase !== "planning") {
102
+ return false;
103
+ }
104
+ return hasPendingDeepStage(prefs, basePath);
105
+ }
96
106
  function missingSliceStop(mid, phase) {
97
107
  return {
98
108
  action: "stop",
@@ -25,7 +25,7 @@ import { verifyExpectedArtifact, resolveExpectedArtifactPath, writeBlockerPlaceh
25
25
  import { regenerateIfMissing } from "./workflow-projections.js";
26
26
  import { syncStateToProjectRoot } from "./auto-worktree.js";
27
27
  import { normalizeWorktreePathForCompare } from "./worktree-root.js";
28
- import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
28
+ import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter, getVerificationEvidence } from "./gsd-db.js";
29
29
  import { renderPlanCheckboxes } from "./markdown-renderer.js";
30
30
  import { consumeSignal } from "./session-status-io.js";
31
31
  import { checkPostUnitHooks, isRetryPending, consumeRetryTrigger, persistHookState, resolveHookArtifactPath, } from "./post-unit-hooks.js";
@@ -719,21 +719,21 @@ export async function postUnitPreVerification(pctx, opts) {
719
719
  }
720
720
  }
721
721
  // Evidence cross-reference (execute-task only)
722
- // Verification evidence is passed via the complete-task tool call and
723
- // stored in the SUMMARY.md on disk not available as structured data
724
- // in the DB. The evidence collector tracks actual bash tool calls, so
725
- // we can still detect units that claimed success but ran no commands.
722
+ // Only compare against concrete command evidence persisted by the task
723
+ // completion tool. A prose Verify field can be satisfied later by the
724
+ // host verification gate, so it is not enough to accuse the unit.
726
725
  if (safetyConfig.evidence_cross_reference && s.currentUnit.type === "execute-task") {
727
726
  try {
728
727
  const actual = getEvidence();
729
728
  const bashCalls = actual.filter(e => e.kind === "bash");
730
- // If the task is marked complete but zero bash commands were run,
731
- // it's suspicious — the LLM may have fabricated results.
732
729
  if (sMid && sSid && sTid && isDbAvailable()) {
733
730
  const taskRow = getTask(sMid, sSid, sTid);
734
- if (taskRow?.status === "complete" && taskRow.verify && bashCalls.length === 0) {
735
- logWarning("safety", "task marked complete with verification commands but no bash calls were executed");
736
- ctx.ui.notify(`Safety: task ${sTid} has verification commands but no bash calls were recorded`, "warning");
731
+ const claimedCommands = getVerificationEvidence(sMid, sSid, sTid)
732
+ .map((row) => row.command)
733
+ .filter((command) => typeof command === "string" && command.trim().length > 0);
734
+ if (taskRow?.status === "complete" && claimedCommands.length > 0 && bashCalls.length === 0) {
735
+ logWarning("safety", "task claimed verification command evidence but no execution tool calls were recorded");
736
+ ctx.ui.notify(`Safety: task ${sTid} claimed command evidence but no execution tool calls were recorded`, "warning");
737
737
  }
738
738
  }
739
739
  }
@@ -6,7 +6,7 @@
6
6
  * utility.
7
7
  */
8
8
  import { loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection, parseTaskPlanFile } from "./files.js";
9
- import { hasVerdict, getUatType } from "./verdict-parser.js";
9
+ import { hasVerdict, getUatType, extractVerdict } from "./verdict-parser.js";
10
10
  import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
11
11
  import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
12
12
  import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences, resolveAllSkillReferences } from "./preferences.js";
@@ -25,6 +25,7 @@ import { logWarning } from "./workflow-logger.js";
25
25
  import { inlineGraphSubgraph } from "./graph-context.js";
26
26
  import { buildExtractionStepsBlock } from "./commands-extract-learnings.js";
27
27
  import { resolveSkillManifest, warnIfManifestHasMissingSkills } from "./skill-manifest.js";
28
+ import { classifyProject } from "./detection.js";
28
29
  // ─── Preamble Cap ─────────────────────────────────────────────────────────────
29
30
  /**
30
31
  * Historical static ceiling for the preamble cap. Kept as an upper bound even
@@ -62,6 +63,104 @@ function resolvePromptBudgets() {
62
63
  function resolveSummaryBudgetChars() {
63
64
  return resolvePromptBudgets().summaryBudgetChars;
64
65
  }
66
+ function formatProjectClassificationForPlanning(classification) {
67
+ const sampleFiles = classification.contentFiles.slice(0, 8);
68
+ const sample = sampleFiles.length > 0 ? sampleFiles.map((file) => `\`${file}\``).join(", ") : "(none)";
69
+ const lines = [
70
+ "### Project Classification",
71
+ "",
72
+ `- **Kind:** ${classification.kind}`,
73
+ `- **Content files:** ${classification.contentFiles.length}`,
74
+ `- **Sample files:** ${sample}`,
75
+ `- **Reason:** ${classification.reason}`,
76
+ "",
77
+ ];
78
+ if (classification.kind === "untyped-existing") {
79
+ if (classification.contentFiles.length <= 2) {
80
+ lines.push("**Workflow sizing:** This is a tiny existing untyped project. Prefer exactly one slice unless the milestone request clearly spans multiple independent user-visible capabilities.");
81
+ }
82
+ else if (classification.contentFiles.length <= 5) {
83
+ lines.push("**Workflow sizing:** This is a small existing untyped project. Prefer 1-2 slices unless the milestone request clearly spans multiple independent user-visible capabilities.");
84
+ }
85
+ else {
86
+ lines.push("**Workflow sizing:** Existing untyped project. Use generic file-level workflow guidance and size slices by real capability boundaries, not by missing tooling markers.");
87
+ }
88
+ }
89
+ else if (classification.kind === "greenfield") {
90
+ lines.push("**Workflow sizing:** No project content exists yet. Use normal greenfield sizing for the requested scope.");
91
+ }
92
+ else if (classification.kind === "typed-existing") {
93
+ lines.push("**Workflow sizing:** Known project markers exist. Use normal ecosystem-aware planning guidance.");
94
+ }
95
+ else {
96
+ lines.push("**Workflow sizing:** Invalid repository state. Planning should surface this as a blocker rather than inventing project structure.");
97
+ }
98
+ return lines.join("\n");
99
+ }
100
+ function normalizeArtifactRef(value) {
101
+ return value.trim().replace(/^[-\s]+/, "").replace(/^["'`]+|["'`]+$/g, "").replaceAll("\\", "/").replace(/^\.\//, "");
102
+ }
103
+ function parseCoveredArtifacts(validationContent) {
104
+ const covered = new Set();
105
+ const lines = validationContent.split(/\r?\n/);
106
+ let inCoveredArtifacts = false;
107
+ for (const line of lines) {
108
+ if (/^\s*covered[-_]?artifacts\s*:/i.test(line)) {
109
+ inCoveredArtifacts = true;
110
+ const inline = line.split(/covered[-_]?artifacts\s*:/i)[1]?.trim();
111
+ if (inline && inline !== "[]") {
112
+ inline.replace(/^\[|\]$/g, "").split(",").map(normalizeArtifactRef).filter(Boolean).forEach((item) => covered.add(item));
113
+ }
114
+ continue;
115
+ }
116
+ if (!inCoveredArtifacts)
117
+ continue;
118
+ if (/^\S/.test(line) && !/^\s*-/.test(line))
119
+ break;
120
+ const item = line.match(/^\s*-\s*(.+)$/)?.[1];
121
+ if (item)
122
+ covered.add(normalizeArtifactRef(item));
123
+ }
124
+ return covered;
125
+ }
126
+ function isValidationFreshOrApplicable(validationContent, currentArtifacts) {
127
+ if (!validationContent)
128
+ return false;
129
+ if (!/validation_metadata:/i.test(validationContent))
130
+ return false;
131
+ const coveredArtifacts = parseCoveredArtifacts(validationContent);
132
+ if (coveredArtifacts.size === 0)
133
+ return false;
134
+ return currentArtifacts
135
+ .map(normalizeArtifactRef)
136
+ .filter(Boolean)
137
+ .every((artifact) => coveredArtifacts.has(artifact));
138
+ }
139
+ function formatCloseoutReviewInstructions(validationContent, validationRel, currentArtifacts) {
140
+ const verdict = validationContent ? extractVerdict(validationContent) : null;
141
+ const validationFresh = isValidationFreshOrApplicable(validationContent, currentArtifacts);
142
+ if (verdict === "pass" && validationFresh) {
143
+ return [
144
+ "### Passing Validation Artifact",
145
+ "",
146
+ `A passing validation artifact is present at \`${validationRel}\`. Treat it as authoritative for success criteria, requirement coverage, verification classes, and cross-slice integration.`,
147
+ "",
148
+ "Do not delegate fresh reviewer/security/tester audits and do not redo the validation evidence review unless the artifact is internally inconsistent with the inlined summaries. Focus this unit on final milestone narrative, learnings, PROJECT/requirements updates, and `gsd_complete_milestone`.",
149
+ ].join("\n");
150
+ }
151
+ if (verdict) {
152
+ return [
153
+ "### Validation Requires Attention",
154
+ "",
155
+ `A validation artifact is present at \`${validationRel}\` with verdict \`${verdict}\`, but it is missing freshness metadata or does not cover current milestone artifacts. Do not treat the milestone as complete unless the issues are resolved and evidence supports completion.`,
156
+ ].join("\n");
157
+ }
158
+ return [
159
+ "### No Passing Validation Artifact",
160
+ "",
161
+ `No passing validation artifact was found at \`${validationRel}\`. Use the full closeout review path before completion.`,
162
+ ].join("\n");
163
+ }
65
164
  function capPreamble(preamble) {
66
165
  // Cap inlined context at min(historical 30K ceiling, scaled inline budget).
67
166
  // The ceiling preserves pre-fix behavior for large-window users; the scaled
@@ -1465,6 +1564,7 @@ export async function buildPlanMilestonePrompt(mid, midTitle, base, level) {
1465
1564
  const researchAnchor = readPhaseAnchor(base, mid, "research-milestone");
1466
1565
  if (researchAnchor)
1467
1566
  inlined.push(formatAnchorForPrompt(researchAnchor));
1567
+ inlined.push(formatProjectClassificationForPlanning(classifyProject(base)));
1468
1568
  inlined.push(await inlineFile(contextPath, contextRel, "Milestone Context"));
1469
1569
  const researchInline = await inlineFileOptional(researchPath, researchRel, "Milestone Research");
1470
1570
  if (researchInline)
@@ -2017,6 +2117,9 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
2017
2117
  const inlineLevel = level ?? resolveInlineLevel();
2018
2118
  const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
2019
2119
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
2120
+ const validationPath = resolveMilestoneFile(base, mid, "VALIDATION");
2121
+ const validationRel = relMilestoneFile(base, mid, "VALIDATION");
2122
+ const validationContent = validationPath ? await loadFile(validationPath) : null;
2020
2123
  const inlined = [];
2021
2124
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
2022
2125
  // Inline all slice summaries (deduplicated by slice ID)
@@ -2056,6 +2159,13 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
2056
2159
  const pathList = summaryRelPaths.map(p => `- \`${p}\``).join("\n");
2057
2160
  inlined.push(`### On-demand Slice Summaries\n\nExcerpted above. Read the full file for any slice when the excerpt's section heads don't carry enough narrative for the milestone summary you're drafting:\n\n${pathList}`);
2058
2161
  }
2162
+ const validationContext = [
2163
+ formatCloseoutReviewInstructions(validationContent, validationRel, [validationRel, roadmapRel, ...summaryRelPaths]),
2164
+ ];
2165
+ if (validationContent) {
2166
+ validationContext.push(`### Milestone Validation\nSource: \`${validationRel}\`\n\n${validationContent.trim()}`);
2167
+ }
2168
+ inlined.unshift(...validationContext);
2059
2169
  // Inline root GSD files (skip for minimal — completion can read these if needed)
2060
2170
  if (inlineLevel !== "minimal") {
2061
2171
  const requirementsInline = await inlineRequirementsFromDb(base, mid, undefined, inlineLevel);
@@ -487,9 +487,8 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
487
487
  hasSurvivorBranch = false;
488
488
  }
489
489
  const effectivePrefs = loadEffectiveGSDPreferences(base)?.preferences;
490
- const deepProjectStagePending = !hasSurvivorBranch
491
- ? (await import("./auto-dispatch.js")).hasPendingDeepStage(effectivePrefs, base)
492
- : false;
490
+ const { shouldRunDeepProjectSetup } = await import("./auto-dispatch.js");
491
+ const deepProjectStagePending = shouldRunDeepProjectSetup(state, effectivePrefs, base, { hasSurvivorBranch });
493
492
  if (deepProjectStagePending) {
494
493
  // Deep project-level setup runs before the first milestone exists. Let
495
494
  // the auto loop dispatch workflow-preferences / project / requirements
@@ -112,11 +112,12 @@ const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
112
112
  * the DB is unavailable (e.g. fresh project before init) we skip registration
113
113
  * silently rather than blocking session start.
114
114
  */
115
- function registerAutoWorkerForSession(session) {
115
+ function registerAutoWorkerForSession(session, projectRootOverride) {
116
116
  if (session.workerId)
117
117
  return; // already registered (e.g. resume re-runs)
118
118
  try {
119
- const projectRootRealpath = normalizeRealPath(session.scope?.workspace.projectRoot
119
+ const projectRootRealpath = normalizeRealPath(projectRootOverride
120
+ ?? session.scope?.workspace.projectRoot
120
121
  ?? (session.originalBasePath || session.basePath));
121
122
  session.workerId = registerAutoWorker({ projectRootRealpath });
122
123
  }
@@ -225,8 +226,16 @@ function synthesizePausedSessionRecovery(basePath, unitType, unitId, sessionFile
225
226
  export function _synthesizePausedSessionRecoveryForTest(basePath, unitType, unitId, sessionFile) {
226
227
  return synthesizePausedSessionRecovery(basePath, unitType, unitId, sessionFile);
227
228
  }
229
+ const DETACHED_AUTO_KEEPALIVE_INTERVAL_MS = 30_000;
230
+ function withDetachedAutoKeepalive(run) {
231
+ const keepAlive = setInterval(() => { }, DETACHED_AUTO_KEEPALIVE_INTERVAL_MS);
232
+ return run.finally(() => {
233
+ clearInterval(keepAlive);
234
+ });
235
+ }
236
+ export const _withDetachedAutoKeepaliveForTest = withDetachedAutoKeepalive;
228
237
  export function startAutoDetached(ctx, pi, base, verboseMode, options) {
229
- void startAuto(ctx, pi, base, verboseMode, options).catch((err) => {
238
+ void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch((err) => {
230
239
  const message = getErrorMessage(err);
231
240
  ctx.ui.notify(`Auto-start failed: ${message}`, "error");
232
241
  logWarning("engine", `auto start error: ${message}`, { file: "auto.ts" });
@@ -1623,6 +1632,10 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
1623
1632
  lockBase,
1624
1633
  buildResolver,
1625
1634
  };
1635
+ // Register the worker before bootstrap enters a milestone worktree.
1636
+ // This ensures enterMilestone can claim a lease and seed dispatch claims
1637
+ // for crash-recovery fidelity (#5405).
1638
+ registerAutoWorkerForSession(s, base);
1626
1639
  const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps, freshStartAssessment);
1627
1640
  if (!ready)
1628
1641
  return;
@@ -52,6 +52,25 @@ async function applyDisabledModelProviderPolicy(ctx) {
52
52
  // Non-fatal: keep default provider visibility if preferences cannot be loaded.
53
53
  }
54
54
  }
55
+ /**
56
+ * Bridge `context_management.compaction_threshold_percent` from GSD preferences
57
+ * into the agent's runtime compaction settings (#5475). The preference is
58
+ * validated to (0.5, 0.95) at load time, but defense-in-depth normalization
59
+ * here protects against a stale or hand-edited prefs file. Calling with
60
+ * `undefined` clears any prior override so a removed preference does not leak.
61
+ */
62
+ async function applyCompactionThresholdOverride(ctx) {
63
+ try {
64
+ const { loadEffectiveGSDPreferences } = await import("../preferences.js");
65
+ const prefs = loadEffectiveGSDPreferences();
66
+ const raw = prefs?.preferences.context_management?.compaction_threshold_percent;
67
+ const value = typeof raw === "number" && Number.isFinite(raw) && raw > 0 && raw < 1 ? raw : undefined;
68
+ ctx.setCompactionThresholdOverride(value);
69
+ }
70
+ catch {
71
+ // Non-fatal: leave any existing override in place.
72
+ }
73
+ }
55
74
  export function resolveNotificationStoreBasePath(cwd = process.cwd()) {
56
75
  return resolveWorktreeProjectRoot(cwd);
57
76
  }
@@ -101,6 +120,7 @@ export function registerHooks(pi, ecosystemHandlers) {
101
120
  await resetAskUserQuestionsTurnCache();
102
121
  await syncServiceTierStatus(ctx);
103
122
  await applyDisabledModelProviderPolicy(ctx);
123
+ await applyCompactionThresholdOverride(ctx);
104
124
  // Skip MCP auto-prep when running inside an auto-worktree (see session_switch below).
105
125
  const { isInAutoWorktree } = await import("../auto-worktree.js");
106
126
  if (!isInAutoWorktree(process.cwd())) {
@@ -149,6 +169,7 @@ export function registerHooks(pi, ecosystemHandlers) {
149
169
  clearDiscussionFlowState(process.cwd());
150
170
  await syncServiceTierStatus(ctx);
151
171
  await applyDisabledModelProviderPolicy(ctx);
172
+ await applyCompactionThresholdOverride(ctx);
152
173
  // Skip MCP auto-prep when running inside an auto-worktree. The worktree
153
174
  // already has .mcp.json from createAutoWorktree, and re-running the writer
154
175
  // post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
@@ -16,6 +16,31 @@ import { execFileSync } from "node:child_process";
16
16
  import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
17
17
  import { logWarning } from "./workflow-logger.js";
18
18
  import { nativeHasChanges } from "./native-git-bridge.js";
19
+ function findPreflightStashRef(basePath, milestoneId, stashMarker) {
20
+ const markerPrefix = `gsd-preflight-stash:${milestoneId}:`;
21
+ let fallbackRef = null;
22
+ try {
23
+ const list = execFileSync("git", ["stash", "list", "--format=%gd%x00%s"], {
24
+ cwd: basePath,
25
+ stdio: ["ignore", "pipe", "pipe"],
26
+ encoding: "utf-8",
27
+ env: GIT_NO_PROMPT_ENV,
28
+ });
29
+ for (const line of list.split("\n")) {
30
+ const [ref, subject] = line.split("\x00");
31
+ if (!ref || !subject)
32
+ continue;
33
+ if (stashMarker && subject.includes(stashMarker))
34
+ return ref;
35
+ if (!fallbackRef && subject.includes(markerPrefix))
36
+ fallbackRef = ref;
37
+ }
38
+ }
39
+ catch (err) {
40
+ logWarning("preflight", `stash list failed before restore: ${err instanceof Error ? err.message : String(err)}`);
41
+ }
42
+ return fallbackRef;
43
+ }
19
44
  /**
20
45
  * Check the working tree for dirty files before a milestone merge.
21
46
  *
@@ -47,7 +72,8 @@ export function preflightCleanRoot(basePath, milestoneId, notify) {
47
72
  notify(warnMsg, "warning");
48
73
  // Push the stash
49
74
  try {
50
- execFileSync("git", ["stash", "push", "--include-untracked", "-m", "gsd-preflight-stash"], {
75
+ const stashMarker = `gsd-preflight-stash:${milestoneId}:${process.pid}:${Date.now()}:${process.hrtime.bigint().toString(36)}`;
76
+ execFileSync("git", ["stash", "push", "--include-untracked", "-m", `gsd-preflight-stash [${stashMarker}]`], {
51
77
  cwd: basePath,
52
78
  stdio: ["ignore", "pipe", "pipe"],
53
79
  encoding: "utf-8",
@@ -55,6 +81,7 @@ export function preflightCleanRoot(basePath, milestoneId, notify) {
55
81
  });
56
82
  return {
57
83
  stashPushed: true,
84
+ stashMarker,
58
85
  summary: `Stashed uncommitted changes before merge (milestone ${milestoneId}).`,
59
86
  };
60
87
  }
@@ -73,9 +100,17 @@ export function preflightCleanRoot(basePath, milestoneId, notify) {
73
100
  * Any pop error (e.g. conflict) is logged and notified but does NOT throw —
74
101
  * the merge already completed successfully.
75
102
  */
76
- export function postflightPopStash(basePath, milestoneId, notify) {
103
+ export function postflightPopStash(basePath, milestoneId, stashMarker, notify) {
104
+ let stashRef = null;
77
105
  try {
78
- execFileSync("git", ["stash", "pop"], {
106
+ stashRef = findPreflightStashRef(basePath, milestoneId, stashMarker);
107
+ if (!stashRef) {
108
+ const msg = `No matching GSD preflight stash found for milestone ${milestoneId}; leaving stash list untouched.`;
109
+ logWarning("preflight", msg);
110
+ notify(msg, "warning");
111
+ return;
112
+ }
113
+ execFileSync("git", ["stash", "pop", stashRef], {
79
114
  cwd: basePath,
80
115
  stdio: ["ignore", "pipe", "pipe"],
81
116
  encoding: "utf-8",
@@ -86,7 +121,10 @@ export function postflightPopStash(basePath, milestoneId, notify) {
86
121
  catch (err) {
87
122
  // Pop conflicts mean the merged code collides with the stashed changes.
88
123
  // Log a warning — the user needs to resolve manually, but the merge succeeded.
89
- const msg = `git stash pop failed after merge of milestone ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. Run "git stash pop" manually to restore your changes.`;
124
+ const restoreHint = stashRef
125
+ ? `Run "git stash pop ${stashRef}" or "git stash apply ${stashRef}" manually to restore the correct stash.`
126
+ : `Run "git stash list" to find the matching GSD preflight stash before restoring manually.`;
127
+ const msg = `git stash pop ${stashRef ?? ""}`.trim() + ` failed after merge of milestone ${milestoneId}: ${err instanceof Error ? err.message : String(err)}. ${restoreHint}`;
90
128
  logWarning("preflight", msg);
91
129
  notify(msg, "warning");
92
130
  }
@@ -33,5 +33,10 @@ export async function handleGSDCommand(args, ctx, pi) {
33
33
  }
34
34
  if (handled)
35
35
  return;
36
+ if (trimmed.includes(" ")) {
37
+ const { handleDo } = await import("../commands-do.js");
38
+ await handleDo(trimmed, ctx, pi);
39
+ return;
40
+ }
36
41
  ctx.ui.notify(`Unknown: /gsd ${trimmed}. Run /gsd help for available commands.`, "warning");
37
42
  }