gsd-pi 2.80.0-dev.96f4d89d5 → 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 (239) 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-recovery.js +154 -8
  9. package/dist/resources/extensions/gsd/auto-start.js +2 -3
  10. package/dist/resources/extensions/gsd/auto.js +16 -3
  11. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
  12. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +129 -1
  13. package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
  14. package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
  15. package/dist/resources/extensions/gsd/commands-extract-learnings.js +17 -12
  16. package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
  17. package/dist/resources/extensions/gsd/db-base-schema.js +14 -0
  18. package/dist/resources/extensions/gsd/db-migration-steps.js +16 -0
  19. package/dist/resources/extensions/gsd/detection.js +106 -0
  20. package/dist/resources/extensions/gsd/gsd-db.js +102 -2
  21. package/dist/resources/extensions/gsd/guided-flow.js +63 -12
  22. package/dist/resources/extensions/gsd/migrate/command.js +40 -1
  23. package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
  24. package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
  25. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  26. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  27. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  28. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  29. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
  30. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  31. package/dist/resources/extensions/gsd/quick.js +34 -2
  32. package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
  33. package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
  34. package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
  35. package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
  36. package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
  37. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  38. package/dist/web/standalone/.next/BUILD_ID +1 -1
  39. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  40. package/dist/web/standalone/.next/build-manifest.json +2 -2
  41. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  42. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/index.html +1 -1
  59. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  66. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  68. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  69. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  70. package/package.json +4 -2
  71. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  72. package/packages/pi-coding-agent/dist/core/agent-session.js +3 -0
  73. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  74. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +76 -0
  75. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
  77. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  78. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
  79. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  80. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
  81. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
  82. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
  83. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
  84. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
  85. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
  86. package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
  87. package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
  88. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
  89. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
  90. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
  91. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
  92. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
  93. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/extensions/runner.js +17 -1
  95. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +99 -0
  97. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
  99. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
  102. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  103. package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
  104. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js +6 -4
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +50 -19
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +2 -2
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +2 -1
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +7 -6
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +13 -0
  119. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +77 -13
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +68 -2
  124. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
  126. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
  129. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  131. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +36 -27
  132. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  133. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +21 -0
  134. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
  136. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
  137. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
  138. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
  139. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
  141. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
  142. package/packages/pi-coding-agent/src/core/agent-session.ts +3 -0
  143. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +89 -0
  144. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
  145. package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
  146. package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
  147. package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
  148. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +110 -0
  149. package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -1
  150. package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
  151. package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
  152. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.ts +7 -5
  153. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +68 -19
  154. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +2 -2
  155. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +1 -0
  156. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +2 -1
  157. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +10 -9
  158. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +88 -13
  159. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
  160. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +91 -3
  161. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
  162. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +36 -27
  163. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +25 -0
  164. package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
  165. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
  166. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  167. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  168. package/packages/pi-tui/dist/tui.js +17 -7
  169. package/packages/pi-tui/dist/tui.js.map +1 -1
  170. package/packages/pi-tui/src/tui.ts +19 -7
  171. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  172. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  173. package/pkg/dist/modes/interactive/theme/themes.js +36 -27
  174. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  175. package/src/resources/GSD-WORKFLOW.md +2 -2
  176. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  177. package/src/resources/extensions/gsd/auto/loop.ts +50 -8
  178. package/src/resources/extensions/gsd/auto/phases.ts +42 -28
  179. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -0
  180. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
  181. package/src/resources/extensions/gsd/auto-prompts.ts +116 -1
  182. package/src/resources/extensions/gsd/auto-recovery.ts +153 -7
  183. package/src/resources/extensions/gsd/auto-start.ts +7 -6
  184. package/src/resources/extensions/gsd/auto.ts +23 -3
  185. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +39 -1
  186. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +135 -1
  187. package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
  188. package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
  189. package/src/resources/extensions/gsd/commands-extract-learnings.ts +17 -12
  190. package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
  191. package/src/resources/extensions/gsd/db-base-schema.ts +15 -0
  192. package/src/resources/extensions/gsd/db-migration-steps.ts +17 -0
  193. package/src/resources/extensions/gsd/detection.ts +128 -0
  194. package/src/resources/extensions/gsd/gsd-db.ts +119 -1
  195. package/src/resources/extensions/gsd/guided-flow.ts +66 -12
  196. package/src/resources/extensions/gsd/migrate/command.ts +47 -1
  197. package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
  198. package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
  199. package/src/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  200. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  201. package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  202. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  203. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
  204. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  205. package/src/resources/extensions/gsd/quick.ts +37 -2
  206. package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
  207. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -1
  208. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +170 -1
  209. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
  210. package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +9 -0
  211. package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
  212. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
  213. package/src/resources/extensions/gsd/tests/db-schema-metadata.test.ts +2 -2
  214. package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
  215. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +44 -0
  216. package/src/resources/extensions/gsd/tests/has-pending-deep-stage.test.ts +33 -1
  217. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
  218. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
  219. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
  220. package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
  221. package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
  222. package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
  223. package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
  224. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
  225. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -0
  226. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +63 -2
  227. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
  228. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +7 -0
  229. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
  230. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
  231. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
  232. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
  233. package/src/resources/extensions/gsd/tests/worktree-write-gate.test.ts +179 -0
  234. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
  235. package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
  236. package/src/resources/extensions/gsd/unit-runtime.ts +14 -0
  237. package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
  238. /package/dist/web/standalone/.next/static/{y7d--cnc6Cibuva-Lzzw_ → FU0iL9dhdc2IBzEYBDOlY}/_buildManifest.js +0 -0
  239. /package/dist/web/standalone/.next/static/{y7d--cnc6Cibuva-Lzzw_ → FU0iL9dhdc2IBzEYBDOlY}/_ssgManifest.js +0 -0
