gsd-pi 2.37.1 → 2.38.0-dev.4d4d14a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/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/resources/extensions/browser-tools/package.json +3 -1
  10. package/dist/resources/extensions/cmux/index.js +55 -1
  11. package/dist/resources/extensions/context7/package.json +1 -1
  12. package/dist/resources/extensions/env-utils.js +29 -0
  13. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  14. package/dist/resources/extensions/github-sync/cli.js +284 -0
  15. package/dist/resources/extensions/github-sync/index.js +73 -0
  16. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  17. package/dist/resources/extensions/github-sync/sync.js +424 -0
  18. package/dist/resources/extensions/github-sync/templates.js +118 -0
  19. package/dist/resources/extensions/github-sync/types.js +7 -0
  20. package/dist/resources/extensions/google-search/package.json +3 -1
  21. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  22. package/dist/resources/extensions/gsd/auto-dispatch.js +74 -9
  23. package/dist/resources/extensions/gsd/auto-loop.js +149 -170
  24. package/dist/resources/extensions/gsd/auto-post-unit.js +105 -68
  25. package/dist/resources/extensions/gsd/auto-prompts.js +98 -33
  26. package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
  27. package/dist/resources/extensions/gsd/auto-start.js +13 -2
  28. package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
  29. package/dist/resources/extensions/gsd/auto.js +143 -96
  30. package/dist/resources/extensions/gsd/captures.js +9 -1
  31. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  32. package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
  33. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  34. package/dist/resources/extensions/gsd/commands.js +22 -2
  35. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  36. package/dist/resources/extensions/gsd/detection.js +1 -2
  37. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  38. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  39. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  40. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  41. package/dist/resources/extensions/gsd/doctor-providers.js +62 -12
  42. package/dist/resources/extensions/gsd/doctor.js +184 -11
  43. package/dist/resources/extensions/gsd/export.js +1 -1
  44. package/dist/resources/extensions/gsd/files.js +43 -2
  45. package/dist/resources/extensions/gsd/forensics.js +1 -1
  46. package/dist/resources/extensions/gsd/git-service.js +8 -1
  47. package/dist/resources/extensions/gsd/index.js +24 -20
  48. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  49. package/dist/resources/extensions/gsd/observability-validator.js +24 -0
  50. package/dist/resources/extensions/gsd/package.json +1 -1
  51. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  52. package/dist/resources/extensions/gsd/preferences-types.js +3 -2
  53. package/dist/resources/extensions/gsd/preferences-validation.js +101 -11
  54. package/dist/resources/extensions/gsd/preferences.js +8 -5
  55. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  56. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
  57. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  58. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  59. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  60. package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  61. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  62. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
  63. package/dist/resources/extensions/gsd/prompts/run-uat.md +25 -10
  64. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  65. package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
  66. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  67. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  68. package/dist/resources/extensions/gsd/state.js +1 -1
  69. package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
  70. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  71. package/dist/resources/extensions/gsd/worktree.js +35 -16
  72. package/dist/resources/extensions/remote-questions/status.js +2 -1
  73. package/dist/resources/extensions/remote-questions/store.js +2 -1
  74. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  75. package/dist/resources/extensions/subagent/index.js +12 -3
  76. package/dist/resources/extensions/subagent/isolation.js +2 -1
  77. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  78. package/dist/resources/extensions/universal-config/package.json +1 -1
  79. package/dist/welcome-screen.d.ts +12 -0
  80. package/dist/welcome-screen.js +53 -0
  81. package/package.json +2 -1
  82. package/packages/pi-ai/dist/env-api-keys.js +13 -0
  83. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  84. package/packages/pi-ai/dist/models.generated.d.ts +172 -0
  85. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  86. package/packages/pi-ai/dist/models.generated.js +172 -0
  87. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  88. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
  89. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
  90. package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
  91. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
  92. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
  93. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
  94. package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
  95. package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
  96. package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
  97. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  98. package/packages/pi-ai/dist/providers/anthropic.js +47 -764
  99. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  100. package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
  101. package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
  102. package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
  103. package/packages/pi-ai/dist/types.d.ts +2 -2
  104. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/types.js.map +1 -1
  106. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  107. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  108. package/packages/pi-ai/package.json +1 -0
  109. package/packages/pi-ai/src/env-api-keys.ts +14 -0
  110. package/packages/pi-ai/src/models.generated.ts +172 -0
  111. package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
  112. package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
  113. package/packages/pi-ai/src/providers/anthropic.ts +76 -868
  114. package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
  115. package/packages/pi-ai/src/types.ts +2 -0
  116. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  117. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  119. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  122. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  123. package/packages/pi-coding-agent/package.json +1 -1
  124. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  125. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  126. package/pkg/package.json +1 -1
  127. package/src/resources/extensions/cmux/index.ts +57 -1
  128. package/src/resources/extensions/env-utils.ts +31 -0
  129. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  130. package/src/resources/extensions/github-sync/cli.ts +364 -0
  131. package/src/resources/extensions/github-sync/index.ts +93 -0
  132. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  133. package/src/resources/extensions/github-sync/sync.ts +556 -0
  134. package/src/resources/extensions/github-sync/templates.ts +183 -0
  135. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  136. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  137. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  138. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  139. package/src/resources/extensions/github-sync/types.ts +47 -0
  140. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  141. package/src/resources/extensions/gsd/auto-dispatch.ts +99 -8
  142. package/src/resources/extensions/gsd/auto-loop.ts +207 -252
  143. package/src/resources/extensions/gsd/auto-post-unit.ts +82 -39
  144. package/src/resources/extensions/gsd/auto-prompts.ts +132 -36
  145. package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
  146. package/src/resources/extensions/gsd/auto-start.ts +18 -2
  147. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  148. package/src/resources/extensions/gsd/auto.ts +139 -101
  149. package/src/resources/extensions/gsd/captures.ts +10 -1
  150. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  151. package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
  152. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  153. package/src/resources/extensions/gsd/commands.ts +24 -2
  154. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  155. package/src/resources/extensions/gsd/detection.ts +2 -2
  156. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  157. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  158. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  159. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  160. package/src/resources/extensions/gsd/doctor-providers.ts +64 -10
  161. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  162. package/src/resources/extensions/gsd/doctor.ts +177 -13
  163. package/src/resources/extensions/gsd/export.ts +1 -1
  164. package/src/resources/extensions/gsd/files.ts +47 -2
  165. package/src/resources/extensions/gsd/forensics.ts +1 -1
  166. package/src/resources/extensions/gsd/git-service.ts +13 -1
  167. package/src/resources/extensions/gsd/index.ts +24 -17
  168. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  169. package/src/resources/extensions/gsd/observability-validator.ts +27 -0
  170. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  171. package/src/resources/extensions/gsd/preferences-types.ts +9 -5
  172. package/src/resources/extensions/gsd/preferences-validation.ts +92 -11
  173. package/src/resources/extensions/gsd/preferences.ts +8 -5
  174. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  175. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
  176. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  177. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  178. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  179. package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  180. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  181. package/src/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
  182. package/src/resources/extensions/gsd/prompts/run-uat.md +25 -10
  183. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  184. package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
  185. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  186. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  187. package/src/resources/extensions/gsd/state.ts +1 -1
  188. package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
  189. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  190. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +16 -37
  191. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  192. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  193. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +191 -3
  194. package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
  195. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  196. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  197. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
  198. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
  199. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  200. package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
  201. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  202. package/src/resources/extensions/gsd/types.ts +43 -1
  203. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  204. package/src/resources/extensions/gsd/worktree.ts +35 -15
  205. package/src/resources/extensions/remote-questions/status.ts +3 -1
  206. package/src/resources/extensions/remote-questions/store.ts +3 -1
  207. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  208. package/src/resources/extensions/subagent/index.ts +12 -3
  209. package/src/resources/extensions/subagent/isolation.ts +3 -1
  210. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  211. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  212. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  213. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  214. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  215. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  216. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  217. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  218. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  219. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  220. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  221. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  222. 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"]);
