gsd-pi 2.33.1-dev.ee47f1b → 2.34.0-dev.bbb5216

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 (135) hide show
  1. package/dist/bundled-resource-path.d.ts +8 -0
  2. package/dist/bundled-resource-path.js +14 -0
  3. package/dist/headless-query.js +6 -6
  4. package/dist/resources/extensions/gsd/auto/session.js +27 -32
  5. package/dist/resources/extensions/gsd/auto-dashboard.js +29 -109
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +6 -1
  7. package/dist/resources/extensions/gsd/auto-dispatch.js +52 -81
  8. package/dist/resources/extensions/gsd/auto-loop.js +956 -0
  9. package/dist/resources/extensions/gsd/auto-observability.js +4 -2
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +75 -185
  11. package/dist/resources/extensions/gsd/auto-prompts.js +133 -101
  12. package/dist/resources/extensions/gsd/auto-recovery.js +59 -97
  13. package/dist/resources/extensions/gsd/auto-start.js +330 -309
  14. package/dist/resources/extensions/gsd/auto-supervisor.js +5 -11
  15. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +7 -7
  16. package/dist/resources/extensions/gsd/auto-timers.js +3 -4
  17. package/dist/resources/extensions/gsd/auto-verification.js +35 -73
  18. package/dist/resources/extensions/gsd/auto-worktree-sync.js +167 -0
  19. package/dist/resources/extensions/gsd/auto-worktree.js +291 -126
  20. package/dist/resources/extensions/gsd/auto.js +283 -1013
  21. package/dist/resources/extensions/gsd/captures.js +10 -4
  22. package/dist/resources/extensions/gsd/dispatch-guard.js +7 -8
  23. package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  24. package/dist/resources/extensions/gsd/doctor-checks.js +3 -4
  25. package/dist/resources/extensions/gsd/git-service.js +1 -1
  26. package/dist/resources/extensions/gsd/gsd-db.js +296 -151
  27. package/dist/resources/extensions/gsd/index.js +92 -228
  28. package/dist/resources/extensions/gsd/post-unit-hooks.js +13 -13
  29. package/dist/resources/extensions/gsd/progress-score.js +61 -156
  30. package/dist/resources/extensions/gsd/quick.js +98 -122
  31. package/dist/resources/extensions/gsd/session-lock.js +13 -0
  32. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  33. package/dist/resources/extensions/gsd/undo.js +43 -48
  34. package/dist/resources/extensions/gsd/unit-runtime.js +16 -15
  35. package/dist/resources/extensions/gsd/verification-evidence.js +0 -1
  36. package/dist/resources/extensions/gsd/verification-gate.js +6 -35
  37. package/dist/resources/extensions/gsd/worktree-command.js +30 -24
  38. package/dist/resources/extensions/gsd/worktree-manager.js +2 -3
  39. package/dist/resources/extensions/gsd/worktree-resolver.js +344 -0
  40. package/dist/resources/extensions/gsd/worktree.js +7 -44
  41. package/dist/tool-bootstrap.js +59 -11
  42. package/dist/worktree-cli.js +7 -7
  43. package/package.json +1 -1
  44. package/packages/pi-ai/dist/models.generated.d.ts +3630 -5483
  45. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  46. package/packages/pi-ai/dist/models.generated.js +735 -2588
  47. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  48. package/packages/pi-ai/src/models.generated.ts +1039 -2892
  49. package/packages/pi-coding-agent/package.json +1 -1
  50. package/pkg/package.json +1 -1
  51. package/src/resources/extensions/gsd/auto/session.ts +47 -30
  52. package/src/resources/extensions/gsd/auto-dashboard.ts +28 -131
  53. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +6 -1
  54. package/src/resources/extensions/gsd/auto-dispatch.ts +135 -91
  55. package/src/resources/extensions/gsd/auto-loop.ts +1665 -0
  56. package/src/resources/extensions/gsd/auto-observability.ts +4 -2
  57. package/src/resources/extensions/gsd/auto-post-unit.ts +85 -228
  58. package/src/resources/extensions/gsd/auto-prompts.ts +138 -109
  59. package/src/resources/extensions/gsd/auto-recovery.ts +124 -118
  60. package/src/resources/extensions/gsd/auto-start.ts +440 -354
  61. package/src/resources/extensions/gsd/auto-supervisor.ts +5 -12
  62. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +8 -8
  63. package/src/resources/extensions/gsd/auto-timers.ts +3 -4
  64. package/src/resources/extensions/gsd/auto-verification.ts +76 -90
  65. package/src/resources/extensions/gsd/auto-worktree-sync.ts +204 -0
  66. package/src/resources/extensions/gsd/auto-worktree.ts +389 -141
  67. package/src/resources/extensions/gsd/auto.ts +515 -1199
  68. package/src/resources/extensions/gsd/captures.ts +10 -4
  69. package/src/resources/extensions/gsd/dispatch-guard.ts +13 -9
  70. package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  71. package/src/resources/extensions/gsd/doctor-checks.ts +3 -4
  72. package/src/resources/extensions/gsd/git-service.ts +8 -1
  73. package/src/resources/extensions/gsd/gitignore.ts +4 -2
  74. package/src/resources/extensions/gsd/gsd-db.ts +375 -180
  75. package/src/resources/extensions/gsd/index.ts +104 -263
  76. package/src/resources/extensions/gsd/post-unit-hooks.ts +13 -13
  77. package/src/resources/extensions/gsd/progress-score.ts +65 -200
  78. package/src/resources/extensions/gsd/quick.ts +121 -125
  79. package/src/resources/extensions/gsd/session-lock.ts +11 -0
  80. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  81. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +32 -59
  82. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +75 -27
  83. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
  84. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +37 -0
  85. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1458 -0
  86. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +8 -162
  87. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -108
  88. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +1 -3
  89. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +0 -3
  90. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  91. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -55
  92. package/src/resources/extensions/gsd/tests/headless-query.test.ts +22 -0
  93. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +8 -11
  94. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +4 -6
  95. package/src/resources/extensions/gsd/tests/run-uat.test.ts +3 -3
  96. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +64 -0
  97. package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +181 -0
  98. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +0 -3
  99. package/src/resources/extensions/gsd/tests/token-profile.test.ts +6 -6
  100. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -6
  101. package/src/resources/extensions/gsd/tests/undo.test.ts +6 -0
  102. package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
  103. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +7 -201
  104. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  105. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  106. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +0 -3
  107. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +705 -0
  108. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +57 -106
  109. package/src/resources/extensions/gsd/tests/worktree.test.ts +5 -1
  110. package/src/resources/extensions/gsd/tests/write-gate.test.ts +43 -132
  111. package/src/resources/extensions/gsd/types.ts +90 -81
  112. package/src/resources/extensions/gsd/undo.ts +42 -46
  113. package/src/resources/extensions/gsd/unit-runtime.ts +14 -18
  114. package/src/resources/extensions/gsd/verification-evidence.ts +1 -3
  115. package/src/resources/extensions/gsd/verification-gate.ts +6 -39
  116. package/src/resources/extensions/gsd/worktree-command.ts +36 -24
  117. package/src/resources/extensions/gsd/worktree-manager.ts +2 -3
  118. package/src/resources/extensions/gsd/worktree-resolver.ts +485 -0
  119. package/src/resources/extensions/gsd/worktree.ts +7 -44
  120. package/dist/resources/extensions/gsd/auto-constants.js +0 -5
  121. package/dist/resources/extensions/gsd/auto-idempotency.js +0 -106
  122. package/dist/resources/extensions/gsd/auto-stuck-detection.js +0 -165
  123. package/dist/resources/extensions/gsd/mechanical-completion.js +0 -351
  124. package/src/resources/extensions/gsd/auto-constants.ts +0 -6
  125. package/src/resources/extensions/gsd/auto-idempotency.ts +0 -151
  126. package/src/resources/extensions/gsd/auto-stuck-detection.ts +0 -221
  127. package/src/resources/extensions/gsd/mechanical-completion.ts +0 -430
  128. package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +0 -691
  129. package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +0 -127
  130. package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +0 -123
  131. package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +0 -126
  132. package/src/resources/extensions/gsd/tests/loop-regression.test.ts +0 -874
  133. package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +0 -356
  134. package/src/resources/extensions/gsd/tests/progress-score.test.ts +0 -206
  135. package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -434
