gsd-pi 2.37.1 → 2.38.0-dev.63ad7e5

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 (201) 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/google-search/package.json +3 -1
  15. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  16. package/dist/resources/extensions/gsd/auto-dispatch.js +74 -9
  17. package/dist/resources/extensions/gsd/auto-loop.js +68 -97
  18. package/dist/resources/extensions/gsd/auto-post-unit.js +87 -69
  19. package/dist/resources/extensions/gsd/auto-prompts.js +98 -33
  20. package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
  21. package/dist/resources/extensions/gsd/auto-start.js +13 -2
  22. package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
  23. package/dist/resources/extensions/gsd/auto.js +143 -96
  24. package/dist/resources/extensions/gsd/captures.js +9 -1
  25. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  26. package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
  27. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  28. package/dist/resources/extensions/gsd/commands.js +22 -2
  29. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  30. package/dist/resources/extensions/gsd/detection.js +1 -2
  31. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  32. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  33. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  34. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  35. package/dist/resources/extensions/gsd/doctor-providers.js +62 -12
  36. package/dist/resources/extensions/gsd/doctor.js +184 -11
  37. package/dist/resources/extensions/gsd/export.js +1 -1
  38. package/dist/resources/extensions/gsd/files.js +43 -2
  39. package/dist/resources/extensions/gsd/forensics.js +1 -1
  40. package/dist/resources/extensions/gsd/index.js +2 -1
  41. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  42. package/dist/resources/extensions/gsd/observability-validator.js +24 -0
  43. package/dist/resources/extensions/gsd/package.json +1 -1
  44. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  45. package/dist/resources/extensions/gsd/preferences-types.js +2 -2
  46. package/dist/resources/extensions/gsd/preferences-validation.js +43 -11
  47. package/dist/resources/extensions/gsd/preferences.js +5 -5
  48. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  49. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
  50. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  51. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  52. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  53. package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  54. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  55. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
  56. package/dist/resources/extensions/gsd/prompts/run-uat.md +25 -10
  57. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  58. package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
  59. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  60. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  61. package/dist/resources/extensions/gsd/state.js +1 -1
  62. package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
  63. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  64. package/dist/resources/extensions/gsd/worktree.js +35 -16
  65. package/dist/resources/extensions/remote-questions/status.js +2 -1
  66. package/dist/resources/extensions/remote-questions/store.js +2 -1
  67. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  68. package/dist/resources/extensions/subagent/index.js +12 -3
  69. package/dist/resources/extensions/subagent/isolation.js +2 -1
  70. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  71. package/dist/resources/extensions/universal-config/package.json +1 -1
  72. package/dist/welcome-screen.d.ts +12 -0
  73. package/dist/welcome-screen.js +53 -0
  74. package/package.json +2 -1
  75. package/packages/pi-ai/dist/env-api-keys.js +13 -0
  76. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  77. package/packages/pi-ai/dist/models.generated.d.ts +172 -0
  78. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  79. package/packages/pi-ai/dist/models.generated.js +172 -0
  80. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  81. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
  82. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
  83. package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
  84. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
  85. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
  86. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
  87. package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
  88. package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
  89. package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
  90. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  91. package/packages/pi-ai/dist/providers/anthropic.js +47 -764
  92. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  93. package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
  94. package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
  95. package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
  96. package/packages/pi-ai/dist/types.d.ts +2 -2
  97. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  98. package/packages/pi-ai/dist/types.js.map +1 -1
  99. package/packages/pi-ai/package.json +1 -0
  100. package/packages/pi-ai/src/env-api-keys.ts +14 -0
  101. package/packages/pi-ai/src/models.generated.ts +172 -0
  102. package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
  103. package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
  104. package/packages/pi-ai/src/providers/anthropic.ts +76 -868
  105. package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
  106. package/packages/pi-ai/src/types.ts +2 -0
  107. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  109. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  112. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  113. package/packages/pi-coding-agent/package.json +1 -1
  114. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  115. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  116. package/pkg/package.json +1 -1
  117. package/src/resources/extensions/cmux/index.ts +57 -1
  118. package/src/resources/extensions/env-utils.ts +31 -0
  119. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  120. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  121. package/src/resources/extensions/gsd/auto-dispatch.ts +99 -8
  122. package/src/resources/extensions/gsd/auto-loop.ts +88 -133
  123. package/src/resources/extensions/gsd/auto-post-unit.ts +64 -40
  124. package/src/resources/extensions/gsd/auto-prompts.ts +132 -36
  125. package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
  126. package/src/resources/extensions/gsd/auto-start.ts +18 -2
  127. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  128. package/src/resources/extensions/gsd/auto.ts +139 -101
  129. package/src/resources/extensions/gsd/captures.ts +10 -1
  130. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  131. package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
  132. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  133. package/src/resources/extensions/gsd/commands.ts +24 -2
  134. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  135. package/src/resources/extensions/gsd/detection.ts +2 -2
  136. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  137. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  138. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  139. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  140. package/src/resources/extensions/gsd/doctor-providers.ts +64 -10
  141. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  142. package/src/resources/extensions/gsd/doctor.ts +177 -13
  143. package/src/resources/extensions/gsd/export.ts +1 -1
  144. package/src/resources/extensions/gsd/files.ts +47 -2
  145. package/src/resources/extensions/gsd/forensics.ts +1 -1
  146. package/src/resources/extensions/gsd/index.ts +3 -1
  147. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  148. package/src/resources/extensions/gsd/observability-validator.ts +27 -0
  149. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  150. package/src/resources/extensions/gsd/preferences-types.ts +5 -5
  151. package/src/resources/extensions/gsd/preferences-validation.ts +42 -11
  152. package/src/resources/extensions/gsd/preferences.ts +5 -5
  153. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  154. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
  155. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  156. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  157. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  158. package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  159. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  160. package/src/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
  161. package/src/resources/extensions/gsd/prompts/run-uat.md +25 -10
  162. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  163. package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
  164. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  165. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  166. package/src/resources/extensions/gsd/state.ts +1 -1
  167. package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
  168. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  169. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +11 -31
  170. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  171. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  172. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +191 -3
  173. package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
  174. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  175. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  176. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
  177. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
  178. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  179. package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
  180. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  181. package/src/resources/extensions/gsd/types.ts +43 -1
  182. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  183. package/src/resources/extensions/gsd/worktree.ts +35 -15
  184. package/src/resources/extensions/remote-questions/status.ts +3 -1
  185. package/src/resources/extensions/remote-questions/store.ts +3 -1
  186. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  187. package/src/resources/extensions/subagent/index.ts +12 -3
  188. package/src/resources/extensions/subagent/isolation.ts +3 -1
  189. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  190. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  191. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  192. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  193. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  194. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  195. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  196. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  197. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  198. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  199. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  200. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  201. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -5,9 +5,9 @@