@@ -168,24 +194,27 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
168
194
  const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
169
195
  const structuredIssues = formatDoctorIssuesForPrompt(actionable);
170
196
  dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
171
- } catch {
172
- // Non-fatal
197
+ return "dispatched";
198
+ } catch (e) {
199
+ debugLog("postUnit", { phase: "doctor-heal-dispatch", error: String(e) });
173
200
  }
174
201
  }
175
202
  }
176
- } catch {
177
- // Non-fatal
203
+ } catch (e) {
204
+ debugLog("postUnit", { phase: "doctor", error: String(e) });
178
205
  }
179
206
 
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
207
+ // Throttled STATE.md rebuild (skipped for lightweight sidecars)
208
+ if (!opts?.skipStateRebuild) {
209
+ const now = Date.now();
210
+ if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
211
+ try {
212
+ await rebuildState(s.basePath);
213
+ s.lastStateRebuildAt = now;
214
+ autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
215
+ } catch (e) {
216
+ debugLog("postUnit", { phase: "state-rebuild", error: String(e) });
217
+ }
189
218
  }
190
219
  }
191
220
 
@@ -193,16 +222,16 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
193
222
  try {
194
223
  const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
195
224
  pruneDeadProcesses();
196
- } catch {
197
- // Non-fatal
225
+ } catch (e) {
226
+ debugLog("postUnit", { phase: "prune-bg-shell", error: String(e) });
198
227
  }