@@ -17,65 +17,35 @@
17
17
  * agent_end — auto-mode advancement
18
18
  * session_before_compact — save continue.md OR block during auto
19
19
  */
20
- import { createBashTool, createEditTool, createReadTool, createWriteTool, importExtensionModule, isToolCallEventType, } from "@gsd/pi-coding-agent";
20
+ import { createBashTool, createWriteTool, createReadTool, createEditTool, isToolCallEventType } from "@gsd/pi-coding-agent";
21
21
  import { Type } from "@sinclair/typebox";
22
22
  import { debugTime } from "./debug-logger.js";
23
- import { registerLazyGSDCommand } from "./commands-bootstrap.js";
23
+ import { registerGSDCommand } from "./commands.js";
24
24
  import { loadToolApiKeys } from "./commands-config.js";
25
25
  import { registerExitCommand } from "./exit-command.js";
26
- import { registerLazyWorktreeCommands } from "./worktree-command-bootstrap.js";
26
+ import { registerWorktreeCommand, getWorktreeOriginalCwd, getActiveWorktreeName } from "./worktree-command.js";
27
+ import { getActiveAutoWorktreeContext } from "./auto-worktree.js";
27
28
  import { saveFile, formatContinue, loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection } from "./files.js";
28
29
  import { loadPrompt } from "./prompt-loader.js";
30
+ import { deriveState } from "./state.js";
31
+ import { isAutoActive, isAutoPaused, pauseAuto, getAutoDashboardData, getAutoModeStartModel, markToolStart, markToolEnd } from "./auto.js";
32
+ import { isSessionSwitchInFlight, resolveAgentEnd } from "./auto-loop.js";
29
33
  import { saveActivityLog } from "./activity-log.js";
34
+ import { checkAutoStartAfterDiscuss, getDiscussionMilestoneId, findMilestoneIds, nextMilestoneId } from "./guided-flow.js";
35
+ import { GSDDashboardOverlay } from "./dashboard-overlay.js";
36
+ import { loadEffectiveGSDPreferences, renderPreferencesForSystemPrompt, resolveAllSkillReferences, resolveModelWithFallbacksForUnit, getNextFallbackModel, isTransientNetworkError, } from "./preferences.js";
30
37
  import { hasSkillSnapshot, detectNewSkills, formatSkillsXml } from "./skill-discovery.js";
31
- import { resolveSlicePath, resolveSliceFile, resolveTaskFile, resolveTaskFiles, resolveTasksDir, relSliceFile, relSlicePath, relTaskFile, buildSliceFileName, buildMilestoneFileName, gsdRoot, resolveMilestonePath, resolveGsdRootFile, } from "./paths.js";
38
+ import { resolveSlicePath, resolveSliceFile, resolveTaskFile, resolveTaskFiles, resolveTasksDir, relSliceFile, relSlicePath, relTaskFile, buildSliceFileName, buildMilestoneFileName, resolveMilestonePath, resolveGsdRootFile, } from "./paths.js";
32
39
  import { Key } from "@gsd/pi-tui";