@@ -32,13 +32,13 @@ import { detectStuck } from "./detect-stuck.js";
32
32
  import { runUnit } from "./run-unit.js";
33
33
  import { debugLog } from "../debug-logger.js";
34
34
  import { resolveWorktreeProjectRoot, normalizeWorktreePathForCompare } from "../worktree-root.js";
35
- import { PROJECT_FILES, hasProjectFileInAncestor } from "../detection.js";
35
+ import { classifyProject } from "../detection.js";
36
36
  import { MergeConflictError } from "../git-service.js";
37
37
  import { setCurrentPhase, clearCurrentPhase } from "../../shared/gsd-phase-state.js";
38
38
  import { pauseAutoForProviderError } from "../provider-error-pause.js";
39
39
  import { resumeAutoAfterProviderDelay } from "../bootstrap/provider-error-resume.js";
40
40
  import { join, basename } from "node:path";
41
- import { existsSync, cpSync, readdirSync } from "node:fs";
41
+ import { existsSync, cpSync } from "node:fs";
42
42
  import {
43
43
  logWarning,
44
44
  logError,
@@ -684,6 +684,7 @@ export async function runPreDispatch(
684
684
  deps.postflightPopStash(
685
685
  s.originalBasePath || s.basePath,
686
686
  s.currentMilestoneId!,
687
+ preflightTransition.stashMarker,
687
688
  ctx.ui.notify.bind(ctx.ui),
688
689
  );
689
690
  }
@@ -797,6 +798,7 @@ export async function runPreDispatch(
797
798
  deps.postflightPopStash(
798
799
  s.originalBasePath || s.basePath,
799
800
  s.currentMilestoneId,
801
+ preflightAllComplete.stashMarker,
800
802
  ctx.ui.notify.bind(ctx.ui),
801
803
  );
802
804
  }
@@ -925,6 +927,7 @@ export async function runPreDispatch(
925
927
  deps.postflightPopStash(
926
928
  s.originalBasePath || s.basePath,
927
929
  s.currentMilestoneId,
930
+ preflightComplete.stashMarker,
928
931
  ctx.ui.notify.bind(ctx.ui),
929
932
  );
930
933
  }
@@ -1484,8 +1487,9 @@ export async function runUnitPhase(
1484
1487
  // Verify the working directory is a valid git checkout with project
1485
1488
  // files before dispatching work. A broken worktree causes agents to
1486
1489
  // hallucinate summaries since they cannot read or write any files.
1487
- // Uses the shared PROJECT_FILES list from detection.ts to support all
1488
- // ecosystems (Rust, Go, Python, Java, etc.), not just JS.
1490
+ // Uses project classification so project presence is not conflated with
1491
+ // ecosystem marker detection. Static/minimal repos become untyped-existing.
1492
+ let projectClassification: ReturnType<typeof classifyProject> | null = null;
1489
1493
  if (s.basePath && unitType === "execute-task") {
1490
1494
  const gitMarker = join(s.basePath, ".git");
1491
1495
  const hasGit = deps.existsSync(gitMarker);
@@ -1496,30 +1500,29 @@ export async function runUnitPhase(
1496
1500
  await deps.stopAuto(ctx, pi, msg);
1497
1501
  return { action: "break", reason: "worktree-invalid" };
1498
1502
  }
1499
- const hasProjectFile = PROJECT_FILES.some((f) => deps.existsSync(join(s.basePath, f)));
1500
- const hasSrcDir = deps.existsSync(join(s.basePath, "src"));
1501
- // Xcode bundles have project-specific names (*.xcodeproj, *.xcworkspace)
1502
- // that cannot be matched by exact filename — scan the directory by suffix.
1503
- let hasXcodeBundle = false;
1504
- try {
1505
- const entries = deps.existsSync(s.basePath) ? readdirSync(s.basePath) : [];
1506
- hasXcodeBundle = entries.some((e: string) => e.endsWith(".xcodeproj") || e.endsWith(".xcworkspace"));
1507
- } catch (err) {
1508
- debugLog("runUnitPhase", { phase: "xcode-bundle-scan-failed", basePath: s.basePath, error: String(err) });
1509
- }
1510
- // Monorepo support (#2347): if no project files in the worktree directory,
1511
- // walk parent directories up to the filesystem root. In monorepos,
1512
- // package.json / Cargo.toml etc. live in a parent directory.
1513
- const hasProjectFileInParent =
1514
- !hasProjectFile && !hasSrcDir && !hasXcodeBundle
1515
- ? hasProjectFileInAncestor(s.basePath, deps.existsSync)
1516
- : false;
1517
- if (!hasProjectFile && !hasSrcDir && !hasXcodeBundle && !hasProjectFileInParent) {
1518
- // Greenfield projects won't have project files yet — the first task creates them.
1519
- // Log a warning but allow execution to proceed. The .git check above is sufficient
1520
- // to ensure we're in a valid working directory.
1521
- debugLog("runUnitPhase", { phase: "worktree-health-warn-greenfield", basePath: s.basePath, hasProjectFile, hasSrcDir, hasXcodeBundle });
1522
- ctx.ui.notify(`Warning: ${s.basePath} has no recognized project files — proceeding as greenfield project`, "warning");
1503
+ projectClassification = classifyProject(s.basePath);
1504
+ if (projectClassification.kind === "invalid-repo") {
1505
+ const msg = `Worktree health check failed: ${s.basePath} classified as invalid-repo (${projectClassification.reason}) — refusing to dispatch ${unitType} ${unitId}`;
1506
+ debugLog("runUnitPhase", { phase: "worktree-health-invalid-repo", basePath: s.basePath, classification: projectClassification });
1507
+ if (projectClassification.reason === "missing .git" && hasGit) {
1508
+ ctx.ui.notify(
1509
+ `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`,
1510
+ "warning",
1511
+ );
1512
+ } else {
1513
+ ctx.ui.notify(msg, "error");
1514
+ await deps.stopAuto(ctx, pi, msg);
1515
+ return { action: "break", reason: "worktree-invalid" };
1516
+ }
1517
+ } else if (projectClassification.kind === "greenfield") {
1518
+ debugLog("runUnitPhase", { phase: "worktree-health-greenfield", basePath: s.basePath, classification: projectClassification });
1519
+ ctx.ui.notify(`Warning: ${s.basePath} has no project content yet — proceeding as greenfield project`, "warning");
1520
+ } else if (projectClassification.kind === "untyped-existing") {
1521
+ debugLog("runUnitPhase", { phase: "worktree-health-untyped-existing", basePath: s.basePath, classification: projectClassification });
1522
+ ctx.ui.notify(
1523
+ `Notice: ${s.basePath} has existing project content but no recognized tooling markers using generic file-level workflow guidance`,
1524
+ "info",
1525
+ );
1523
1526
  }
1524
1527
  }
1525
1528
 
@@ -1598,6 +1601,17 @@ export async function runUnitPhase(
1598
1601
  // Prompt injection
1599
1602
  let finalPrompt = prompt;
1600
1603
 
1604
+ if (unitType === "execute-task") {
1605
+ projectClassification ??= classifyProject(s.basePath);
1606
+ if (projectClassification.kind === "untyped-existing") {
1607
+ const samples = projectClassification.contentFiles.slice(0, 8).join(", ") || "project files";
1608
+ finalPrompt +=
1609
+ "\n\n**Project classification:** Existing untyped project. No recognized build/tooling markers were detected, " +
1610
+ "so use generic file-level workflow guidance. Task plans and completion summaries must list every concrete " +
1611
+ `project file changed in \`files\` or \`expected_output\`. Detected content sample: ${samples}.`;
1612
+ }
1613
+ }
1614
+
1601
1615
  if (s.pendingVerificationRetry) {
1602
1616
  const retryCtx = s.pendingVerificationRetry;
1603
1617
  s.pendingVerificationRetry = null;
@@ -211,6 +211,23 @@ export function hasPendingDeepStage(prefs: GSDPreferences | undefined, basePath:
211
211
  return gate.status === "pending" || gate.status === "blocked";
212
212
  }
213
213
 
214
+ export function shouldRunDeepProjectSetup(
215
+ state: Pick<GSDState, "phase">,
216
+ prefs: GSDPreferences | undefined,
217
+ basePath: string,
218
+ options: { hasSurvivorBranch?: boolean } = {},
219
+ ): boolean {
220
+ if (options.hasSurvivorBranch === true) return false;
221
+ if (
222
+ state.phase !== "pre-planning" &&
223
+ state.phase !== "needs-discussion" &&
224
+ state.phase !== "planning"
225
+ ) {
226
+ return false;
227
+ }
228
+ return hasPendingDeepStage(prefs, basePath);
229
+ }
230
+
214
231
  function missingSliceStop(mid: string, phase: string): DispatchAction {
215
232
  return {
216
233
  action: "stop",
@@ -43,7 +43,7 @@ import {
43
43
  import { regenerateIfMissing } from "./workflow-projections.js";
44
44
  import { syncStateToProjectRoot } from "./auto-worktree.js";
45
45
  import { normalizeWorktreePathForCompare } from "./worktree-root.js";
46
- import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
46
+ import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter, getVerificationEvidence } from "./gsd-db.js";
47
47
  import { renderPlanCheckboxes } from "./markdown-renderer.js";
48
48
  import { consumeSignal } from "./session-status-io.js";
49
49
  import {
@@ -852,22 +852,22 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
852
852
  }
853
853
 
854
854
  // Evidence cross-reference (execute-task only)
855
- // Verification evidence is passed via the complete-task tool call and
856
- // stored in the SUMMARY.md on disk not available as structured data
857
- // in the DB. The evidence collector tracks actual bash tool calls, so
858
- // we can still detect units that claimed success but ran no commands.
855
+ // Only compare against concrete command evidence persisted by the task
856
+ // completion tool. A prose Verify field can be satisfied later by the
857
+ // host verification gate, so it is not enough to accuse the unit.
859
858
  if (safetyConfig.evidence_cross_reference && s.currentUnit.type === "execute-task") {
860
859
  try {
861
860
  const actual = getEvidence();
862
861
  const bashCalls = actual.filter(e => e.kind === "bash");
863
- // If the task is marked complete but zero bash commands were run,
864
- // it's suspicious — the LLM may have fabricated results.
865
862
  if (sMid && sSid && sTid && isDbAvailable()) {
866
863
  const taskRow = getTask(sMid, sSid, sTid);
867
- if (taskRow?.status === "complete" && taskRow.verify && bashCalls.length === 0) {
868
- logWarning("safety", "task marked complete with verification commands but no bash calls were executed");
864
+ const claimedCommands = getVerificationEvidence(sMid, sSid, sTid)
865
+ .map((row) => row.command)
866
+ .filter((command): command is string => typeof command === "string" && command.trim().length > 0);
867
+ if (taskRow?.status === "complete" && claimedCommands.length > 0 && bashCalls.length === 0) {
868
+ logWarning("safety", "task claimed verification command evidence but no execution tool calls were recorded");
869
869
  ctx.ui.notify(
870
- `Safety: task ${sTid} has verification commands but no bash calls were recorded`,
870
+ `Safety: task ${sTid} claimed command evidence but no execution tool calls were recorded`,
871
871
  "warning",
872
872
  );
873
873
  }
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection, parseTaskPlanFile } from "./files.js";
10
10
  import type { Override, UatType } from "./files.js";
11
- import { hasVerdict, getUatType } from "./verdict-parser.js";
11
+ import { hasVerdict, getUatType, extractVerdict } from "./verdict-parser.js";
12
12
  import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
13
13
  import {
14
14
  resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
@@ -39,6 +39,7 @@ import { logWarning } from "./workflow-logger.js";
39
39
  import { inlineGraphSubgraph } from "./graph-context.js";
40
40
  import { buildExtractionStepsBlock } from "./commands-extract-learnings.js";
41
41
  import { resolveSkillManifest, warnIfManifestHasMissingSkills } from "./skill-manifest.js";
42
+ import { classifyProject, type ProjectClassification } from "./detection.js";
42
43
 
43
44
  // ─── Preamble Cap ─────────────────────────────────────────────────────────────
44
45
 
@@ -80,6 +81,108 @@ function resolveSummaryBudgetChars(): number {
80
81
  return resolvePromptBudgets().summaryBudgetChars;
81
82
  }
82
83
 
84
+ function formatProjectClassificationForPlanning(classification: ProjectClassification): string {
85
+ const sampleFiles = classification.contentFiles.slice(0, 8);
86
+ const sample = sampleFiles.length > 0 ? sampleFiles.map((file) => `\`${file}\``).join(", ") : "(none)";
87
+ const lines = [
88
+ "### Project Classification",
89
+ "",
90
+ `- **Kind:** ${classification.kind}`,
91
+ `- **Content files:** ${classification.contentFiles.length}`,
92
+ `- **Sample files:** ${sample}`,
93
+ `- **Reason:** ${classification.reason}`,
94
+ "",
95
+ ];
96
+
97
+ if (classification.kind === "untyped-existing") {
98
+ if (classification.contentFiles.length <= 2) {
99
+ lines.push(
100
+ "**Workflow sizing:** This is a tiny existing untyped project. Prefer exactly one slice unless the milestone request clearly spans multiple independent user-visible capabilities.",
101
+ );
102
+ } else if (classification.contentFiles.length <= 5) {
103
+ lines.push(
104
+ "**Workflow sizing:** This is a small existing untyped project. Prefer 1-2 slices unless the milestone request clearly spans multiple independent user-visible capabilities.",
105
+ );
106
+ } else {
107
+ lines.push(
108
+ "**Workflow sizing:** Existing untyped project. Use generic file-level workflow guidance and size slices by real capability boundaries, not by missing tooling markers.",
109
+ );
110
+ }
111
+ } else if (classification.kind === "greenfield") {
112
+ lines.push("**Workflow sizing:** No project content exists yet. Use normal greenfield sizing for the requested scope.");
113
+ } else if (classification.kind === "typed-existing") {
114
+ lines.push("**Workflow sizing:** Known project markers exist. Use normal ecosystem-aware planning guidance.");
115
+ } else {
116
+ lines.push("**Workflow sizing:** Invalid repository state. Planning should surface this as a blocker rather than inventing project structure.");
117
+ }
118
+
119
+ return lines.join("\n");
120
+ }
121
+
122
+ function normalizeArtifactRef(value: string): string {
123
+ return value.trim().replace(/^[-\s]+/, "").replace(/^["'`]+|["'`]+$/g, "").replaceAll("\\", "/").replace(/^\.\//, "");
124
+ }
125
+
126
+ function parseCoveredArtifacts(validationContent: string): Set<string> {
127
+ const covered = new Set<string>();
128
+ const lines = validationContent.split(/\r?\n/);
129
+ let inCoveredArtifacts = false;
130
+ for (const line of lines) {
131
+ if (/^\s*covered[-_]?artifacts\s*:/i.test(line)) {
132
+ inCoveredArtifacts = true;
133
+ const inline = line.split(/covered[-_]?artifacts\s*:/i)[1]?.trim();
134
+ if (inline && inline !== "[]") {
135
+ inline.replace(/^\[|\]$/g, "").split(",").map(normalizeArtifactRef).filter(Boolean).forEach((item) => covered.add(item));
136
+ }
137
+ continue;
138
+ }
139
+ if (!inCoveredArtifacts) continue;
140
+ if (/^\S/.test(line) && !/^\s*-/.test(line)) break;
141
+ const item = line.match(/^\s*-\s*(.+)$/)?.[1];
142
+ if (item) covered.add(normalizeArtifactRef(item));
143
+ }
144
+ return covered;
145
+ }
146
+
147
+ function isValidationFreshOrApplicable(validationContent: string | null, currentArtifacts: string[]): boolean {
148
+ if (!validationContent) return false;
149
+ if (!/validation_metadata:/i.test(validationContent)) return false;
150
+ const coveredArtifacts = parseCoveredArtifacts(validationContent);
151
+ if (coveredArtifacts.size === 0) return false;
152
+ return currentArtifacts
153
+ .map(normalizeArtifactRef)
154
+ .filter(Boolean)
155
+ .every((artifact) => coveredArtifacts.has(artifact));
156
+ }
157
+
158
+ function formatCloseoutReviewInstructions(validationContent: string | null, validationRel: string, currentArtifacts: string[]): string {
159
+ const verdict = validationContent ? extractVerdict(validationContent) : null;
160
+ const validationFresh = isValidationFreshOrApplicable(validationContent, currentArtifacts);
161
+ if (verdict === "pass" && validationFresh) {
162
+ return [
163
+ "### Passing Validation Artifact",
164
+ "",
165
+ `A passing validation artifact is present at \`${validationRel}\`. Treat it as authoritative for success criteria, requirement coverage, verification classes, and cross-slice integration.`,
166
+ "",
167
+ "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`.",
168
+ ].join("\n");
169
+ }
170
+
171
+ if (verdict) {
172
+ return [
173
+ "### Validation Requires Attention",
174
+ "",
175
+ `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.`,
176
+ ].join("\n");
177
+ }
178
+
179
+ return [
180
+ "### No Passing Validation Artifact",
181
+ "",
182
+ `No passing validation artifact was found at \`${validationRel}\`. Use the full closeout review path before completion.`,
183
+ ].join("\n");
184
+ }
185
+
83
186
  function capPreamble(preamble: string): string {
84
187
  // Cap inlined context at min(historical 30K ceiling, scaled inline budget).
85
188
  // The ceiling preserves pre-fix behavior for large-window users; the scaled
@@ -1658,6 +1761,8 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
1658
1761
  const researchAnchor = readPhaseAnchor(base, mid, "research-milestone");
1659
1762
  if (researchAnchor) inlined.push(formatAnchorForPrompt(researchAnchor));
1660
1763
 
1764
+ inlined.push(formatProjectClassificationForPlanning(classifyProject(base)));
1765
+
1661
1766
  inlined.push(await inlineFile(contextPath, contextRel, "Milestone Context"));
1662
1767
  const researchInline = await inlineFileOptional(researchPath, researchRel, "Milestone Research");
1663
1768
  if (researchInline) inlined.push(researchInline);
@@ -2348,6 +2453,9 @@ export async function buildCompleteMilestonePrompt(
2348
2453
  const inlineLevel = level ?? resolveInlineLevel();
2349
2454
  const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
2350
2455
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
2456
+ const validationPath = resolveMilestoneFile(base, mid, "VALIDATION");
2457
+ const validationRel = relMilestoneFile(base, mid, "VALIDATION");
2458
+ const validationContent = validationPath ? await loadFile(validationPath) : null;
2351
2459
 
2352
2460
  const inlined: string[] = [];
2353
2461
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
@@ -2389,6 +2497,13 @@ export async function buildCompleteMilestonePrompt(
2389
2497
  `### 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}`,
2390
2498
  );
2391
2499
  }
2500
+ const validationContext = [
2501
+ formatCloseoutReviewInstructions(validationContent, validationRel, [validationRel, roadmapRel, ...summaryRelPaths]),
2502
+ ];
2503
+ if (validationContent) {
2504
+ validationContext.push(`### Milestone Validation\nSource: \`${validationRel}\`\n\n${validationContent.trim()}`);
2505
+ }
2506
+ inlined.unshift(...validationContext);
2392
2507
 
2393
2508
  // Inline root GSD files (skip for minimal — completion can read these if needed)
2394
2509
  if (inlineLevel !== "minimal") {
@@ -14,7 +14,7 @@ import { appendEvent } from "./workflow-events.js";
14
14
  import { atomicWriteSync } from "./atomic-write.js";
15
15
  import { clearParseCache } from "./files.js";
16
16
  import { parseRoadmap as parseLegacyRoadmap, parsePlan as parseLegacyPlan } from "./parsers-legacy.js";
17
- import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone, refreshOpenDatabaseFromDisk } from "./gsd-db.js";
17
+ import { isDbAvailable, getTask, getSlice, getSliceTasks, getPendingGates, updateTaskStatus, updateSliceStatus, insertSlice, getMilestone, refreshOpenDatabaseFromDisk, getCompletedMilestoneTaskFileHints, getMilestoneCommitAttributionShas, recordMilestoneCommitAttribution } from "./gsd-db.js";
18
18
  import { isValidationTerminal } from "./state.js";
19
19
  import { getErrorMessage } from "./error-utils.js";
20
20
  import { logWarning, logError } from "./workflow-logger.js";
@@ -147,15 +147,29 @@ export function hasImplementationArtifacts(basePath: string, milestoneId?: strin
147
147
  // milestone commits instead of treating the self-diff as proof of no work.
148
148
  if (changedFiles.length === 0) {
149
149
  if (milestoneId && currentBranch === integrationBranch) {
150
- const tagged = getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId);
151
- if (!tagged.ok) return "unknown";
152
- if (tagged.matched) return classifyImplementationFiles(tagged.files);
150
+ const milestoneEvidence = getChangedFilesFromMilestoneEvidence(basePath, milestoneId);
151
+ if (!milestoneEvidence.ok) return "unknown";
152
+ if (milestoneEvidence.matched) return classifyImplementationFiles(milestoneEvidence.files);
153
153
  }
154
154
  if (currentBranch && currentBranch !== "HEAD") return "absent";
155
155
  return "unknown";
156
156
  }
157
157
 
158
- return classifyImplementationFiles(changedFiles);
158
+ const branchClassification = classifyImplementationFiles(changedFiles);
159
+ if (branchClassification === "present") return "present";
160
+
161
+ // A completing milestone branch can have a non-empty diff containing only
162
+ // .gsd/ closeout files after implementation commits already landed on the
163
+ // recorded integration branch. In that topology, the branch diff alone is
164
+ // insufficient; use the same milestone-tagged evidence fallback as the
165
+ // self-diff retry path before declaring the milestone implementation-free.
166
+ if (milestoneId) {
167
+ const milestoneEvidence = getChangedFilesFromMilestoneEvidence(basePath, milestoneId);
168
+ if (!milestoneEvidence.ok) return "unknown";
169
+ if (milestoneEvidence.matched) return classifyImplementationFiles(milestoneEvidence.files);
170
+ }
171
+
172
+ return "absent";
159
173
  } catch (e) {
160
174
  // Non-fatal — if git operations fail, return unknown so callers can decide
161
175
  logWarning("recovery", `implementation artifact check failed: ${(e as Error).message}`);
@@ -185,6 +199,10 @@ function isImplementationPath(file: string): boolean {
185
199
  return !file.startsWith(".gsd/") && !file.startsWith(".gsd\\");
186
200
  }
187
201
 
202
+ function normalizeRepoPath(file: string): string {
203
+ return file.trim().replace(/\\/g, "/").replace(/^\.\/+/, "");
204
+ }
205
+
188
206
  /**
189
207
  * Detect the main/master branch name.
190
208
  */
@@ -263,7 +281,7 @@ function getChangedFilesFromMilestoneTaggedCommits(
263
281
  "log", "--format=%H%x1f%B%x1e", "HEAD", "--", `.gsd/milestones/${milestoneId}`,
264
282
  ]);
265
283
  if (!scoped.ok) return scoped;
266
- if (scoped.matched) return scoped;
284
+ if (scoped.matched && classifyImplementationFiles(scoped.files) === "present") return scoped;
267
285
 
268
286
  // Fallback (#5033): when .gsd/ is gitignored / external / untracked, the
269
287
  // path-scoped scan matches no commits even though GSD-tagged commits
@@ -274,9 +292,137 @@ function getChangedFilesFromMilestoneTaggedCommits(
274
292
  // Intentionally unbounded — symmetric with the primary scan, and avoids
275
293
  // reintroducing the rolling-depth failure class removed in #4699 where
276
294
  // milestone evidence aged out behind unrelated activity.
277
- return scanGsdTaggedCommits(basePath, milestoneId, [
295
+ const unscoped = scanGsdTaggedCommits(basePath, milestoneId, [
278
296
  "log", "--format=%H%x1f%B%x1e", "HEAD",
279
297
  ]);
298
+ if (!unscoped.ok) return scoped.matched ? scoped : unscoped;
299
+ if (!unscoped.matched) return scoped;
300
+
301
+ return {
302
+ ok: true,
303
+ matched: true,
304
+ files: [...new Set([...scoped.files, ...unscoped.files])],
305
+ };
306
+ }
307
+
308
+ function getChangedFilesFromMilestoneEvidence(
309
+ basePath: string,
310
+ milestoneId: string,
311
+ ): { ok: boolean; matched: boolean; files: string[] } {
312
+ const tagged = getChangedFilesFromMilestoneTaggedCommits(basePath, milestoneId);
313
+ if (!tagged.ok) return tagged;
314
+ if (tagged.matched && classifyImplementationFiles(tagged.files) === "present") return tagged;
315
+
316
+ const attributed = getChangedFilesFromAttributedMilestoneCommits(basePath, milestoneId);
317
+ if (!attributed.ok) return tagged.matched ? tagged : attributed;
318
+ if (attributed.matched && classifyImplementationFiles(attributed.files) === "present") return attributed;
319
+
320
+ const backfilled = backfillChangedFilesFromUntaggedMilestoneCommits(basePath, milestoneId);
321
+ if (!backfilled.ok) return tagged.matched ? tagged : attributed.matched ? attributed : backfilled;
322
+ if (!backfilled.matched) {
323
+ if (tagged.matched) return tagged;
324
+ return attributed.matched ? attributed : backfilled;
325
+ }
326
+
327
+ return {
328
+ ok: true,
329
+ matched: true,
330
+ files: [...new Set([...tagged.files, ...attributed.files, ...backfilled.files])],
331
+ };
332
+ }
333
+
334
+ function getChangedFilesFromAttributedMilestoneCommits(
335
+ basePath: string,
336
+ milestoneId: string,
337
+ ): { ok: boolean; matched: boolean; files: string[] } {
338
+ try {
339
+ const shas = getMilestoneCommitAttributionShas(milestoneId);
340
+ if (shas.length === 0) return { ok: true, matched: false, files: [] };
341
+
342
+ const files = new Set<string>();
343
+ let matched = false;
344
+ for (const sha of shas) {
345
+ if (!isFullCommitSha(sha)) continue;
346
+ const commitFiles = getChangedFilesForCommit(basePath, sha);
347
+ if (commitFiles.length === 0) continue;
348
+ matched = true;
349
+ for (const file of commitFiles) files.add(file);
350
+ }
351
+ return { ok: true, matched, files: [...files] };
352
+ } catch (e) {
353
+ logWarning("recovery", `milestone attribution scan failed: ${(e as Error).message}`);
354
+ return { ok: false, matched: false, files: [] };
355
+ }
356
+ }
357
+
358
+ function backfillChangedFilesFromUntaggedMilestoneCommits(
359
+ basePath: string,
360
+ milestoneId: string,
361
+ ): { ok: boolean; matched: boolean; files: string[] } {
362
+ try {
363
+ const milestone = getMilestone(milestoneId);
364
+ const milestoneStartedAt = milestone?.created_at ? Math.floor(Date.parse(milestone.created_at) / 1000) * 1000 : NaN;
365
+ if (!Number.isFinite(milestoneStartedAt)) return { ok: true, matched: false, files: [] };
366
+
367
+ const taskFileHints = getCompletedMilestoneTaskFileHints(milestoneId);
368
+ if (taskFileHints.length === 0) return { ok: true, matched: false, files: [] };
369
+
370
+ const hintSet = new Set(taskFileHints.map(normalizeRepoPath).filter(Boolean));
371
+ if (hintSet.size === 0) return { ok: true, matched: false, files: [] };
372
+
373
+ const records = getCommitRecords(basePath);
374
+ const files = new Set<string>();
375
+ let matched = false;
376
+ for (const record of records) {
377
+ if (!isFullCommitSha(record.hash)) continue;
378
+ if (Date.parse(record.committedAt) < milestoneStartedAt) continue;
379
+ if (record.parents.trim().split(/\s+/).filter(Boolean).length > 1) continue;
380
+ if (commitMessageHasGsdTrailer(record.message)) continue;
381
+
382
+ const commitFiles = getChangedFilesForCommit(basePath, record.hash);
383
+ const implementationFiles = commitFiles.map(normalizeRepoPath).filter(isImplementationPath);
384
+ if (implementationFiles.length === 0) continue;
385
+ if (!implementationFiles.some((file) => hintSet.has(file))) continue;
386
+
387
+ matched = true;
388
+ for (const file of implementationFiles) files.add(file);
389
+ recordMilestoneCommitAttribution({
390
+ commitSha: record.hash,
391
+ milestoneId,
392
+ source: "backfill",
393
+ confidence: 0.8,
394
+ files: implementationFiles,
395
+ createdAt: new Date().toISOString(),
396
+ });
397
+ }
398
+
399
+ return { ok: true, matched, files: [...files] };
400
+ } catch (e) {
401
+ logWarning("recovery", `milestone attribution backfill failed: ${(e as Error).message}`);
402
+ return { ok: false, matched: false, files: [] };
403
+ }
404
+ }
405
+
406
+ function getCommitRecords(basePath: string): Array<{ hash: string; parents: string; committedAt: string; message: string }> {
407
+ const logOutput = execFileSync("git", ["log", "--format=%H%x1f%P%x1f%cI%x1f%B%x1e", "HEAD"], {
408
+ cwd: basePath,
409
+ stdio: ["ignore", "pipe", "pipe"],
410
+ encoding: "utf-8",
411
+ });
412
+ return logOutput
413
+ .split("\x1e")
414
+ .map((record) => record.trim())
415
+ .filter(Boolean)
416
+ .flatMap((record) => {
417
+ const parts = record.split("\x1f");
418
+ if (parts.length < 4) return [];
419
+ const [hash, parents, committedAt, ...messageParts] = parts;
420
+ return [{ hash: hash.trim(), parents: parents.trim(), committedAt: committedAt.trim(), message: messageParts.join("\x1f") }];
421
+ });
422
+ }
423
+
424
+ function isFullCommitSha(value: string): boolean {
425
+ return /^[0-9a-f]{40}$/i.test(value);
280
426
  }
281
427
 
282
428
  function scanGsdTaggedCommits(
@@ -664,12 +664,13 @@ export async function bootstrapAutoSession(
664
664
  }
665
665
 
666
666
  const effectivePrefs = loadEffectiveGSDPreferences(base)?.preferences;
667
- const deepProjectStagePending = !hasSurvivorBranch
668
- ? (await import("./auto-dispatch.js")).hasPendingDeepStage(
669
- effectivePrefs,
670
- base,
671
- )
672
- : false;
667
+ const { shouldRunDeepProjectSetup } = await import("./auto-dispatch.js");
668
+ const deepProjectStagePending = shouldRunDeepProjectSetup(
669
+ state,
670
+ effectivePrefs,
671
+ base,
672
+ { hasSurvivorBranch },
673
+ );
673
674
 
674
675
  if (deepProjectStagePending) {
675
676
  // Deep project-level setup runs before the first milestone exists. Let
@@ -293,11 +293,15 @@ const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
293
293
  * the DB is unavailable (e.g. fresh project before init) we skip registration
294
294
  * silently rather than blocking session start.
295
295
  */
296
- function registerAutoWorkerForSession(session: AutoSession): void {
296
+ function registerAutoWorkerForSession(
297
+ session: AutoSession,
298
+ projectRootOverride?: string,
299
+ ): void {
297
300
  if (session.workerId) return; // already registered (e.g. resume re-runs)
298
301
  try {
299
302
  const projectRootRealpath = normalizeRealPath(
300
- session.scope?.workspace.projectRoot
303
+ projectRootOverride
304
+ ?? session.scope?.workspace.projectRoot
301
305
  ?? (session.originalBasePath || session.basePath),
302
306
  );
303
307
  session.workerId = registerAutoWorker({ projectRootRealpath });
@@ -420,6 +424,17 @@ export function _synthesizePausedSessionRecoveryForTest(
420
424
  return synthesizePausedSessionRecovery(basePath, unitType, unitId, sessionFile);
421
425
  }
422
426
 
427
+ const DETACHED_AUTO_KEEPALIVE_INTERVAL_MS = 30_000;
428
+
429
+ function withDetachedAutoKeepalive<T>(run: Promise<T>): Promise<T> {
430
+ const keepAlive = setInterval(() => {}, DETACHED_AUTO_KEEPALIVE_INTERVAL_MS);
431
+ return run.finally(() => {
432
+ clearInterval(keepAlive);
433
+ });
434
+ }
435
+
436
+ export const _withDetachedAutoKeepaliveForTest = withDetachedAutoKeepalive;
437
+
423
438
  export function startAutoDetached(
424
439
  ctx: ExtensionCommandContext,
425
440
  pi: ExtensionAPI,
@@ -431,7 +446,7 @@ export function startAutoDetached(
431
446
  milestoneLock?: string | null;
432
447
  },
433
448
  ): void {
434
- void startAuto(ctx, pi, base, verboseMode, options).catch((err) => {
449
+ void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch((err) => {
435
450
  const message = getErrorMessage(err);
436
451
  ctx.ui.notify(`Auto-start failed: ${message}`, "error");
437
452
  logWarning("engine", `auto start error: ${message}`, { file: "auto.ts" });
@@ -2046,6 +2061,11 @@ export async function startAuto(
2046
2061
  buildResolver,
2047
2062
  };
2048
2063
 
2064
+ // Register the worker before bootstrap enters a milestone worktree.
2065
+ // This ensures enterMilestone can claim a lease and seed dispatch claims
2066
+ // for crash-recovery fidelity (#5405).
2067
+ registerAutoWorkerForSession(s, base);
2068
+
2049
2069
  const ready = await bootstrapAutoSession(
2050
2070
  s,
2051
2071
  ctx,