199
228
 
200
- // Sync worktree state back to project root
201
- if (s.originalBasePath && s.originalBasePath !== s.basePath) {
229
+ // Sync worktree state back to project root (skipped for lightweight sidecars)
230
+ if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath !== s.basePath) {
202
231
  try {
203
232
  syncStateToProjectRoot(s.basePath, s.originalBasePath, s.currentMilestoneId);
204
- } catch {
205
- // Non-fatal
233
+ } catch (e) {
234
+ debugLog("postUnit", { phase: "worktree-sync", error: String(e) });
206
235
  }
207
236
  }
208
237
 
@@ -210,10 +239,24 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
210
239
  if (s.currentUnit.type === "rewrite-docs") {
211
240
  try {
212
241
  await resolveAllOverrides(s.basePath);
213
- resetRewriteCircuitBreaker();
242
+ s.rewriteAttemptCount = 0;
214
243
  ctx.ui.notify("Override(s) resolved — rewrite-docs completed.", "info");
215
- } catch {
216
- // Non-fatal
244
+ } catch (e) {
245
+ debugLog("postUnit", { phase: "rewrite-docs-resolve", error: String(e) });
246
+ }
247
+ }
248
+
249
+ // Reactive state cleanup on slice completion
250
+ if (s.currentUnit.type === "complete-slice") {
251
+ try {
252
+ const parts = s.currentUnit.id.split("/");
253
+ const [mid, sid] = parts;
254
+ if (mid && sid) {
255
+ const { clearReactiveState } = await import("./reactive-graph.js");
256
+ clearReactiveState(s.basePath, mid, sid);
257
+ }
258
+ } catch (e) {
259
+ debugLog("postUnit", { phase: "reactive-state-cleanup", error: String(e) });
217
260
  }
218
261
  }
219
262
 
@@ -266,8 +309,8 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
266
309
  if (triggerArtifactVerified) {
267
310
  invalidateAllCaches();
268
311
  }
269
- } catch {
270
- // Non-fatal
312
+ } catch (e) {
313
+ debugLog("postUnit", { phase: "artifact-verify", error: String(e) });
271
314
  }
272
315
  } else {
273
316
  // Hook unit completed — finalize its runtime record
@@ -278,8 +321,8 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
278
321
  lastProgressKind: "hook-completed",
279
322
  });
280
323
  clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
281
- } catch {
282
- // Non-fatal
324
+ } catch (e) {
325
+ debugLog("postUnit", { phase: "hook-finalize", error: String(e) });
283
326
  }
284
327
  }
285
328
  }
@@ -415,8 +458,8 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
415
458
  }
416
459
  }
417
460
  }
418
- } catch {
419
- // Triage check failure is non-fatal
461
+ } catch (e) {
462
+ debugLog("postUnit", { phase: "triage-check", error: String(e) });
420
463
  }
421
464
  }
422
465
 
@@ -461,8 +504,8 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
461
504
  );
462
505
 
463
506
  return "continue";
464
- } catch {
465
- // Non-fatal proceed to normal dispatch
507
+ } catch (e) {
508
+ debugLog("postUnit", { phase: "quick-task-dispatch", error: String(e) });
466
509
  }
467
510
  }
468
511
 
@@ -21,10 +21,7 @@ import type { GSDPreferences } from "./preferences.js";
21
21
  import { join } from "node:path";
22
22
  import { existsSync } from "node:fs";
23
23
  import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
24
- import { compressToTarget } from "./prompt-compressor.js";
25
- import { distillSummaries } from "./summary-distiller.js";
26
24
  import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
27
- import { chunkByRelevance, formatChunks } from "./semantic-chunker.js";
28
25
 
