gsd-pi 2.37.1 → 2.38.0-dev.29edcdc

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/README.md +1 -1
  2. package/dist/app-paths.js +1 -1
  3. package/dist/cli.js +9 -0
  4. package/dist/extension-discovery.d.ts +5 -3
  5. package/dist/extension-discovery.js +14 -9
  6. package/dist/extension-registry.js +2 -2
  7. package/dist/onboarding.js +1 -0
  8. package/dist/remote-questions-config.js +2 -2
  9. package/dist/resource-loader.js +34 -1
  10. package/dist/resources/extensions/browser-tools/package.json +3 -1
  11. package/dist/resources/extensions/cmux/index.js +55 -1
  12. package/dist/resources/extensions/context7/package.json +1 -1
  13. package/dist/resources/extensions/env-utils.js +29 -0
  14. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  15. package/dist/resources/extensions/github-sync/cli.js +284 -0
  16. package/dist/resources/extensions/github-sync/index.js +73 -0
  17. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  18. package/dist/resources/extensions/github-sync/sync.js +424 -0
  19. package/dist/resources/extensions/github-sync/templates.js +118 -0
  20. package/dist/resources/extensions/github-sync/types.js +7 -0
  21. package/dist/resources/extensions/google-search/package.json +3 -1
  22. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  23. package/dist/resources/extensions/gsd/auto-dispatch.js +75 -10
  24. package/dist/resources/extensions/gsd/auto-loop.js +597 -588
  25. package/dist/resources/extensions/gsd/auto-post-unit.js +111 -68
  26. package/dist/resources/extensions/gsd/auto-prompts.js +114 -45
  27. package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
  28. package/dist/resources/extensions/gsd/auto-start.js +13 -2
  29. package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
  30. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  31. package/dist/resources/extensions/gsd/auto.js +143 -96
  32. package/dist/resources/extensions/gsd/captures.js +9 -1
  33. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  34. package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
  35. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  36. package/dist/resources/extensions/gsd/commands.js +24 -3
  37. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  38. package/dist/resources/extensions/gsd/detection.js +1 -2
  39. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  40. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  41. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  42. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  43. package/dist/resources/extensions/gsd/doctor-providers.js +62 -12
  44. package/dist/resources/extensions/gsd/doctor.js +204 -12
  45. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  46. package/dist/resources/extensions/gsd/export.js +1 -1
  47. package/dist/resources/extensions/gsd/files.js +47 -2
  48. package/dist/resources/extensions/gsd/forensics.js +1 -1
  49. package/dist/resources/extensions/gsd/git-service.js +15 -12
  50. package/dist/resources/extensions/gsd/guided-flow.js +82 -32
  51. package/dist/resources/extensions/gsd/index.js +24 -20
  52. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  53. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  54. package/dist/resources/extensions/gsd/observability-validator.js +24 -0
  55. package/dist/resources/extensions/gsd/package.json +1 -1
  56. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  57. package/dist/resources/extensions/gsd/preferences-types.js +3 -2
  58. package/dist/resources/extensions/gsd/preferences-validation.js +101 -11
  59. package/dist/resources/extensions/gsd/preferences.js +8 -5
  60. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  61. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
  62. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  63. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  64. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  65. package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  66. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  67. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
  68. package/dist/resources/extensions/gsd/prompts/run-uat.md +27 -10
  69. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  70. package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
  71. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  72. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  73. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  74. package/dist/resources/extensions/gsd/state.js +1 -1
  75. package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
  76. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  77. package/dist/resources/extensions/gsd/worktree.js +35 -16
  78. package/dist/resources/extensions/mcp-client/index.js +14 -1
  79. package/dist/resources/extensions/remote-questions/status.js +2 -1
  80. package/dist/resources/extensions/remote-questions/store.js +2 -1
  81. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  82. package/dist/resources/extensions/subagent/index.js +12 -3
  83. package/dist/resources/extensions/subagent/isolation.js +2 -1
  84. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  85. package/dist/resources/extensions/universal-config/package.json +1 -1
  86. package/dist/welcome-screen.d.ts +12 -0
  87. package/dist/welcome-screen.js +53 -0
  88. package/package.json +2 -1
  89. package/packages/pi-ai/dist/env-api-keys.js +13 -0
  90. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  91. package/packages/pi-ai/dist/models.generated.d.ts +172 -0
  92. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  93. package/packages/pi-ai/dist/models.generated.js +172 -0
  94. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  95. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
  96. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
  97. package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
  98. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
  99. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
  100. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
  101. package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
  102. package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
  103. package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
  104. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/anthropic.js +47 -764
  106. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
  108. package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
  109. package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
  110. package/packages/pi-ai/dist/types.d.ts +2 -2
  111. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  112. package/packages/pi-ai/dist/types.js.map +1 -1
  113. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  114. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  115. package/packages/pi-ai/package.json +1 -0
  116. package/packages/pi-ai/src/env-api-keys.ts +14 -0
  117. package/packages/pi-ai/src/models.generated.ts +172 -0
  118. package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
  119. package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
  120. package/packages/pi-ai/src/providers/anthropic.ts +76 -868
  121. package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
  122. package/packages/pi-ai/src/types.ts +2 -0
  123. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  124. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  126. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  129. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  132. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  133. package/packages/pi-coding-agent/package.json +1 -1
  134. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  135. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  136. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  137. package/pkg/package.json +1 -1
  138. package/src/resources/extensions/cmux/index.ts +57 -1
  139. package/src/resources/extensions/env-utils.ts +31 -0
  140. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  141. package/src/resources/extensions/github-sync/cli.ts +364 -0
  142. package/src/resources/extensions/github-sync/index.ts +93 -0
  143. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  144. package/src/resources/extensions/github-sync/sync.ts +556 -0
  145. package/src/resources/extensions/github-sync/templates.ts +183 -0
  146. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  147. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  148. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  149. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  150. package/src/resources/extensions/github-sync/types.ts +47 -0
  151. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  152. package/src/resources/extensions/gsd/auto-dispatch.ts +100 -9
  153. package/src/resources/extensions/gsd/auto-loop.ts +484 -546
  154. package/src/resources/extensions/gsd/auto-post-unit.ts +92 -42
  155. package/src/resources/extensions/gsd/auto-prompts.ts +150 -48
  156. package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
  157. package/src/resources/extensions/gsd/auto-start.ts +18 -2
  158. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  159. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  160. package/src/resources/extensions/gsd/auto.ts +139 -101
  161. package/src/resources/extensions/gsd/captures.ts +10 -1
  162. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  163. package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
  164. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  165. package/src/resources/extensions/gsd/commands.ts +26 -4
  166. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  167. package/src/resources/extensions/gsd/detection.ts +2 -2
  168. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  169. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  170. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  171. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  172. package/src/resources/extensions/gsd/doctor-providers.ts +64 -10
  173. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  174. package/src/resources/extensions/gsd/doctor.ts +199 -14
  175. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  176. package/src/resources/extensions/gsd/export.ts +1 -1
  177. package/src/resources/extensions/gsd/files.ts +50 -3
  178. package/src/resources/extensions/gsd/forensics.ts +1 -1
  179. package/src/resources/extensions/gsd/git-service.ts +20 -10
  180. package/src/resources/extensions/gsd/guided-flow.ts +110 -38
  181. package/src/resources/extensions/gsd/index.ts +24 -17
  182. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  183. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  184. package/src/resources/extensions/gsd/observability-validator.ts +27 -0
  185. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  186. package/src/resources/extensions/gsd/preferences-types.ts +9 -5
  187. package/src/resources/extensions/gsd/preferences-validation.ts +92 -11
  188. package/src/resources/extensions/gsd/preferences.ts +8 -5
  189. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  190. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
  191. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  192. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  193. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  194. package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  195. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  196. package/src/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
  197. package/src/resources/extensions/gsd/prompts/run-uat.md +27 -10
  198. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  199. package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
  200. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  201. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  202. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  203. package/src/resources/extensions/gsd/state.ts +1 -1
  204. package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
  205. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  206. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
  207. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  208. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  209. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +191 -3
  210. package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
  211. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  212. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  213. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
  214. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
  215. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  216. package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
  217. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  218. package/src/resources/extensions/gsd/types.ts +43 -1
  219. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  220. package/src/resources/extensions/gsd/worktree.ts +35 -15
  221. package/src/resources/extensions/mcp-client/index.ts +17 -1
  222. package/src/resources/extensions/remote-questions/status.ts +3 -1
  223. package/src/resources/extensions/remote-questions/store.ts +3 -1
  224. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  225. package/src/resources/extensions/subagent/index.ts +12 -3
  226. package/src/resources/extensions/subagent/isolation.ts +3 -1
  227. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  228. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  229. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  230. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  231. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  232. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  233. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  234. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  235. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  236. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  237. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  238. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  239. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -33,7 +33,6 @@ import { writeUnitRuntimeRecord, clearUnitRuntimeRecord } from "./unit-runtime.j