5
5
  * pattern with a while loop. The agent_end event resolves a promise instead
6
6
  * of recursing.
7
7
  *
8
- * MAINTENANCE RULE: The only module-level mutable state here is `_activeSession`,
9
- * used by the agent_end bridge. Promise state itself lives on AutoSession so
10
- * concurrent auto sessions cannot corrupt each other.
8
+ * MAINTENANCE RULE: Module-level mutable state is limited to `_currentResolve`
9
+ * (per-unit one-shot resolver) and `_sessionSwitchInFlight` (guard for
10
+ * session rotation). No queue stale agent_end events are dropped.
11
11
  */
12
12
  import { NEW_SESSION_TIMEOUT_MS } from "./auto/session.js";
13
13
  import { debugLog } from "./debug-logger.js";
@@ -18,71 +18,66 @@ import { debugLog } from "./debug-logger.js";
18
18
  * generous headroom including retries and sidecar work.
19
19
  */
20
20
  const MAX_LOOP_ITERATIONS = 500;
21
- // ─── Session-scoped promise state ───────────────────────────────────────────
21
+ /** Data-driven budget threshold notifications (75/80/90%). The 100% case is
22
+ * handled inline because it requires break/pause/stop control flow. */
23
+ const BUDGET_THRESHOLDS = [
24
+ { pct: 90, label: "Budget 90%", notifyLevel: "warning", cmuxLevel: "warning" },
25
+ { pct: 80, label: "Approaching budget ceiling — 80%", notifyLevel: "warning", cmuxLevel: "warning" },
26
+ { pct: 75, label: "Budget 75%", notifyLevel: "info", cmuxLevel: "progress" },
27
+ ];
28
+ // ─── Per-unit one-shot promise state ────────────────────────────────────────
22
29
  //
