gsd-pi 2.80.0-dev.cf9433f56 → 2.80.0-dev.d4fc28e6b

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 (237) hide show
  1. package/dist/cli.js +0 -19
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +29 -0
  4. package/dist/resources/extensions/gsd/auto/loop.js +71 -8
  5. package/dist/resources/extensions/gsd/auto/phases.js +150 -94
  6. package/dist/resources/extensions/gsd/auto/resolve.js +12 -0
  7. package/dist/resources/extensions/gsd/auto/run-unit.js +10 -30
  8. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  9. package/dist/resources/extensions/gsd/auto/workflow-dispatch-claim.js +33 -1
  10. package/dist/resources/extensions/gsd/auto/workflow-worker-heartbeat.js +9 -1
  11. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +5 -32
  12. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -0
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +17 -4
  14. package/dist/resources/extensions/gsd/auto-prompts.js +90 -15
  15. package/dist/resources/extensions/gsd/auto-start.js +197 -6
  16. package/dist/resources/extensions/gsd/auto-worktree.js +111 -1
  17. package/dist/resources/extensions/gsd/auto.js +18 -22
  18. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +86 -19
  19. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +49 -36
  20. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +15 -5
  21. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +9 -3
  22. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +7 -1
  23. package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +9 -3
  24. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +8 -2
  25. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +298 -54
  26. package/dist/resources/extensions/gsd/bootstrap/system-context.js +82 -23
  27. package/dist/resources/extensions/gsd/clean-root-preflight.js +24 -6
  28. package/dist/resources/extensions/gsd/commands-handlers.js +23 -9
  29. package/dist/resources/extensions/gsd/db/unit-dispatches.js +53 -0
  30. package/dist/resources/extensions/gsd/ecosystem/gsd-extension-api.js +2 -0
  31. package/dist/resources/extensions/gsd/guided-flow.js +47 -28
  32. package/dist/resources/extensions/gsd/native-git-bridge.js +32 -8
  33. package/dist/resources/extensions/gsd/orphan-stash-audit.js +101 -0
  34. package/dist/resources/extensions/gsd/parallel-orchestrator.js +13 -3
  35. package/dist/resources/extensions/gsd/pre-execution-checks.js +15 -0
  36. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  37. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/execute-task.md +4 -2
  39. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  40. package/dist/resources/extensions/gsd/prompts/replan-slice.md +2 -2
  41. package/dist/resources/extensions/gsd/workflow-protocol.js +131 -0
  42. package/dist/resources/extensions/gsd/worktree-resolver.js +35 -4
  43. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  44. package/dist/web/standalone/.next/BUILD_ID +1 -1
  45. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  46. package/dist/web/standalone/.next/build-manifest.json +2 -2
  47. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  48. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/index.html +1 -1
  65. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  72. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  74. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  75. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  76. package/dist/welcome-screen.d.ts +2 -0
  77. package/dist/welcome-screen.js +9 -7
  78. package/package.json +1 -1
  79. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  80. package/packages/pi-agent-core/dist/agent-loop.js +4 -1
  81. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  82. package/packages/pi-agent-core/dist/agent.d.ts +5 -0
  83. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  84. package/packages/pi-agent-core/dist/agent.js +2 -0
  85. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  86. package/packages/pi-agent-core/dist/index.d.ts +1 -0
  87. package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
  88. package/packages/pi-agent-core/dist/index.js +2 -0
  89. package/packages/pi-agent-core/dist/index.js.map +1 -1
  90. package/packages/pi-agent-core/dist/token-audit.d.ts +47 -0
  91. package/packages/pi-agent-core/dist/token-audit.d.ts.map +1 -0
  92. package/packages/pi-agent-core/dist/token-audit.js +221 -0
  93. package/packages/pi-agent-core/dist/token-audit.js.map +1 -0
  94. package/packages/pi-agent-core/dist/types.d.ts +9 -0
  95. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  96. package/packages/pi-agent-core/dist/types.js.map +1 -1
  97. package/packages/pi-agent-core/src/agent-loop.test.ts +128 -0
  98. package/packages/pi-agent-core/src/agent-loop.ts +4 -1
  99. package/packages/pi-agent-core/src/agent.ts +8 -0
  100. package/packages/pi-agent-core/src/index.ts +2 -0
  101. package/packages/pi-agent-core/src/token-audit.test.ts +189 -0
  102. package/packages/pi-agent-core/src/token-audit.ts +287 -0
  103. package/packages/pi-agent-core/src/types.ts +14 -0
  104. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  105. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +18 -0
  106. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
  108. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  109. package/packages/pi-coding-agent/dist/core/agent-session.js +36 -7
  110. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -0
  113. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
  115. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -6
  117. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +3 -3
  119. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +32 -1
  121. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/hooks-runner.test.js +2 -0
  124. package/packages/pi-coding-agent/dist/core/hooks-runner.test.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts +2 -0
  126. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts.map +1 -0
  127. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js +46 -0
  128. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js.map +1 -0
  129. package/packages/pi-coding-agent/dist/core/sdk.d.ts +10 -2
  130. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/sdk.js +74 -2
  132. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  133. package/packages/pi-coding-agent/dist/core/skill-tool.test.js +22 -0
  134. package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +6 -7
  136. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -3
  138. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  139. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +25 -0
  140. package/packages/pi-coding-agent/src/core/agent-session.ts +40 -7
  141. package/packages/pi-coding-agent/src/core/extensions/loader.ts +10 -0
  142. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +3 -3
  143. package/packages/pi-coding-agent/src/core/extensions/runner.ts +5 -5
  144. package/packages/pi-coding-agent/src/core/extensions/types.ts +35 -1
  145. package/packages/pi-coding-agent/src/core/hooks-runner.test.ts +2 -0
  146. package/packages/pi-coding-agent/src/core/sdk-tool-filter.test.ts +60 -0
  147. package/packages/pi-coding-agent/src/core/sdk.ts +85 -3
  148. package/packages/pi-coding-agent/src/core/skill-tool.test.ts +28 -0
  149. package/packages/pi-coding-agent/src/core/system-prompt.ts +8 -10
  150. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  151. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +30 -0
  152. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +26 -0
  153. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -2
  154. package/src/resources/extensions/gsd/auto/loop.ts +84 -8
  155. package/src/resources/extensions/gsd/auto/phases.ts +218 -154
  156. package/src/resources/extensions/gsd/auto/resolve.ts +19 -0
  157. package/src/resources/extensions/gsd/auto/run-unit.ts +10 -29
  158. package/src/resources/extensions/gsd/auto/session.ts +8 -0
  159. package/src/resources/extensions/gsd/auto/workflow-dispatch-claim.ts +63 -1
  160. package/src/resources/extensions/gsd/auto/workflow-worker-heartbeat.ts +14 -1
  161. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +8 -34
  162. package/src/resources/extensions/gsd/auto-dispatch.ts +16 -0
  163. package/src/resources/extensions/gsd/auto-post-unit.ts +18 -4
  164. package/src/resources/extensions/gsd/auto-prompts.ts +95 -14
  165. package/src/resources/extensions/gsd/auto-start.ts +230 -9
  166. package/src/resources/extensions/gsd/auto-worktree.ts +123 -0
  167. package/src/resources/extensions/gsd/auto.ts +18 -18
  168. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +100 -18
  169. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +50 -36
  170. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +16 -5
  171. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +10 -3
  172. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +8 -1
  173. package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +10 -3
  174. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +9 -2
  175. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +347 -54
  176. package/src/resources/extensions/gsd/bootstrap/system-context.ts +90 -22
  177. package/src/resources/extensions/gsd/clean-root-preflight.ts +32 -7
  178. package/src/resources/extensions/gsd/commands-handlers.ts +34 -15
  179. package/src/resources/extensions/gsd/db/unit-dispatches.ts +66 -0
  180. package/src/resources/extensions/gsd/ecosystem/gsd-extension-api.ts +3 -0
  181. package/src/resources/extensions/gsd/guided-flow.ts +52 -35
  182. package/src/resources/extensions/gsd/native-git-bridge.ts +39 -6
  183. package/src/resources/extensions/gsd/orphan-stash-audit.ts +117 -0
  184. package/src/resources/extensions/gsd/parallel-orchestrator.ts +13 -3
  185. package/src/resources/extensions/gsd/pre-execution-checks.ts +16 -0
  186. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  187. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  188. package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
  189. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  190. package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
  191. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +2 -2
  192. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +361 -10
  193. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +168 -6
  194. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +15 -6
  195. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +31 -0
  196. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +3 -2
  197. package/src/resources/extensions/gsd/tests/context-store.test.ts +7 -1
  198. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +5 -1
  199. package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +5 -2
  200. package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +219 -0
  201. package/src/resources/extensions/gsd/tests/finalize-survivor-branch.test.ts +132 -0
  202. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +6 -3
  203. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +5 -1
  204. package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +32 -0
  205. package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -0
  206. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -0
  207. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +242 -0
  208. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +34 -2
  209. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -0
  210. package/src/resources/extensions/gsd/tests/orphan-merge-bootstrap.test.ts +133 -0
  211. package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +201 -0
  212. package/src/resources/extensions/gsd/tests/parallel-orchestrator-fast-forward.test.ts +113 -0
  213. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +7 -5
  214. package/src/resources/extensions/gsd/tests/prompt-duplication-cuts.test.ts +230 -0
  215. package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +3 -3
  216. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +38 -17
  217. package/src/resources/extensions/gsd/tests/select-resumable-milestone.test.ts +96 -0
  218. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +77 -0
  219. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +166 -0
  220. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
  221. package/src/resources/extensions/gsd/tests/system-context-memory.test.ts +112 -0
  222. package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +7 -9
  223. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +291 -0
  224. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +50 -1
  225. package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +5 -4
  226. package/src/resources/extensions/gsd/tests/workflow-dispatch-claim.test.ts +142 -0
  227. package/src/resources/extensions/gsd/tests/workflow-protocol-excerpt.test.ts +99 -0
  228. package/src/resources/extensions/gsd/tests/workflow-worker-heartbeat.test.ts +32 -1
  229. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +1 -0
  230. package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +22 -19
  231. package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +66 -0
  232. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +104 -3
  233. package/src/resources/extensions/gsd/workflow-protocol.ts +160 -0
  234. package/src/resources/extensions/gsd/worktree-resolver.ts +49 -4
  235. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +0 -97
  236. /package/dist/web/standalone/.next/static/{-5nHJWzSdG-WkPMul_khA → cWaxzf-sdbSSbbwYu8q7a}/_buildManifest.js +0 -0
  237. /package/dist/web/standalone/.next/static/{-5nHJWzSdG-WkPMul_khA → cWaxzf-sdbSSbbwYu8q7a}/_ssgManifest.js +0 -0