33
33
  import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
34
34
  import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js";
35
35
  import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
36
- import { resetRewriteCircuitBreaker } from "./auto-dispatch.js";
37
36
  import { isDbAvailable } from "./gsd-db.js";
38
37
  import { consumeSignal } from "./session-status-io.js";
39
38
  import {
@@ -56,6 +55,13 @@ import { join } from "node:path";
56
55
  /** Throttle STATE.md rebuilds — at most once per 30 seconds */
57
56
  const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
58
57
 
58
+ export interface PreVerificationOpts {
59
+ skipSettleDelay?: boolean;
60
+ skipDoctor?: boolean;
61
+ skipStateRebuild?: boolean;
62
+ skipWorktreeSync?: boolean;
63
+ }
64
+
59
65
  export interface PostUnitContext {
60
66
  s: AutoSession;
61
67
  ctx: ExtensionContext;
@@ -73,7 +79,7 @@ export interface PostUnitContext {
73
79
  *
74
80
  * Returns "dispatched" if a signal caused stop/pause, "continue" to proceed.
75
81
  */
76
- export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"dispatched" | "continue"> {
82
+ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreVerificationOpts): Promise<"dispatched" | "continue"> {
77
83
  const { s, ctx, pi, buildSnapshotOpts, stopAuto, pauseAuto } = pctx;
78
84
 
79
85
  // ── Parallel worker signal check ──
@@ -95,8 +101,10 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
95
101
  // Invalidate all caches
96
102
  invalidateAllCaches();
97
103
 
98
- // Small delay to let files settle
99
- await new Promise(r => setTimeout(r, 500));
104
+ // Small delay to let files settle (skipped for sidecars where latency matters more)
105
+ if (!opts?.skipSettleDelay) {
106
+ await new Promise(r => setTimeout(r, 100));
107
+ }
100
108
 
101
109
  // Auto-commit
102
110
  if (s.currentUnit) {
@@ -113,15 +121,25 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
113
121
  const summaryContent = await loadFile(summaryPath);
114
122
  if (summaryContent) {
115
123
  const summary = parseSummary(summaryContent);
124
+ // Look up GitHub issue number for commit linking
125
+ let ghIssueNumber: number | undefined;
126
+ try {
127
+ const { getTaskIssueNumberForCommit } = await import("../github-sync/sync.js");
128
+ ghIssueNumber = getTaskIssueNumberForCommit(s.basePath, mid, sid, tid) ?? undefined;
129
+ } catch {
130
+ // GitHub sync not available — skip
131
+ }
132
+
116
133
  taskContext = {
117
134
  taskId: `${sid}/${tid}`,
118
135
  taskTitle: summary.title?.replace(/^T\d+:\s*/, "") || tid,
119
136
  oneLiner: summary.oneLiner || undefined,
120
137
  keyFiles: summary.frontmatter.key_files?.filter(f => !f.includes("{{")) || undefined,
138
+ issueNumber: ghIssueNumber,
121
139
  };
122
140
  }
123
- } catch {
124
- // Non-fatal
141
+ } catch (e) {
142
+ debugLog("postUnit", { phase: "task-summary-parse", error: String(e) });
125
143
  }
126
144
  }
127
145
  }
@@ -131,12 +149,20 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
131
149
  if (commitMsg) {
132
150
  ctx.ui.notify(`Committed: ${commitMsg.split("\n")[0]}`, "info");
133
151
  }
134
- } catch {
135
- // Non-fatal
152
+ } catch (e) {
153
+ debugLog("postUnit", { phase: "auto-commit", error: String(e) });
136
154
  }
137
155
 
138
- // Doctor: fix mechanical bookkeeping
156
+ // GitHub sync (non-blocking, opt-in)
139
157
  try {
158
+ const { runGitHubSync } = await import("../github-sync/sync.js");
159
+ await runGitHubSync(s.basePath, s.currentUnit.type, s.currentUnit.id);
160
+ } catch (e) {
161
+ debugLog("postUnit", { phase: "github-sync", error: String(e) });
162
+ }
163
+
164
+ // Doctor: fix mechanical bookkeeping (skipped for lightweight sidecars)
165
+ if (!opts?.skipDoctor) try {
140
166
  const scopeParts = s.currentUnit.id.split("/").slice(0, 2);
141
167
  const doctorScope = scopeParts.join("/");
142
168
  const sliceTerminalUnits = new Set(["complete-slice", "run-uat"]);
@@ -146,13 +172,20 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
146
172
  ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
147
173
  }
148
174
 
149
- // Proactive health tracking
150
- const summary = summarizeDoctorIssues(report.issues);
175
+ // Proactive health tracking — filter to current milestone to avoid
176
+ // cross-milestone stale errors inflating the escalation counter
177
+ const currentMilestoneId = s.currentUnit.id.split("/")[0];
178
+ const milestoneIssues = currentMilestoneId
179
+ ? report.issues.filter(i =>
180
+ i.unitId === currentMilestoneId ||
181
+ i.unitId.startsWith(`${currentMilestoneId}/`))
182
+ : report.issues;
183
+ const summary = summarizeDoctorIssues(milestoneIssues);
151
184
  recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
152
185
 
153
186
  // Check if we should escalate to LLM-assisted heal
154
187
  if (summary.errors > 0) {
155
- const unresolvedErrors = report.issues
188
+ const unresolvedErrors = milestoneIssues
156
189
  .filter(i => i.severity === "error" && !i.fixable)
157
190
  .map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
158
191
  const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
@@ -168,24 +201,27 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
168
201
  const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
169
202
  const structuredIssues = formatDoctorIssuesForPrompt(actionable);
170
203
  dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
171
- } catch {
172
- // Non-fatal
204
+ return "dispatched";
205
+ } catch (e) {
206
+ debugLog("postUnit", { phase: "doctor-heal-dispatch", error: String(e) });
173
207
  }
174
208
  }