29
26
  // ─── Executor Constraints ─────────────────────────────────────────────────────
30
27
 
@@ -159,16 +156,10 @@ export async function inlineFileSmart(
159
156
  return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
160
157
  }
161
158
 
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}`;
159
+ // For large files, truncate at section boundary
160
+ const { truncateAtSectionBoundary } = await import("./context-budget.js");
161
+ const truncated = truncateAtSectionBoundary(content, threshold).content;
162
+ return `### ${label}\nSource: \`${relPath}\`\n\n${truncated}`;
172
163
  }
173
164
 
174
165
  /**
@@ -202,20 +193,6 @@ export async function inlineDependencySummaries(
202
193
 
203
194
  const result = sections.join("\n\n");
204
195
  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
196
  const { truncateAtSectionBoundary } = await import("./context-budget.js");
220
197
  return truncateAtSectionBoundary(result, budgetChars).content;
221
198
  }
@@ -485,6 +462,41 @@ export async function getPriorTaskSummaryPaths(
485
462
  .map(f => `${sRel}/tasks/${f}`);
486
463
  }
487
464
 
465
+ /**
466
+ * Get carry-forward summary paths scoped to a task's derived dependencies.
467
+ *
468
+ * Instead of all prior tasks (order-based), returns only summaries for task
469
+ * IDs in `dependsOn`. Used by reactive-execute to give each subagent only
470
+ * the context it actually needs — not sibling tasks from a parallel batch.
471
+ *
472
+ * Falls back to order-based when dependsOn is empty (root tasks still get
473
+ * any available prior summaries for continuity).
474
+ */
475
+ export async function getDependencyTaskSummaryPaths(
476
+ mid: string, sid: string, currentTid: string,
477
+ dependsOn: string[], base: string,
478
+ ): Promise<string[]> {
479
+ // If no dependencies, fall back to order-based for root tasks
480
+ if (dependsOn.length === 0) {
481
+ return getPriorTaskSummaryPaths(mid, sid, currentTid, base);
482
+ }
483
+
484
+ const tDir = resolveTasksDir(base, mid, sid);
485
+ if (!tDir) return [];
486
+
487
+ const summaryFiles = resolveTaskFiles(tDir, "SUMMARY");
488
+ const sRel = relSlicePath(base, mid, sid);
489
+ const depSet = new Set(dependsOn.map((d) => d.toUpperCase()));
490
+
491
+ return summaryFiles
492
+ .filter((f) => {
493
+ // Extract task ID from filename: "T02-SUMMARY.md" → "T02"
494
+ const tid = f.replace(/-SUMMARY\.md$/i, "").toUpperCase();
495
+ return depSet.has(tid);
496
+ })
497
+ .map((f) => `${sRel}/tasks/${f}`);
498
+ }
499
+
488
500
  // ─── Adaptive Replanning Checks ────────────────────────────────────────────
489
501
 