@@ -23,6 +23,9 @@ import { bumpTurnGeneration } from "./turn-epoch.js";
23
23
  let _currentResolve: ((result: UnitResult) => void) | null = null;
24
24
  let _sessionSwitchInFlight = false;
25
25
  let _pendingSwitchCancellation: { errorContext?: ErrorContext } | null = null;
26
+ let _sessionSwitchAbortGraceUntil = 0;
27
+
28
+ const DEFAULT_SESSION_SWITCH_ABORT_GRACE_MS = 2_000;
26
29
 
27
30
  // ─── Setters (needed for cross-module mutation) ─────────────────────────────
28
31
 
@@ -34,6 +37,21 @@ export function _setSessionSwitchInFlight(v: boolean): void {
34
37
  _sessionSwitchInFlight = v;
35
38
  }
36
39
 
40
+ export function _markSessionSwitchAbortGraceWindow(durationMs = DEFAULT_SESSION_SWITCH_ABORT_GRACE_MS): void {
41
+ _sessionSwitchAbortGraceUntil = Math.max(
42
+ _sessionSwitchAbortGraceUntil,
43
+ Date.now() + durationMs,
44
+ );
45
+ }
46
+
47
+ export function _clearSessionSwitchAbortGraceWindow(): void {
48
+ _sessionSwitchAbortGraceUntil = 0;
49
+ }
50
+
51
+ export function isSessionSwitchAbortGraceActive(now = Date.now()): boolean {
52
+ return now < _sessionSwitchAbortGraceUntil;
53
+ }
54
+
37
55
  export function _clearCurrentResolve(): void {
38
56
  _currentResolve = null;
39
57
  }