175
209
  }
176
- } catch {
177
- // Non-fatal
210
+ } catch (e) {
211
+ debugLog("postUnit", { phase: "doctor", error: String(e) });
178
212
  }
179
213
 
180
- // Throttled STATE.md rebuild
181
- const now = Date.now();
182
- if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
183
- try {
184
- await rebuildState(s.basePath);
185
- s.lastStateRebuildAt = now;
186
- autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
187
- } catch {
188
- // Non-fatal
214
+ // Throttled STATE.md rebuild (skipped for lightweight sidecars)
215
+ if (!opts?.skipStateRebuild) {
216
+ const now = Date.now();
217
+ if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
218
+ try {
219
+ await rebuildState(s.basePath);
220
+ s.lastStateRebuildAt = now;
221
+ autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
222
+ } catch (e) {
223
+ debugLog("postUnit", { phase: "state-rebuild", error: String(e) });
224
+ }
189
225
  }
190
226
  }
191
227
 
@@ -193,16 +229,16 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
193
229
  try {
194
230
  const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
195
231
  pruneDeadProcesses();
196
- } catch {
197
- // Non-fatal
232
+ } catch (e) {
233
+ debugLog("postUnit", { phase: "prune-bg-shell", error: String(e) });
198
234
  }
199
235
 
