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.
- package/dist/bundled-resource-path.d.ts +8 -0
- package/dist/bundled-resource-path.js +14 -0
- package/dist/headless-query.js +6 -6
- package/dist/resources/extensions/gsd/auto/session.js +27 -32
- package/dist/resources/extensions/gsd/auto-dashboard.js +29 -109
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +6 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +52 -81
- package/dist/resources/extensions/gsd/auto-loop.js +956 -0
- package/dist/resources/extensions/gsd/auto-observability.js +4 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +75 -185
- package/dist/resources/extensions/gsd/auto-prompts.js +133 -101
- package/dist/resources/extensions/gsd/auto-recovery.js +59 -97
- package/dist/resources/extensions/gsd/auto-start.js +330 -309
- package/dist/resources/extensions/gsd/auto-supervisor.js +5 -11
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +7 -7
- package/dist/resources/extensions/gsd/auto-timers.js +3 -4
- package/dist/resources/extensions/gsd/auto-verification.js +35 -73
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +167 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +291 -126
- package/dist/resources/extensions/gsd/auto.js +283 -1013
- package/dist/resources/extensions/gsd/captures.js +10 -4
- package/dist/resources/extensions/gsd/dispatch-guard.js +7 -8
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -18
- package/dist/resources/extensions/gsd/doctor-checks.js +3 -4
- package/dist/resources/extensions/gsd/git-service.js +1 -1
- package/dist/resources/extensions/gsd/gsd-db.js +296 -151
- package/dist/resources/extensions/gsd/index.js +92 -228
- package/dist/resources/extensions/gsd/post-unit-hooks.js +13 -13
- package/dist/resources/extensions/gsd/progress-score.js +61 -156
- package/dist/resources/extensions/gsd/quick.js +98 -122
- package/dist/resources/extensions/gsd/session-lock.js +13 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
- package/dist/resources/extensions/gsd/undo.js +43 -48
- package/dist/resources/extensions/gsd/unit-runtime.js +16 -15
- package/dist/resources/extensions/gsd/verification-evidence.js +0 -1
- package/dist/resources/extensions/gsd/verification-gate.js +6 -35
- package/dist/resources/extensions/gsd/worktree-command.js +30 -24
- package/dist/resources/extensions/gsd/worktree-manager.js +2 -3
- package/dist/resources/extensions/gsd/worktree-resolver.js +344 -0
- package/dist/resources/extensions/gsd/worktree.js +7 -44
- package/dist/tool-bootstrap.js +59 -11
- package/dist/worktree-cli.js +7 -7
- package/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +3630 -5483
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +735 -2588
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/src/models.generated.ts +1039 -2892
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/session.ts +47 -30
- package/src/resources/extensions/gsd/auto-dashboard.ts +28 -131
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +6 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +135 -91
- package/src/resources/extensions/gsd/auto-loop.ts +1665 -0
- package/src/resources/extensions/gsd/auto-observability.ts +4 -2
- package/src/resources/extensions/gsd/auto-post-unit.ts +85 -228
- package/src/resources/extensions/gsd/auto-prompts.ts +138 -109
- package/src/resources/extensions/gsd/auto-recovery.ts +124 -118
- package/src/resources/extensions/gsd/auto-start.ts +440 -354
- package/src/resources/extensions/gsd/auto-supervisor.ts +5 -12
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +8 -8
- package/src/resources/extensions/gsd/auto-timers.ts +3 -4
- package/src/resources/extensions/gsd/auto-verification.ts +76 -90
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +204 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +389 -141
- package/src/resources/extensions/gsd/auto.ts +515 -1199
- package/src/resources/extensions/gsd/captures.ts +10 -4
- package/src/resources/extensions/gsd/dispatch-guard.ts +13 -9
- package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -18
- package/src/resources/extensions/gsd/doctor-checks.ts +3 -4
- package/src/resources/extensions/gsd/git-service.ts +8 -1
- package/src/resources/extensions/gsd/gitignore.ts +4 -2
- package/src/resources/extensions/gsd/gsd-db.ts +375 -180
- package/src/resources/extensions/gsd/index.ts +104 -263
- package/src/resources/extensions/gsd/post-unit-hooks.ts +13 -13
- package/src/resources/extensions/gsd/progress-score.ts +65 -200
- package/src/resources/extensions/gsd/quick.ts +121 -125
- package/src/resources/extensions/gsd/session-lock.ts +11 -0
- package/src/resources/extensions/gsd/templates/preferences.md +1 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +32 -59
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +75 -27
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +37 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1458 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +8 -162
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -108
- package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +1 -3
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -55
- package/src/resources/extensions/gsd/tests/headless-query.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +8 -11
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +4 -6
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/undo.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +7 -201
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +0 -3
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +705 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +57 -106
- package/src/resources/extensions/gsd/tests/worktree.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +43 -132
- package/src/resources/extensions/gsd/types.ts +90 -81
- package/src/resources/extensions/gsd/undo.ts +42 -46
- package/src/resources/extensions/gsd/unit-runtime.ts +14 -18
- package/src/resources/extensions/gsd/verification-evidence.ts +1 -3
- package/src/resources/extensions/gsd/verification-gate.ts +6 -39
- package/src/resources/extensions/gsd/worktree-command.ts +36 -24
- package/src/resources/extensions/gsd/worktree-manager.ts +2 -3
- package/src/resources/extensions/gsd/worktree-resolver.ts +485 -0
- package/src/resources/extensions/gsd/worktree.ts +7 -44
- package/dist/resources/extensions/gsd/auto-constants.js +0 -5
- package/dist/resources/extensions/gsd/auto-idempotency.js +0 -106
- package/dist/resources/extensions/gsd/auto-stuck-detection.js +0 -165
- package/dist/resources/extensions/gsd/mechanical-completion.js +0 -351
- package/src/resources/extensions/gsd/auto-constants.ts +0 -6
- package/src/resources/extensions/gsd/auto-idempotency.ts +0 -151
- package/src/resources/extensions/gsd/auto-stuck-detection.ts +0 -221
- package/src/resources/extensions/gsd/mechanical-completion.ts +0 -430
- package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +0 -691
- package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +0 -127
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +0 -123
- package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +0 -126
- package/src/resources/extensions/gsd/tests/loop-regression.test.ts +0 -874
- package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +0 -356
- package/src/resources/extensions/gsd/tests/progress-score.test.ts +0 -206
- package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -434
|
@@ -23,24 +23,31 @@ import type {
|
|
|
23
23
|
ExtensionCommandContext,
|
|
24
24
|
ExtensionContext,
|
|
25
25
|
} from "@gsd/pi-coding-agent";
|
|
26
|
-
import {
|
|
27
|
-
createBashTool,
|
|
28
|
-
createEditTool,
|
|
29
|
-
createReadTool,
|
|
30
|
-
createWriteTool,
|
|
31
|
-
importExtensionModule,
|
|
32
|
-
isToolCallEventType,
|
|
33
|
-
} from "@gsd/pi-coding-agent";
|
|
26
|
+
import { createBashTool, createWriteTool, createReadTool, createEditTool, isToolCallEventType } from "@gsd/pi-coding-agent";
|
|
34
27
|
import { Type } from "@sinclair/typebox";
|
|
35
28
|
|
|
36
29
|
import { debugLog, debugTime } from "./debug-logger.js";
|
|
37
|
-
import {
|
|
30
|
+
import { registerGSDCommand } from "./commands.js";
|
|
38
31
|
import { loadToolApiKeys } from "./commands-config.js";
|
|
39
32
|
import { registerExitCommand } from "./exit-command.js";
|
|
40
|
-
import {
|
|
33
|
+
import { registerWorktreeCommand, getWorktreeOriginalCwd, getActiveWorktreeName } from "./worktree-command.js";
|
|
34
|
+
import { getActiveAutoWorktreeContext } from "./auto-worktree.js";
|
|
41
35
|
import { saveFile, formatContinue, loadFile, parseContinue, parseSummary, loadActiveOverrides, formatOverridesSection } from "./files.js";
|
|
42
36
|
import { loadPrompt } from "./prompt-loader.js";
|
|
37
|
+
import { deriveState } from "./state.js";
|
|
38
|
+
import { isAutoActive, isAutoPaused, pauseAuto, getAutoDashboardData, getAutoModeStartModel, markToolStart, markToolEnd } from "./auto.js";
|
|
39
|
+
import { isSessionSwitchInFlight, resolveAgentEnd } from "./auto-loop.js";
|
|
43
40
|
import { saveActivityLog } from "./activity-log.js";
|
|
41
|
+
import { checkAutoStartAfterDiscuss, getDiscussionMilestoneId, findMilestoneIds, nextMilestoneId } from "./guided-flow.js";
|
|
42
|
+
import { GSDDashboardOverlay } from "./dashboard-overlay.js";
|
|
43
|
+
import {
|
|
44
|
+
loadEffectiveGSDPreferences,
|
|
45
|
+
renderPreferencesForSystemPrompt,
|
|
46
|
+
resolveAllSkillReferences,
|
|
47
|
+
resolveModelWithFallbacksForUnit,
|
|
48
|
+
getNextFallbackModel,
|
|
49
|
+
isTransientNetworkError,
|
|
50
|
+
} from "./preferences.js";
|
|
44
51
|
import { hasSkillSnapshot, detectNewSkills, formatSkillsXml } from "./skill-discovery.js";
|
|
45
52
|
import {
|
|
46
53
|
resolveSlicePath, resolveSliceFile, resolveTaskFile, resolveTaskFiles, resolveTasksDir,
|
|
@@ -54,48 +61,10 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
54
61
|
import { homedir } from "node:os";
|
|
55
62
|
import { shortcutDesc } from "../shared/mod.js";
|
|
56
63
|
import { Text } from "@gsd/pi-tui";
|
|
64
|
+
import { pauseAutoForProviderError, classifyProviderError } from "./provider-error-pause.js";
|
|
57
65
|
import { toPosixPath } from "../shared/mod.js";
|
|
66
|
+
import { isParallelActive, shutdownParallel } from "./parallel-orchestrator.js";
|
|
58
67
|
import { DEFAULT_BASH_TIMEOUT_SECS } from "./constants.js";
|
|
59
|
-
import { getErrorMessage } from "./error-utils.js";
|
|
60
|
-
|
|
61
|
-
function memoizeImport<T>(loader: () => Promise<T>): () => Promise<T> {
|
|
62
|
-
let promise: Promise<T> | null = null;
|
|
63
|
-
return () => {
|
|
64
|
-
if (!promise) {
|
|
65
|
-
promise = loader();
|
|
66
|
-
}
|
|
67
|
-
return promise;
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const loadAutoModule = memoizeImport(() => importExtensionModule<typeof import("./auto.js")>(import.meta.url, "./auto.js"));
|
|
72
|
-
const loadStateModule = memoizeImport(() => importExtensionModule<typeof import("./state.js")>(import.meta.url, "./state.js"));
|
|
73
|
-
const loadGuidedFlowModule = memoizeImport(() => importExtensionModule<typeof import("./guided-flow.js")>(import.meta.url, "./guided-flow.js"));
|
|
74
|
-
const loadPreferencesModule = memoizeImport(() => importExtensionModule<typeof import("./preferences.js")>(import.meta.url, "./preferences.js"));
|
|
75
|
-
const loadDashboardOverlayModule = memoizeImport(() => importExtensionModule<typeof import("./dashboard-overlay.js")>(import.meta.url, "./dashboard-overlay.js"));
|
|
76
|
-
const loadWorktreeCommandModule = memoizeImport(() => importExtensionModule<typeof import("./worktree-command.js")>(import.meta.url, "./worktree-command.js"));
|
|
77
|
-
const loadAutoWorktreeModule = memoizeImport(() => importExtensionModule<typeof import("./auto-worktree.js")>(import.meta.url, "./auto-worktree.js"));
|
|
78
|
-
const loadProviderErrorPauseModule = memoizeImport(() => importExtensionModule<typeof import("./provider-error-pause.js")>(import.meta.url, "./provider-error-pause.js"));
|
|
79
|
-
const loadParallelOrchestratorModule = memoizeImport(() => importExtensionModule<typeof import("./parallel-orchestrator.js")>(import.meta.url, "./parallel-orchestrator.js"));
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Ensure the GSD database is available, auto-initializing if needed.
|
|
83
|
-
* Returns true if the DB is ready, false if initialization failed.
|
|
84
|
-
*/
|
|
85
|
-
async function ensureDbAvailable(): Promise<boolean> {
|
|
86
|
-
try {
|
|
87
|
-
const db = await importExtensionModule<typeof import("./gsd-db.js")>(import.meta.url, "./gsd-db.js");
|
|
88
|
-
if (db.isDbAvailable()) return true;
|
|
89
|
-
|
|
90
|
-
// Auto-initialize: open (and create if needed) the DB at the standard path
|
|
91
|
-
const gsdDir = gsdRoot(process.cwd());
|
|
92
|
-
if (!existsSync(gsdDir)) return false; // No GSD project — can't create DB
|
|
93
|
-
const dbPath = join(gsdDir, "gsd.db");
|
|
94
|
-
return db.openDatabase(dbPath);
|
|
95
|
-
} catch {
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
68
|
|
|
100
69
|
// ── Agent Instructions ────────────────────────────────────────────────────
|
|
101
70
|
// Lightweight "always follow" files injected into every GSD agent session.
|
|
@@ -126,9 +95,7 @@ function loadAgentInstructions(): string | null {
|
|
|
126
95
|
}
|
|
127
96
|
|
|
128
97
|
// ── Depth verification state ──────────────────────────────────────────────
|
|
129
|
-
|
|
130
|
-
// Single-milestone flows set '*' (wildcard). Multi-milestone flows set per-ID.
|
|
131
|
-
const depthVerifiedMilestones = new Set<string>();
|
|
98
|
+
let depthVerificationDone = false;
|
|
132
99
|
|
|
133
100
|
// ── Queue phase tracking ──────────────────────────────────────────────────
|
|
134
101
|
// When true, the LLM is in a queue flow writing CONTEXT.md files.
|
|
@@ -139,28 +106,11 @@ let activeQueuePhase = false;
|
|
|
139
106
|
// Tracks per-model retry attempts for transient network errors.
|
|
140
107
|
// Cleared when a model switch occurs or retries are exhausted.
|
|
141
108
|
const networkRetryCounters = new Map<string, number>();
|
|
142
|
-
|
|
143
|
-
// ── Transient error escalation ───────────────────────────────────────────
|
|
144
|
-
// Tracks consecutive transient auto-resume attempts. Each attempt doubles
|
|
145
|
-
// the delay. After MAX_TRANSIENT_AUTO_RESUMES consecutive failures, auto-mode
|
|
146
|
-
// pauses indefinitely to avoid infinite rapid-fire retries (#1166).
|
|
147
|
-
const MAX_TRANSIENT_AUTO_RESUMES = 5;
|
|
109
|
+
const MAX_TRANSIENT_AUTO_RESUMES = 3;
|
|
148
110
|
let consecutiveTransientErrors = 0;
|
|
149
111
|
|
|
150
112
|
export function isDepthVerified(): boolean {
|
|
151
|
-
return
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** Check whether a specific milestone has passed depth verification. */
|
|
155
|
-
export function isDepthVerifiedFor(milestoneId: string): boolean {
|
|
156
|
-
// Wildcard means "all milestones verified" (single-milestone flow)
|
|
157
|
-
if (depthVerifiedMilestones.has("*")) return true;
|
|
158
|
-
return depthVerifiedMilestones.has(milestoneId);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** Mark a specific milestone as depth-verified. */
|
|
162
|
-
export function markDepthVerified(milestoneId: string): void {
|
|
163
|
-
depthVerifiedMilestones.add(milestoneId);
|
|
113
|
+
return depthVerificationDone;
|
|
164
114
|
}
|
|
165
115
|
|
|
166
116
|
/** Check whether a queue phase is active. */
|
|
@@ -191,25 +141,11 @@ export function shouldBlockContextWrite(
|
|
|
191
141
|
if (!inDiscussion && !inQueue) return { block: false };
|
|
192
142
|
|
|
193
143
|
if (!MILESTONE_CONTEXT_RE.test(inputPath)) return { block: false };
|
|
194
|
-
|
|
195
|
-
// For discussion flows: check global depth verification (backward compat)
|
|
196
|
-
if (inDiscussion && depthVerified) return { block: false };
|
|
197
|
-
|
|
198
|
-
// For queue flows: extract milestone ID from the path and check per-milestone verification
|
|
199
|
-
if (inQueue) {
|
|
200
|
-
const pathMatch = inputPath.match(/\/(M\d+(?:-[a-z0-9]{6})?)-CONTEXT\.md$/);
|
|
201
|
-
const targetMid = pathMatch?.[1];
|
|
202
|
-
if (targetMid && depthVerifiedMilestones.has(targetMid)) return { block: false };
|
|
203
|
-
// Wildcard passes all
|
|
204
|
-
if (depthVerifiedMilestones.has("*")) return { block: false };
|
|
205
|
-
}
|
|
144
|
+
if (depthVerified) return { block: false };
|
|
206
145
|
|
|
207
146
|
return {
|
|
208
147
|
block: true,
|
|
209
|
-
reason: `Blocked: Cannot write milestone CONTEXT.md without depth verification.
|
|
210
|
-
`Use ask_user_questions with a question id containing "depth_verification" first. ` +
|
|
211
|
-
`For multi-milestone flows, include the milestone ID in the question id (e.g., "depth_verification_M001"). ` +
|
|
212
|
-
`This ensures each milestone's context has been critically examined before being written.`,
|
|
148
|
+
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.`,
|
|
213
149
|
};
|
|
214
150
|
}
|
|
215
151
|
|
|
@@ -224,8 +160,8 @@ const GSD_LOGO_LINES = [
|
|
|
224
160
|
];
|
|
225
161
|
|
|
226
162
|
export default function (pi: ExtensionAPI) {
|
|
227
|
-
|
|
228
|
-
|
|
163
|
+
registerGSDCommand(pi);
|
|
164
|
+
registerWorktreeCommand(pi);
|
|
229
165
|
registerExitCommand(pi);
|
|
230
166
|
|
|
231
167
|
// ── EPIPE guard — prevent crash when stdout/stderr pipe closes unexpectedly ──
|
|
@@ -235,22 +171,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
235
171
|
// chance to persist state and pause instead of crashing (see issue #739).
|
|
236
172
|
if (!process.listeners("uncaughtException").some(l => l.name === "_gsdEpipeGuard")) {
|
|
237
173
|
const _gsdEpipeGuard = (err: Error): void => {
|
|
238
|
-
|
|
239
|
-
if (code === "EPIPE") {
|
|
174
|
+
if ((err as NodeJS.ErrnoException).code === "EPIPE") {
|
|
240
175
|
// Pipe closed — nothing we can write; just exit cleanly
|
|
241
176
|
process.exit(0);
|
|
242
177
|
}
|
|
243
|
-
//
|
|
244
|
-
// sleep, heavy event loop stall, or filesystem precision mismatch on Node.js
|
|
245
|
-
// v25+). The onCompromised callback already set _lockCompromised = true, but
|
|
246
|
-
// due to a subtle interaction between the synchronous fs adapter and the
|
|
247
|
-
// setTimeout boundary, the error can still propagate here as an uncaught
|
|
248
|
-
// exception. Exit cleanly so the process.once("exit") handler removes the
|
|
249
|
-
// lock directory — allowing the next session to acquire cleanly (#1322).
|
|
250
|
-
if (code === "ECOMPROMISED") {
|
|
251
|
-
process.exit(1);
|
|
252
|
-
}
|
|
253
|
-
// Re-throw anything that isn't EPIPE or ECOMPROMISED so real crashes still surface
|
|
178
|
+
// Re-throw anything that isn't EPIPE so real crashes still surface
|
|
254
179
|
throw err;
|
|
255
180
|
};
|
|
256
181
|
process.on("uncaughtException", _gsdEpipeGuard);
|
|
@@ -371,8 +296,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
371
296
|
when_context: Type.Optional(Type.String({ description: "When/context for the decision (e.g. milestone ID)" })),
|
|
372
297
|
}),
|
|
373
298
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
374
|
-
//
|
|
375
|
-
|
|
299
|
+
// Check DB availability
|
|
300
|
+
let dbAvailable = false;
|
|
301
|
+
try {
|
|
302
|
+
const db = await import("./gsd-db.js");
|
|
303
|
+
dbAvailable = db.isDbAvailable();
|
|
304
|
+
} catch { /* dynamic import failed */ }
|
|
305
|
+
|
|
306
|
+
if (!dbAvailable) {
|
|
376
307
|
return {
|
|
377
308
|
content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot save decision." }],
|
|
378
309
|
isError: true,
|
|
@@ -381,7 +312,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
381
312
|
}
|
|
382
313
|
|
|
383
314
|
try {
|
|
384
|
-
const { saveDecisionToDb } = await
|
|
315
|
+
const { saveDecisionToDb } = await import("./db-writer.js");
|
|
385
316
|
const { id } = await saveDecisionToDb(
|
|
386
317
|
{
|
|
387
318
|
scope: params.scope,
|
|
@@ -398,7 +329,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
398
329
|
details: { operation: "save_decision", id },
|
|
399
330
|
};
|
|
400
331
|
} catch (err) {
|
|
401
|
-
const msg =
|
|
332
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
402
333
|
process.stderr.write(`gsd-db: gsd_save_decision tool failed: ${msg}\n`);
|
|
403
334
|
return {
|
|
404
335
|
content: [{ type: "text" as const, text: `Error saving decision: ${msg}` }],
|
|
@@ -432,8 +363,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
432
363
|
supporting_slices: Type.Optional(Type.String({ description: "Supporting slices" })),
|
|
433
364
|
}),
|
|
434
365
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
435
|
-
|
|
436
|
-
|
|
366
|
+
let dbAvailable = false;
|
|
367
|
+
try {
|
|
368
|
+
const db = await import("./gsd-db.js");
|
|
369
|
+
dbAvailable = db.isDbAvailable();
|
|
370
|
+
} catch { /* dynamic import failed */ }
|
|
371
|
+
|
|
372
|
+
if (!dbAvailable) {
|
|
437
373
|
return {
|
|
438
374
|
content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot update requirement." }],
|
|
439
375
|
isError: true,
|
|
@@ -443,7 +379,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
443
379
|
|
|
444
380
|
try {
|
|
445
381
|
// Verify requirement exists
|
|
446
|
-
const db = await
|
|
382
|
+
const db = await import("./gsd-db.js");
|
|
447
383
|
const existing = db.getRequirementById(params.id);
|
|
448
384
|
if (!existing) {
|
|
449
385
|
return {
|
|
@@ -453,7 +389,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
453
389
|
};
|
|
454
390
|
}
|
|
455
391
|
|
|
456
|
-
const { updateRequirementInDb } = await
|
|
392
|
+
const { updateRequirementInDb } = await import("./db-writer.js");
|
|
457
393
|
const updates: Record<string, string | undefined> = {};
|
|
458
394
|
if (params.status !== undefined) updates.status = params.status;
|
|
459
395
|
if (params.validation !== undefined) updates.validation = params.validation;
|
|
@@ -469,7 +405,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
469
405
|
details: { operation: "update_requirement", id: params.id },
|
|
470
406
|
};
|
|
471
407
|
} catch (err) {
|
|
472
|
-
const msg =
|
|
408
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
473
409
|
process.stderr.write(`gsd-db: gsd_update_requirement tool failed: ${msg}\n`);
|
|
474
410
|
return {
|
|
475
411
|
content: [{ type: "text" as const, text: `Error updating requirement: ${msg}` }],
|
|
@@ -501,8 +437,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
501
437
|
content: Type.String({ description: "The full markdown content of the artifact" }),
|
|
502
438
|
}),
|
|
503
439
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
504
|
-
|
|
505
|
-
|
|
440
|
+
let dbAvailable = false;
|
|
441
|
+
try {
|
|
442
|
+
const db = await import("./gsd-db.js");
|
|
443
|
+
dbAvailable = db.isDbAvailable();
|
|
444
|
+
} catch { /* dynamic import failed */ }
|
|
445
|
+
|
|
446
|
+
if (!dbAvailable) {
|
|
506
447
|
return {
|
|
507
448
|
content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot save artifact." }],
|
|
508
449
|
isError: true,
|
|
@@ -531,7 +472,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
531
472
|
relativePath = `milestones/${params.milestone_id}/${params.milestone_id}-${params.artifact_type}.md`;
|
|
532
473
|
}
|
|
533
474
|
|
|
534
|
-
const { saveArtifactToDb } = await
|
|
475
|
+
const { saveArtifactToDb } = await import("./db-writer.js");
|
|
535
476
|
await saveArtifactToDb(
|
|
536
477
|
{
|
|
537
478
|
path: relativePath,
|
|
@@ -549,7 +490,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
549
490
|
details: { operation: "save_summary", path: relativePath, artifact_type: params.artifact_type },
|
|
550
491
|
};
|
|
551
492
|
} catch (err) {
|
|
552
|
-
const msg =
|
|
493
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
553
494
|
process.stderr.write(`gsd-db: gsd_save_summary tool failed: ${msg}\n`);
|
|
554
495
|
return {
|
|
555
496
|
content: [{ type: "text" as const, text: `Error saving artifact: ${msg}` }],
|
|
@@ -586,10 +527,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
586
527
|
parameters: Type.Object({}),
|
|
587
528
|
async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
|
|
588
529
|
try {
|
|
589
|
-
const [{ findMilestoneIds, nextMilestoneId }, { loadEffectiveGSDPreferences }] = await Promise.all([
|
|
590
|
-
loadGuidedFlowModule(),
|
|
591
|
-
loadPreferencesModule(),
|
|
592
|
-
]);
|
|
593
530
|
const basePath = process.cwd();
|
|
594
531
|
const existingIds = findMilestoneIds(basePath);
|
|
595
532
|
const uniqueEnabled = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
@@ -602,7 +539,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
602
539
|
details: { operation: "generate_milestone_id", id: newId, existingCount: existingIds.length, reservedCount: reservedMilestoneIds.size, uniqueEnabled },
|
|
603
540
|
};
|
|
604
541
|
} catch (err) {
|
|
605
|
-
const msg =
|
|
542
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
606
543
|
return {
|
|
607
544
|
content: [{ type: "text" as const, text: `Error generating milestone ID: ${msg}` }],
|
|
608
545
|
isError: true,
|
|
@@ -614,9 +551,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
614
551
|
|
|
615
552
|
// ── session_start: render branded GSD header + load tool keys + remote status ──
|
|
616
553
|
pi.on("session_start", async (_event, ctx) => {
|
|
617
|
-
// Clear
|
|
618
|
-
|
|
619
|
-
activeQueuePhase = false;
|
|
554
|
+
// Clear per-session state that must not leak across sessions (e.g. RPC mode)
|
|
555
|
+
depthVerificationDone = false;
|
|
620
556
|
|
|
621
557
|
// Theme access throws in RPC mode (no TUI) — header is decorative, skip it
|
|
622
558
|
try {
|
|
@@ -635,17 +571,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
635
571
|
// Load tool API keys from auth.json into environment
|
|
636
572
|
loadToolApiKeys();
|
|
637
573
|
|
|
638
|
-
// Always-on health widget — ambient system health signal below the editor
|
|
639
|
-
try {
|
|
640
|
-
const { initHealthWidget } = await importExtensionModule<typeof import("./health-widget.js")>(import.meta.url, "./health-widget.js");
|
|
641
|
-
initHealthWidget(ctx);
|
|
642
|
-
} catch { /* non-fatal — widget is best-effort */ }
|
|
643
|
-
|
|
644
574
|
// Notify remote questions status if configured
|
|
645
575
|
try {
|
|
646
576
|
const [{ getRemoteConfigStatus }, { getLatestPromptSummary }] = await Promise.all([
|
|
647
|
-
|
|
648
|
-
|
|
577
|
+
import("../remote-questions/config.js"),
|
|
578
|
+
import("../remote-questions/status.js"),
|
|
649
579
|
]);
|
|
650
580
|
const status = getRemoteConfigStatus();
|
|
651
581
|
const latest = getLatestPromptSummary();
|
|
@@ -663,13 +593,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
663
593
|
description: shortcutDesc("Open GSD dashboard", "/gsd status"),
|
|
664
594
|
handler: async (ctx) => {
|
|
665
595
|
// Only show if .gsd/ exists
|
|
666
|
-
if (!existsSync(
|
|
596
|
+
if (!existsSync(join(process.cwd(), ".gsd"))) {
|
|
667
597
|
ctx.ui.notify("No .gsd/ directory found. Run /gsd to start.", "info");
|
|
668
598
|
return;
|
|
669
599
|
}
|
|
670
600
|
|
|
671
|
-
|
|
672
|
-
const result = await ctx.ui.custom<void>(
|
|
601
|
+
await ctx.ui.custom<void>(
|
|
673
602
|
(tui, theme, _kb, done) => {
|
|
674
603
|
return new GSDDashboardOverlay(tui, theme, () => done());
|
|
675
604
|
},
|
|
@@ -683,23 +612,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
683
612
|
},
|
|
684
613
|
},
|
|
685
614
|
);
|
|
686
|
-
|
|
687
|
-
// Fallback for RPC mode where ctx.ui.custom() returns undefined.
|
|
688
|
-
if (result === undefined) {
|
|
689
|
-
const { fireStatusViaCommand } = await importExtensionModule<typeof import("./commands.js")>(import.meta.url, "./commands.js");
|
|
690
|
-
await fireStatusViaCommand(ctx);
|
|
691
|
-
}
|
|
692
615
|
},
|
|
693
616
|
});
|
|
694
617
|
|
|
695
618
|
// ── before_agent_start: inject GSD contract into true system prompt ─────
|
|
696
619
|
pi.on("before_agent_start", async (event, ctx: ExtensionContext) => {
|
|
697
|
-
if (!existsSync(
|
|
620
|
+
if (!existsSync(join(process.cwd(), ".gsd"))) return;
|
|
698
621
|
|
|
699
622
|
const stopContextTimer = debugTime("context-inject");
|
|
700
623
|
const systemContent = loadPrompt("system");
|
|
701
|
-
const { loadEffectiveGSDPreferences, resolveAllSkillReferences, renderPreferencesForSystemPrompt } =
|
|
702
|
-
await loadPreferencesModule();
|
|
703
624
|
const loadedPreferences = loadEffectiveGSDPreferences();
|
|
704
625
|
let preferenceBlock = "";
|
|
705
626
|
if (loadedPreferences) {
|
|
@@ -733,7 +654,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
733
654
|
// Inject auto-learned project memories
|
|
734
655
|
let memoryBlock = "";
|
|
735
656
|
try {
|
|
736
|
-
const { getActiveMemoriesRanked, formatMemoriesForPrompt } = await
|
|
657
|
+
const { getActiveMemoriesRanked, formatMemoriesForPrompt } = await import("./memory-store.js");
|
|
737
658
|
const memories = getActiveMemoriesRanked(30);
|
|
738
659
|
if (memories.length > 0) {
|
|
739
660
|
const formatted = formatMemoriesForPrompt(memories, 2000);
|
|
@@ -763,10 +684,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
763
684
|
|
|
764
685
|
// Worktree context — override the static CWD in the system prompt
|
|
765
686
|
let worktreeBlock = "";
|
|
766
|
-
const [{ getActiveWorktreeName, getWorktreeOriginalCwd }, { getActiveAutoWorktreeContext }] = await Promise.all([
|
|
767
|
-
loadWorktreeCommandModule(),
|
|
768
|
-
loadAutoWorktreeModule(),
|
|
769
|
-
]);
|
|
770
687
|
const worktreeName = getActiveWorktreeName();
|
|
771
688
|
const worktreeMainCwd = getWorktreeOriginalCwd();
|
|
772
689
|
const autoWorktree = getActiveAutoWorktreeContext();
|
|
@@ -830,37 +747,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
830
747
|
|
|
831
748
|
// ── agent_end: auto-mode advancement or auto-start after discuss ───────────
|
|
832
749
|
pi.on("agent_end", async (event, ctx: ExtensionContext) => {
|
|
833
|
-
const [
|
|
834
|
-
{
|
|
835
|
-
isAutoActive,
|
|
836
|
-
pauseAuto,
|
|
837
|
-
getAutoDashboardData,
|
|
838
|
-
getAutoModeStartModel,
|
|
839
|
-
handleAgentEnd,
|
|
840
|
-
},
|
|
841
|
-
{ checkAutoStartAfterDiscuss },
|
|
842
|
-
{
|
|
843
|
-
isTransientNetworkError,
|
|
844
|
-
resolveModelWithFallbacksForUnit,
|
|
845
|
-
getNextFallbackModel,
|
|
846
|
-
},
|
|
847
|
-
{ classifyProviderError, pauseAutoForProviderError },
|
|
848
|
-
] = await Promise.all([
|
|
849
|
-
loadAutoModule(),
|
|
850
|
-
loadGuidedFlowModule(),
|
|
851
|
-
loadPreferencesModule(),
|
|
852
|
-
loadProviderErrorPauseModule(),
|
|
853
|
-
]);
|
|
854
|
-
|
|
855
|
-
// Clean up quick-task branch if one just completed (#1269)
|
|
856
|
-
try {
|
|
857
|
-
const { cleanupQuickBranch } = await importExtensionModule<typeof import("./quick.js")>(import.meta.url, "./quick.js");
|
|
858
|
-
cleanupQuickBranch();
|
|
859
|
-
} catch { /* non-fatal */ }
|
|
860
|
-
|
|
861
750
|
// If discuss phase just finished, start auto-mode
|
|
862
751
|
if (checkAutoStartAfterDiscuss()) {
|
|
863
|
-
|
|
752
|
+
depthVerificationDone = false;
|
|
864
753
|
activeQueuePhase = false;
|
|
865
754
|
return;
|
|
866
755
|
}
|
|
@@ -868,6 +757,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
868
757
|
// If auto-mode is already running, advance to next unit
|
|
869
758
|
if (!isAutoActive()) return;
|
|
870
759
|
|
|
760
|
+
// Fresh-session auto-mode intentionally aborts the previous session during
|
|
761
|
+
// cmdCtx.newSession(). Ignore that agent_end so we neither pause nor
|
|
762
|
+
// resolve the new unit with an event from the old session.
|
|
763
|
+
if (isSessionSwitchInFlight()) {
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
|
|
871
767
|
// If the agent was aborted (user pressed Escape) or hit a provider
|
|
872
768
|
// error (fetch failure, rate limit, etc.), pause auto-mode instead of
|
|
873
769
|
// advancing. This preserves the conversation so the user can inspect
|
|
@@ -1007,50 +903,46 @@ export default function (pi: ExtensionAPI) {
|
|
|
1007
903
|
const explicitRetryAfterMs = ("retryAfterMs" in lastMsg && typeof lastMsg.retryAfterMs === "number")
|
|
1008
904
|
? lastMsg.retryAfterMs
|
|
1009
905
|
: undefined;
|
|
1010
|
-
let retryAfterMs = explicitRetryAfterMs ?? classification.suggestedDelayMs;
|
|
1011
|
-
|
|
1012
|
-
// ── Escalating backoff for repeated transient errors ──────────────
|
|
1013
|
-
// Each consecutive transient auto-resume doubles the delay. After
|
|
1014
|
-
// MAX_TRANSIENT_AUTO_RESUMES consecutive failures, treat as permanent
|
|
1015
|
-
// to avoid infinite rapid-fire retries (#1166).
|
|
1016
|
-
let effectiveTransient = classification.isTransient;
|
|
1017
906
|
if (classification.isTransient) {
|
|
1018
|
-
consecutiveTransientErrors
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
907
|
+
consecutiveTransientErrors += 1;
|
|
908
|
+
} else {
|
|
909
|
+
consecutiveTransientErrors = 0;
|
|
910
|
+
}
|
|
911
|
+
const baseRetryAfterMs = explicitRetryAfterMs ?? classification.suggestedDelayMs;
|
|
912
|
+
const retryAfterMs = classification.isTransient ? baseRetryAfterMs * 2 ** Math.max(0, consecutiveTransientErrors - 1) : baseRetryAfterMs;
|
|
913
|
+
const allowAutoResume = classification.isTransient
|
|
914
|
+
&& consecutiveTransientErrors <= MAX_TRANSIENT_AUTO_RESUMES;
|
|
915
|
+
|
|
916
|
+
if (classification.isTransient && !allowAutoResume) {
|
|
917
|
+
ctx.ui.notify(
|
|
918
|
+
`Transient provider errors persisted after ${MAX_TRANSIENT_AUTO_RESUMES} auto-resume attempts. Pausing for manual review.`,
|
|
919
|
+
"warning",
|
|
920
|
+
);
|
|
1030
921
|
}
|
|
1031
922
|
|
|
1032
923
|
await pauseAutoForProviderError(ctx.ui, errorDetail, () => pauseAuto(ctx, pi), {
|
|
1033
924
|
isRateLimit: classification.isRateLimit,
|
|
1034
|
-
isTransient:
|
|
925
|
+
isTransient: allowAutoResume,
|
|
1035
926
|
retryAfterMs,
|
|
1036
|
-
resume:
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
927
|
+
resume: allowAutoResume
|
|
928
|
+
? () => {
|
|
929
|
+
pi.sendMessage(
|
|
930
|
+
{ customType: "gsd-auto-timeout-recovery", content: "Continue execution \u2014 provider error recovery delay elapsed.", display: false },
|
|
931
|
+
{ triggerTurn: true },
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
: undefined,
|
|
1042
935
|
});
|
|
1043
936
|
return;
|
|
1044
937
|
}
|
|
1045
938
|
|
|
1046
939
|
try {
|
|
940
|
+
consecutiveTransientErrors = 0;
|
|
1047
941
|
networkRetryCounters.clear(); // Clear network retry state on successful unit completion
|
|
1048
|
-
|
|
1049
|
-
await handleAgentEnd(ctx, pi);
|
|
942
|
+
resolveAgentEnd(event);
|
|
1050
943
|
} catch (err) {
|
|
1051
|
-
// Safety net: if
|
|
1052
|
-
|
|
1053
|
-
const message = getErrorMessage(err);
|
|
944
|
+
// Safety net: if resolveAgentEnd throws, ensure auto-mode stops gracefully (#381).
|
|
945
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1054
946
|
ctx.ui.notify(
|
|
1055
947
|
`Auto-mode error in agent_end handler: ${message}. Stopping auto-mode.`,
|
|
1056
948
|
"error",
|
|
@@ -1065,11 +957,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
1065
957
|
|
|
1066
958
|
// ── session_before_compact ────────────────────────────────────────────────
|
|
1067
959
|
pi.on("session_before_compact", async (_event, _ctx: ExtensionContext) => {
|
|
1068
|
-
const [{ isAutoActive, isAutoPaused }, { deriveState }] = await Promise.all([
|
|
1069
|
-
loadAutoModule(),
|
|
1070
|
-
loadStateModule(),
|
|
1071
|
-
]);
|
|
1072
|
-
|
|
1073
960
|
// Block compaction during auto-mode — each unit is a fresh session
|
|
1074
961
|
// Also block during paused state — context is valuable for the user
|
|
1075
962
|
if (isAutoActive() || isAutoPaused()) {
|
|
@@ -1116,31 +1003,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
1116
1003
|
|
|
1117
1004
|
// ── session_shutdown: save activity log on Ctrl+C / SIGTERM ─────────────
|
|
1118
1005
|
pi.on("session_shutdown", async (_event, ctx: ExtensionContext) => {
|
|
1119
|
-
const [{ isParallelActive, shutdownParallel }, { isAutoActive, isAutoPaused, getAutoDashboardData }] =
|
|
1120
|
-
await Promise.all([
|
|
1121
|
-
loadParallelOrchestratorModule(),
|
|
1122
|
-
loadAutoModule(),
|
|
1123
|
-
]);
|
|
1124
|
-
|
|
1125
1006
|
if (isParallelActive()) {
|
|
1126
1007
|
try {
|
|
1127
1008
|
await shutdownParallel(process.cwd());
|
|
1128
1009
|
} catch { /* best-effort */ }
|
|
1129
1010
|
}
|
|
1130
1011
|
|
|
1131
|
-
// Auto-commit dirty work in CLI-spawned worktrees so nothing is lost.
|
|
1132
|
-
// The CLI sets GSD_CLI_WORKTREE when launched with -w.
|
|
1133
|
-
const cliWorktree = process.env.GSD_CLI_WORKTREE;
|
|
1134
|
-
if (cliWorktree) {
|
|
1135
|
-
try {
|
|
1136
|
-
const { autoCommitCurrentBranch } = await importExtensionModule<typeof import("./worktree.js")>(import.meta.url, "./worktree.js");
|
|
1137
|
-
const msg = autoCommitCurrentBranch(process.cwd(), "session-end", cliWorktree);
|
|
1138
|
-
if (msg) {
|
|
1139
|
-
ctx.ui.notify(`Auto-committed worktree ${cliWorktree} before exit.`, "info");
|
|
1140
|
-
}
|
|
1141
|
-
} catch { /* best-effort */ }
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
1012
|
if (!isAutoActive() && !isAutoPaused()) return;
|
|
1145
1013
|
|
|
1146
1014
|
// Save the current session — the lock file stays on disk
|
|
@@ -1151,14 +1019,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
1151
1019
|
}
|
|
1152
1020
|
});
|
|
1153
1021
|
|
|
1154
|
-
// ── tool_call: block CONTEXT.md writes without depth verification ──
|
|
1155
|
-
// Active during both discussion flows (pendingAutoStart set) and
|
|
1156
|
-
// queue flows (activeQueuePhase set). For multi-milestone queue flows,
|
|
1157
|
-
// each milestone must pass its own depth verification before its
|
|
1158
|
-
// CONTEXT.md can be written.
|
|
1022
|
+
// ── tool_call: block CONTEXT.md writes during discussion without depth verification ──
|
|
1159
1023
|
pi.on("tool_call", async (event) => {
|
|
1160
1024
|
if (!isToolCallEventType("write", event)) return;
|
|
1161
|
-
const { getDiscussionMilestoneId } = await loadGuidedFlowModule();
|
|
1162
1025
|
const result = shouldBlockContextWrite(
|
|
1163
1026
|
event.toolName,
|
|
1164
1027
|
event.input.path,
|
|
@@ -1170,43 +1033,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
1170
1033
|
});
|
|
1171
1034
|
|
|
1172
1035
|
// ── tool_result: persist discussion exchanges & detect depth gate ──────
|
|
1173
|
-
// Handles both discussion flows and queue flows. For queue flows,
|
|
1174
|
-
// depth verification question IDs may include milestone IDs
|
|
1175
|
-
// (e.g., "depth_verification_M001") for per-milestone gating.
|
|
1176
1036
|
pi.on("tool_result", async (event) => {
|
|
1177
1037
|
if (event.toolName !== "ask_user_questions") return;
|
|
1178
1038
|
|
|
1179
|
-
const { getDiscussionMilestoneId } = await loadGuidedFlowModule();
|
|
1180
1039
|
const milestoneId = getDiscussionMilestoneId();
|
|
1181
|
-
|
|
1182
|
-
// Depth gate detection still applies — it sets per-milestone flags.
|
|
1183
|
-
const inQueue = activeQueuePhase;
|
|
1040
|
+
if (!milestoneId) return;
|
|
1184
1041
|
|
|
1185
1042
|
const details = event.details as any;
|
|
1186
1043
|
if (details?.cancelled || !details?.response) return;
|
|
1187
1044
|
|
|
1188
1045
|
// ── Depth gate detection ──────────────────────────────────────────
|
|
1189
|
-
// Supports two patterns:
|
|
1190
|
-
// 1. "depth_verification" — wildcard, marks all milestones verified
|
|
1191
|
-
// 2. "depth_verification_M001" — per-milestone verification
|
|
1192
1046
|
const questions: any[] = (event.input as any)?.questions ?? [];
|
|
1193
1047
|
for (const q of questions) {
|
|
1194
1048
|
if (typeof q.id === "string" && q.id.includes("depth_verification")) {
|
|
1195
|
-
|
|
1196
|
-
const midMatch = q.id.match(/depth_verification[_-](M\d+(?:-[a-z0-9]{6})?)/i);
|
|
1197
|
-
if (midMatch) {
|
|
1198
|
-
depthVerifiedMilestones.add(midMatch[1]);
|
|
1199
|
-
} else {
|
|
1200
|
-
// Wildcard — all milestones verified (backward compat for single-milestone)
|
|
1201
|
-
depthVerifiedMilestones.add("*");
|
|
1202
|
-
}
|
|
1049
|
+
depthVerificationDone = true;
|
|
1203
1050
|
break;
|
|
1204
1051
|
}
|
|
1205
1052
|
}
|
|
1206
1053
|
|
|
1207
|
-
// Discussion persistence only applies when in a discussion flow with a known milestone
|
|
1208
|
-
if (!milestoneId) return;
|
|
1209
|
-
|
|
1210
1054
|
// ── Persist exchange to DISCUSSION.md ──────────────────────────────
|
|
1211
1055
|
const basePath = process.cwd();
|
|
1212
1056
|
const milestoneDir = resolveMilestonePath(basePath, milestoneId);
|
|
@@ -1252,13 +1096,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
1252
1096
|
|
|
1253
1097
|
// ── tool_execution_start/end: track in-flight tools for idle detection ──
|
|
1254
1098
|
pi.on("tool_execution_start", async (event) => {
|
|
1255
|
-
const { isAutoActive, markToolStart } = await loadAutoModule();
|
|
1256
1099
|
if (!isAutoActive()) return;
|
|
1257
1100
|
markToolStart(event.toolCallId);
|
|
1258
1101
|
});
|
|
1259
1102
|
|
|
1260
1103
|
pi.on("tool_execution_end", async (event) => {
|
|
1261
|
-
const { markToolEnd } = await loadAutoModule();
|
|
1262
1104
|
markToolEnd(event.toolCallId);
|
|
1263
1105
|
});
|
|
1264
1106
|
}
|
|
@@ -1273,7 +1115,6 @@ async function buildGuidedExecuteContextInjection(prompt: string, basePath: stri
|
|
|
1273
1115
|
const resumeMatch = prompt.match(/Resume interrupted work\.[\s\S]*?slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i);
|
|
1274
1116
|
if (resumeMatch) {
|
|
1275
1117
|
const [, sliceId, milestoneId] = resumeMatch;
|
|
1276
|
-
const { deriveState } = await loadStateModule();
|
|
1277
1118
|
const state = await deriveState(basePath);
|
|
1278
1119
|
if (
|
|
1279
1120
|
state.activeMilestone?.id === milestoneId &&
|