33
40
  import { join } from "node:path";
34
41
  import { existsSync, readFileSync } from "node:fs";
35
42
  import { homedir } from "node:os";
36
43
  import { shortcutDesc } from "../shared/mod.js";
37
44
  import { Text } from "@gsd/pi-tui";
45
+ import { pauseAutoForProviderError, classifyProviderError } from "./provider-error-pause.js";
38
46
  import { toPosixPath } from "../shared/mod.js";
47
+ import { isParallelActive, shutdownParallel } from "./parallel-orchestrator.js";
39
48
  import { DEFAULT_BASH_TIMEOUT_SECS } from "./constants.js";
40
- import { getErrorMessage } from "./error-utils.js";
41
- function memoizeImport(loader) {
42
- let promise = null;
43
- return () => {
44
- if (!promise) {
45
- promise = loader();
46
- }
47
- return promise;
48
- };
49
- }
50
- const loadAutoModule = memoizeImport(() => importExtensionModule(import.meta.url, "./auto.js"));
51
- const loadStateModule = memoizeImport(() => importExtensionModule(import.meta.url, "./state.js"));
52
- const loadGuidedFlowModule = memoizeImport(() => importExtensionModule(import.meta.url, "./guided-flow.js"));
53
- const loadPreferencesModule = memoizeImport(() => importExtensionModule(import.meta.url, "./preferences.js"));
54
- const loadDashboardOverlayModule = memoizeImport(() => importExtensionModule(import.meta.url, "./dashboard-overlay.js"));
55
- const loadWorktreeCommandModule = memoizeImport(() => importExtensionModule(import.meta.url, "./worktree-command.js"));
56
- const loadAutoWorktreeModule = memoizeImport(() => importExtensionModule(import.meta.url, "./auto-worktree.js"));
57
- const loadProviderErrorPauseModule = memoizeImport(() => importExtensionModule(import.meta.url, "./provider-error-pause.js"));
58
- const loadParallelOrchestratorModule = memoizeImport(() => importExtensionModule(import.meta.url, "./parallel-orchestrator.js"));
59
- /**
60
- * Ensure the GSD database is available, auto-initializing if needed.
61
- * Returns true if the DB is ready, false if initialization failed.
62
- */
63
- async function ensureDbAvailable() {
64
- try {
65
- const db = await importExtensionModule(import.meta.url, "./gsd-db.js");
66
- if (db.isDbAvailable())
67
- return true;
68
- // Auto-initialize: open (and create if needed) the DB at the standard path
69
- const gsdDir = gsdRoot(process.cwd());
70
- if (!existsSync(gsdDir))
71
- return false; // No GSD project — can't create DB
72
- const dbPath = join(gsdDir, "gsd.db");
73
- return db.openDatabase(dbPath);
74
- }
75
- catch {
76
- return false;
77
- }
78
- }
79
49
  // ── Agent Instructions ────────────────────────────────────────────────────
80
50
  // Lightweight "always follow" files injected into every GSD agent session.
81
51
  // Global: ~/.gsd/agent-instructions.md Project: .gsd/agent-instructions.md
@@ -105,9 +75,7 @@ function loadAgentInstructions() {
105
75
  return parts.join("\n\n");
106
76
  }
107
77
  // ── Depth verification state ──────────────────────────────────────────────
108
- // Tracks which milestones have passed depth verification.
109
- // Single-milestone flows set '*' (wildcard). Multi-milestone flows set per-ID.
110
- const depthVerifiedMilestones = new Set();
78
+ let depthVerificationDone = false;
111
79
  // ── Queue phase tracking ──────────────────────────────────────────────────
112
80
  // When true, the LLM is in a queue flow writing CONTEXT.md files.
113
81
  // The write-gate applies during queue flows just like discussion flows.
@@ -116,25 +84,10 @@ let activeQueuePhase = false;
116
84
  // Tracks per-model retry attempts for transient network errors.
117
85
  // Cleared when a model switch occurs or retries are exhausted.
118
86
  const networkRetryCounters = new Map();
119
- // ── Transient error escalation ───────────────────────────────────────────
120
- // Tracks consecutive transient auto-resume attempts. Each attempt doubles
121
- // the delay. After MAX_TRANSIENT_AUTO_RESUMES consecutive failures, auto-mode
122
- // pauses indefinitely to avoid infinite rapid-fire retries (#1166).
123
- const MAX_TRANSIENT_AUTO_RESUMES = 5;
87
+ const MAX_TRANSIENT_AUTO_RESUMES = 3;
124
88
  let consecutiveTransientErrors = 0;
125
89
  export function isDepthVerified() {
126
- return depthVerifiedMilestones.has("*") || depthVerifiedMilestones.size > 0;
127
- }
128
- /** Check whether a specific milestone has passed depth verification. */
129
- export function isDepthVerifiedFor(milestoneId) {
130
- // Wildcard means "all milestones verified" (single-milestone flow)
131
- if (depthVerifiedMilestones.has("*"))
132
- return true;
133
- return depthVerifiedMilestones.has(milestoneId);
134
- }
135
- /** Mark a specific milestone as depth-verified. */
136
- export function markDepthVerified(milestoneId) {
137
- depthVerifiedMilestones.add(milestoneId);
90
+ return depthVerificationDone;
138
91
  }