200
- // Sync worktree state back to project root
201
- if (s.originalBasePath && s.originalBasePath !== s.basePath) {
236
+ // Sync worktree state back to project root (skipped for lightweight sidecars)
237
+ if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath !== s.basePath) {
202
238
  try {
203
239
  syncStateToProjectRoot(s.basePath, s.originalBasePath, s.currentMilestoneId);
204
- } catch {
205
- // Non-fatal
240
+ } catch (e) {
241
+ debugLog("postUnit", { phase: "worktree-sync", error: String(e) });
206
242
  }
207
243
  }
208
244
 
@@ -210,10 +246,24 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
210
246
  if (s.currentUnit.type === "rewrite-docs") {
211
247
  try {
212
248
  await resolveAllOverrides(s.basePath);
213
- resetRewriteCircuitBreaker();
249
+ s.rewriteAttemptCount = 0;
214
250
  ctx.ui.notify("Override(s) resolved — rewrite-docs completed.", "info");
215
- } catch {
216
- // Non-fatal
251
+ } catch (e) {
252
+ debugLog("postUnit", { phase: "rewrite-docs-resolve", error: String(e) });
253
+ }
254
+ }
255
+
256
+ // Reactive state cleanup on slice completion
257
+ if (s.currentUnit.type === "complete-slice") {
258
+ try {
259
+ const parts = s.currentUnit.id.split("/");
260
+ const [mid, sid] = parts;
261
+ if (mid && sid) {
262
+ const { clearReactiveState } = await import("./reactive-graph.js");
263
+ clearReactiveState(s.basePath, mid, sid);
264
+ }
265
+ } catch (e) {
266
+ debugLog("postUnit", { phase: "reactive-state-cleanup", error: String(e) });
217
267
  }
218
268
  }
219
269
 
@@ -266,8 +316,8 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
266
316
  if (triggerArtifactVerified) {
267
317
  invalidateAllCaches();
268
318
  }
269
- } catch {
270
- // Non-fatal
319
+ } catch (e) {
320
+ debugLog("postUnit", { phase: "artifact-verify", error: String(e) });
271
321
  }
272
322
  } else {
273
323
  // Hook unit completed — finalize its runtime record
@@ -278,8 +328,8 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
278
328
  lastProgressKind: "hook-completed",
279
329
  });
280
330
  clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
281
- } catch {
282
- // Non-fatal
331
+ } catch (e) {
332
+ debugLog("postUnit", { phase: "hook-finalize", error: String(e) });
283
333
  }
