gsd-pi 2.37.1-dev.d3ace49 → 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 (188) hide show
  1. package/dist/app-paths.js +1 -1
  2. package/dist/cli.js +9 -0
  3. package/dist/extension-discovery.d.ts +5 -3
  4. package/dist/extension-discovery.js +14 -9
  5. package/dist/extension-registry.js +2 -2
  6. package/dist/remote-questions-config.js +2 -2
  7. package/dist/resource-loader.js +34 -1
  8. package/dist/resources/extensions/browser-tools/package.json +3 -1
  9. package/dist/resources/extensions/cmux/index.js +55 -1
  10. package/dist/resources/extensions/context7/package.json +1 -1
  11. package/dist/resources/extensions/env-utils.js +29 -0
  12. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  13. package/dist/resources/extensions/github-sync/cli.js +284 -0
  14. package/dist/resources/extensions/github-sync/index.js +73 -0
  15. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  16. package/dist/resources/extensions/github-sync/sync.js +424 -0
  17. package/dist/resources/extensions/github-sync/templates.js +118 -0
  18. package/dist/resources/extensions/github-sync/types.js +7 -0
  19. package/dist/resources/extensions/google-search/package.json +3 -1
  20. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  21. package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
  22. package/dist/resources/extensions/gsd/auto-loop.js +597 -588
  23. package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
  24. package/dist/resources/extensions/gsd/auto-prompts.js +23 -43
  25. package/dist/resources/extensions/gsd/auto-start.js +13 -2
  26. package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
  27. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  28. package/dist/resources/extensions/gsd/auto.js +143 -96
  29. package/dist/resources/extensions/gsd/captures.js +9 -1
  30. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  31. package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
  32. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  33. package/dist/resources/extensions/gsd/commands.js +24 -3
  34. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  35. package/dist/resources/extensions/gsd/detection.js +1 -2
  36. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  37. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  38. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  39. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  40. package/dist/resources/extensions/gsd/doctor-providers.js +27 -11
  41. package/dist/resources/extensions/gsd/doctor.js +204 -12
  42. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  43. package/dist/resources/extensions/gsd/export.js +1 -1
  44. package/dist/resources/extensions/gsd/files.js +6 -2
  45. package/dist/resources/extensions/gsd/forensics.js +1 -1
  46. package/dist/resources/extensions/gsd/git-service.js +15 -12
  47. package/dist/resources/extensions/gsd/guided-flow.js +82 -32
  48. package/dist/resources/extensions/gsd/index.js +24 -20
  49. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  50. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  51. package/dist/resources/extensions/gsd/package.json +1 -1
  52. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  53. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  54. package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
  55. package/dist/resources/extensions/gsd/preferences.js +8 -5
  56. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  57. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
  58. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  59. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  60. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  61. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  62. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  63. package/dist/resources/extensions/gsd/prompts/run-uat.md +27 -10
  64. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  65. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  66. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  67. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  68. package/dist/resources/extensions/gsd/state.js +1 -1
  69. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  70. package/dist/resources/extensions/gsd/worktree.js +35 -16
  71. package/dist/resources/extensions/mcp-client/index.js +14 -1
  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 +1 -1
  82. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  83. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  84. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  85. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  86. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  87. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  88. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  89. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  90. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  91. package/packages/pi-coding-agent/package.json +1 -1
  92. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  93. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  94. package/pkg/package.json +1 -1
  95. package/src/resources/extensions/cmux/index.ts +57 -1
  96. package/src/resources/extensions/env-utils.ts +31 -0
  97. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  98. package/src/resources/extensions/github-sync/cli.ts +364 -0
  99. package/src/resources/extensions/github-sync/index.ts +93 -0
  100. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  101. package/src/resources/extensions/github-sync/sync.ts +556 -0
  102. package/src/resources/extensions/github-sync/templates.ts +183 -0
  103. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  104. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  105. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  106. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  107. package/src/resources/extensions/github-sync/types.ts +47 -0
  108. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  109. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
  110. package/src/resources/extensions/gsd/auto-loop.ts +484 -546
  111. package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
  112. package/src/resources/extensions/gsd/auto-prompts.ts +25 -45
  113. package/src/resources/extensions/gsd/auto-start.ts +18 -2
  114. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  115. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  116. package/src/resources/extensions/gsd/auto.ts +139 -101
  117. package/src/resources/extensions/gsd/captures.ts +10 -1
  118. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  119. package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
  120. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  121. package/src/resources/extensions/gsd/commands.ts +26 -4
  122. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  123. package/src/resources/extensions/gsd/detection.ts +2 -2
  124. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  125. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  126. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  127. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  128. package/src/resources/extensions/gsd/doctor-providers.ts +26 -9
  129. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  130. package/src/resources/extensions/gsd/doctor.ts +199 -14
  131. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  132. package/src/resources/extensions/gsd/export.ts +1 -1
  133. package/src/resources/extensions/gsd/files.ts +5 -3
  134. package/src/resources/extensions/gsd/forensics.ts +1 -1
  135. package/src/resources/extensions/gsd/git-service.ts +20 -10
  136. package/src/resources/extensions/gsd/guided-flow.ts +110 -38
  137. package/src/resources/extensions/gsd/index.ts +24 -17
  138. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  139. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  140. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  141. package/src/resources/extensions/gsd/preferences-types.ts +4 -4
  142. package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
  143. package/src/resources/extensions/gsd/preferences.ts +8 -5
  144. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  145. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
  146. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  147. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  148. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  149. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  150. package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  151. package/src/resources/extensions/gsd/prompts/run-uat.md +27 -10
  152. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  153. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  154. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  155. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  156. package/src/resources/extensions/gsd/state.ts +1 -1
  157. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  158. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
  159. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  160. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  161. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
  162. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  163. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  164. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  165. package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
  166. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  167. package/src/resources/extensions/gsd/types.ts +0 -1
  168. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  169. package/src/resources/extensions/gsd/worktree.ts +35 -15
  170. package/src/resources/extensions/mcp-client/index.ts +17 -1
  171. package/src/resources/extensions/remote-questions/status.ts +3 -1
  172. package/src/resources/extensions/remote-questions/store.ts +3 -1
  173. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  174. package/src/resources/extensions/subagent/index.ts +12 -3
  175. package/src/resources/extensions/subagent/isolation.ts +3 -1
  176. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  177. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  178. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  179. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  180. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  181. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  182. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  183. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  184. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  185. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  186. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  187. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  188. 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,10 @@ 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) });