490
502
  /**
@@ -772,13 +784,24 @@ export async function buildPlanSlicePrompt(
772
784
  });
773
785
  }
774
786
 
787
+ /** Options for customizing execute-task prompt construction. */
788
+ export interface ExecuteTaskPromptOptions {
789
+ level?: InlineLevel;
790
+ /** Override carry-forward paths (dependency-based instead of order-based). */
791
+ carryForwardPaths?: string[];
792
+ }
793
+
775
794
  export async function buildExecuteTaskPrompt(
776
795
  mid: string, sid: string, sTitle: string,
777
- tid: string, tTitle: string, base: string, level?: InlineLevel,
796
+ tid: string, tTitle: string, base: string,
797
+ level?: InlineLevel | ExecuteTaskPromptOptions,
778
798
  ): Promise<string> {
779
- const inlineLevel = level ?? resolveInlineLevel();
799
+ const opts: ExecuteTaskPromptOptions = typeof level === "object" && level !== null && !Array.isArray(level)
800
+ ? level
801
+ : { level: level as InlineLevel | undefined };
802
+ const inlineLevel = opts.level ?? resolveInlineLevel();
780
803
 
781
- const priorSummaries = await getPriorTaskSummaryPaths(mid, sid, tid, base);
804
+ const priorSummaries = opts.carryForwardPaths ?? await getPriorTaskSummaryPaths(mid, sid, tid, base);
782
805
  const priorLines = priorSummaries.length > 0
783
806
  ? priorSummaries.map(p => `- \`${p}\``).join("\n")
784
807
  : "- (no prior tasks)";
@@ -854,15 +877,12 @@ export async function buildExecuteTaskPrompt(
854
877
  const budgets = computeBudgets(contextWindow);
855
878
  const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
856
879
 
857
- // Compress carry-forward section when it exceeds 40% of inline context budget.
858
- // Only compress when compression_strategy is "compress" (budget/balanced profiles).
880
+ // Truncate carry-forward section when it exceeds 40% of inline context budget.
859
881
  const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
860
882
  let finalCarryForward = carryForwardSection;
861
883
  if (carryForwardSection.length > carryForwardBudget) {
862
- const { resolveCompressionStrategy } = await import("./preferences.js");
863
- if (resolveCompressionStrategy() === "compress") {
864
- finalCarryForward = compressToTarget(carryForwardSection, carryForwardBudget).content;
865
- }
884
+ const { truncateAtSectionBoundary } = await import("./context-budget.js");
885
+ finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
866
886
  }
867
887
 
868
888
  return loadPrompt("execute-task", {
@@ -1234,6 +1254,82 @@ export async function buildReassessRoadmapPrompt(
1234
1254
  });
1235
1255
  }
1236
1256
 
1257
+ // ─── Reactive Execute Prompt ──────────────────────────────────────────────
1258
+
1259
+ export async function buildReactiveExecutePrompt(
1260
+ mid: string, midTitle: string, sid: string, sTitle: string,
1261
+ readyTaskIds: string[], base: string,
1262
+ ): Promise<string> {
1263
+ const { loadSliceTaskIO, deriveTaskGraph, graphMetrics } = await import("./reactive-graph.js");
1264
+
1265
+ // Build graph for context
1266
+ const taskIO = await loadSliceTaskIO(base, mid, sid);
1267
+ const graph = deriveTaskGraph(taskIO);
1268
+ const metrics = graphMetrics(graph);
1269
+
1270
+ // Build graph context section
1271
+ const graphLines: string[] = [];
1272
+ for (const node of graph) {
1273
+ const status = node.done ? "✅ done" : readyTaskIds.includes(node.id) ? "🟢 ready" : "⏳ waiting";
1274
+ const deps = node.dependsOn.length > 0 ? ` (depends on: ${node.dependsOn.join(", ")})` : "";
1275
+ graphLines.push(`- **${node.id}: ${node.title}** — ${status}${deps}`);
1276
+ if (node.outputFiles.length > 0) {
1277
+ graphLines.push(` - Outputs: ${node.outputFiles.map(f => `\`${f}\``).join(", ")}`);
1278
+ }
1279
+ }
1280
+ const graphContext = [
1281
+ `Tasks: ${metrics.taskCount}, Edges: ${metrics.edgeCount}, Ready: ${metrics.readySetSize}`,
1282
+ "",
1283
+ ...graphLines,
1284
+ ].join("\n");
1285
+
1286
+ // Build individual subagent prompts for each ready task
1287
+ const subagentSections: string[] = [];
1288
+ const readyTaskListLines: string[] = [];
1289
+
1290
+ for (const tid of readyTaskIds) {
1291
+ const node = graph.find((n) => n.id === tid);
1292
+ const tTitle = node?.title ?? tid;
1293
+ readyTaskListLines.push(`- **${tid}: ${tTitle}**`);
1294
+
1295
+ // Build dependency-scoped carry-forward paths for this task
1296
+ const depPaths = await getDependencyTaskSummaryPaths(
1297
+ mid, sid, tid, node?.dependsOn ?? [], base,
1298
+ );
1299
+
1300
+ // Build a full execute-task prompt with dependency-based carry-forward
1301
+ const taskPrompt = await buildExecuteTaskPrompt(
1302
+ mid, sid, sTitle, tid, tTitle, base,
1303
+ { carryForwardPaths: depPaths },
1304
+ );
1305
+
1306
+ subagentSections.push([
1307
+ `### ${tid}: ${tTitle}`,
1308
+ "",
1309
+ "Use this as the prompt for a `subagent` call:",
1310
+ "",
1311
+ "```",
1312
+ taskPrompt,
1313
+ "```",
1314
+ ].join("\n"));
1315
+ }
1316
+
1317
+ const inlinedTemplates = inlineTemplate("task-summary", "Task Summary");
1318
+
1319
+ return loadPrompt("reactive-execute", {
1320
+ workingDirectory: base,
1321
+ milestoneId: mid,
1322
+ milestoneTitle: midTitle,
1323
+ sliceId: sid,
1324
+ sliceTitle: sTitle,
1325
+ graphContext,
1326
+ readyTaskCount: String(readyTaskIds.length),
1327
+ readyTaskList: readyTaskListLines.join("\n"),
1328
+ subagentPrompts: subagentSections.join("\n\n---\n\n"),
1329
+ inlinedTemplates,
1330
+ });
1331
+ }
1332
+
1237
1333
  export async function buildRewriteDocsPrompt(
1238
1334
  mid: string, midTitle: string,
1239
1335
  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).