284
334
  }
285
335
  }
@@ -415,8 +465,8 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
415
465
  }
416
466
  }
417
467
  }
418
- } catch {
419
- // Triage check failure is non-fatal
468
+ } catch (e) {
469
+ debugLog("postUnit", { phase: "triage-check", error: String(e) });
420
470
  }
421
471
  }
422
472
 
@@ -461,8 +511,8 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
461
511
  );
462
512
 
463
513
  return "continue";
464
- } catch {
465
- // Non-fatal proceed to normal dispatch
514
+ } catch (e) {
515
+ debugLog("postUnit", { phase: "quick-task-dispatch", error: String(e) });
466
516
  }
467
517
  }
468
518
 
@@ -20,11 +20,17 @@ import type { GSDState, InlineLevel } from "./types.js";
20
20
  import type { GSDPreferences } from "./preferences.js";
21
21
  import { join } from "node:path";
22
22
  import { existsSync } from "node:fs";
23
- import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
24
- import { compressToTarget } from "./prompt-compressor.js";
25
- import { distillSummaries } from "./summary-distiller.js";
23
+ import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
26
24
  import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
27
- import { chunkByRelevance, formatChunks } from "./semantic-chunker.js";
25
+
26
+ // ─── Preamble Cap ─────────────────────────────────────────────────────────────
27
+
28
+ const MAX_PREAMBLE_CHARS = 30_000;
29
+
30
+ function capPreamble(preamble: string): string {
31
+ if (preamble.length <= MAX_PREAMBLE_CHARS) return preamble;
32
+ return truncateAtSectionBoundary(preamble, MAX_PREAMBLE_CHARS).content;
33
+ }
28
34
 
29
35
  // ─── Executor Constraints ─────────────────────────────────────────────────────
30
36
 
@@ -159,16 +165,9 @@ export async function inlineFileSmart(
159
165
  return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
160
166
  }
161
167
 
162
- // Use semantic chunking for large files
163
- const result = chunkByRelevance(content, query, { maxChunks: 5, minScore: 0.05 });
164
-
165
- // If chunking didn't save much (< 20%), just include full content
166
- if (result.savingsPercent < 20) {
167
- return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
168
- }
169
-
170
- const formatted = formatChunks(result, relPath);
171
- return `### ${label} (${result.omittedChunks} sections omitted for relevance)\nSource: \`${relPath}\`\n\n${formatted}`;
168
+ // For large files, truncate at section boundary
169
+ const truncated = truncateAtSectionBoundary(content, threshold).content;
170
+ return `### ${label}\nSource: \`${relPath}\`\n\n${truncated}`;
172
171
  }
173
172
 
174
173
  /**
@@ -202,21 +201,6 @@ export async function inlineDependencySummaries(
202
201
 
203
202
  const result = sections.join("\n\n");
204
203
  if (budgetChars !== undefined && result.length > budgetChars) {
205
- // For 3+ summaries, try distillation first (preserves more information)
206
- if (sections.length >= 3) {
207
- const rawSummaries = sections.map(s => {
208
- // Extract content after the header line
209
- const lines = s.split("\n");
210
- const contentStart = lines.findIndex(l => l.startsWith("Source:"));
211
- return contentStart >= 0 ? lines.slice(contentStart + 1).join("\n").trim() : s;
212
- });
213
- const distilled = distillSummaries(rawSummaries, budgetChars);
214
- if (distilled.content.length <= budgetChars) {
215
- return distilled.content;
216
- }
217
- }
218
- // Fall back to section-boundary truncation
219
- const { truncateAtSectionBoundary } = await import("./context-budget.js");
220
204
  return truncateAtSectionBoundary(result, budgetChars).content;
221
205
  }
222
206
  return result;
@@ -485,6 +469,41 @@ export async function getPriorTaskSummaryPaths(
485
469
  .map(f => `${sRel}/tasks/${f}`);
486
470
  }
487
471
 
472
+ /**
473
+ * Get carry-forward summary paths scoped to a task's derived dependencies.
474
+ *
475
+ * Instead of all prior tasks (order-based), returns only summaries for task
476
+ * IDs in `dependsOn`. Used by reactive-execute to give each subagent only
477
+ * the context it actually needs — not sibling tasks from a parallel batch.
478
+ *
479
+ * Falls back to order-based when dependsOn is empty (root tasks still get
480
+ * any available prior summaries for continuity).
481
+ */
482
+ export async function getDependencyTaskSummaryPaths(
483
+ mid: string, sid: string, currentTid: string,
484
+ dependsOn: string[], base: string,
485
+ ): Promise<string[]> {
486
+ // If no dependencies, fall back to order-based for root tasks
487
+ if (dependsOn.length === 0) {
488
+ return getPriorTaskSummaryPaths(mid, sid, currentTid, base);
489
+ }
490
+
491
+ const tDir = resolveTasksDir(base, mid, sid);
492
+ if (!tDir) return [];
493
+
494
+ const summaryFiles = resolveTaskFiles(tDir, "SUMMARY");
495
+ const sRel = relSlicePath(base, mid, sid);
496
+ const depSet = new Set(dependsOn.map((d) => d.toUpperCase()));
497
+
498
+ return summaryFiles
499
+ .filter((f) => {
500
+ // Extract task ID from filename: "T02-SUMMARY.md" → "T02"
501
+ const tid = f.replace(/-SUMMARY\.md$/i, "").toUpperCase();
502
+ return depSet.has(tid);
503
+ })
504
+ .map((f) => `${sRel}/tasks/${f}`);
505
+ }
506
+
488
507
  // ─── Adaptive Replanning Checks ────────────────────────────────────────────