23
- // pendingResolve and pendingAgentEndQueue live on AutoSession (not module-level)
24
- // so concurrent sessions cannot corrupt each other's promises.
25
- /**
26
- * The singleton session reference used by resolveAgentEnd. Set by autoLoop
27
- * on entry so that the agent_end handler in index.ts can resolve the correct
28
- * session's promise without needing a direct reference to `s`.
29
- */
30
- let _activeSession = null;
30
+ // A single module-level resolve function scoped to the current unit execution.
31
+ // No queue if an agent_end arrives with no pending resolver, it is dropped
32
+ // (logged as warning). This is simpler and safer than the previous session-
33
+ // scoped pendingResolve + pendingAgentEndQueue pattern.
34
+ let _currentResolve = null;
35
+ let _sessionSwitchInFlight = false;
31
36
  // ─── resolveAgentEnd ─────────────────────────────────────────────────────────
32
37
  /**
33
38
  * Called from the agent_end event handler in index.ts to resolve the
34
39
  * in-flight unit promise. One-shot: the resolver is nulled before calling
35
40
  * to prevent double-resolution from model fallback retries.
36
41
  *
37
- * If no pendingResolve exists (event arrived between loop iterations),
38
- * the event is queued on the session so the next runUnit can drain it.
42
+ * If no resolver exists (event arrived between loop iterations or during
43
+ * session switch), the event is dropped with a debug warning.
39
44
  */