@@ -20,7 +20,7 @@ import {
20
20
  resolveSkillDiscoveryMode,
21
21
  getIsolationMode,
22
22
  } from "./preferences.js";
23
- import { ensureGsdSymlink } from "./repo-identity.js";
23
+ import { ensureGsdSymlink, validateProjectId } from "./repo-identity.js";
24
24
  import { migrateToExternalState, recoverFailedMigration } from "./migrate-external.js";
25
25
  import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
26
26
  import { gsdRoot, resolveMilestoneFile, milestonesDir } from "./paths.js";
@@ -130,6 +130,16 @@ export async function bootstrapAutoSession(
130
130
  }
131
131
 
132
132
  try {
133
+ // Validate GSD_PROJECT_ID early so the user gets immediate feedback
134
+ const customProjectId = process.env.GSD_PROJECT_ID;
135
+ if (customProjectId && !validateProjectId(customProjectId)) {
136
+ ctx.ui.notify(
137
+ `GSD_PROJECT_ID must contain only alphanumeric characters, hyphens, and underscores. Got: "${customProjectId}"`,
138
+ "error",
139
+ );
140
+ return releaseLockAndReturn();
141
+ }
142
+
133
143
  // Ensure git repo exists
134
144
  if (!nativeIsRepo(base)) {
135
145
  const mainBranch =
@@ -429,10 +439,16 @@ export async function bootstrapAutoSession(
429
439
  s.originalBasePath = base;
430
440
 
431
441
  const isUnderGsdWorktrees = (p: string): boolean => {
442
+ // Direct layout: /.gsd/worktrees/
432
443
  const marker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
433
444
  if (p.includes(marker)) return true;
434
445
  const worktreesSuffix = `${pathSep}.gsd${pathSep}worktrees`;
435
- return p.endsWith(worktreesSuffix);
446
+ if (p.endsWith(worktreesSuffix)) return true;
447
+ // Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
448
+ const symlinkRe = new RegExp(
449
+ `\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees(?:\\${pathSep}|$)`,
450
+ );
451
+ return symlinkRe.test(p);
436
452
  };
437
453
 
438
454
  if (
@@ -22,6 +22,8 @@ import { join, sep as pathSep } from "node:path";
22
22
  import { homedir } from "node:os";
23
23
  import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
24
24
 
25
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
26
+
25
27
  // ─── Project Root → Worktree Sync ─────────────────────────────────────────
26
28
 
27
29
  /**
@@ -111,7 +113,7 @@ export function syncStateToProjectRoot(
111
113
  */
112
114
  export function readResourceVersion(): string | null {
113
115
  const agentDir =
114
- process.env.GSD_CODING_AGENT_DIR || join(homedir(), ".gsd", "agent");
116
+ process.env.GSD_CODING_AGENT_DIR || join(gsdHome, "agent");
115
117
  const manifestPath = join(agentDir, "managed-resources.json");
116
118
  try {
117
119
  const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
@@ -153,9 +155,18 @@ export function checkResourcesStale(
153
155
  * Returns the corrected base path.
154
156
  */
155
157
  export function escapeStaleWorktree(base: string): string {
156
- const marker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
157
- const idx = base.indexOf(marker);
158
- if (idx === -1) return base;
158
+ // Direct layout: /.gsd/worktrees/
159
+ const directMarker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
160
+ let idx = base.indexOf(directMarker);
161
+ if (idx === -1) {
162
+ // Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
163
+ const symlinkRe = new RegExp(
164
+ `\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees\\${pathSep}`,
165
+ );
166
+ const match = base.match(symlinkRe);
167
+ if (!match || match.index === undefined) return base;
168
+ idx = match.index;
169
+ }
159
170
 
160
171
  // base is inside .gsd/worktrees/<something> — extract the project root
161
172
  const projectRoot = base.slice(0, idx);