489
508
 
490
509
  /**
@@ -599,7 +618,7 @@ export async function buildResearchMilestonePrompt(mid: string, midTitle: string
599
618
  if (knowledgeInlineRM) inlined.push(knowledgeInlineRM);
600
619
  inlined.push(inlineTemplate("research", "Research"));
601
620
 
602
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
621
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
603
622
 
604
623
  const outputRelPath = relMilestoneFile(base, mid, "RESEARCH");
605
624
  return loadPrompt("research-milestone", {
@@ -649,7 +668,7 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
649
668
  inlined.push(inlineTemplate("task-plan", "Task Plan"));
650
669
  }
651
670
 
652
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
671
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
653
672
 
654
673
  const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
655
674
  const researchOutputPath = join(base, relMilestoneFile(base, mid, "RESEARCH"));
@@ -698,7 +717,7 @@ export async function buildResearchSlicePrompt(
698
717
  const overridesInline = formatOverridesSection(activeOverrides);
699
718
  if (overridesInline) inlined.unshift(overridesInline);
700
719
 
701
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
720
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
702
721
 
703
722
  const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH");
704
723
  return loadPrompt("research-slice", {
@@ -746,7 +765,7 @@ export async function buildPlanSlicePrompt(
746
765
  const planOverridesInline = formatOverridesSection(planActiveOverrides);
747
766
  if (planOverridesInline) inlined.unshift(planOverridesInline);
748
767
 
749
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
768
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
750
769
 
751
770
  // Build executor context constraints from the budget engine
752
771
  const executorContextConstraints = formatExecutorConstraints();
@@ -772,13 +791,24 @@ export async function buildPlanSlicePrompt(
772
791
  });
773
792
  }
774
793
 
794
+ /** Options for customizing execute-task prompt construction. */
795
+ export interface ExecuteTaskPromptOptions {
796
+ level?: InlineLevel;
797
+ /** Override carry-forward paths (dependency-based instead of order-based). */
798
+ carryForwardPaths?: string[];
799
+ }
800
+
775
801
  export async function buildExecuteTaskPrompt(
776
802
  mid: string, sid: string, sTitle: string,
777
- tid: string, tTitle: string, base: string, level?: InlineLevel,
803
+ tid: string, tTitle: string, base: string,
804
+ level?: InlineLevel | ExecuteTaskPromptOptions,
778
805
  ): Promise<string> {
779
- const inlineLevel = level ?? resolveInlineLevel();
806
+ const opts: ExecuteTaskPromptOptions = typeof level === "object" && level !== null && !Array.isArray(level)
807
+ ? level
808
+ : { level: level as InlineLevel | undefined };
809
+ const inlineLevel = opts.level ?? resolveInlineLevel();
780
810
 
781
- const priorSummaries = await getPriorTaskSummaryPaths(mid, sid, tid, base);
811
+ const priorSummaries = opts.carryForwardPaths ?? await getPriorTaskSummaryPaths(mid, sid, tid, base);
782
812
  const priorLines = priorSummaries.length > 0
783
813
  ? priorSummaries.map(p => `- \`${p}\``).join("\n")
784
814
  : "- (no prior tasks)";
@@ -854,15 +884,11 @@ export async function buildExecuteTaskPrompt(
854
884
  const budgets = computeBudgets(contextWindow);
855
885
  const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
856
886
 
857
- // Compress carry-forward section when it exceeds 40% of inline context budget.
858
- // Only compress when compression_strategy is "compress" (budget/balanced profiles).
887
+ // Truncate carry-forward section when it exceeds 40% of inline context budget.
859
888
  const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
860
889
  let finalCarryForward = carryForwardSection;
861
890
  if (carryForwardSection.length > carryForwardBudget) {
862
- const { resolveCompressionStrategy } = await import("./preferences.js");
863
- if (resolveCompressionStrategy() === "compress") {
864
- finalCarryForward = compressToTarget(carryForwardSection, carryForwardBudget).content;
865
- }
891
+ finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
866
892
  }
867
893
 
868
894
  return loadPrompt("execute-task", {
@@ -925,7 +951,7 @@ export async function buildCompleteSlicePrompt(
925
951
  const completeOverridesInline = formatOverridesSection(completeActiveOverrides);
926
952
  if (completeOverridesInline) inlined.unshift(completeOverridesInline);
927
953
 
928
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
954
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
929
955
 
930
956
  const sliceRel = relSlicePath(base, mid, sid);
931
957
  const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
@@ -984,7 +1010,7 @@ export async function buildCompleteMilestonePrompt(
984
1010
  if (contextInline) inlined.push(contextInline);
985
1011
  inlined.push(inlineTemplate("milestone-summary", "Milestone Summary"));
986
1012
 
987
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
1013
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
988
1014
 
989
1015
  const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`);
990
1016
 
@@ -1055,7 +1081,7 @@ export async function buildValidateMilestonePrompt(
1055
1081
  const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
1056
1082
  if (contextInline) inlined.push(contextInline);
1057
1083
 
1058
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
1084
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
1059
1085
 
1060
1086
  const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
1061
1087
  const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
@@ -1109,7 +1135,7 @@ export async function buildReplanSlicePrompt(
1109
1135
  const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
1110
1136
  if (replanOverridesInline) inlined.unshift(replanOverridesInline);
1111
1137
 
1112
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
1138
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
1113
1139
 
1114
1140
  const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`);
1115
1141
 
@@ -1157,7 +1183,7 @@ export async function buildRunUatPrompt(
1157
1183
  const projectInline = await inlineProjectFromDb(base);
1158
1184
  if (projectInline) inlined.push(projectInline);
1159
1185
 
1160
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
1186
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
1161
1187
 
1162
1188
  const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT"));
1163
1189
  const uatType = extractUatType(uatContent) ?? "human-experience";
@@ -1196,7 +1222,7 @@ export async function buildReassessRoadmapPrompt(
1196
1222
  const knowledgeInlineRA = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
1197
1223
  if (knowledgeInlineRA) inlined.push(knowledgeInlineRA);
1198
1224
 
1199
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
1225
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
1200
1226
 
1201
1227
  const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT"));
1202
1228
 
@@ -1234,6 +1260,82 @@ export async function buildReassessRoadmapPrompt(
1234
1260
  });
1235
1261
  }
1236
1262
 
1263
+ // ─── Reactive Execute Prompt ──────────────────────────────────────────────
1264
+
1265
+ export async function buildReactiveExecutePrompt(
1266
+ mid: string, midTitle: string, sid: string, sTitle: string,
1267
+ readyTaskIds: string[], base: string,
1268
+ ): Promise<string> {
1269
+ const { loadSliceTaskIO, deriveTaskGraph, graphMetrics } = await import("./reactive-graph.js");
1270
+
1271
+ // Build graph for context
1272
+ const taskIO = await loadSliceTaskIO(base, mid, sid);
1273
+ const graph = deriveTaskGraph(taskIO);
1274
+ const metrics = graphMetrics(graph);
1275
+
1276
+ // Build graph context section
1277
+ const graphLines: string[] = [];
1278
+ for (const node of graph) {
1279
+ const status = node.done ? "✅ done" : readyTaskIds.includes(node.id) ? "🟢 ready" : "⏳ waiting";
1280
+ const deps = node.dependsOn.length > 0 ? ` (depends on: ${node.dependsOn.join(", ")})` : "";
1281
+ graphLines.push(`- **${node.id}: ${node.title}** — ${status}${deps}`);
1282
+ if (node.outputFiles.length > 0) {
1283
+ graphLines.push(` - Outputs: ${node.outputFiles.map(f => `\`${f}\``).join(", ")}`);
1284
+ }
1285
+ }
1286
+ const graphContext = [
1287
+ `Tasks: ${metrics.taskCount}, Edges: ${metrics.edgeCount}, Ready: ${metrics.readySetSize}`,
1288
+ "",
1289
+ ...graphLines,
1290
+ ].join("\n");
1291
+
1292
+ // Build individual subagent prompts for each ready task
1293
+ const subagentSections: string[] = [];
1294
+ const readyTaskListLines: string[] = [];
1295
+
1296
+ for (const tid of readyTaskIds) {
1297
+ const node = graph.find((n) => n.id === tid);
1298
+ const tTitle = node?.title ?? tid;
1299
+ readyTaskListLines.push(`- **${tid}: ${tTitle}**`);
1300
+
1301
+ // Build dependency-scoped carry-forward paths for this task
1302
+ const depPaths = await getDependencyTaskSummaryPaths(
1303
+ mid, sid, tid, node?.dependsOn ?? [], base,
1304
+ );
1305
+
1306
+ // Build a full execute-task prompt with dependency-based carry-forward
1307
+ const taskPrompt = await buildExecuteTaskPrompt(
1308
+ mid, sid, sTitle, tid, tTitle, base,
1309
+ { carryForwardPaths: depPaths },
1310
+ );
1311
+
1312
+ subagentSections.push([
1313
+ `### ${tid}: ${tTitle}`,
1314
+ "",
1315
+ "Use this as the prompt for a `subagent` call:",
1316
+ "",
1317
+ "```",
1318
+ taskPrompt,
1319
+ "```",
1320
+ ].join("\n"));
1321
+ }
1322
+
1323
+ const inlinedTemplates = inlineTemplate("task-summary", "Task Summary");
1324
+
1325
+ return loadPrompt("reactive-execute", {
1326
+ workingDirectory: base,
1327
+ milestoneId: mid,
1328
+ milestoneTitle: midTitle,
1329
+ sliceId: sid,
1330
+ sliceTitle: sTitle,
1331
+ graphContext,
1332
+ readyTaskCount: String(readyTaskIds.length),
1333
+ readyTaskList: readyTaskListLines.join("\n"),
1334
+ subagentPrompts: subagentSections.join("\n\n---\n\n"),
1335
+ inlinedTemplates,
1336
+ });
1337
+ }
1338
+
1237
1339
  export async function buildRewriteDocsPrompt(
1238
1340
  mid: string, midTitle: string,
1239
1341
  activeSlice: { id: string; title: string } | null,
@@ -26,6 +26,7 @@ import {
26
26
  resolveSlicePath,
27
27
  resolveSliceFile,
28
28
  resolveTasksDir,
29
+ resolveTaskFiles,
29
30
  relMilestoneFile,
30
31
  relSliceFile,
31
32
  relSlicePath,
@@ -110,6 +111,9 @@ export function resolveExpectedArtifactPath(
110
111
  }
111
112
  case "rewrite-docs":
112
113
  return null;
114
+ case "reactive-execute":
115
+ // Reactive execute produces multiple task summaries — verified separately
116
+ return null;
113
117
  default:
114
118
  return null;
115
119
  }
@@ -148,6 +152,44 @@ export function verifyExpectedArtifact(
148
152
  return !content.includes("**Scope:** active");
149
153
  }
150
154
 
155
+ // Reactive-execute: verify that each dispatched task's summary exists.
156
+ // The unitId encodes the batch: "{mid}/{sid}/reactive+T02,T03"
157
+ if (unitType === "reactive-execute") {
158
+ const parts = unitId.split("/");
159
+ const mid = parts[0];
160
+ const sidAndBatch = parts[1];
161
+ const batchPart = parts[2]; // "reactive+T02,T03"
162
+ if (!mid || !sidAndBatch || !batchPart) return false;
163
+
164
+ const sid = sidAndBatch;
165
+ const plusIdx = batchPart.indexOf("+");
166
+ if (plusIdx === -1) {
167
+ // Legacy format "reactive" without batch IDs — fall back to "any summary"
168
+ const tDir = resolveTasksDir(base, mid, sid);
169
+ if (!tDir) return false;
170
+ const summaryFiles = resolveTaskFiles(tDir, "SUMMARY");
171
+ return summaryFiles.length > 0;
172
+ }
173
+
174
+ const batchIds = batchPart.slice(plusIdx + 1).split(",").filter(Boolean);
175
+ if (batchIds.length === 0) return false;
176
+
177
+ const tDir = resolveTasksDir(base, mid, sid);
178
+ if (!tDir) return false;
179
+
180
+ const existingSummaries = new Set(
181
+ resolveTaskFiles(tDir, "SUMMARY").map((f) =>
182
+ f.replace(/-SUMMARY\.md$/i, "").toUpperCase(),
183
+ ),
184
+ );
185
+
186
+ // Every dispatched task must have a summary file
187
+ for (const tid of batchIds) {
188
+ if (!existingSummaries.has(tid.toUpperCase())) return false;
189
+ }
190
+ return true;
191
+ }
192
+
151
193
  const absPath = resolveExpectedArtifactPath(unitType, unitId, base);
152
194
  // For unit types with no verifiable artifact (null path), the parent directory
153
195
  // is missing on disk — treat as stale completion state so the key gets evicted (#313).