40
45
  export function resolveAgentEnd(event) {
41
- const s = _activeSession;
42
- if (!s) {
43
- debugLog("resolveAgentEnd", {
44
- status: "no-active-session",
45
- warning: "agent_end with no active loop session",
46
- });
46
+ if (_sessionSwitchInFlight) {
47
+ debugLog("resolveAgentEnd", { status: "ignored-during-switch" });
47
48
  return;
48
49
  }
49
- if (s.pendingResolve) {
50
+ if (_currentResolve) {
50
51
  debugLog("resolveAgentEnd", { status: "resolving", hasEvent: true });
51
- const r = s.pendingResolve;
52
- s.pendingResolve = null;
52
+ const r = _currentResolve;
53
+ _currentResolve = null;
53
54
  r({ status: "completed", event });
54
55
  }
55
56
  else {
56
- // Queue the event so the next runUnit picks it up immediately
57
57
  debugLog("resolveAgentEnd", {
58
- status: "queued",
59
- queueLength: s.pendingAgentEndQueue.length + 1,
60
- warning: "agent_end arrived between loop iterations — queued for next runUnit",
58
+ status: "no-pending-resolve",
59
+ warning: "agent_end with no pending unit",
61
60
  });
62
- s.pendingAgentEndQueue.push(event);
63
61
  }
64
62
  }
65
63
  export function isSessionSwitchInFlight() {
66
- return _activeSession?.sessionSwitchInFlight ?? false;
64
+ return _sessionSwitchInFlight;
67
65
  }
68
66
  // ─── resetPendingResolve (test helper) ───────────────────────────────────────
69
67
  /**
70
- * Reset session promise state. Only exported for test cleanup — production code
71
- * should never call this.
68
+ * Reset module-level promise state. Only exported for test cleanup —
69
+ * production code should never call this.
72
70
  */
73
71
  export function _resetPendingResolve() {
74
- if (_activeSession) {
75
- _activeSession.pendingResolve = null;
76
- _activeSession.pendingAgentEndQueue = [];
77
- }
78
- _activeSession = null;
72
+ _currentResolve = null;
73
+ _sessionSwitchInFlight = false;
79
74
  }
80
75
  /**
81
- * Set the active session for resolveAgentEnd. Only exported for test setup —
82
- * production code sets this via autoLoop entry.
76
+ * No-op for backward compatibility with tests that previously set the
77
+ * active session. The module no longer holds a session reference.
83
78
  */
84
- export function _setActiveSession(session) {
85
- _activeSession = session;
79
+ export function _setActiveSession(_session) {
80
+ // No-op — kept for test backward compatibility
86
81
  }
87
82
  // ─── runUnit ─────────────────────────────────────────────────────────────────
88
83
  /**
@@ -95,39 +90,14 @@ export function _setActiveSession(session) {
95
90
  */
96
91
  export async function runUnit(ctx, pi, s, unitType, unitId, prompt, _prefs) {
97
92
  debugLog("runUnit", { phase: "start", unitType, unitId });
98
- // ── Drain queued events from error-recovery retries ──
99
- // If an agent_end arrived between iterations (e.g. from a model fallback
100
- // sendMessage retry), consume it immediately instead of creating a new promise.
101
- // Cap queue to 3 entries to prevent unbounded growth from stale events.
102
- if (s.pendingAgentEndQueue.length > 3) {
103
- debugLog("runUnit", {
104
- phase: "queue-overflow",
105
- dropped: s.pendingAgentEndQueue.length - 1,
106
- unitType,
107
- unitId,
108
- });
109
- s.pendingAgentEndQueue = [
110
- s.pendingAgentEndQueue[s.pendingAgentEndQueue.length - 1],
111
- ];
112
- }
113
- if (s.pendingAgentEndQueue.length > 0) {
114
- const queued = s.pendingAgentEndQueue.shift();
115
- debugLog("runUnit", {
116
- phase: "drained-queued-event",
117
- unitType,
118
- unitId,
119
- queueRemaining: s.pendingAgentEndQueue.length,
120
- });
121
- return { status: "completed", event: queued };
122
- }
123
93
  // ── Session creation with timeout ──
124
94
  debugLog("runUnit", { phase: "session-create", unitType, unitId });
125
95
  let sessionResult;
126
96
  let sessionTimeoutHandle;
127
- s.sessionSwitchInFlight = true;
97
+ _sessionSwitchInFlight = true;
128
98
  try {
129
99
  const sessionPromise = s.cmdCtx.newSession().finally(() => {
130
- s.sessionSwitchInFlight = false;
100
+ _sessionSwitchInFlight = false;
131
101
  });
132
102
  const timeoutPromise = new Promise((resolve) => {
133
103
  sessionTimeoutHandle = setTimeout(() => resolve({ cancelled: true }), NEW_SESSION_TIMEOUT_MS);
@@ -155,11 +125,12 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt, _prefs) {
155
125
  if (!s.active) {
156
126
  return { status: "cancelled" };
157
127
  }
158
- // ── Create the agent_end promise (session-scoped) ──
128
+ // ── Create the agent_end promise (per-unit one-shot) ──
159
129
  // This happens after newSession completes so session-switch agent_end events
160
130
  // from the previous session cannot resolve the new unit.
131
+ _sessionSwitchInFlight = false;
161
132
  const unitPromise = new Promise((resolve) => {
162
- s.pendingResolve = resolve;
133
+ _currentResolve = resolve;
163
134
  });
164
135
  // Ensure cwd matches basePath before dispatch (#1389).
165
136
  // async_bash and background jobs can drift cwd away from the worktree.
@@ -195,7 +166,6 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt, _prefs) {
195
166
  */
196
167
  export async function autoLoop(ctx, pi, s, deps) {
197
168
  debugLog("autoLoop", { phase: "enter" });
198
- _activeSession = s;
199
169
  let iteration = 0;
200
170
  let lastDerivedUnit = "";
201
171
  let sameUnitCount = 0;
@@ -373,7 +343,7 @@ export async function autoLoop(ctx, pi, s, deps) {
373
343
  await deps.closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, deps.buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
374
344
  }
375
345
  const incomplete = state.registry.filter((m) => m.status !== "complete" && m.status !== "parked");
376
- if (incomplete.length === 0) {
346
+ if (incomplete.length === 0 && state.registry.length > 0) {
377
347
  // All milestones complete — merge milestone branch before stopping
378
348
  if (s.currentMilestoneId) {
379
349
  deps.resolver.mergeAndExit(s.currentMilestoneId, ctx.ui);
@@ -382,6 +352,12 @@ export async function autoLoop(ctx, pi, s, deps) {
382
352
  deps.logCmuxEvent(deps.loadEffectiveGSDPreferences()?.preferences, "All milestones complete.", "success");
383
353
  await deps.stopAuto(ctx, pi, "All milestones complete");
384
354
  }
355
+ else if (incomplete.length === 0 && state.registry.length === 0) {
356
+ // Empty registry — no milestones visible, likely a path resolution bug
357
+ const diag = `basePath=${s.basePath}, phase=${state.phase}`;
358
+ ctx.ui.notify(`No milestones visible in current scope. Possible path resolution issue.\n Diagnostic: ${diag}`, "error");
359
+ await deps.stopAuto(ctx, pi, `No milestones found — check basePath resolution`);
360
+ }
385
361
  else if (state.phase === "blocked") {
386
362
  const blockerMsg = `Blocked: ${state.blockers.join(", ")}`;
387
363
  await deps.stopAuto(ctx, pi, blockerMsg);
@@ -487,29 +463,20 @@ export async function autoLoop(ctx, pi, s, deps) {
487
463
  deps.sendDesktopNotification("GSD", msg, "warning", "budget");
488
464
  deps.logCmuxEvent(prefs, msg, "warning");
489
465
  }
490
- else if (newBudgetAlertLevel === 90) {
491
- s.lastBudgetAlertLevel =
492
- newBudgetAlertLevel;
493
- ctx.ui.notify(`Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
494
- deps.sendDesktopNotification("GSD", `Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning", "budget");
495
- deps.logCmuxEvent(prefs, `Budget 90%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
496
- }
497
- else if (newBudgetAlertLevel === 80) {
498
- s.lastBudgetAlertLevel =
499
- newBudgetAlertLevel;
500
- ctx.ui.notify(`Approaching budget ceiling — 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
501
- deps.sendDesktopNotification("GSD", `Approaching budget ceiling — 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning", "budget");
502
- deps.logCmuxEvent(prefs, `Budget 80%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "warning");
503
- }
504
- else if (newBudgetAlertLevel === 75) {
505
- s.lastBudgetAlertLevel =
506
- newBudgetAlertLevel;
507
- ctx.ui.notify(`Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "info");
508
- deps.sendDesktopNotification("GSD", `Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "info", "budget");
509
- deps.logCmuxEvent(prefs, `Budget 75%: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`, "progress");
510
- }
511
- else if (budgetAlertLevel === 0) {
512
- s.lastBudgetAlertLevel = 0;
466
+ else {
467
+ // Data-driven 75/80/90% threshold notifications
468
+ const threshold = BUDGET_THRESHOLDS.find((t) => newBudgetAlertLevel === t.pct);
469
+ if (threshold) {
470
+ s.lastBudgetAlertLevel =
471
+ newBudgetAlertLevel;
472
+ const msg = `${threshold.label}: ${deps.formatCost(totalCost)} / ${deps.formatCost(budgetCeiling)}`;
473
+ ctx.ui.notify(msg, threshold.notifyLevel);
474
+ deps.sendDesktopNotification("GSD", msg, threshold.notifyLevel, "budget");
475
+ deps.logCmuxEvent(prefs, msg, threshold.cmuxLevel);
476
+ }
477
+ else if (budgetAlertLevel === 0) {
478
+ s.lastBudgetAlertLevel = 0;
479
+ }
513
480
  }
514
481
  }
515
482
  else {
@@ -557,6 +524,7 @@ export async function autoLoop(ctx, pi, s, deps) {
557
524
  midTitle: midTitle,
558
525
  state,
559
526
  prefs,
527
+ session: s,
560
528
  });
561
529
  if (dispatchResult.action === "stop") {
562
530
  if (s.currentUnit) {
@@ -916,8 +884,11 @@ export async function autoLoop(ctx, pi, s, deps) {
916
884
  sidecarBroke = true;
917
885
  break;
918
886
  }
919
- // Run pre-verification for the sidecar unit
920
- const sidecarPreResult = await deps.postUnitPreVerification(postUnitCtx);
887
+ // Run pre-verification for the sidecar unit (lightweight path)
888
+ const sidecarPreOpts = item.kind === "hook"
889
+ ? { skipSettleDelay: true, skipDoctor: true, skipStateRebuild: true, skipWorktreeSync: true }
890
+ : { skipSettleDelay: true, skipStateRebuild: true };
891
+ const sidecarPreResult = await deps.postUnitPreVerification(postUnitCtx, sidecarPreOpts);
921
892
  if (sidecarPreResult === "dispatched") {
922
893
  // Pre-verification caused stop/pause
923
894
  debugLog("autoLoop", {
@@ -990,6 +961,6 @@ export async function autoLoop(ctx, pi, s, deps) {
990
961
  }
991
962
  }
992
963
  }
993
- _activeSession = null;
964
+ _currentResolve = null;
994
965
  debugLog("autoLoop", { phase: "exit", totalIterations: iteration });
995
966
  }
@@ -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 {
@@ -79,8 +80,8 @@ export async function postUnitPreVerification(pctx) {
79
80
  };
80
81
  }
81
82
  }
82
- catch {
83
- // Non-fatal
83
+ catch (e) {
84
+ debugLog("postUnit", { phase: "task-summary-parse", error: String(e) });
84
85
  }
85
86
  }
86
87
  }
@@ -90,57 +91,60 @@ export async function postUnitPreVerification(pctx) {
90
91
  ctx.ui.notify(`Committed: ${commitMsg.split("\n")[0]}`, "info");
91
92
  }
92
93
  }
93
- catch {
94
- // Non-fatal
94
+ catch (e) {
95
+ debugLog("postUnit", { phase: "auto-commit", error: String(e) });
95
96
  }
96
- // Doctor: fix mechanical bookkeeping
97
- 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
97
+ // Doctor: fix mechanical bookkeeping (skipped for lightweight sidecars)
98
+ if (!opts?.skipDoctor)
99
+ try {
100
+ const scopeParts = s.currentUnit.id.split("/").slice(0, 2);
101
+ const doctorScope = scopeParts.join("/");
102
+ const sliceTerminalUnits = new Set(["complete-slice", "run-uat"]);
103
+ const effectiveFixLevel = sliceTerminalUnits.has(s.currentUnit.type) ? "all" : "task";
104
+ const report = await runGSDDoctor(s.basePath, { fix: true, scope: doctorScope, fixLevel: effectiveFixLevel });
105
+ if (report.fixesApplied.length > 0) {
106
+ ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
107
+ }
108
+ // Proactive health tracking
109
+ const summary = summarizeDoctorIssues(report.issues);
110
+ recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
111
+ // Check if we should escalate to LLM-assisted heal
112
+ if (summary.errors > 0) {
113
+ const unresolvedErrors = report.issues
114
+ .filter(i => i.severity === "error" && !i.fixable)
115
+ .map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
116
+ const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
117
+ if (escalation.shouldEscalate) {
118
+ ctx.ui.notify(`Doctor heal escalation: ${escalation.reason}. Dispatching LLM-assisted heal.`, "warning");
119
+ try {
120
+ const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
121
+ const { dispatchDoctorHeal } = await import("./commands-handlers.js");
122
+ const actionable = report.issues.filter(i => i.severity === "error");
123
+ const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
124
+ const structuredIssues = formatDoctorIssuesForPrompt(actionable);
125
+ dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
126
+ }
127
+ catch (e) {
128
+ debugLog("postUnit", { phase: "doctor-heal-dispatch", error: String(e) });
129
+ }
127
130
  }
128
131
  }
129
132
  }
130
- }
131
- catch {
132
- // Non-fatal
133
- }
134
- // Throttled STATE.md rebuild
135
- const now = Date.now();
136
- if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
137
- try {
138
- await rebuildState(s.basePath);
139
- s.lastStateRebuildAt = now;
140
- autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
133
+ catch (e) {
134
+ debugLog("postUnit", { phase: "doctor", error: String(e) });
141
135
  }
142
- catch {
143
- // Non-fatal
136
+ // Throttled STATE.md rebuild (skipped for lightweight sidecars)
137
+ if (!opts?.skipStateRebuild) {
138
+ const now = Date.now();
139
+ if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
140
+ try {
141
+ await rebuildState(s.basePath);
142
+ s.lastStateRebuildAt = now;
143
+ autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
144
+ }
145
+ catch (e) {
146
+ debugLog("postUnit", { phase: "state-rebuild", error: String(e) });
147
+ }
144
148
  }
145
149
  }
146
150
  // Prune dead bg-shell processes
@@ -148,27 +152,41 @@ export async function postUnitPreVerification(pctx) {
148
152
  const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
149
153
  pruneDeadProcesses();
150
154
  }
151
- catch {
152
- // Non-fatal
155
+ catch (e) {
156
+ debugLog("postUnit", { phase: "prune-bg-shell", error: String(e) });
153
157
  }
154
- // Sync worktree state back to project root
155
- if (s.originalBasePath && s.originalBasePath !== s.basePath) {
158
+ // Sync worktree state back to project root (skipped for lightweight sidecars)
159
+ if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath !== s.basePath) {
156
160
  try {
157
161
  syncStateToProjectRoot(s.basePath, s.originalBasePath, s.currentMilestoneId);
158
162
  }
159
- catch {
160
- // Non-fatal
163
+ catch (e) {
164
+ debugLog("postUnit", { phase: "worktree-sync", error: String(e) });
161
165
  }
162
166
  }
163
167
  // Rewrite-docs completion
164
168
  if (s.currentUnit.type === "rewrite-docs") {
165
169
  try {
166
170
  await resolveAllOverrides(s.basePath);
167
- resetRewriteCircuitBreaker();
171
+ s.rewriteAttemptCount = 0;
168
172
  ctx.ui.notify("Override(s) resolved — rewrite-docs completed.", "info");
169
173
  }
170
- catch {
171
- // Non-fatal
174
+ catch (e) {
175
+ debugLog("postUnit", { phase: "rewrite-docs-resolve", error: String(e) });
176
+ }
177
+ }
178
+ // Reactive state cleanup on slice completion
179
+ if (s.currentUnit.type === "complete-slice") {
180
+ try {
181
+ const parts = s.currentUnit.id.split("/");
182
+ const [mid, sid] = parts;
183
+ if (mid && sid) {
184
+ const { clearReactiveState } = await import("./reactive-graph.js");
185
+ clearReactiveState(s.basePath, mid, sid);
186
+ }
187
+ }
188
+ catch (e) {
189
+ debugLog("postUnit", { phase: "reactive-state-cleanup", error: String(e) });
172
190
  }
173
191
  }
174
192
  // Post-triage: execute actionable resolutions
@@ -210,8 +228,8 @@ export async function postUnitPreVerification(pctx) {
210
228
  invalidateAllCaches();
211
229
  }
212
230
  }
213
- catch {
214
- // Non-fatal
231
+ catch (e) {
232
+ debugLog("postUnit", { phase: "artifact-verify", error: String(e) });
215
233
  }
216
234
  }
217
235
  else {
@@ -224,8 +242,8 @@ export async function postUnitPreVerification(pctx) {
224
242
  });
225
243
  clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
226
244
  }
227
- catch {
228
- // Non-fatal
245
+ catch (e) {
246
+ debugLog("postUnit", { phase: "hook-finalize", error: String(e) });
229
247
  }
230
248
  }
231
249
  }
@@ -338,8 +356,8 @@ export async function postUnitPostVerification(pctx) {
338
356
  }
339
357
  }
340
358
  }
341
- catch {
342
- // Triage check failure is non-fatal
359
+ catch (e) {
360
+ debugLog("postUnit", { phase: "triage-check", error: String(e) });
343
361
  }
344
362
  }
345
363
  // ── Quick-task dispatch ──
@@ -373,8 +391,8 @@ export async function postUnitPostVerification(pctx) {
373
391
  ctx.ui.notify(`Executing quick-task: ${capture.id} — "${capture.text}"`, "info");
374
392
  return "continue";
375
393
  }
376
- catch {
377
- // Non-fatal proceed to normal dispatch
394
+ catch (e) {
395
+ debugLog("postUnit", { phase: "quick-task-dispatch", error: String(e) });
378
396
  }
379
397
  }
380
398
  // Step mode → show wizard instead of dispatch