gsd-pi 2.37.1-dev.d3ace49 → 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.
- package/dist/app-paths.js +1 -1
- package/dist/cli.js +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- package/dist/extension-registry.js +2 -2
- package/dist/remote-questions-config.js +2 -2
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/env-utils.js +29 -0
- package/dist/resources/extensions/get-secrets-from-user.js +5 -24
- package/dist/resources/extensions/google-search/package.json +3 -1
- package/dist/resources/extensions/gsd/auto/session.js +6 -23
- package/dist/resources/extensions/gsd/auto-dispatch.js +7 -8
- package/dist/resources/extensions/gsd/auto-loop.js +68 -97
- package/dist/resources/extensions/gsd/auto-post-unit.js +75 -71
- package/dist/resources/extensions/gsd/auto-prompts.js +7 -31
- package/dist/resources/extensions/gsd/auto-start.js +13 -2
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
- package/dist/resources/extensions/gsd/auto.js +143 -96
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +22 -2
- package/dist/resources/extensions/gsd/context-budget.js +2 -10
- package/dist/resources/extensions/gsd/detection.js +1 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +27 -11
- package/dist/resources/extensions/gsd/doctor.js +184 -11
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +2 -2
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/index.js +2 -1
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/preferences-models.js +0 -12
- package/dist/resources/extensions/gsd/preferences-types.js +0 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +1 -11
- package/dist/resources/extensions/gsd/preferences.js +5 -5
- package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/dist/resources/extensions/gsd/prompts/run-uat.md +25 -10
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/dist/resources/extensions/gsd/repo-identity.js +21 -4
- package/dist/resources/extensions/gsd/resource-version.js +2 -1
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/remote-questions/status.js +2 -1
- package/dist/resources/extensions/remote-questions/store.js +2 -1
- package/dist/resources/extensions/search-the-web/provider.js +2 -1
- package/dist/resources/extensions/subagent/index.js +12 -3
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +12 -0
- package/dist/welcome-screen.js +53 -0
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/index.ts +57 -1
- package/src/resources/extensions/env-utils.ts +31 -0
- package/src/resources/extensions/get-secrets-from-user.ts +5 -24
- package/src/resources/extensions/gsd/auto/session.ts +7 -25
- package/src/resources/extensions/gsd/auto-dispatch.ts +6 -8
- package/src/resources/extensions/gsd/auto-loop.ts +88 -133
- package/src/resources/extensions/gsd/auto-post-unit.ts +52 -42
- package/src/resources/extensions/gsd/auto-prompts.ts +7 -33
- package/src/resources/extensions/gsd/auto-start.ts +18 -2
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
- package/src/resources/extensions/gsd/auto.ts +139 -101
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +24 -2
- package/src/resources/extensions/gsd/context-budget.ts +2 -12
- package/src/resources/extensions/gsd/detection.ts +2 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +26 -9
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +177 -13
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +2 -2
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/index.ts +3 -1
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +0 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +1 -11
- package/src/resources/extensions/gsd/preferences.ts +5 -5
- package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/src/resources/extensions/gsd/prompts/run-uat.md +25 -10
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/src/resources/extensions/gsd/repo-identity.ts +23 -4
- package/src/resources/extensions/gsd/resource-version.ts +3 -1
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +11 -31
- package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
- package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
- package/src/resources/extensions/gsd/types.ts +0 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/remote-questions/status.ts +3 -1
- package/src/resources/extensions/remote-questions/store.ts +3 -1
- package/src/resources/extensions/search-the-web/provider.ts +2 -1
- package/src/resources/extensions/subagent/index.ts +12 -3
- package/src/resources/extensions/subagent/isolation.ts +3 -1
- package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
- package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
- package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
- package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
- package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
- package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
- package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
- package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
- package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
- package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
- package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
- 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:
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
|
|
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
|
-
//
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
38
|
-
* the event is
|
|
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
|
-
|
|
42
|
-
|
|
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 (
|
|
50
|
+
if (_currentResolve) {
|
|
50
51
|
debugLog("resolveAgentEnd", { status: "resolving", hasEvent: true });
|
|
51
|
-
const r =
|
|
52
|
-
|
|
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: "
|
|
59
|
-
|
|
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
|
|
64
|
+
return _sessionSwitchInFlight;
|
|
67
65
|
}
|
|
68
66
|
// ─── resetPendingResolve (test helper) ───────────────────────────────────────
|
|
69
67
|
/**
|
|
70
|
-
* Reset
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
_activeSession.pendingAgentEndQueue = [];
|
|
77
|
-
}
|
|
78
|
-
_activeSession = null;
|
|
72
|
+
_currentResolve = null;
|
|
73
|
+
_sessionSwitchInFlight = false;
|
|
79
74
|
}
|
|
80
75
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
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(
|
|
85
|
-
|
|
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
|
-
|
|
97
|
+
_sessionSwitchInFlight = true;
|
|
128
98
|
try {
|
|
129
99
|
const sessionPromise = s.cmdCtx.newSession().finally(() => {
|
|
130
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
+
catch (e) {
|
|
95
|
+
debugLog("postUnit", { phase: "auto-commit", error: String(e) });
|
|
95
96
|
}
|
|
96
|
-
// Doctor: fix mechanical bookkeeping
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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
|
-
|
|
143
|
-
|
|
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,27 @@ export async function postUnitPreVerification(pctx) {
|
|
|
148
152
|
const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
|
|
149
153
|
pruneDeadProcesses();
|
|
150
154
|
}
|
|
151
|
-
catch {
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
171
|
+
s.rewriteAttemptCount = 0;
|
|
168
172
|
ctx.ui.notify("Override(s) resolved — rewrite-docs completed.", "info");
|
|
169
173
|
}
|
|
170
|
-
catch {
|
|
171
|
-
|
|
174
|
+
catch (e) {
|
|
175
|
+
debugLog("postUnit", { phase: "rewrite-docs-resolve", error: String(e) });
|
|
172
176
|
}
|
|
173
177
|
}
|
|
174
178
|
// Reactive state cleanup on slice completion
|
|
@@ -181,8 +185,8 @@ export async function postUnitPreVerification(pctx) {
|
|
|
181
185
|
clearReactiveState(s.basePath, mid, sid);
|
|
182
186
|
}
|
|
183
187
|
}
|
|
184
|
-
catch {
|
|
185
|
-
|
|
188
|
+
catch (e) {
|
|
189
|
+
debugLog("postUnit", { phase: "reactive-state-cleanup", error: String(e) });
|
|
186
190
|
}
|
|
187
191
|
}
|
|
188
192
|
// Post-triage: execute actionable resolutions
|
|
@@ -224,8 +228,8 @@ export async function postUnitPreVerification(pctx) {
|
|
|
224
228
|
invalidateAllCaches();
|
|
225
229
|
}
|
|
226
230
|
}
|
|
227
|
-
catch {
|
|
228
|
-
|
|
231
|
+
catch (e) {
|
|
232
|
+
debugLog("postUnit", { phase: "artifact-verify", error: String(e) });
|
|
229
233
|
}
|
|
230
234
|
}
|
|
231
235
|
else {
|
|
@@ -238,8 +242,8 @@ export async function postUnitPreVerification(pctx) {
|
|
|
238
242
|
});
|
|
239
243
|
clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
|
|
240
244
|
}
|
|
241
|
-
catch {
|
|
242
|
-
|
|
245
|
+
catch (e) {
|
|
246
|
+
debugLog("postUnit", { phase: "hook-finalize", error: String(e) });
|
|
243
247
|
}
|
|
244
248
|
}
|
|
245
249
|
}
|
|
@@ -352,8 +356,8 @@ export async function postUnitPostVerification(pctx) {
|
|
|
352
356
|
}
|
|
353
357
|
}
|
|
354
358
|
}
|
|
355
|
-
catch {
|
|
356
|
-
|
|
359
|
+
catch (e) {
|
|
360
|
+
debugLog("postUnit", { phase: "triage-check", error: String(e) });
|
|
357
361
|
}
|
|
358
362
|
}
|
|
359
363
|
// ── Quick-task dispatch ──
|
|
@@ -387,8 +391,8 @@ export async function postUnitPostVerification(pctx) {
|
|
|
387
391
|
ctx.ui.notify(`Executing quick-task: ${capture.id} — "${capture.text}"`, "info");
|
|
388
392
|
return "continue";
|
|
389
393
|
}
|
|
390
|
-
catch {
|
|
391
|
-
|
|
394
|
+
catch (e) {
|
|
395
|
+
debugLog("postUnit", { phase: "quick-task-dispatch", error: String(e) });
|
|
392
396
|
}
|
|
393
397
|
}
|
|
394
398
|
// Step mode → show wizard instead of dispatch
|
|
@@ -12,10 +12,7 @@ import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferen
|
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { existsSync } from "node:fs";
|
|
14
14
|
import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
|
|
15
|
-
import { compressToTarget } from "./prompt-compressor.js";
|
|
16
|
-
import { distillSummaries } from "./summary-distiller.js";
|
|
17
15
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
18
|
-
import { chunkByRelevance, formatChunks } from "./semantic-chunker.js";
|
|
19
16
|
// ─── Executor Constraints ─────────────────────────────────────────────────────
|
|
20
17
|
/**
|
|
21
18
|
* Format executor context constraints for injection into the plan-slice prompt.
|
|
@@ -126,14 +123,10 @@ export async function inlineFileSmart(absPath, relPath, label, query, threshold
|
|
|
126
123
|
if (content.length <= threshold || !query) {
|
|
127
124
|
return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
|
|
128
125
|
}
|
|
129
|
-
//
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
|
|
134
|
-
}
|
|
135
|
-
const formatted = formatChunks(result, relPath);
|
|
136
|
-
return `### ${label} (${result.omittedChunks} sections omitted for relevance)\nSource: \`${relPath}\`\n\n${formatted}`;
|
|
126
|
+
// For large files, truncate at section boundary
|
|
127
|
+
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
128
|
+
const truncated = truncateAtSectionBoundary(content, threshold).content;
|
|
129
|
+
return `### ${label}\nSource: \`${relPath}\`\n\n${truncated}`;
|
|
137
130
|
}
|
|
138
131
|
/**
|
|
139
132
|
* Load and inline dependency slice summaries (full content, not just paths).
|
|
@@ -165,20 +158,6 @@ export async function inlineDependencySummaries(mid, sid, base, budgetChars) {
|
|
|
165
158
|
}
|
|
166
159
|
const result = sections.join("\n\n");
|
|
167
160
|
if (budgetChars !== undefined && result.length > budgetChars) {
|
|
168
|
-
// For 3+ summaries, try distillation first (preserves more information)
|
|
169
|
-
if (sections.length >= 3) {
|
|
170
|
-
const rawSummaries = sections.map(s => {
|
|
171
|
-
// Extract content after the header line
|
|
172
|
-
const lines = s.split("\n");
|
|
173
|
-
const contentStart = lines.findIndex(l => l.startsWith("Source:"));
|
|
174
|
-
return contentStart >= 0 ? lines.slice(contentStart + 1).join("\n").trim() : s;
|
|
175
|
-
});
|
|
176
|
-
const distilled = distillSummaries(rawSummaries, budgetChars);
|
|
177
|
-
if (distilled.content.length <= budgetChars) {
|
|
178
|
-
return distilled.content;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
// Fall back to section-boundary truncation
|
|
182
161
|
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
183
162
|
return truncateAtSectionBoundary(result, budgetChars).content;
|
|
184
163
|
}
|
|
@@ -777,15 +756,12 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
777
756
|
const contextWindow = resolveExecutorContextWindow(undefined, prefs?.preferences);
|
|
778
757
|
const budgets = computeBudgets(contextWindow);
|
|
779
758
|
const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
|
|
780
|
-
//
|
|
781
|
-
// Only compress when compression_strategy is "compress" (budget/balanced profiles).
|
|
759
|
+
// Truncate carry-forward section when it exceeds 40% of inline context budget.
|
|
782
760
|
const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
|
|
783
761
|
let finalCarryForward = carryForwardSection;
|
|
784
762
|
if (carryForwardSection.length > carryForwardBudget) {
|
|
785
|
-
const {
|
|
786
|
-
|
|
787
|
-
finalCarryForward = compressToTarget(carryForwardSection, carryForwardBudget).content;
|
|
788
|
-
}
|
|
763
|
+
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
764
|
+
finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
|
|
789
765
|
}
|
|
790
766
|
return loadPrompt("execute-task", {
|
|
791
767
|
overridesSection,
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { deriveState } from "./state.js";
|
|
12
12
|
import { loadFile, getManifestStatus } from "./files.js";
|
|
13
13
|
import { loadEffectiveGSDPreferences, resolveSkillDiscoveryMode, getIsolationMode, } from "./preferences.js";
|
|
14
|
-
import { ensureGsdSymlink } from "./repo-identity.js";
|
|
14
|
+
import { ensureGsdSymlink, validateProjectId } from "./repo-identity.js";
|
|
15
15
|
import { migrateToExternalState, recoverFailedMigration } from "./migrate-external.js";
|
|
16
16
|
import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
|
|
17
17
|
import { gsdRoot, resolveMilestoneFile } from "./paths.js";
|
|
@@ -63,6 +63,12 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
63
63
|
return false;
|
|
64
64
|
}
|
|
65
65
|
try {
|
|
66
|
+
// Validate GSD_PROJECT_ID early so the user gets immediate feedback
|
|
67
|
+
const customProjectId = process.env.GSD_PROJECT_ID;
|
|
68
|
+
if (customProjectId && !validateProjectId(customProjectId)) {
|
|
69
|
+
ctx.ui.notify(`GSD_PROJECT_ID must contain only alphanumeric characters, hyphens, and underscores. Got: "${customProjectId}"`, "error");
|
|
70
|
+
return releaseLockAndReturn();
|
|
71
|
+
}
|
|
66
72
|
// Ensure git repo exists
|
|
67
73
|
if (!nativeIsRepo(base)) {
|
|
68
74
|
const mainBranch = loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
|
|
@@ -303,11 +309,16 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
303
309
|
// ── Auto-worktree setup ──
|
|
304
310
|
s.originalBasePath = base;
|
|
305
311
|
const isUnderGsdWorktrees = (p) => {
|
|
312
|
+
// Direct layout: /.gsd/worktrees/
|
|
306
313
|
const marker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
|
|
307
314
|
if (p.includes(marker))
|
|
308
315
|
return true;
|
|
309
316
|
const worktreesSuffix = `${pathSep}.gsd${pathSep}worktrees`;
|
|
310
|
-
|
|
317
|
+
if (p.endsWith(worktreesSuffix))
|
|
318
|
+
return true;
|
|
319
|
+
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
320
|
+
const symlinkRe = new RegExp(`\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees(?:\\${pathSep}|$)`);
|
|
321
|
+
return symlinkRe.test(p);
|
|
311
322
|
};
|
|
312
323
|
if (s.currentMilestoneId &&
|
|
313
324
|
shouldUseWorktreeIsolation() &&
|