139
92
  /** Check whether a queue phase is active. */
140
93
  export function isQueuePhaseActive() {
@@ -156,25 +109,11 @@ export function shouldBlockContextWrite(toolName, inputPath, milestoneId, depthV
156
109
  return { block: false };
157
110
  if (!MILESTONE_CONTEXT_RE.test(inputPath))
158
111
  return { block: false };
159
- // For discussion flows: check global depth verification (backward compat)
160
- if (inDiscussion && depthVerified)
112
+ if (depthVerified)
161
113
  return { block: false };
162
- // For queue flows: extract milestone ID from the path and check per-milestone verification
163
- if (inQueue) {
164
- const pathMatch = inputPath.match(/\/(M\d+(?:-[a-z0-9]{6})?)-CONTEXT\.md$/);
165
- const targetMid = pathMatch?.[1];
166
- if (targetMid && depthVerifiedMilestones.has(targetMid))
167
- return { block: false };
168
- // Wildcard passes all
169
- if (depthVerifiedMilestones.has("*"))
170
- return { block: false };
171
- }
172
114
  return {
173
115
  block: true,
174
- reason: `Blocked: Cannot write milestone CONTEXT.md without depth verification. ` +
175
- `Use ask_user_questions with a question id containing "depth_verification" first. ` +
176
- `For multi-milestone flows, include the milestone ID in the question id (e.g., "depth_verification_M001"). ` +
177
- `This ensures each milestone's context has been critically examined before being written.`,
116
+ reason: `Blocked: Cannot write to milestone CONTEXT.md during discussion phase without depth verification. Call ask_user_questions with question id "depth_verification" first to confirm discussion depth before writing context.`,
178
117
  };
179
118
  }
180
119
  // ── ASCII logo ────────────────────────────────────────────────────────────
@@ -187,8 +126,8 @@ const GSD_LOGO_LINES = [
187
126
  " ╚═════╝ ╚══════╝╚═════╝ ",
188
127
  ];
189
128
  export default function (pi) {
190
- registerLazyGSDCommand(pi);
191
- registerLazyWorktreeCommands(pi);
129
+ registerGSDCommand(pi);
130
+ registerWorktreeCommand(pi);
192
131
  registerExitCommand(pi);
193
132
  // ── EPIPE guard — prevent crash when stdout/stderr pipe closes unexpectedly ──
194
133
  // Node.js throws a fatal `Error: write EPIPE` when the parent process closes
@@ -197,22 +136,11 @@ export default function (pi) {
197
136
  // chance to persist state and pause instead of crashing (see issue #739).
198
137
  if (!process.listeners("uncaughtException").some(l => l.name === "_gsdEpipeGuard")) {
199
138
  const _gsdEpipeGuard = (err) => {
200
- const code = err.code;
201
- if (code === "EPIPE") {
139
+ if (err.code === "EPIPE") {
202
140
  // Pipe closed — nothing we can write; just exit cleanly
203
141
  process.exit(0);
204
142
  }
205
- // ECOMPROMISED: proper-lockfile's update timer detected mtime drift (system
206
- // sleep, heavy event loop stall, or filesystem precision mismatch on Node.js
207
- // v25+). The onCompromised callback already set _lockCompromised = true, but
208
- // due to a subtle interaction between the synchronous fs adapter and the
209
- // setTimeout boundary, the error can still propagate here as an uncaught
210
- // exception. Exit cleanly so the process.once("exit") handler removes the
211
- // lock directory — allowing the next session to acquire cleanly (#1322).
212
- if (code === "ECOMPROMISED") {
213
- process.exit(1);
214
- }
215
- // Re-throw anything that isn't EPIPE or ECOMPROMISED so real crashes still surface
143
+ // Re-throw anything that isn't EPIPE so real crashes still surface
216
144
  throw err;
217
145
  };
218
146
  process.on("uncaughtException", _gsdEpipeGuard);
@@ -301,8 +229,14 @@ export default function (pi) {
301
229
  when_context: Type.Optional(Type.String({ description: "When/context for the decision (e.g. milestone ID)" })),
302
230
  }),
303
231
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
304
- // Ensure DB is available (auto-initialize if needed)
305
- if (!await ensureDbAvailable()) {
232
+ // Check DB availability
233
+ let dbAvailable = false;
234
+ try {
235
+ const db = await import("./gsd-db.js");
236
+ dbAvailable = db.isDbAvailable();
237
+ }
238
+ catch { /* dynamic import failed */ }
239
+ if (!dbAvailable) {
306
240
  return {
307
241
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot save decision." }],
308
242
  isError: true,
@@ -310,7 +244,7 @@ export default function (pi) {
310
244
  };
311
245
  }
312
246
  try {
313
- const { saveDecisionToDb } = await importExtensionModule(import.meta.url, "./db-writer.js");
247
+ const { saveDecisionToDb } = await import("./db-writer.js");
314
248
  const { id } = await saveDecisionToDb({
315
249
  scope: params.scope,
316
250
  decision: params.decision,
@@ -325,7 +259,7 @@ export default function (pi) {
325
259
  };
326
260
  }
327
261
  catch (err) {
328
- const msg = getErrorMessage(err);
262
+ const msg = err instanceof Error ? err.message : String(err);
329
263
  process.stderr.write(`gsd-db: gsd_save_decision tool failed: ${msg}\n`);
330
264
  return {
331
265
  content: [{ type: "text", text: `Error saving decision: ${msg}` }],
@@ -357,8 +291,13 @@ export default function (pi) {
357
291
  supporting_slices: Type.Optional(Type.String({ description: "Supporting slices" })),
358
292
  }),
359
293
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
360
- // Ensure DB is available (auto-initialize if needed)
361
- if (!await ensureDbAvailable()) {
294
+ let dbAvailable = false;
295
+ try {
296
+ const db = await import("./gsd-db.js");
297
+ dbAvailable = db.isDbAvailable();
298
+ }
299
+ catch { /* dynamic import failed */ }
300
+ if (!dbAvailable) {
362
301
  return {
363
302
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot update requirement." }],
364
303
  isError: true,
@@ -367,7 +306,7 @@ export default function (pi) {
367
306
  }
368
307
  try {
369
308
  // Verify requirement exists
370
- const db = await importExtensionModule(import.meta.url, "./gsd-db.js");
309
+ const db = await import("./gsd-db.js");
371
310
  const existing = db.getRequirementById(params.id);
372
311
  if (!existing) {
373
312
  return {
@@ -376,7 +315,7 @@ export default function (pi) {
376
315
  details: { operation: "update_requirement", id: params.id, error: "not_found" },
377
316
  };
378
317
  }
379
- const { updateRequirementInDb } = await importExtensionModule(import.meta.url, "./db-writer.js");
318
+ const { updateRequirementInDb } = await import("./db-writer.js");
380
319
  const updates = {};
381
320
  if (params.status !== undefined)
382
321
  updates.status = params.status;
@@ -397,7 +336,7 @@ export default function (pi) {
397
336
  };
398
337
  }
399
338
  catch (err) {
400
- const msg = getErrorMessage(err);
339
+ const msg = err instanceof Error ? err.message : String(err);
401
340
  process.stderr.write(`gsd-db: gsd_update_requirement tool failed: ${msg}\n`);
402
341
  return {
403
342
  content: [{ type: "text", text: `Error updating requirement: ${msg}` }],
@@ -427,8 +366,13 @@ export default function (pi) {
427
366
  content: Type.String({ description: "The full markdown content of the artifact" }),
428
367
  }),
429
368
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
430
- // Ensure DB is available (auto-initialize if needed)
431
- if (!await ensureDbAvailable()) {
369
+ let dbAvailable = false;
370
+ try {
371
+ const db = await import("./gsd-db.js");
372
+ dbAvailable = db.isDbAvailable();
373
+ }
374
+ catch { /* dynamic import failed */ }
375
+ if (!dbAvailable) {
432
376
  return {
433
377
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot save artifact." }],
434
378
  isError: true,
@@ -456,7 +400,7 @@ export default function (pi) {
456
400
  else {
457
401
  relativePath = `milestones/${params.milestone_id}/${params.milestone_id}-${params.artifact_type}.md`;
458
402
  }
459
- const { saveArtifactToDb } = await importExtensionModule(import.meta.url, "./db-writer.js");
403
+ const { saveArtifactToDb } = await import("./db-writer.js");
460
404
  await saveArtifactToDb({
461
405
  path: relativePath,
462
406
  artifact_type: params.artifact_type,
@@ -471,7 +415,7 @@ export default function (pi) {
471
415
  };
472
416
  }
473
417
  catch (err) {
474
- const msg = getErrorMessage(err);
418
+ const msg = err instanceof Error ? err.message : String(err);
475
419
  process.stderr.write(`gsd-db: gsd_save_summary tool failed: ${msg}\n`);
476
420
  return {
477
421
  content: [{ type: "text", text: `Error saving artifact: ${msg}` }],
@@ -505,10 +449,6 @@ export default function (pi) {
505
449
  parameters: Type.Object({}),
506
450
  async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
507
451
  try {
508
- const [{ findMilestoneIds, nextMilestoneId }, { loadEffectiveGSDPreferences }] = await Promise.all([
509
- loadGuidedFlowModule(),
510
- loadPreferencesModule(),
511
- ]);
512
452
  const basePath = process.cwd();
513
453
  const existingIds = findMilestoneIds(basePath);
514
454
  const uniqueEnabled = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
@@ -522,7 +462,7 @@ export default function (pi) {
522
462
  };
523
463
  }
524
464
  catch (err) {
525
- const msg = getErrorMessage(err);
465
+ const msg = err instanceof Error ? err.message : String(err);
526
466
  return {
527
467
  content: [{ type: "text", text: `Error generating milestone ID: ${msg}` }],
528
468
  isError: true,
@@ -533,9 +473,8 @@ export default function (pi) {
533
473
  });
534
474
  // ── session_start: render branded GSD header + load tool keys + remote status ──
535
475
  pi.on("session_start", async (_event, ctx) => {
536
- // Clear depth verification and queue phase state from any prior session
537
- depthVerifiedMilestones.clear();
538
- activeQueuePhase = false;
476
+ // Clear per-session state that must not leak across sessions (e.g. RPC mode)
477
+ depthVerificationDone = false;
539
478
  // Theme access throws in RPC mode (no TUI) — header is decorative, skip it
540
479
  try {
541
480
  const theme = ctx.ui.theme;
@@ -550,17 +489,11 @@ export default function (pi) {
550
489
  }
551
490
  // Load tool API keys from auth.json into environment
552
491
  loadToolApiKeys();
553
- // Always-on health widget — ambient system health signal below the editor
554
- try {
555
- const { initHealthWidget } = await importExtensionModule(import.meta.url, "./health-widget.js");
556
- initHealthWidget(ctx);
557
- }
558
- catch { /* non-fatal — widget is best-effort */ }
559
492
  // Notify remote questions status if configured
560
493
  try {
561
494
  const [{ getRemoteConfigStatus }, { getLatestPromptSummary }] = await Promise.all([
562
- importExtensionModule(import.meta.url, "../remote-questions/config.js"),
563
- importExtensionModule(import.meta.url, "../remote-questions/status.js"),
495
+ import("../remote-questions/config.js"),
496
+ import("../remote-questions/status.js"),
564
497
  ]);
565
498
  const status = getRemoteConfigStatus();
566
499
  const latest = getLatestPromptSummary();
@@ -578,12 +511,11 @@ export default function (pi) {
578
511
  description: shortcutDesc("Open GSD dashboard", "/gsd status"),
579
512
  handler: async (ctx) => {
580
513
  // Only show if .gsd/ exists
581
- if (!existsSync(gsdRoot(process.cwd()))) {
514
+ if (!existsSync(join(process.cwd(), ".gsd"))) {
582
515
  ctx.ui.notify("No .gsd/ directory found. Run /gsd to start.", "info");
583
516
  return;
584
517
  }
585
- const { GSDDashboardOverlay } = await loadDashboardOverlayModule();
586
- const result = await ctx.ui.custom((tui, theme, _kb, done) => {
518
+ await ctx.ui.custom((tui, theme, _kb, done) => {
587
519
  return new GSDDashboardOverlay(tui, theme, () => done());
588
520
  }, {
589
521
  overlay: true,
@@ -594,20 +526,14 @@ export default function (pi) {
594
526
  anchor: "center",
595
527
  },
596
528
  });
597
- // Fallback for RPC mode where ctx.ui.custom() returns undefined.
598
- if (result === undefined) {
599
- const { fireStatusViaCommand } = await importExtensionModule(import.meta.url, "./commands.js");
600
- await fireStatusViaCommand(ctx);
601
- }
602
529
  },
603
530
  });
604
531
  // ── before_agent_start: inject GSD contract into true system prompt ─────
605
532
  pi.on("before_agent_start", async (event, ctx) => {
606
- if (!existsSync(gsdRoot(process.cwd())))
533
+ if (!existsSync(join(process.cwd(), ".gsd")))
607
534
  return;
608
535
  const stopContextTimer = debugTime("context-inject");
609
536
  const systemContent = loadPrompt("system");
610
- const { loadEffectiveGSDPreferences, resolveAllSkillReferences, renderPreferencesForSystemPrompt } = await loadPreferencesModule();
611
537
  const loadedPreferences = loadEffectiveGSDPreferences();
612
538
  let preferenceBlock = "";
613
539
  if (loadedPreferences) {
@@ -636,7 +562,7 @@ export default function (pi) {
636
562
  // Inject auto-learned project memories
637
563
  let memoryBlock = "";
638
564
  try {
639
- const { getActiveMemoriesRanked, formatMemoriesForPrompt } = await importExtensionModule(import.meta.url, "./memory-store.js");
565
+ const { getActiveMemoriesRanked, formatMemoriesForPrompt } = await import("./memory-store.js");
640
566
  const memories = getActiveMemoriesRanked(30);
641
567
  if (memories.length > 0) {
642
568
  const formatted = formatMemoriesForPrompt(memories, 2000);
@@ -663,10 +589,6 @@ export default function (pi) {
663
589
  const injection = await buildGuidedExecuteContextInjection(event.prompt, process.cwd());
664
590
  // Worktree context — override the static CWD in the system prompt
665
591
  let worktreeBlock = "";
666
- const [{ getActiveWorktreeName, getWorktreeOriginalCwd }, { getActiveAutoWorktreeContext }] = await Promise.all([
667
- loadWorktreeCommandModule(),
668
- loadAutoWorktreeModule(),
669
- ]);
670
592
  const worktreeName = getActiveWorktreeName();
671
593
  const worktreeMainCwd = getWorktreeOriginalCwd();
672
594
  const autoWorktree = getActiveAutoWorktreeContext();
@@ -728,27 +650,21 @@ export default function (pi) {
728
650
  });
729
651
  // ── agent_end: auto-mode advancement or auto-start after discuss ───────────
730
652
  pi.on("agent_end", async (event, ctx) => {
731
- const [{ isAutoActive, pauseAuto, getAutoDashboardData, getAutoModeStartModel, handleAgentEnd, }, { checkAutoStartAfterDiscuss }, { isTransientNetworkError, resolveModelWithFallbacksForUnit, getNextFallbackModel, }, { classifyProviderError, pauseAutoForProviderError },] = await Promise.all([
732
- loadAutoModule(),
733
- loadGuidedFlowModule(),
734
- loadPreferencesModule(),
735
- loadProviderErrorPauseModule(),
736
- ]);
737
- // Clean up quick-task branch if one just completed (#1269)
738
- try {
739
- const { cleanupQuickBranch } = await importExtensionModule(import.meta.url, "./quick.js");
740
- cleanupQuickBranch();
741
- }
742
- catch { /* non-fatal */ }
743
653
  // If discuss phase just finished, start auto-mode
744
654
  if (checkAutoStartAfterDiscuss()) {
745
- depthVerifiedMilestones.clear();
655
+ depthVerificationDone = false;
746
656
  activeQueuePhase = false;
747
657
  return;
748
658
  }
749
659
  // If auto-mode is already running, advance to next unit
750
660
  if (!isAutoActive())
751
661
  return;
662
+ // Fresh-session auto-mode intentionally aborts the previous session during
663
+ // cmdCtx.newSession(). Ignore that agent_end so we neither pause nor
664
+ // resolve the new unit with an event from the old session.
665
+ if (isSessionSwitchInFlight()) {
666
+ return;
667
+ }
752
668
  // If the agent was aborted (user pressed Escape) or hit a provider
753
669
  // error (fetch failure, rate limit, etc.), pause auto-mode instead of
754
670
  // advancing. This preserves the conversation so the user can inspect
@@ -853,43 +769,39 @@ export default function (pi) {
853
769
  const explicitRetryAfterMs = ("retryAfterMs" in lastMsg && typeof lastMsg.retryAfterMs === "number")
854
770
  ? lastMsg.retryAfterMs
855
771
  : undefined;
856
- let retryAfterMs = explicitRetryAfterMs ?? classification.suggestedDelayMs;
857
- // ── Escalating backoff for repeated transient errors ──────────────
858
- // Each consecutive transient auto-resume doubles the delay. After
859
- // MAX_TRANSIENT_AUTO_RESUMES consecutive failures, treat as permanent
860
- // to avoid infinite rapid-fire retries (#1166).
861
- let effectiveTransient = classification.isTransient;
862
772
  if (classification.isTransient) {
863
- consecutiveTransientErrors++;
864
- if (consecutiveTransientErrors > MAX_TRANSIENT_AUTO_RESUMES) {
865
- effectiveTransient = false;
866
- ctx.ui.notify(`${consecutiveTransientErrors} consecutive transient errors. Pausing indefinitely — resume manually with /gsd auto.`, "error");
867
- consecutiveTransientErrors = 0;
868
- }
869
- else {
870
- // Escalate: base delay × 2^(consecutive-1) → 30s, 60s, 120s, 240s, 480s
871
- retryAfterMs = retryAfterMs * 2 ** (consecutiveTransientErrors - 1);
872
- }
773
+ consecutiveTransientErrors += 1;
774
+ }
775
+ else {
776
+ consecutiveTransientErrors = 0;
777
+ }
778
+ const baseRetryAfterMs = explicitRetryAfterMs ?? classification.suggestedDelayMs;
779
+ const retryAfterMs = classification.isTransient ? baseRetryAfterMs * 2 ** Math.max(0, consecutiveTransientErrors - 1) : baseRetryAfterMs;
780
+ const allowAutoResume = classification.isTransient
781
+ && consecutiveTransientErrors <= MAX_TRANSIENT_AUTO_RESUMES;
782
+ if (classification.isTransient && !allowAutoResume) {
783
+ ctx.ui.notify(`Transient provider errors persisted after ${MAX_TRANSIENT_AUTO_RESUMES} auto-resume attempts. Pausing for manual review.`, "warning");
873
784
  }
874
785
  await pauseAutoForProviderError(ctx.ui, errorDetail, () => pauseAuto(ctx, pi), {
875
786
  isRateLimit: classification.isRateLimit,
876
- isTransient: effectiveTransient,
787
+ isTransient: allowAutoResume,
877
788
  retryAfterMs,
878
- resume: () => {
879
- pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution \u2014 provider error recovery delay elapsed.", display: false }, { triggerTurn: true });
880
- },
789
+ resume: allowAutoResume
790
+ ? () => {
791
+ pi.sendMessage({ customType: "gsd-auto-timeout-recovery", content: "Continue execution \u2014 provider error recovery delay elapsed.", display: false }, { triggerTurn: true });
792
+ }
793
+ : undefined,
881
794
  });
882
795
  return;
883
796
  }
884
797
  try {
798
+ consecutiveTransientErrors = 0;
885
799
  networkRetryCounters.clear(); // Clear network retry state on successful unit completion
886
- consecutiveTransientErrors = 0; // Reset escalating backoff on success
887
- await handleAgentEnd(ctx, pi);
800
+ resolveAgentEnd(event);
888
801
  }
889
802
  catch (err) {
890
- // Safety net: if handleAgentEnd throws despite its internal try-catch,
891
- // ensure auto-mode stops gracefully instead of silently stalling (#381).
892
- const message = getErrorMessage(err);
803
+ // Safety net: if resolveAgentEnd throws, ensure auto-mode stops gracefully (#381).
804
+ const message = err instanceof Error ? err.message : String(err);
893
805
  ctx.ui.notify(`Auto-mode error in agent_end handler: ${message}. Stopping auto-mode.`, "error");
894
806
  try {
895
807
  await pauseAuto(ctx, pi);
@@ -901,10 +813,6 @@ export default function (pi) {
901
813
  });
902
814
  // ── session_before_compact ────────────────────────────────────────────────
903
815
  pi.on("session_before_compact", async (_event, _ctx) => {
904
- const [{ isAutoActive, isAutoPaused }, { deriveState }] = await Promise.all([
905
- loadAutoModule(),
906
- loadStateModule(),
907
- ]);
908
816
  // Block compaction during auto-mode — each unit is a fresh session
909
817
  // Also block during paused state — context is valuable for the user
910
818
  if (isAutoActive() || isAutoPaused()) {
@@ -948,29 +856,12 @@ export default function (pi) {
948
856
  });
949
857
  // ── session_shutdown: save activity log on Ctrl+C / SIGTERM ─────────────
950
858
  pi.on("session_shutdown", async (_event, ctx) => {
951
- const [{ isParallelActive, shutdownParallel }, { isAutoActive, isAutoPaused, getAutoDashboardData }] = await Promise.all([
952
- loadParallelOrchestratorModule(),
953
- loadAutoModule(),
954
- ]);
955
859
  if (isParallelActive()) {
956
860
  try {
957
861
  await shutdownParallel(process.cwd());
958
862
  }
959
863
  catch { /* best-effort */ }
960
864
  }
961
- // Auto-commit dirty work in CLI-spawned worktrees so nothing is lost.
962
- // The CLI sets GSD_CLI_WORKTREE when launched with -w.
963
- const cliWorktree = process.env.GSD_CLI_WORKTREE;
964
- if (cliWorktree) {
965
- try {
966
- const { autoCommitCurrentBranch } = await importExtensionModule(import.meta.url, "./worktree.js");
967
- const msg = autoCommitCurrentBranch(process.cwd(), "session-end", cliWorktree);
968
- if (msg) {
969
- ctx.ui.notify(`Auto-committed worktree ${cliWorktree} before exit.`, "info");
970
- }
971
- }
972
- catch { /* best-effort */ }
973
- }
974
865
  if (!isAutoActive() && !isAutoPaused())
975
866
  return;
976
867
  // Save the current session — the lock file stays on disk
@@ -980,56 +871,32 @@ export default function (pi) {
980
871
  saveActivityLog(ctx, dash.basePath, dash.currentUnit.type, dash.currentUnit.id);
981
872
  }
982
873
  });
983
- // ── tool_call: block CONTEXT.md writes without depth verification ──
984
- // Active during both discussion flows (pendingAutoStart set) and
985
- // queue flows (activeQueuePhase set). For multi-milestone queue flows,
986
- // each milestone must pass its own depth verification before its
987
- // CONTEXT.md can be written.
874
+ // ── tool_call: block CONTEXT.md writes during discussion without depth verification ──
988
875
  pi.on("tool_call", async (event) => {
989
876
  if (!isToolCallEventType("write", event))
990
877
  return;
991
- const { getDiscussionMilestoneId } = await loadGuidedFlowModule();
992
878
  const result = shouldBlockContextWrite(event.toolName, event.input.path, getDiscussionMilestoneId(), isDepthVerified(), activeQueuePhase);
993
879
  if (result.block)
994
880
  return result;
995
881
  });
996
882
  // ── tool_result: persist discussion exchanges & detect depth gate ──────
997
- // Handles both discussion flows and queue flows. For queue flows,
998
- // depth verification question IDs may include milestone IDs
999
- // (e.g., "depth_verification_M001") for per-milestone gating.
1000
883
  pi.on("tool_result", async (event) => {
1001
884
  if (event.toolName !== "ask_user_questions")
1002
885
  return;
1003
- const { getDiscussionMilestoneId } = await loadGuidedFlowModule();
1004
886
  const milestoneId = getDiscussionMilestoneId();
1005
- // Queue flows don't set pendingAutoStart, so milestoneId may be null.
1006
- // Depth gate detection still applies — it sets per-milestone flags.
1007
- const inQueue = activeQueuePhase;
887
+ if (!milestoneId)
888
+ return;
1008
889
  const details = event.details;
1009
890
  if (details?.cancelled || !details?.response)
1010
891
  return;
1011
892
  // ── Depth gate detection ──────────────────────────────────────────
1012
- // Supports two patterns:
1013
- // 1. "depth_verification" — wildcard, marks all milestones verified
1014
- // 2. "depth_verification_M001" — per-milestone verification
1015
893
  const questions = event.input?.questions ?? [];
1016
894
  for (const q of questions) {
1017
895
  if (typeof q.id === "string" && q.id.includes("depth_verification")) {
1018
- // Extract milestone ID from question ID if present
1019
- const midMatch = q.id.match(/depth_verification[_-](M\d+(?:-[a-z0-9]{6})?)/i);
1020
- if (midMatch) {
1021
- depthVerifiedMilestones.add(midMatch[1]);
1022
- }
1023
- else {
1024
- // Wildcard — all milestones verified (backward compat for single-milestone)
1025
- depthVerifiedMilestones.add("*");
1026
- }
896
+ depthVerificationDone = true;
1027
897
  break;
1028
898
  }
1029
899
  }
1030
- // Discussion persistence only applies when in a discussion flow with a known milestone
1031
- if (!milestoneId)
1032
- return;
1033
900
  // ── Persist exchange to DISCUSSION.md ──────────────────────────────
1034
901
  const basePath = process.cwd();
1035
902
  const milestoneDir = resolveMilestonePath(basePath, milestoneId);
@@ -1069,13 +936,11 @@ export default function (pi) {
1069
936
  });
1070
937
  // ── tool_execution_start/end: track in-flight tools for idle detection ──
1071
938
  pi.on("tool_execution_start", async (event) => {
1072
- const { isAutoActive, markToolStart } = await loadAutoModule();
1073
939
  if (!isAutoActive())
1074
940
  return;
1075
941
  markToolStart(event.toolCallId);
1076
942
  });
1077
943
  pi.on("tool_execution_end", async (event) => {
1078
- const { markToolEnd } = await loadAutoModule();
1079
944
  markToolEnd(event.toolCallId);
1080
945
  });
1081
946
  }
@@ -1088,7 +953,6 @@ async function buildGuidedExecuteContextInjection(prompt, basePath) {
1088
953
  const resumeMatch = prompt.match(/Resume interrupted work\.[\s\S]*?slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i);
1089
954
  if (resumeMatch) {
1090
955
  const [, sliceId, milestoneId] = resumeMatch;
1091
- const { deriveState } = await loadStateModule();
1092
956
  const state = await deriveState(basePath);
1093
957
  if (state.activeMilestone?.id === milestoneId &&
1094
958
  state.activeSlice?.id === sliceId &&