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
@@ -22,7 +22,6 @@ import { writeUnitRuntimeRecord, clearUnitRuntimeRecord } from "./unit-runtime.j
22
22
  import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
23
23
  import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js";
24
24
  import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
25
- import { resetRewriteCircuitBreaker } from "./auto-dispatch.js";
26
25
  import { isDbAvailable } from "./gsd-db.js";
27
26
  import { consumeSignal } from "./session-status-io.js";
28
27
  import { checkPostUnitHooks, isRetryPending, consumeRetryTrigger, persistHookState, } from "./post-unit-hooks.js";
@@ -36,7 +35,7 @@ const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
36
35
  *
37
36
  * Returns "dispatched" if a signal caused stop/pause, "continue" to proceed.
38
37
  */
39
- export async function postUnitPreVerification(pctx) {
38
+ export async function postUnitPreVerification(pctx, opts) {
40
39
  const { s, ctx, pi, buildSnapshotOpts, stopAuto, pauseAuto } = pctx;
41
40
  // ── Parallel worker signal check ──
42
41
  const milestoneLock = process.env.GSD_MILESTONE_LOCK;
@@ -55,8 +54,10 @@ export async function postUnitPreVerification(pctx) {
55
54
  }
56
55
  // Invalidate all caches
57
56
  invalidateAllCaches();
58
- // Small delay to let files settle
59
- await new Promise(r => setTimeout(r, 500));
57
+ // Small delay to let files settle (skipped for sidecars where latency matters more)
58
+ if (!opts?.skipSettleDelay) {
59
+ await new Promise(r => setTimeout(r, 100));
60
+ }
60
61
  // Auto-commit
61
62
  if (s.currentUnit) {
62
63
  try {
@@ -71,16 +72,26 @@ export async function postUnitPreVerification(pctx) {
71
72
  const summaryContent = await loadFile(summaryPath);
72
73
  if (summaryContent) {
73
74
  const summary = parseSummary(summaryContent);
75
+ // Look up GitHub issue number for commit linking
76
+ let ghIssueNumber;
77
+ try {
78
+ const { getTaskIssueNumberForCommit } = await import("../github-sync/sync.js");
79
+ ghIssueNumber = getTaskIssueNumberForCommit(s.basePath, mid, sid, tid) ?? undefined;
80
+ }
81
+ catch {
82
+ // GitHub sync not available — skip
83
+ }
74
84
  taskContext = {
75
85
  taskId: `${sid}/${tid}`,
76
86
  taskTitle: summary.title?.replace(/^T\d+:\s*/, "") || tid,
77
87
  oneLiner: summary.oneLiner || undefined,
78
88
  keyFiles: summary.frontmatter.key_files?.filter(f => !f.includes("{{")) || undefined,
89
+ issueNumber: ghIssueNumber,
79
90
  };
80
91
  }
81
92
  }
82
- catch {
83
- // Non-fatal
93
+ catch (e) {
94
+ debugLog("postUnit", { phase: "task-summary-parse", error: String(e) });
84
95
  }
85
96
  }
86
97
  }
@@ -90,57 +101,75 @@ export async function postUnitPreVerification(pctx) {
90
101
  ctx.ui.notify(`Committed: ${commitMsg.split("\n")[0]}`, "info");
91
102
  }
92
103
  }
93
- catch {
94
- // Non-fatal
104
+ catch (e) {
105
+ debugLog("postUnit", { phase: "auto-commit", error: String(e) });
95
106
  }
96
- // Doctor: fix mechanical bookkeeping
107
+ // GitHub sync (non-blocking, opt-in)
97
108
  try {
98
- const scopeParts = s.currentUnit.id.split("/").slice(0, 2);
99
- const doctorScope = scopeParts.join("/");
100
- const sliceTerminalUnits = new Set(["complete-slice", "run-uat"]);
101
- const effectiveFixLevel = sliceTerminalUnits.has(s.currentUnit.type) ? "all" : "task";
102
- const report = await runGSDDoctor(s.basePath, { fix: true, scope: doctorScope, fixLevel: effectiveFixLevel });
103
- if (report.fixesApplied.length > 0) {
104
- ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
105
- }
106
- // Proactive health tracking
107
- const summary = summarizeDoctorIssues(report.issues);
108
- recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
109
- // Check if we should escalate to LLM-assisted heal
110
- if (summary.errors > 0) {
111
- const unresolvedErrors = report.issues
112
- .filter(i => i.severity === "error" && !i.fixable)
113
- .map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
114
- const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
115
- if (escalation.shouldEscalate) {
116
- ctx.ui.notify(`Doctor heal escalation: ${escalation.reason}. Dispatching LLM-assisted heal.`, "warning");
117
- try {
118
- const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
119
- const { dispatchDoctorHeal } = await import("./commands-handlers.js");
120
- const actionable = report.issues.filter(i => i.severity === "error");
121
- const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
122
- const structuredIssues = formatDoctorIssuesForPrompt(actionable);
123
- dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
124
- }
125
- catch {
126
- // Non-fatal
127
- }
128
- }
129
- }
109
+ const { runGitHubSync } = await import("../github-sync/sync.js");
110
+ await runGitHubSync(s.basePath, s.currentUnit.type, s.currentUnit.id);
130
111
  }
131
- catch {
132
- // Non-fatal
112
+ catch (e) {
113
+ debugLog("postUnit", { phase: "github-sync", error: String(e) });
133
114
  }
134
- // Throttled STATE.md rebuild
135
- const now = Date.now();
136
- if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
115
+ // Doctor: fix mechanical bookkeeping (skipped for lightweight sidecars)
116
+ if (!opts?.skipDoctor)
137
117
  try {
138
- await rebuildState(s.basePath);
139
- s.lastStateRebuildAt = now;
140
- autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
118
+ const scopeParts = s.currentUnit.id.split("/").slice(0, 2);
119
+ const doctorScope = scopeParts.join("/");
120
+ const sliceTerminalUnits = new Set(["complete-slice", "run-uat"]);
121
+ const effectiveFixLevel = sliceTerminalUnits.has(s.currentUnit.type) ? "all" : "task";
122
+ const report = await runGSDDoctor(s.basePath, { fix: true, scope: doctorScope, fixLevel: effectiveFixLevel });
123
+ if (report.fixesApplied.length > 0) {
124
+ ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
125
+ }
126
+ // Proactive health tracking — filter to current milestone to avoid
127
+ // cross-milestone stale errors inflating the escalation counter
128
+ const currentMilestoneId = s.currentUnit.id.split("/")[0];
129
+ const milestoneIssues = currentMilestoneId
130
+ ? report.issues.filter(i => i.unitId === currentMilestoneId ||
131
+ i.unitId.startsWith(`${currentMilestoneId}/`))
132
+ : report.issues;
133
+ const summary = summarizeDoctorIssues(milestoneIssues);
134
+ recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
135
+ // Check if we should escalate to LLM-assisted heal
136
+ if (summary.errors > 0) {
137
+ const unresolvedErrors = milestoneIssues
138
+ .filter(i => i.severity === "error" && !i.fixable)
139
+ .map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
140
+ const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
141
+ if (escalation.shouldEscalate) {
142
+ ctx.ui.notify(`Doctor heal escalation: ${escalation.reason}. Dispatching LLM-assisted heal.`, "warning");
143
+ try {
144
+ const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
145
+ const { dispatchDoctorHeal } = await import("./commands-handlers.js");
146
+ const actionable = report.issues.filter(i => i.severity === "error");
147
+ const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
148
+ const structuredIssues = formatDoctorIssuesForPrompt(actionable);
149
+ dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
150
+ return "dispatched";
151
+ }
152
+ catch (e) {
153
+ debugLog("postUnit", { phase: "doctor-heal-dispatch", error: String(e) });
154
+ }
155
+ }
156
+ }
141
157
  }
142
- catch {
143
- // Non-fatal
158
+ catch (e) {
159
+ debugLog("postUnit", { phase: "doctor", error: String(e) });
160
+ }
161
+ // Throttled STATE.md rebuild (skipped for lightweight sidecars)
162
+ if (!opts?.skipStateRebuild) {
163
+ const now = Date.now();
164
+ if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
165
+ try {
166
+ await rebuildState(s.basePath);
167
+ s.lastStateRebuildAt = now;
168
+ autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
169
+ }
170
+ catch (e) {
171
+ debugLog("postUnit", { phase: "state-rebuild", error: String(e) });
172
+ }
144
173
  }
145
174
  }
146
175
  // Prune dead bg-shell processes
@@ -148,27 +177,27 @@ export async function postUnitPreVerification(pctx) {
148
177
  const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
149
178
  pruneDeadProcesses();
150
179
  }
151
- catch {
152
- // Non-fatal
180
+ catch (e) {
181
+ debugLog("postUnit", { phase: "prune-bg-shell", error: String(e) });
153
182
  }
154
- // Sync worktree state back to project root
155
- if (s.originalBasePath && s.originalBasePath !== s.basePath) {
183
+ // Sync worktree state back to project root (skipped for lightweight sidecars)
184
+ if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath !== s.basePath) {
156
185
  try {
157
186
  syncStateToProjectRoot(s.basePath, s.originalBasePath, s.currentMilestoneId);
158
187
  }
159
- catch {
160
- // Non-fatal
188
+ catch (e) {
189
+ debugLog("postUnit", { phase: "worktree-sync", error: String(e) });
161
190
  }
162
191
  }
163
192
  // Rewrite-docs completion
164
193
  if (s.currentUnit.type === "rewrite-docs") {
165
194
  try {
166
195
  await resolveAllOverrides(s.basePath);
167
- resetRewriteCircuitBreaker();
196
+ s.rewriteAttemptCount = 0;
168
197
  ctx.ui.notify("Override(s) resolved — rewrite-docs completed.", "info");
169
198
  }
170
- catch {
171
- // Non-fatal
199
+ catch (e) {
200
+ debugLog("postUnit", { phase: "rewrite-docs-resolve", error: String(e) });
172
201
  }
173
202
  }
174
203
  // Reactive state cleanup on slice completion
@@ -181,8 +210,8 @@ export async function postUnitPreVerification(pctx) {
181
210
  clearReactiveState(s.basePath, mid, sid);
182
211
  }
183
212
  }
184
- catch {
185
- // Non-fatal
213
+ catch (e) {
214
+ debugLog("postUnit", { phase: "reactive-state-cleanup", error: String(e) });
186
215
  }
187
216
  }
188
217
  // Post-triage: execute actionable resolutions
@@ -224,8 +253,8 @@ export async function postUnitPreVerification(pctx) {
224
253
  invalidateAllCaches();
225
254
  }
226
255
  }
227
- catch {
228
- // Non-fatal
256
+ catch (e) {
257
+ debugLog("postUnit", { phase: "artifact-verify", error: String(e) });
229
258
  }
230
259
  }
231
260
  else {
@@ -238,8 +267,8 @@ export async function postUnitPreVerification(pctx) {
238
267
  });
239
268
  clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
240
269
  }
241
- catch {
242
- // Non-fatal
270
+ catch (e) {
271
+ debugLog("postUnit", { phase: "hook-finalize", error: String(e) });
243
272
  }
244
273
  }
245
274
  }
@@ -352,8 +381,8 @@ export async function postUnitPostVerification(pctx) {
352
381
  }
353
382
  }
354
383
  }
355
- catch {
356
- // Triage check failure is non-fatal
384
+ catch (e) {
385
+ debugLog("postUnit", { phase: "triage-check", error: String(e) });
357
386
  }
358
387
  }
359
388
  // ── Quick-task dispatch ──
@@ -387,8 +416,8 @@ export async function postUnitPostVerification(pctx) {
387
416
  ctx.ui.notify(`Executing quick-task: ${capture.id} — "${capture.text}"`, "info");
388
417
  return "continue";
389
418
  }
390
- catch {
391
- // Non-fatal proceed to normal dispatch
419
+ catch (e) {
420
+ debugLog("postUnit", { phase: "quick-task-dispatch", error: String(e) });
392
421
  }
393
422
  }
394
423
  // Step mode → show wizard instead of dispatch
@@ -11,11 +11,15 @@ import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksD
11
11
  import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
12
12
  import { join } from "node:path";
13
13
  import { existsSync } from "node:fs";
14
- import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
15
- import { compressToTarget } from "./prompt-compressor.js";
16
- import { distillSummaries } from "./summary-distiller.js";
14
+ import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
17
15
  import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
18
- import { chunkByRelevance, formatChunks } from "./semantic-chunker.js";
16
+ // ─── Preamble Cap ─────────────────────────────────────────────────────────────
17
+ const MAX_PREAMBLE_CHARS = 30_000;
18
+ function capPreamble(preamble) {
19
+ if (preamble.length <= MAX_PREAMBLE_CHARS)
20
+ return preamble;
21
+ return truncateAtSectionBoundary(preamble, MAX_PREAMBLE_CHARS).content;
22
+ }
19
23
  // ─── Executor Constraints ─────────────────────────────────────────────────────
20
24
  /**
21
25
  * Format executor context constraints for injection into the plan-slice prompt.
@@ -126,14 +130,9 @@ export async function inlineFileSmart(absPath, relPath, label, query, threshold
126
130
  if (content.length <= threshold || !query) {
127
131
  return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
128
132
  }
129
- // Use semantic chunking for large files
130
- const result = chunkByRelevance(content, query, { maxChunks: 5, minScore: 0.05 });
131
- // If chunking didn't save much (< 20%), just include full content
132
- if (result.savingsPercent < 20) {
133
- return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
134
- }
135
- const formatted = formatChunks(result, relPath);
136
- return `### ${label} (${result.omittedChunks} sections omitted for relevance)\nSource: \`${relPath}\`\n\n${formatted}`;
133
+ // For large files, truncate at section boundary
134
+ const truncated = truncateAtSectionBoundary(content, threshold).content;
135
+ return `### ${label}\nSource: \`${relPath}\`\n\n${truncated}`;
137
136
  }
138
137
  /**
139
138
  * Load and inline dependency slice summaries (full content, not just paths).
@@ -165,21 +164,6 @@ export async function inlineDependencySummaries(mid, sid, base, budgetChars) {
165
164
  }
166
165
  const result = sections.join("\n\n");
167
166
  if (budgetChars !== undefined && result.length > budgetChars) {
168
- // For 3+ summaries, try distillation first (preserves more information)
169
- if (sections.length >= 3) {
170
- const rawSummaries = sections.map(s => {
171
- // Extract content after the header line
172
- const lines = s.split("\n");
173
- const contentStart = lines.findIndex(l => l.startsWith("Source:"));
174
- return contentStart >= 0 ? lines.slice(contentStart + 1).join("\n").trim() : s;
175
- });
176
- const distilled = distillSummaries(rawSummaries, budgetChars);
177
- if (distilled.content.length <= budgetChars) {
178
- return distilled.content;
179
- }
180
- }
181
- // Fall back to section-boundary truncation
182
- const { truncateAtSectionBoundary } = await import("./context-budget.js");
183
167
  return truncateAtSectionBoundary(result, budgetChars).content;
184
168
  }
185
169
  return result;
@@ -546,7 +530,7 @@ export async function buildResearchMilestonePrompt(mid, midTitle, base) {
546
530
  if (knowledgeInlineRM)
547
531
  inlined.push(knowledgeInlineRM);
548
532
  inlined.push(inlineTemplate("research", "Research"));
549
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
533
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
550
534
  const outputRelPath = relMilestoneFile(base, mid, "RESEARCH");
551
535
  return loadPrompt("research-milestone", {
552
536
  workingDirectory: base,
@@ -599,7 +583,7 @@ export async function buildPlanMilestonePrompt(mid, midTitle, base, level) {
599
583
  inlined.push(inlineTemplate("plan", "Slice Plan"));
600
584
  inlined.push(inlineTemplate("task-plan", "Task Plan"));
601
585
  }
602
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
586
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
603
587
  const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
604
588
  const researchOutputPath = join(base, relMilestoneFile(base, mid, "RESEARCH"));
605
589
  const secretsOutputPath = join(base, relMilestoneFile(base, mid, "SECRETS"));
@@ -647,7 +631,7 @@ export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base
647
631
  const overridesInline = formatOverridesSection(activeOverrides);
648
632
  if (overridesInline)
649
633
  inlined.unshift(overridesInline);
650
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
634
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
651
635
  const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH");
652
636
  return loadPrompt("research-slice", {
653
637
  workingDirectory: base,
@@ -693,7 +677,7 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
693
677
  const planOverridesInline = formatOverridesSection(planActiveOverrides);
694
678
  if (planOverridesInline)
695
679
  inlined.unshift(planOverridesInline);
696
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
680
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
697
681
  // Build executor context constraints from the budget engine
698
682
  const executorContextConstraints = formatExecutorConstraints();
699
683
  const outputRelPath = relSliceFile(base, mid, sid, "PLAN");
@@ -777,15 +761,11 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
777
761
  const contextWindow = resolveExecutorContextWindow(undefined, prefs?.preferences);
778
762
  const budgets = computeBudgets(contextWindow);
779
763
  const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
780
- // Compress carry-forward section when it exceeds 40% of inline context budget.
781
- // Only compress when compression_strategy is "compress" (budget/balanced profiles).
764
+ // Truncate carry-forward section when it exceeds 40% of inline context budget.
782
765
  const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
783
766
  let finalCarryForward = carryForwardSection;
784
767
  if (carryForwardSection.length > carryForwardBudget) {
785
- const { resolveCompressionStrategy } = await import("./preferences.js");
786
- if (resolveCompressionStrategy() === "compress") {
787
- finalCarryForward = compressToTarget(carryForwardSection, carryForwardBudget).content;
788
- }
768
+ finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
789
769
  }
790
770
  return loadPrompt("execute-task", {
791
771
  overridesSection,
@@ -843,7 +823,7 @@ export async function buildCompleteSlicePrompt(mid, _midTitle, sid, sTitle, base
843
823
  const completeOverridesInline = formatOverridesSection(completeActiveOverrides);
844
824
  if (completeOverridesInline)
845
825
  inlined.unshift(completeOverridesInline);
846
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
826
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
847
827
  const sliceRel = relSlicePath(base, mid, sid);
848
828
  const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
849
829
  const sliceUatPath = join(base, `${sliceRel}/${sid}-UAT.md`);
@@ -899,7 +879,7 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
899
879
  if (contextInline)
900
880
  inlined.push(contextInline);
901
881
  inlined.push(inlineTemplate("milestone-summary", "Milestone Summary"));
902
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
882
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
903
883
  const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`);
904
884
  return loadPrompt("complete-milestone", {
905
885
  workingDirectory: base,
@@ -966,7 +946,7 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) {
966
946
  const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
967
947
  if (contextInline)
968
948
  inlined.push(contextInline);
969
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
949
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
970
950
  const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
971
951
  const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
972
952
  return loadPrompt("validate-milestone", {
@@ -1014,7 +994,7 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
1014
994
  const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
1015
995
  if (replanOverridesInline)
1016
996
  inlined.unshift(replanOverridesInline);
1017
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
997
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
1018
998
  const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`);
1019
999
  // Build capture context for replan prompt (captures that triggered this replan)
1020
1000
  let captureContext = "(none)";
@@ -1054,7 +1034,7 @@ export async function buildRunUatPrompt(mid, sliceId, uatPath, uatContent, base)
1054
1034
  const projectInline = await inlineProjectFromDb(base);
1055
1035
  if (projectInline)
1056
1036
  inlined.push(projectInline);
1057
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
1037
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
1058
1038
  const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT"));
1059
1039
  const uatType = extractUatType(uatContent) ?? "human-experience";
1060
1040
  return loadPrompt("run-uat", {
@@ -1090,7 +1070,7 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
1090
1070
  const knowledgeInlineRA = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
1091
1071
  if (knowledgeInlineRA)
1092
1072
  inlined.push(knowledgeInlineRA);
1093
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
1073
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
1094
1074
  const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT"));
1095
1075
  // Build deferred captures context for reassess prompt
1096
1076
  let deferredCaptures = "(none)";
@@ -11,7 +11,7 @@
11
11
  import { deriveState } from "./state.js";
12
12
  import { loadFile, getManifestStatus } from "./files.js";
13
13
  import { loadEffectiveGSDPreferences, resolveSkillDiscoveryMode, getIsolationMode, } from "./preferences.js";
14
- import { ensureGsdSymlink } from "./repo-identity.js";
14
+ import { ensureGsdSymlink, validateProjectId } from "./repo-identity.js";
15
15
  import { migrateToExternalState, recoverFailedMigration } from "./migrate-external.js";
16
16
  import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
17
17
  import { gsdRoot, resolveMilestoneFile } from "./paths.js";
@@ -63,6 +63,12 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
63
63
  return false;
64
64
  }
65
65
  try {
66
+ // Validate GSD_PROJECT_ID early so the user gets immediate feedback
67
+ const customProjectId = process.env.GSD_PROJECT_ID;
68
+ if (customProjectId && !validateProjectId(customProjectId)) {
69
+ ctx.ui.notify(`GSD_PROJECT_ID must contain only alphanumeric characters, hyphens, and underscores. Got: "${customProjectId}"`, "error");
70
+ return releaseLockAndReturn();
71
+ }
66
72
  // Ensure git repo exists
67
73
  if (!nativeIsRepo(base)) {
68
74
  const mainBranch = loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
@@ -303,11 +309,16 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
303
309
  // ── Auto-worktree setup ──
304
310
  s.originalBasePath = base;
305
311
  const isUnderGsdWorktrees = (p) => {
312
+ // Direct layout: /.gsd/worktrees/
306
313
  const marker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
307
314
  if (p.includes(marker))
308
315
  return true;
309
316
  const worktreesSuffix = `${pathSep}.gsd${pathSep}worktrees`;
310
- return p.endsWith(worktreesSuffix);
317
+ if (p.endsWith(worktreesSuffix))
318
+ return true;
319
+ // Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
320
+ const symlinkRe = new RegExp(`\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees(?:\\${pathSep}|$)`);
321
+ return symlinkRe.test(p);
311
322
  };
312
323
  if (s.currentMilestoneId &&
313
324
  shouldUseWorktreeIsolation() &&
@@ -13,6 +13,7 @@ import { existsSync, readFileSync, unlinkSync, readdirSync, } from "node:fs";
13
13
  import { join, sep as pathSep } from "node:path";
14
14
  import { homedir } from "node:os";
15
15
  import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
16
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
16
17
  // ─── Project Root → Worktree Sync ─────────────────────────────────────────
17
18
  /**
18
19
  * Sync milestone artifacts from project root INTO worktree before deriveState.
@@ -75,7 +76,7 @@ export function syncStateToProjectRoot(worktreePath, projectRoot, milestoneId) {
75
76
  * doesn't falsely trigger staleness (#804).
76
77
  */
77
78
  export function readResourceVersion() {
78
- const agentDir = process.env.GSD_CODING_AGENT_DIR || join(homedir(), ".gsd", "agent");
79
+ const agentDir = process.env.GSD_CODING_AGENT_DIR || join(gsdHome, "agent");
79
80
  const manifestPath = join(agentDir, "managed-resources.json");
80
81
  try {
81
82
  const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
@@ -115,10 +116,17 @@ export function checkResourcesStale(versionOnStart) {
115
116
  * Returns the corrected base path.
116
117
  */
117
118
  export function escapeStaleWorktree(base) {
118
- const marker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
119
- const idx = base.indexOf(marker);
120
- if (idx === -1)
121
- return base;
119
+ // Direct layout: /.gsd/worktrees/
120
+ const directMarker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
121
+ let idx = base.indexOf(directMarker);
122
+ if (idx === -1) {
123
+ // Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
124
+ const symlinkRe = new RegExp(`\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees\\${pathSep}`);
125
+ const match = base.match(symlinkRe);
126
+ if (!match || match.index === undefined)
127
+ return base;
128
+ idx = match.index;
129
+ }
122
130
  // base is inside .gsd/worktrees/<something> — extract the project root
123
131
  const projectRoot = base.slice(0, idx);
124
132
  try {
@@ -15,10 +15,10 @@ import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
15
15
  import { gsdRoot } from "./paths.js";
16
16
  import { createWorktree, removeWorktree, worktreePath, } from "./worktree-manager.js";
17
17
  import { detectWorktreeName, nudgeGitBranchCache, } from "./worktree.js";
18
- import { MergeConflictError, readIntegrationBranch } from "./git-service.js";
18
+ import { MergeConflictError, readIntegrationBranch, RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
19
19
  import { parseRoadmap } from "./files.js";
20
20
  import { loadEffectiveGSDPreferences } from "./preferences.js";
21
- import { nativeGetCurrentBranch, nativeWorkingTreeStatus, nativeAddAll, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeConflictFiles, nativeCheckoutTheirs, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchExists, } from "./native-git-bridge.js";
21
+ import { nativeGetCurrentBranch, nativeWorkingTreeStatus, nativeAddAllWithExclusions, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeConflictFiles, nativeCheckoutTheirs, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchExists, } from "./native-git-bridge.js";
22
22
  // ─── Module State ──────────────────────────────────────────────────────────
23
23
  /** Original project root before chdir into auto-worktree. */
24
24
  let originalBase = null;
@@ -656,7 +656,7 @@ function autoCommitDirtyState(cwd) {
656
656
  const status = nativeWorkingTreeStatus(cwd);
657
657
  if (!status)
658
658
  return false;
659
- nativeAddAll(cwd);
659
+ nativeAddAllWithExclusions(cwd, RUNTIME_EXCLUSION_PATHS);
660
660
  const result = nativeCommit(cwd, "chore: auto-commit before milestone merge");
661
661
  return result !== null;
662
662
  }