217
253
  }
218
254
  }
219
255
 
@@ -226,8 +262,8 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
226
262
  const { clearReactiveState } = await import("./reactive-graph.js");
227
263
  clearReactiveState(s.basePath, mid, sid);
228
264
  }
229
- } catch {
230
- // Non-fatal
265
+ } catch (e) {
266
+ debugLog("postUnit", { phase: "reactive-state-cleanup", error: String(e) });
231
267
  }
232
268
  }
233
269
 
@@ -280,8 +316,8 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
280
316
  if (triggerArtifactVerified) {
281
317
  invalidateAllCaches();
282
318
  }
283
- } catch {
284
- // Non-fatal
319
+ } catch (e) {
320
+ debugLog("postUnit", { phase: "artifact-verify", error: String(e) });
285
321
  }
286
322
  } else {
287
323
  // Hook unit completed — finalize its runtime record
@@ -292,8 +328,8 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
292
328
  lastProgressKind: "hook-completed",
293
329
  });
294
330
  clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
295
- } catch {
296
- // Non-fatal
331
+ } catch (e) {
332
+ debugLog("postUnit", { phase: "hook-finalize", error: String(e) });
297
333
  }
298
334
  }
299
335
  }
@@ -429,8 +465,8 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
429
465
  }
430
466
  }
431
467
  }
432
- } catch {
433
- // Triage check failure is non-fatal
468
+ } catch (e) {
469
+ debugLog("postUnit", { phase: "triage-check", error: String(e) });
434
470
  }
435
471
  }
436
472
 
@@ -475,8 +511,8 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
475
511
  );
476
512
 
477
513
  return "continue";
478
- } catch {
479
- // Non-fatal proceed to normal dispatch
514
+ } catch (e) {
515
+ debugLog("postUnit", { phase: "quick-task-dispatch", error: String(e) });
480
516
  }
481
517
  }