@@ -142,6 +160,7 @@ export function _resetPendingResolve(): void {
142
160
  _currentResolve = null;
143
161
  _sessionSwitchInFlight = false;
144
162
  _pendingSwitchCancellation = null;
163
+ _sessionSwitchAbortGraceUntil = 0;
145
164
  }
146
165
 
147
166
  export function _hasPendingResolveForTest(): boolean {
@@ -14,6 +14,7 @@ import type { UnitResult } from "./types.js";
14
14
  import {
15
15
  _clearCurrentResolve,
16
16
  _consumePendingSwitchCancellation,
17
+ _markSessionSwitchAbortGraceWindow,
17
18
  _setCurrentResolve,
18
19
  _setSessionSwitchInFlight,
19
20
  } from "./resolve.js";
@@ -48,45 +49,24 @@ export async function runUnit(
48
49
  ): Promise<UnitResult> {
49
50
  debugLog("runUnit", { phase: "start", unitType, unitId });
50
51
 
51
- // Ensure cwd matches basePath BEFORE newSession() captures it. The new
52
- // session reads process.cwd() during construction to anchor its tool
53
- // runtime and system prompt; if cwd has drifted (async_bash, background
54
- // jobs, prior unit cleanup), the session would otherwise be rooted to
55
- // the wrong directory. Must be synchronous — no awaits between chdir
56
- // and newSession (#1389, #4762 follow-up).
57
- try {
58
- if (process.cwd() !== s.basePath) {
59
- process.chdir(s.basePath);
60
- }
61
- } catch (e) {
62
- const msg = `Failed to chdir to basePath before newSession (basePath: ${s.basePath}): ${String(e)}`;
63
- logWarning("engine", msg, { basePath: s.basePath, error: String(e) });
64
- return {
65
- status: "cancelled",
66
- errorContext: {
67
- message: msg,
68
- category: "session-failed",
69
- isTransient: true,
70
- },
71
- };
72
- }
73
-
74
52
  // ── Session creation with timeout ──
75
53
  debugLog("runUnit", { phase: "session-create", unitType, unitId });
76
54
 
77
55
  let sessionResult: { cancelled: boolean };
78
56
  let sessionTimeoutHandle: ReturnType<typeof setTimeout> | undefined;
79
57
  const mySessionSwitchGeneration = ++sessionSwitchGeneration;
80
- // #3731: Cancellation controller for newSession(). When the session-creation
81
- // timeout fires, we abort this controller so that the still-in-flight
82
- // newSession() discards itself after await this.abort() completes, preventing
83
- // it from capturing the (now-root) process.cwd() and rebuilding the tool
84
- // runtime with the wrong cwd.
58
+ // #3731: Cancellation controller for newSession(). When session creation
59
+ // times out, abort before a late session switch can rebuild the tool runtime
60
+ // against a stale workspace root.
85
61
  const sessionAbortController = new AbortController();
86
62
  _setSessionSwitchInFlight(true);
87
63
  try {
88
- const sessionPromise = s.cmdCtx!.newSession({ abortSignal: sessionAbortController.signal }).finally(() => {
64
+ const sessionPromise = s.cmdCtx!.newSession({
65
+ abortSignal: sessionAbortController.signal,
66
+ workspaceRoot: s.basePath,
67
+ }).finally(() => {
89
68
  if (sessionSwitchGeneration === mySessionSwitchGeneration) {
69
+ _markSessionSwitchAbortGraceWindow();
90
70
  _setSessionSwitchInFlight(false);
91
71
  }
92
72
  });
@@ -150,6 +130,7 @@ export async function runUnit(
150
130
  // ── Create the agent_end promise (per-unit one-shot) ──
151
131
  // This happens after newSession completes so session-switch agent_end events
152
132
  // from the previous session cannot resolve the new unit.
133
+ _markSessionSwitchAbortGraceWindow();
153
134
  _setSessionSwitchInFlight(false);
154
135
  const unitPromise = new Promise<UnitResult>((resolve) => {
155
136
  _setCurrentResolve(resolve);
@@ -178,6 +178,13 @@ export class AutoSession {
178
178
  * stale context bleeding into unrelated slices.
179
179
  */
180
180
  lastPreExecFailure: PreExecFailure | null = null;
181
+ /**
182
+ * Tracks how many consecutive times each slice unit has failed pre-execution
183
+ * checks. Keyed by unitId (e.g. "M001/S01"). Used to break the infinite
184
+ * plan-slice → pre-exec fail → re-dispatch loop when the planner cannot fix
185
+ * the issues after MAX_PRE_EXEC_RETRIES re-attempts.
186
+ */
187
+ readonly preExecRetryCount: Map<string, number> = new Map();
181
188
 
182
189
  // ── Tool invocation errors (#2883) ──────────────────────────────────
183
190
  /** Set when a GSD tool execution ends with isError due to malformed/truncated
@@ -343,6 +350,7 @@ export class AutoSession {
343
350
  this.rewriteAttemptCount = 0;
344
351
  this.consecutiveCompleteBootstraps = 0;
345
352
  this.lastPreExecFailure = null;
353
+ this.preExecRetryCount.clear();
346
354
  this.lastToolInvocationError = null;
347
355
  this.lastUnitAgentEndMessages = null;
348
356
  this.lastGitActionFailure = null;
@@ -9,6 +9,16 @@ export type DispatchClaimOutcome =
9
9
  | { kind: "skip"; reason: "already-active" | "stale-lease"; existingId?: number; existingWorker?: string }
10
10
  | { kind: "degraded" };
11
11
 
12
+ export type DispatchLeaseOutcome =
13
+ | { kind: "ready"; token: number; recovered: boolean }
14
+ | { kind: "degraded"; reason: "missing-worker" | "missing-milestone" }
15
+ | { kind: "blocked"; reason: string }
16
+ | { kind: "failed"; reason: string };
17
+
18
+ type ClaimMilestoneLeaseResult =
19
+ | { ok: true; token: number; expiresAt: string }
20
+ | { ok: false; error: "held_by"; byWorker: string; expiresAt: string };
21
+
12
22
  interface RecentDispatch {
13
23
  attempt_n?: number | null;
14
24
  }
@@ -44,6 +54,58 @@ export interface OpenDispatchClaimDeps {
44
54
  logClaimFailed: (err: unknown) => void;
45
55
  }
46
56
 
57
+ export interface EnsureDispatchLeaseDeps {
58
+ claimMilestoneLease: (workerId: string, milestoneId: string) => ClaimMilestoneLeaseResult;
59
+ logLeaseRecovered: (details: {
60
+ milestoneId: string;
61
+ workerId: string;
62
+ token: number;
63
+ recovered: boolean;
64
+ }) => void;
65
+ logLeaseRecoveryFailed: (details: {
66
+ milestoneId?: string;
67
+ workerId?: string;
68
+ reason: string;
69
+ }) => void;
70
+ }
71
+
72
+ export function ensureDispatchLease(
73
+ s: AutoSession,
74
+ milestoneId: string | undefined,
75
+ deps: EnsureDispatchLeaseDeps,
76
+ opts: { forceReclaim?: boolean } = {},
77
+ ): DispatchLeaseOutcome {
78
+ if (!s.workerId) return { kind: "degraded", reason: "missing-worker" };
79
+ if (!milestoneId) return { kind: "degraded", reason: "missing-milestone" };
80
+ if (!opts.forceReclaim && typeof s.milestoneLeaseToken === "number") {
81
+ return { kind: "ready", token: s.milestoneLeaseToken, recovered: false };
82
+ }
83
+
84
+ s.milestoneLeaseToken = null;
85
+ try {
86
+ const claim = deps.claimMilestoneLease(s.workerId, milestoneId);
87
+ if (!claim.ok) {
88
+ const reason = `Milestone ${milestoneId} is held by worker ${claim.byWorker} until ${claim.expiresAt}.`;
89
+ deps.logLeaseRecoveryFailed({ milestoneId, workerId: s.workerId, reason });
90
+ return { kind: "blocked", reason };
91
+ }
92
+
93
+ s.currentMilestoneId = milestoneId;
94
+ s.milestoneLeaseToken = claim.token;
95
+ deps.logLeaseRecovered({
96
+ milestoneId,
97
+ workerId: s.workerId,
98
+ token: claim.token,
99
+ recovered: opts.forceReclaim === true,
100
+ });
101
+ return { kind: "ready", token: claim.token, recovered: opts.forceReclaim === true };
102
+ } catch (err) {
103
+ const reason = err instanceof Error ? err.message : String(err);
104
+ deps.logLeaseRecoveryFailed({ milestoneId, workerId: s.workerId, reason });
105
+ return { kind: "failed", reason };
106
+ }
107
+ }
108
+
47
109
  export function openDispatchClaim(
48
110
  s: AutoSession,
49
111
  flowId: string,
@@ -51,7 +113,7 @@ export function openDispatchClaim(
51
113
  iterData: IterationData,
52
114
  deps: OpenDispatchClaimDeps,
53
115
  ): DispatchClaimOutcome {
54
- if (!s.workerId || s.milestoneLeaseToken === null) return { kind: "degraded" };
116
+ if (!s.workerId || typeof s.milestoneLeaseToken !== "number") return { kind: "degraded" };
55
117
  const mid = iterData.mid;
56
118
  if (!mid) return { kind: "degraded" };
57
119
 
@@ -15,6 +15,11 @@ export interface MaintainWorkerHeartbeatDeps {
15
15
  fencingToken: number,
16
16
  ) => boolean;
17
17
  logHeartbeatFailure: (err: unknown) => void;
18
+ logLeaseRefreshMiss?: (details: {
19
+ workerId: string;
20
+ milestoneId: string;
21
+ fencingToken: number;
22
+ }) => void;
18
23
  }
19
24
 
20
25
  export function maintainWorkerHeartbeat(
@@ -26,11 +31,19 @@ export function maintainWorkerHeartbeat(
26
31
  try {
27
32
  deps.heartbeatAutoWorker(session.workerId);
28
33
  if (session.currentMilestoneId && session.milestoneLeaseToken) {
29
- deps.refreshMilestoneLease(
34
+ const refreshed = deps.refreshMilestoneLease(
30
35
  session.workerId,
31
36
  session.currentMilestoneId,
32
37
  session.milestoneLeaseToken,
33
38
  );
39
+ if (!refreshed) {
40
+ deps.logLeaseRefreshMiss?.({
41
+ workerId: session.workerId,
42
+ milestoneId: session.currentMilestoneId,
43
+ fencingToken: session.milestoneLeaseToken,
44
+ });
45
+ session.milestoneLeaseToken = null;
46
+ }
34
47
  }
35
48
  } catch (err) {
36
49
  deps.logHeartbeatFailure(err);
@@ -31,7 +31,6 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
31
31
  import type { MinimalModelRegistry } from "./context-budget.js";
32
32
  import { pauseAuto } from "./auto.js";
33
33
  import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
34
- import { logWarning } from "./workflow-logger.js";
35
34
  import {
36
35
  getWorkflowTransportSupportError,
37
36
  getRequiredWorkflowToolsForAutoUnit,
@@ -290,38 +289,13 @@ export async function dispatchDirectPhase(
290
289
 
291
290
  ctx.ui.notify(`Dispatching ${unitType} for ${unitId}...`, "info");
292
291
 
293
- const originalCwd = process.cwd();
294
-
295
- try {
296
- // Ensure cwd matches dispatchBase BEFORE newSession() captures it. Synchronous —
297
- // no awaits between chdir and newSession.
298
- try {
299
- if (process.cwd() !== dispatchBase) {
300
- process.chdir(dispatchBase);
301
- }
302
- } catch (err) {
303
- const msg = `Failed to chdir before direct-dispatch newSession (basePath: ${dispatchBase}): ${err instanceof Error ? err.message : String(err)}`;
304
- logWarning("engine", msg, { file: "auto-direct-dispatch.ts", basePath: dispatchBase, error: err instanceof Error ? err.message : String(err) });
305
- ctx.ui.notify(`${msg}. Cancelling dispatch to avoid running in the wrong directory.`, "error");
306
- return;
307
- }
308
-
309
- const result = await ctx.newSession();
310
- if (result.cancelled) {
311
- ctx.ui.notify("Session creation cancelled.", "warning");
312
- return;
313
- }
314
- pi.sendMessage(
315
- { customType: "gsd-dispatch", content: prompt, display: false },
316
- { triggerTurn: true },
317
- );
318
- } finally {
319
- try {
320
- if (process.cwd() !== originalCwd) {
321
- process.chdir(originalCwd);
322
- }
323
- } catch (err) {
324
- logWarning("engine", `Failed to restore cwd after direct dispatch: ${err instanceof Error ? err.message : String(err)}`, { file: "auto-direct-dispatch.ts", basePath: originalCwd });
325
- }
292
+ const result = await ctx.newSession({ workspaceRoot: dispatchBase });
293
+ if (result.cancelled) {
294
+ ctx.ui.notify("Session creation cancelled.", "warning");
295
+ return;
326
296
  }
297
+ pi.sendMessage(
298
+ { customType: "gsd-dispatch", content: prompt, display: false },
299
+ { triggerTurn: true },
300
+ );
327
301
  }
@@ -938,6 +938,22 @@ export const DISPATCH_RULES: DispatchRule[] = [
938
938
  const unitId = `${mid}/${sid}`;
939
939
  let priorPreExecFailure: { blockingFindings: string[]; verdictExcerpt: string } | undefined;
940
940
  if (session?.lastPreExecFailure?.unitId === unitId) {
941
+ // Circuit breaker: stop re-dispatching after 2 failed retries. The
942
+ // planner has had multiple attempts with injected failure context and
943
+ // still cannot produce a valid plan — human review is required.
944
+ const MAX_PRE_EXEC_RETRIES = 2;
945
+ const retryCount = session.preExecRetryCount?.get(unitId) ?? 0;
946
+ if (retryCount >= MAX_PRE_EXEC_RETRIES) {
947
+ const findings = session.lastPreExecFailure.blockingFindings.join("; ");
948
+ session.lastPreExecFailure = null;
949
+ session.preExecRetryCount?.delete(unitId);
950
+ return {
951
+ action: "stop",
952
+ reason: `Pre-execution checks failed ${retryCount} times for ${unitId} — manual intervention required. Blocking findings: ${findings}. Fix the plan manually, then run /gsd auto to resume.`,
953
+ level: "error",
954
+ matchedRule: "planning → plan-slice",
955
+ };
956
+ }
941
957
  priorPreExecFailure = {
942
958
  blockingFindings: session.lastPreExecFailure.blockingFindings,
943
959
  verdictExcerpt: session.lastPreExecFailure.verdictExcerpt,
@@ -1320,8 +1320,11 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
1320
1320
 
1321
1321
  const strictMode = prefs?.enhanced_verification_strict === true;
1322
1322
 
1323
- // Run pre-execution checks
1324
- const result: PreExecutionResult = await runPreExecutionChecks(tasks, s.basePath);
1323
+ // Run pre-execution checks against the canonical project root. In
1324
+ // worktree isolation, s.basePath can point at a metadata-only worktree,
1325
+ // while source files remain under the project root.
1326
+ const preExecutionBasePath = s.canonicalProjectRoot;
1327
+ const result: PreExecutionResult = await runPreExecutionChecks(tasks, preExecutionBasePath);
1325
1328
 
1326
1329
  // Log summary to stderr in existing verification output format
1327
1330
  const emoji = result.status === "pass" ? "✅" : result.status === "warn" ? "⚠️" : "❌";
@@ -1338,12 +1341,12 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
1338
1341
  }
1339
1342
 
1340
1343
  // Write evidence JSON to slice artifacts directory
1341
- const slicePath = resolveSlicePath(s.basePath, mid, sid);
1344
+ const slicePath = resolveSlicePath(preExecutionBasePath, mid, sid);
1342
1345
  const evidenceFileName = `${sid}-PRE-EXEC-VERIFY.json`;
1343
1346
  let evidencePath = join(".gsd", "milestones", mid, "slices", sid, evidenceFileName);
1344
1347
  if (slicePath) {
1345
1348
  writePreExecutionEvidence(result, slicePath, mid, sid);
1346
- evidencePath = relative(s.basePath, join(slicePath, evidenceFileName)) || evidenceFileName;
1349
+ evidencePath = relative(preExecutionBasePath, join(slicePath, evidenceFileName)) || evidenceFileName;
1347
1350
  }
1348
1351
 
1349
1352
  if (uokFlags.gates) {
@@ -1398,6 +1401,9 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
1398
1401
  ),
1399
1402
  verdictExcerpt: `status=${result.status}; ${blockingCount} blocking issue${blockingCount === 1 ? "" : "s"} detected`,
1400
1403
  };
1404
+ // Track consecutive pre-exec failures per slice for loop detection.
1405
+ const retryKey = currentUnit.id;
1406
+ s.preExecRetryCount.set(retryKey, (s.preExecRetryCount.get(retryKey) ?? 0) + 1);
1401
1407
  preExecPauseNeeded = true;
1402
1408
  } else if (result.status === "warn") {
1403
1409
  ctx.ui.notify(
@@ -1414,10 +1420,18 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
1414
1420
  ),
1415
1421
  verdictExcerpt: `status=${result.status} (strict mode); ${warnChecks.length} warning${warnChecks.length === 1 ? "" : "s"} treated as blocking`,
1416
1422
  };
1423
+ const retryKey = currentUnit.id;
1424
+ s.preExecRetryCount.set(retryKey, (s.preExecRetryCount.get(retryKey) ?? 0) + 1);
1417
1425
  preExecPauseNeeded = true;
1418
1426
  }
1419
1427
  }
1420
1428
 
1429
+ // Reset the retry counter when checks pass — a successful re-plan
1430
+ // should not carry over a stale failure count into future slices.
1431
+ if (result.status === "pass") {
1432
+ s.preExecRetryCount.delete(currentUnit.id);
1433
+ }
1434
+
1421
1435
  debugLog("postUnitPostVerification", {
1422
1436
  phase: "pre-execution-checks",
1423
1437
  status: result.status,
@@ -45,12 +45,12 @@ import { classifyProject, type ProjectClassification } from "./detection.js";
45
45
  // ─── Preamble Cap ─────────────────────────────────────────────────────────────
46
46
 
47
47
  /**
48
- * Historical static ceiling for the preamble cap. Kept as an upper bound even
48
+ * Static ceiling for the preamble cap. Kept as an upper bound even
49
49
  * after context-window-aware sizing so large-window users don't suddenly see
50
- * 10× looser caps than before. Small-window users get a tighter cap derived
50
+ * 10× looser caps than needed. Small-window users get a tighter cap derived
51
51
  * from their configured executor window.
52
52
  */
53
- const MAX_PREAMBLE_CHARS = 30_000;
53
+ const MAX_PREAMBLE_CHARS = 20_000;
54
54
 
55
55
  /**
56
56
  * Resolve prompt budgets from the configured executor context window.
@@ -185,8 +185,8 @@ function formatCloseoutReviewInstructions(validationContent: string | null, vali
185
185
  }
186
186
 
187
187
  function capPreamble(preamble: string): string {
188
- // Cap inlined context at min(historical 30K ceiling, scaled inline budget).
189
- // The ceiling preserves pre-fix behavior for large-window users; the scaled
188
+ // Cap inlined context at min(static ceiling, scaled inline budget).
189
+ // The ceiling bounds repeated auto prompt payloads; the scaled
190
190
  // budget tightens the cap for small-window users whose true safe limit is
191
191
  // below 30K. `computeBudgets` allocates 40% of total chars to inline context.
192
192
  const budget = Math.min(MAX_PREAMBLE_CHARS, resolvePromptBudgets().inlineContextBudgetChars);
@@ -636,10 +636,83 @@ export async function buildSliceSummaryExcerpt(
636
636
  return lines.join("\n");
637
637
  } catch {
638
638
  // Defensive — any parse failure falls back to full inline.
639
- return `### ${sid} Summary\nSource: \`${relPath}\`\n\n${content.trim()}`;
639
+ return `### ${sid} Summary\nSource: \`${relPath}\`\n\n${capMalformedSummary(content, relPath)}`;
640
640
  }
641
641
  }
642
642
 
643
+ export async function buildTaskSummaryExcerpt(
644
+ absPath: string | null, relPath: string, tid: string, options?: { blocker?: boolean },
645
+ ): Promise<string> {
646
+ const label = options?.blocker ? "Blocker Task Summary" : "Task Summary";
647
+ const header = `### ${label}: ${tid} (excerpt)\nSource: \`${relPath}\``;
648
+ const content = absPath ? await loadFile(absPath) : null;
649
+ if (!content) {
650
+ return `${header}\n\n_(not found — file does not exist yet)_`;
651
+ }
652
+
653
+ try {
654
+ const s = parseSummary(content);
655
+ if (!s.frontmatter.id) {
656
+ return `### ${label}: ${tid}\nSource: \`${relPath}\`\n\n${capMalformedSummary(content, relPath)}`;
657
+ }
658
+
659
+ const lines: string[] = [header, ""];
660
+ if (s.title) lines.push(`**Title:** ${s.title}`);
661
+ if (s.oneLiner) lines.push(`**One-liner:** ${s.oneLiner}`);
662
+ if (s.frontmatter.verification_result) {
663
+ lines.push(`**Verification:** \`${s.frontmatter.verification_result}\``);
664
+ }
665
+ lines.push(`**Blocker discovered:** ${s.frontmatter.blocker_discovered ? "yes — read full summary if blocker details are insufficient" : "no"}`);
666
+ if (s.frontmatter.provides.length > 0) lines.push(`**Provides:** ${s.frontmatter.provides.slice(0, 4).join("; ")}`);
667
+ if (s.frontmatter.key_decisions.length > 0) lines.push(`**Key decisions:** ${s.frontmatter.key_decisions.slice(0, 4).join("; ")}`);
668
+ if (s.frontmatter.patterns_established.length > 0) lines.push(`**Patterns established:** ${s.frontmatter.patterns_established.slice(0, 4).join("; ")}`);
669
+ if (s.frontmatter.key_files.length > 0) {
670
+ const files = s.frontmatter.key_files.slice(0, 6);
671
+ const more = s.frontmatter.key_files.length > files.length ? ` (+${s.frontmatter.key_files.length - files.length} more)` : "";
672
+ lines.push(`**Key files:** ${files.join(", ")}${more}`);
673
+ }
674
+
675
+ const SECTION_CAP_CHARS = 500;
676
+ const capSection = (body: string): string => {
677
+ const trimmed = body.trim();
678
+ if (trimmed.length <= SECTION_CAP_CHARS) return trimmed;
679
+ return `${trimmed.slice(0, SECTION_CAP_CHARS)}\n… (truncated — see full \`${relPath}\`)`;
680
+ };
681
+
682
+ const verification = extractMarkdownSection(content, "Verification");
683
+ const diagnostics = extractMarkdownSection(content, "Diagnostics");
684
+ const knownIssues = extractMarkdownSection(content, "Known Issues");
685
+
686
+ if (verification && verification.trim()) {
687
+ lines.push("", "#### Verification", capSection(verification));
688
+ }
689
+ if (diagnostics && diagnostics.trim()) {
690
+ lines.push("", "#### Diagnostics", capSection(diagnostics));
691
+ }
692
+ if (s.deviations && s.deviations.trim()) {
693
+ lines.push("", "#### Deviations", capSection(s.deviations));
694
+ }
695
+ if (knownIssues && knownIssues.trim()) {
696
+ lines.push("", "#### Known issues", capSection(knownIssues));
697
+ }
698
+
699
+ lines.push(
700
+ "",
701
+ `> **On-demand:** read \`${relPath}\` only when this excerpt is absent/truncated or you need fuller blocker, implementation, or file-change evidence.`,
702
+ );
703
+ return lines.join("\n");
704
+ } catch {
705
+ return `### ${label}: ${tid}\nSource: \`${relPath}\`\n\n${capMalformedSummary(content, relPath)}`;
706
+ }
707
+ }
708
+
709
+ function capMalformedSummary(content: string, relPath: string): string {
710
+ const trimmed = content.trim();
711
+ const limit = 1_500;
712
+ if (trimmed.length <= limit) return trimmed;
713
+ return `${trimmed.slice(0, limit).trimEnd()}\n\n[Truncated malformed summary — read \`${relPath}\` for full details.]`;
714
+ }
715
+
643
716
  /**
644
717
  * Load and inline dependency slice summaries (full content, not just paths).
645
718
  */
@@ -914,7 +987,7 @@ export async function inlineKnowledgeScoped(
914
987
  * plan-milestone, complete-slice, complete-milestone, validate-milestone,
915
988
  * reassess-roadmap) previously injected the full KNOWLEDGE.md (~226KB for a
916
989
  * real project) on every invocation. This helper scopes by caller-supplied
917
- * keywords and caps the payload at `maxChars` (default 30,000 chars).
990
+ * keywords and caps the payload at `maxChars` (default 12,000 chars).
918
991
  *
919
992
  * Returns null when no KNOWLEDGE.md exists or no entries match any keyword.
920
993
  */
@@ -923,7 +996,7 @@ export async function inlineKnowledgeBudgeted(
923
996
  keywords: string[],
924
997
  options?: { maxChars?: number },
925
998
  ): Promise<string | null> {
926
- const DEFAULT_MAX_CHARS = 30_000;
999
+ const DEFAULT_MAX_CHARS = 12_000;
927
1000
  const HARD_MAX_CHARS = 100_000;
928
1001
  const raw = Number(options?.maxChars ?? DEFAULT_MAX_CHARS);
929
1002
  const maxChars = Number.isFinite(raw)
@@ -2085,8 +2158,17 @@ export async function buildPlanSlicePrompt(
2085
2158
  `The previous plan-slice attempt was blocked by pre-execution validation.\n` +
2086
2159
  `Gate verdict: ${verdictExcerpt}\n\n` +
2087
2160
  `Blocked references that must be resolved in this plan:\n${findingsList}\n\n` +
2088
- `Revise the plan so that every reference listed above is satisfied before execution begins. ` +
2089
- `Do not reproduce the same file paths, package names, or task ordering that caused these failures.`,
2161
+ `**How to fix each type of issue:**\n` +
2162
+ `- **"[file] X doesn't exist and isn't created by prior or same-task outputs"**: ` +
2163
+ `Either (a) add an earlier task that creates X on disk before the task that needs it, ` +
2164
+ `or (b) if this task IS the one that creates X, move X from inputs to expected_output. ` +
2165
+ `Do NOT put X in a task's expected_output if that task only reads or verifies X — only tasks that actually write X to disk should list it in expected_output.\n` +
2166
+ `- **"[file] X: Task T_early reads X but it's created by task T_late (sequence violation)"**: ` +
2167
+ `Either (a) reorder tasks so T_late (the creator) runs before T_early (the reader), ` +
2168
+ `or (b) if T_late doesn't actually create X (it only reads/tests it), remove X from T_late's expected_output entirely.\n` +
2169
+ `- **"[package] P not found on npm"**: Either remove the npm install for P, or use the correct package name.\n\n` +
2170
+ `Every file listed in a task's inputs must either exist on disk already or appear in an earlier task's expected_output. ` +
2171
+ `A task's expected_output must only list files it actually writes to disk.`,
2090
2172
  );
2091
2173
  }
2092
2174
  return renderSlicePrompt({
@@ -2369,10 +2451,9 @@ export async function buildCompleteSlicePrompt(
2369
2451
  const blocks: string[] = [];
2370
2452
  for (const file of summaryFiles) {
2371
2453
  const absPath = join(tDir, file);
2372
- const content = await loadFile(absPath);
2373
- if (!content) continue;
2374
2454
  const relPath = `${sRel}/tasks/${file}`;
2375
- blocks.push(`### Task Summary: ${file.replace(/-SUMMARY\.md$/i, "")}\nSource: \`${relPath}\`\n\n${content.trim()}`);
2455
+ const taskId = file.replace(/-SUMMARY\.md$/i, "");
2456
+ blocks.push(await buildTaskSummaryExcerpt(absPath, relPath, taskId));
2376
2457
  }
2377
2458
  return blocks.length > 0 ? blocks.join("\n\n---\n\n") : null;
2378
2459
  }
@@ -2749,7 +2830,7 @@ export async function buildReplanSlicePrompt(
2749
2830
  const relPath = `${sRel}/tasks/${file}`;
2750
2831
  if (summary.frontmatter.blocker_discovered) {
2751
2832
  blockerTaskId = summary.frontmatter.id || file.replace(/-SUMMARY\.md$/i, "");
2752
- inlined.push(`### Blocker Task Summary: ${blockerTaskId}\nSource: \`${relPath}\`\n\n${content.trim()}`);
2833
+ inlined.push(await buildTaskSummaryExcerpt(absPath, relPath, blockerTaskId, { blocker: true }));
2753
2834
  }
2754
2835
  }
2755
2836
  }