482
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;
@@ -634,7 +618,7 @@ export async function buildResearchMilestonePrompt(mid: string, midTitle: string
634
618
  if (knowledgeInlineRM) inlined.push(knowledgeInlineRM);
635
619
  inlined.push(inlineTemplate("research", "Research"));
636
620
 
637
- 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")}`);
638
622
 
639
623
  const outputRelPath = relMilestoneFile(base, mid, "RESEARCH");
640
624
  return loadPrompt("research-milestone", {
@@ -684,7 +668,7 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
684
668
  inlined.push(inlineTemplate("task-plan", "Task Plan"));
685
669
  }
686
670
 
687
- 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")}`);
688
672
 
689
673
  const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
690
674
  const researchOutputPath = join(base, relMilestoneFile(base, mid, "RESEARCH"));
@@ -733,7 +717,7 @@ export async function buildResearchSlicePrompt(
733
717
  const overridesInline = formatOverridesSection(activeOverrides);
734
718
  if (overridesInline) inlined.unshift(overridesInline);
735
719
 
736
- 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")}`);
737
721
 
738
722
  const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH");
739
723
  return loadPrompt("research-slice", {
@@ -781,7 +765,7 @@ export async function buildPlanSlicePrompt(
781
765
  const planOverridesInline = formatOverridesSection(planActiveOverrides);
782
766
  if (planOverridesInline) inlined.unshift(planOverridesInline);
783
767
 
784
- 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")}`);
785
769
 
786
770
  // Build executor context constraints from the budget engine
787
771
  const executorContextConstraints = formatExecutorConstraints();
@@ -900,15 +884,11 @@ export async function buildExecuteTaskPrompt(
900
884
  const budgets = computeBudgets(contextWindow);
901
885
  const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
902
886
 
903
- // Compress carry-forward section when it exceeds 40% of inline context budget.
904
- // Only compress when compression_strategy is "compress" (budget/balanced profiles).
887
+ // Truncate carry-forward section when it exceeds 40% of inline context budget.
905
888
  const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
906
889
  let finalCarryForward = carryForwardSection;
907
890
  if (carryForwardSection.length > carryForwardBudget) {
908
- const { resolveCompressionStrategy } = await import("./preferences.js");
909
- if (resolveCompressionStrategy() === "compress") {
910
- finalCarryForward = compressToTarget(carryForwardSection, carryForwardBudget).content;
911
- }
891
+ finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
912
892
  }
913
893
 
914
894
  return loadPrompt("execute-task", {
@@ -971,7 +951,7 @@ export async function buildCompleteSlicePrompt(
971
951
  const completeOverridesInline = formatOverridesSection(completeActiveOverrides);
972
952
  if (completeOverridesInline) inlined.unshift(completeOverridesInline);
973
953
 
974
- 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")}`);
975
955
 
976
956
  const sliceRel = relSlicePath(base, mid, sid);
977
957
  const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
@@ -1030,7 +1010,7 @@ export async function buildCompleteMilestonePrompt(
1030
1010
  if (contextInline) inlined.push(contextInline);
1031
1011
  inlined.push(inlineTemplate("milestone-summary", "Milestone Summary"));
1032
1012
 
1033
- 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")}`);
1034
1014
 
1035
1015
  const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`);
1036
1016
 
@@ -1101,7 +1081,7 @@ export async function buildValidateMilestonePrompt(
1101
1081
  const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
1102
1082
  if (contextInline) inlined.push(contextInline);
1103
1083
 
1104
- 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")}`);
1105
1085
 
1106
1086
  const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
1107
1087
  const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
@@ -1155,7 +1135,7 @@ export async function buildReplanSlicePrompt(
1155
1135
  const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
1156
1136
  if (replanOverridesInline) inlined.unshift(replanOverridesInline);
1157
1137
 
1158
- 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")}`);
1159
1139
 
1160
1140
  const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`);
1161
1141
 
@@ -1203,7 +1183,7 @@ export async function buildRunUatPrompt(
1203
1183
  const projectInline = await inlineProjectFromDb(base);
1204
1184
  if (projectInline) inlined.push(projectInline);
1205
1185
 
1206
- 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")}`);
1207
1187
 
1208
1188
  const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT"));
1209
1189
  const uatType = extractUatType(uatContent) ?? "human-experience";
@@ -1242,7 +1222,7 @@ export async function buildReassessRoadmapPrompt(
1242
1222
  const knowledgeInlineRA = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
1243
1223
  if (knowledgeInlineRA) inlined.push(knowledgeInlineRA);
1244
1224
 
1245
- 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")}`);
1246
1226
 
1247
1227
  const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT"));
1248
1228
 
@@ -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);
@@ -37,13 +37,13 @@ import {
37
37
  resolveGitHeadPath,
38
38
  nudgeGitBranchCache,
39
39
  } from "./worktree.js";
40
- import { MergeConflictError, readIntegrationBranch } from "./git-service.js";
40
+ import { MergeConflictError, readIntegrationBranch, RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
41
41
  import { parseRoadmap } from "./files.js";
42
42
  import { loadEffectiveGSDPreferences } from "./preferences.js";
43
43
  import {
44
44
  nativeGetCurrentBranch,
45
45
  nativeWorkingTreeStatus,
46
- nativeAddAll,
46
+ nativeAddAllWithExclusions,
47
47
  nativeCommit,
48
48
  nativeCheckoutBranch,
49
49
  nativeMergeSquash,
@@ -768,7 +768,7 @@ function autoCommitDirtyState(cwd: string): boolean {
768
768
  try {
769
769
  const status = nativeWorkingTreeStatus(cwd);
770
770
  if (!status) return false;
771
- nativeAddAll(cwd);
771
+ nativeAddAllWithExclusions(cwd, RUNTIME_EXCLUSION_PATHS);
772
772
  const result = nativeCommit(
773
773
  cwd,
774
774
  "chore: auto-commit before milestone merge",