gsd-pi 2.78.1-dev.8a893322c → 2.78.1-dev.a7b6e59b7
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/cli-auto-routing.d.ts +1 -0
- package/dist/cli-auto-routing.js +5 -0
- package/dist/cli.js +5 -14
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/run-unit.js +23 -11
- package/dist/resources/extensions/gsd/auto-direct-dispatch.js +55 -21
- package/dist/resources/extensions/gsd/auto-prompts.js +6 -0
- package/dist/resources/extensions/gsd/auto-worktree.js +15 -0
- package/dist/resources/extensions/gsd/auto.js +25 -9
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +2 -0
- package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +2 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +24 -0
- package/dist/resources/skills/lint/SKILL.md +4 -0
- package/dist/resources/skills/review/SKILL.md +4 -0
- package/dist/resources/skills/test/SKILL.md +3 -0
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +278 -0
- package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +125 -55
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +319 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +128 -59
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/run-unit.ts +23 -11
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +60 -24
- package/src/resources/extensions/gsd/auto-prompts.ts +6 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +15 -0
- package/src/resources/extensions/gsd/auto.ts +23 -6
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +2 -0
- package/src/resources/extensions/gsd/prompts/rewrite-docs.md +2 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +8 -2
- package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +12 -6
- package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +235 -0
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +85 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +24 -0
- package/src/resources/skills/lint/SKILL.md +4 -0
- package/src/resources/skills/review/SKILL.md +4 -0
- package/src/resources/skills/test/SKILL.md +3 -0
- /package/dist/web/standalone/.next/static/{QK8fABiGPmonfTgboN0Y9 → GlYncvckBGG33CSoJaSnB}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{QK8fABiGPmonfTgboN0Y9 → GlYncvckBGG33CSoJaSnB}/_ssgManifest.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function shouldRedirectAutoToHeadless(subcommand: string | undefined, stdinIsTTY: boolean | undefined, stdoutIsTTY: boolean | undefined): boolean;
|
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import { shouldRunOnboarding, runOnboarding } from './onboarding.js';
|
|
|
9
9
|
import chalk from 'chalk';
|
|
10
10
|
import { checkForUpdates } from './update-check.js';
|
|
11
11
|
import { shouldBypassManagedResourceMismatchGate } from './cli-policy.js';
|
|
12
|
+
import { shouldRedirectAutoToHeadless } from './cli-auto-routing.js';
|
|
12
13
|
import { printHelp, printSubcommandHelp } from './help-text.js';
|
|
13
14
|
import { applySecurityOverrides } from './security-overrides.js';
|
|
14
15
|
import { validateConfiguredModel } from './startup-model-validation.js';
|
|
@@ -409,10 +410,10 @@ function flushPendingProviderRegistrations(resourceLoader, modelRegistry) {
|
|
|
409
410
|
}
|
|
410
411
|
runtime.pendingProviderRegistrations = [];
|
|
411
412
|
}
|
|
412
|
-
// `gsd auto [args...]` — shorthand for
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
if (cliFlags.messages[0]
|
|
413
|
+
// `gsd auto [args...]` with piped stdin/stdout — shorthand for
|
|
414
|
+
// `gsd headless auto [args...]` (#2732). Keep terminal TTY launches in the
|
|
415
|
+
// interactive path so Warp/iTerm/Terminal retain foreground ownership.
|
|
416
|
+
if (shouldRedirectAutoToHeadless(cliFlags.messages[0], process.stdin.isTTY, process.stdout.isTTY)) {
|
|
416
417
|
await runHeadlessFromAuto(buildHeadlessAutoArgs(cliFlags));
|
|
417
418
|
}
|
|
418
419
|
// ---------------------------------------------------------------------------
|
|
@@ -656,16 +657,6 @@ if (!cliFlags.worktree && !isPrintMode) {
|
|
|
656
657
|
}
|
|
657
658
|
markStartup('worktreeStatusBanner');
|
|
658
659
|
// ---------------------------------------------------------------------------
|
|
659
|
-
// Auto-redirect: `gsd auto` with piped stdout → headless mode (#2732)
|
|
660
|
-
// When stdout is not a TTY (e.g. `gsd auto | cat`, `gsd auto > file`),
|
|
661
|
-
// the TUI cannot render and the process hangs. Redirect to headless mode
|
|
662
|
-
// which handles non-interactive output gracefully.
|
|
663
|
-
// ---------------------------------------------------------------------------
|
|
664
|
-
if (cliFlags.messages[0] === 'auto' && !process.stdout.isTTY) {
|
|
665
|
-
process.stderr.write('[gsd] stdout is not a terminal — running auto-mode in headless mode.\n');
|
|
666
|
-
await runHeadlessFromAuto(cliFlags.messages.slice(1));
|
|
667
|
-
}
|
|
668
|
-
// ---------------------------------------------------------------------------
|
|
669
660
|
// Interactive mode — normal TTY session
|
|
670
661
|
// ---------------------------------------------------------------------------
|
|
671
662
|
await ensureRtkBootstrap();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
38cf2a787658e575
|
|
@@ -22,6 +22,29 @@ let sessionSwitchGeneration = 0;
|
|
|
22
22
|
*/
|
|
23
23
|
export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
24
24
|
debugLog("runUnit", { phase: "start", unitType, unitId });
|
|
25
|
+
// Ensure cwd matches basePath BEFORE newSession() captures it. The new
|
|
26
|
+
// session reads process.cwd() during construction to anchor its tool
|
|
27
|
+
// runtime and system prompt; if cwd has drifted (async_bash, background
|
|
28
|
+
// jobs, prior unit cleanup), the session would otherwise be rooted to
|
|
29
|
+
// the wrong directory. Must be synchronous — no awaits between chdir
|
|
30
|
+
// and newSession (#1389, #4762 follow-up).
|
|
31
|
+
try {
|
|
32
|
+
if (process.cwd() !== s.basePath) {
|
|
33
|
+
process.chdir(s.basePath);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
const msg = `Failed to chdir to basePath before newSession (basePath: ${s.basePath}): ${String(e)}`;
|
|
38
|
+
logWarning("engine", msg, { basePath: s.basePath, error: String(e) });
|
|
39
|
+
return {
|
|
40
|
+
status: "cancelled",
|
|
41
|
+
errorContext: {
|
|
42
|
+
message: msg,
|
|
43
|
+
category: "session-failed",
|
|
44
|
+
isTransient: true,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
25
48
|
// ── Session creation with timeout ──
|
|
26
49
|
debugLog("runUnit", { phase: "session-create", unitType, unitId });
|
|
27
50
|
let sessionResult;
|
|
@@ -91,17 +114,6 @@ export async function runUnit(ctx, pi, s, unitType, unitId, prompt) {
|
|
|
91
114
|
const unitPromise = new Promise((resolve) => {
|
|
92
115
|
_setCurrentResolve(resolve);
|
|
93
116
|
});
|
|
94
|
-
// Ensure cwd matches basePath before dispatch (#1389).
|
|
95
|
-
// async_bash and background jobs can drift cwd away from the worktree.
|
|
96
|
-
// Realigning here prevents commits from landing on the wrong branch.
|
|
97
|
-
try {
|
|
98
|
-
if (process.cwd() !== s.basePath) {
|
|
99
|
-
process.chdir(s.basePath);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
catch (e) {
|
|
103
|
-
logWarning("engine", "Failed to chdir to basePath before dispatch", { basePath: s.basePath, error: String(e) });
|
|
104
|
-
}
|
|
105
117
|
// ── Provider request-readiness pre-check (#4555) ──
|
|
106
118
|
// Verify the provider can accept requests before dispatching. If the token
|
|
107
119
|
// has expired since bootstrap, return cancelled immediately so the unit is
|
|
@@ -10,6 +10,8 @@ import { resolveMilestoneFile, resolveSliceFile, relSliceFile, } from "./paths.j
|
|
|
10
10
|
import { buildResearchSlicePrompt, buildResearchMilestonePrompt, buildPlanSlicePrompt, buildPlanMilestonePrompt, buildExecuteTaskPrompt, buildCompleteSlicePrompt, buildCompleteMilestonePrompt, buildReassessRoadmapPrompt, buildRunUatPrompt, buildReplanSlicePrompt, } from "./auto-prompts.js";
|
|
11
11
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
12
12
|
import { pauseAuto } from "./auto.js";
|
|
13
|
+
import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
|
|
14
|
+
import { logWarning } from "./workflow-logger.js";
|
|
13
15
|
import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForAutoUnit, } from "./workflow-mcp.js";
|
|
14
16
|
export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
15
17
|
const state = await deriveState(base);
|
|
@@ -19,6 +21,12 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
19
21
|
ctx.ui.notify("Cannot dispatch: no active milestone.", "warning");
|
|
20
22
|
return;
|
|
21
23
|
}
|
|
24
|
+
const projectRoot = base;
|
|
25
|
+
// Switch the dispatch base to the canonical milestone worktree if one
|
|
26
|
+
// exists. Without this, /gsd dispatch invoked from the project root would
|
|
27
|
+
// build prompts and create a session anchored to the project root even
|
|
28
|
+
// though the milestone's actual code lives in the worktree.
|
|
29
|
+
const dispatchBase = resolveCanonicalMilestoneRoot(base, mid);
|
|
22
30
|
const normalized = phase.toLowerCase();
|
|
23
31
|
let unitType;
|
|
24
32
|
let unitId;
|
|
@@ -37,7 +45,7 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
37
45
|
}
|
|
38
46
|
// When require_slice_discussion is enabled, pause auto-mode before
|
|
39
47
|
// each new slice so the user can discuss requirements first (#789).
|
|
40
|
-
const sliceContextFile = resolveSliceFile(
|
|
48
|
+
const sliceContextFile = resolveSliceFile(dispatchBase, mid, sid, "CONTEXT");
|
|
41
49
|
const requireDiscussion = loadEffectiveGSDPreferences()?.preferences?.phases?.require_slice_discussion;
|
|
42
50
|
if (requireDiscussion && !sliceContextFile) {
|
|
43
51
|
ctx.ui.notify(`Slice ${sid} requires discussion before planning. Run /gsd discuss to discuss this slice, then /gsd auto to resume.`, "info");
|
|
@@ -46,12 +54,12 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
46
54
|
}
|
|
47
55
|
unitType = "research-slice";
|
|
48
56
|
unitId = `${mid}/${sid}`;
|
|
49
|
-
prompt = await buildResearchSlicePrompt(mid, midTitle, sid, sTitle,
|
|
57
|
+
prompt = await buildResearchSlicePrompt(mid, midTitle, sid, sTitle, dispatchBase);
|
|
50
58
|
}
|
|
51
59
|
else {
|
|
52
60
|
unitType = "research-milestone";
|
|
53
61
|
unitId = mid;
|
|
54
|
-
prompt = await buildResearchMilestonePrompt(mid, midTitle,
|
|
62
|
+
prompt = await buildResearchMilestonePrompt(mid, midTitle, dispatchBase);
|
|
55
63
|
}
|
|
56
64
|
break;
|
|
57
65
|
}
|
|
@@ -68,7 +76,7 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
68
76
|
}
|
|
69
77
|
unitType = "plan-slice";
|
|
70
78
|
unitId = `${mid}/${sid}`;
|
|
71
|
-
prompt = await buildPlanSlicePrompt(mid, midTitle, sid, sTitle,
|
|
79
|
+
prompt = await buildPlanSlicePrompt(mid, midTitle, sid, sTitle, dispatchBase, undefined, {
|
|
72
80
|
sessionContextWindow: ctx.model?.contextWindow,
|
|
73
81
|
modelRegistry: ctx.modelRegistry,
|
|
74
82
|
});
|
|
@@ -76,7 +84,7 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
76
84
|
else {
|
|
77
85
|
unitType = "plan-milestone";
|
|
78
86
|
unitId = mid;
|
|
79
|
-
prompt = await buildPlanMilestonePrompt(mid, midTitle,
|
|
87
|
+
prompt = await buildPlanMilestonePrompt(mid, midTitle, dispatchBase);
|
|
80
88
|
}
|
|
81
89
|
break;
|
|
82
90
|
}
|
|
@@ -96,7 +104,7 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
96
104
|
}
|
|
97
105
|
unitType = "execute-task";
|
|
98
106
|
unitId = `${mid}/${sid}/${tid}`;
|
|
99
|
-
prompt = await buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle,
|
|
107
|
+
prompt = await buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, dispatchBase, {
|
|
100
108
|
sessionContextWindow: ctx.model?.contextWindow,
|
|
101
109
|
modelRegistry: ctx.modelRegistry,
|
|
102
110
|
});
|
|
@@ -115,12 +123,12 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
115
123
|
}
|
|
116
124
|
unitType = "complete-slice";
|
|
117
125
|
unitId = `${mid}/${sid}`;
|
|
118
|
-
prompt = await buildCompleteSlicePrompt(mid, midTitle, sid, sTitle,
|
|
126
|
+
prompt = await buildCompleteSlicePrompt(mid, midTitle, sid, sTitle, dispatchBase);
|
|
119
127
|
}
|
|
120
128
|
else {
|
|
121
129
|
unitType = "complete-milestone";
|
|
122
130
|
unitId = mid;
|
|
123
|
-
prompt = await buildCompleteMilestonePrompt(mid, midTitle,
|
|
131
|
+
prompt = await buildCompleteMilestonePrompt(mid, midTitle, dispatchBase);
|
|
124
132
|
}
|
|
125
133
|
break;
|
|
126
134
|
}
|
|
@@ -133,7 +141,7 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
133
141
|
}
|
|
134
142
|
if (completedSliceIds.length === 0) {
|
|
135
143
|
// File-based fallback: parse roadmap checkboxes
|
|
136
|
-
const roadmapPath = resolveMilestoneFile(
|
|
144
|
+
const roadmapPath = resolveMilestoneFile(dispatchBase, mid, "ROADMAP");
|
|
137
145
|
if (roadmapPath) {
|
|
138
146
|
const roadmapContent = await loadFile(roadmapPath);
|
|
139
147
|
if (roadmapContent) {
|
|
@@ -148,7 +156,7 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
148
156
|
const completedSliceId = completedSliceIds[completedSliceIds.length - 1];
|
|
149
157
|
unitType = "reassess-roadmap";
|
|
150
158
|
unitId = `${mid}/${completedSliceId}`;
|
|
151
|
-
prompt = await buildReassessRoadmapPrompt(mid, midTitle, completedSliceId,
|
|
159
|
+
prompt = await buildReassessRoadmapPrompt(mid, midTitle, completedSliceId, dispatchBase);
|
|
152
160
|
break;
|
|
153
161
|
}
|
|
154
162
|
case "uat":
|
|
@@ -163,7 +171,7 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
163
171
|
}
|
|
164
172
|
if (uatCompletedSliceIds.length === 0) {
|
|
165
173
|
// File-based fallback: parse roadmap checkboxes
|
|
166
|
-
const roadmapPath = resolveMilestoneFile(
|
|
174
|
+
const roadmapPath = resolveMilestoneFile(dispatchBase, mid, "ROADMAP");
|
|
167
175
|
if (roadmapPath) {
|
|
168
176
|
const roadmapContent = await loadFile(roadmapPath);
|
|
169
177
|
if (roadmapContent) {
|
|
@@ -176,7 +184,7 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
176
184
|
return;
|
|
177
185
|
}
|
|
178
186
|
const sid = uatCompletedSliceIds[uatCompletedSliceIds.length - 1];
|
|
179
|
-
const uatFile = resolveSliceFile(
|
|
187
|
+
const uatFile = resolveSliceFile(dispatchBase, mid, sid, "UAT");
|
|
180
188
|
if (!uatFile) {
|
|
181
189
|
ctx.ui.notify("Cannot dispatch run-uat: no UAT file found.", "warning");
|
|
182
190
|
return;
|
|
@@ -186,10 +194,10 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
186
194
|
ctx.ui.notify("Cannot dispatch run-uat: UAT file is empty.", "warning");
|
|
187
195
|
return;
|
|
188
196
|
}
|
|
189
|
-
const uatPath = relSliceFile(
|
|
197
|
+
const uatPath = relSliceFile(dispatchBase, mid, sid, "UAT");
|
|
190
198
|
unitType = "run-uat";
|
|
191
199
|
unitId = `${mid}/${sid}`;
|
|
192
|
-
prompt = await buildRunUatPrompt(mid, sid, uatPath, uatContent,
|
|
200
|
+
prompt = await buildRunUatPrompt(mid, sid, uatPath, uatContent, dispatchBase);
|
|
193
201
|
break;
|
|
194
202
|
}
|
|
195
203
|
case "replan":
|
|
@@ -202,7 +210,7 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
202
210
|
}
|
|
203
211
|
unitType = "replan-slice";
|
|
204
212
|
unitId = `${mid}/${sid}`;
|
|
205
|
-
prompt = await buildReplanSlicePrompt(mid, midTitle, sid, sTitle,
|
|
213
|
+
prompt = await buildReplanSlicePrompt(mid, midTitle, sid, sTitle, dispatchBase);
|
|
206
214
|
break;
|
|
207
215
|
}
|
|
208
216
|
default:
|
|
@@ -210,7 +218,7 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
210
218
|
return;
|
|
211
219
|
}
|
|
212
220
|
const compatibilityError = getWorkflowTransportSupportError(ctx.model?.provider, getRequiredWorkflowToolsForAutoUnit(unitType), {
|
|
213
|
-
projectRoot
|
|
221
|
+
projectRoot,
|
|
214
222
|
surface: "direct phase dispatch",
|
|
215
223
|
unitType,
|
|
216
224
|
authMode: ctx.model?.provider ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider) : undefined,
|
|
@@ -221,10 +229,36 @@ export async function dispatchDirectPhase(ctx, pi, phase, base) {
|
|
|
221
229
|
return;
|
|
222
230
|
}
|
|
223
231
|
ctx.ui.notify(`Dispatching ${unitType} for ${unitId}...`, "info");
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
232
|
+
const originalCwd = process.cwd();
|
|
233
|
+
try {
|
|
234
|
+
// Ensure cwd matches dispatchBase BEFORE newSession() captures it. Synchronous —
|
|
235
|
+
// no awaits between chdir and newSession.
|
|
236
|
+
try {
|
|
237
|
+
if (process.cwd() !== dispatchBase) {
|
|
238
|
+
process.chdir(dispatchBase);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
const msg = `Failed to chdir before direct-dispatch newSession (basePath: ${dispatchBase}): ${err instanceof Error ? err.message : String(err)}`;
|
|
243
|
+
logWarning("engine", msg, { file: "auto-direct-dispatch.ts", basePath: dispatchBase, error: err instanceof Error ? err.message : String(err) });
|
|
244
|
+
ctx.ui.notify(`${msg}. Cancelling dispatch to avoid running in the wrong directory.`, "error");
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const result = await ctx.newSession();
|
|
248
|
+
if (result.cancelled) {
|
|
249
|
+
ctx.ui.notify("Session creation cancelled.", "warning");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
pi.sendMessage({ customType: "gsd-dispatch", content: prompt, display: false }, { triggerTurn: true });
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
try {
|
|
256
|
+
if (process.cwd() !== originalCwd) {
|
|
257
|
+
process.chdir(originalCwd);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
logWarning("engine", `Failed to restore cwd after direct dispatch: ${err instanceof Error ? err.message : String(err)}`, { file: "auto-direct-dispatch.ts", basePath: originalCwd });
|
|
262
|
+
}
|
|
228
263
|
}
|
|
229
|
-
pi.sendMessage({ customType: "gsd-dispatch", content: prompt, display: false }, { triggerTurn: true });
|
|
230
264
|
}
|
|
@@ -1120,6 +1120,7 @@ export async function checkNeedsRunUat(base, mid, state, prefs) {
|
|
|
1120
1120
|
export async function buildDiscussMilestonePrompt(mid, midTitle, base, structuredQuestionsAvailable = "false") {
|
|
1121
1121
|
const discussTemplates = inlineTemplate("context", "Context");
|
|
1122
1122
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
1123
|
+
workingDirectory: base,
|
|
1123
1124
|
milestoneId: mid,
|
|
1124
1125
|
milestoneTitle: midTitle,
|
|
1125
1126
|
inlinedTemplates: discussTemplates,
|
|
@@ -2323,6 +2324,7 @@ export async function buildParallelResearchSlicesPrompt(mid, midTitle, slices, b
|
|
|
2323
2324
|
].join("\n"));
|
|
2324
2325
|
}
|
|
2325
2326
|
return loadPrompt("parallel-research-slices", {
|
|
2327
|
+
workingDirectory: basePath,
|
|
2326
2328
|
mid,
|
|
2327
2329
|
midTitle,
|
|
2328
2330
|
sliceCount: String(slices.length),
|
|
@@ -2350,11 +2352,14 @@ export async function buildGateEvaluatePrompt(mid, midTitle, sid, sTitle, base,
|
|
|
2350
2352
|
const gateDefs = getGatesForTurn("gate-evaluate").filter((def) => pendingIds.has(def.id));
|
|
2351
2353
|
const subagentSections = [];
|
|
2352
2354
|
const gateListLines = [];
|
|
2355
|
+
const normalizedBase = base.replaceAll("\\", "/");
|
|
2353
2356
|
for (const def of gateDefs) {
|
|
2354
2357
|
gateListLines.push(`- **${def.id}**: ${def.question}`);
|
|
2355
2358
|
const subPrompt = [
|
|
2356
2359
|
`You are evaluating quality gate **${def.id}** for slice ${sid} (${sTitle}).`,
|
|
2357
2360
|
"",
|
|
2361
|
+
`**Working directory:** \`${normalizedBase}\`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT \`cd\` to any other directory.`,
|
|
2362
|
+
"",
|
|
2358
2363
|
`## Question: ${def.question}`,
|
|
2359
2364
|
"",
|
|
2360
2365
|
def.guidance,
|
|
@@ -2462,6 +2467,7 @@ export async function buildRewriteDocsPrompt(mid, midTitle, activeSlice, base, o
|
|
|
2462
2467
|
].join("\n")).join("\n\n");
|
|
2463
2468
|
const documentList = docList.length > 0 ? docList.join("\n") : "- No active plan documents found.";
|
|
2464
2469
|
return loadPrompt("rewrite-docs", {
|
|
2470
|
+
workingDirectory: base,
|
|
2465
2471
|
milestoneId: mid,
|
|
2466
2472
|
milestoneTitle: midTitle,
|
|
2467
2473
|
sliceId: sid ?? "none",
|
|
@@ -2076,5 +2076,20 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
|
|
|
2076
2076
|
// 14. Clear module state
|
|
2077
2077
|
originalBase = null;
|
|
2078
2078
|
nudgeGitBranchCache(previousCwd);
|
|
2079
|
+
// 15. Anchor cwd at the project root on success-return. Step 12 removed
|
|
2080
|
+
// the worktree dir; if cwd was inside it, every subsequent process.cwd()
|
|
2081
|
+
// would throw ENOENT and trip auto/run-unit.ts:50's session-failed cancel
|
|
2082
|
+
// path (the de73fb43d regression that closes headless gsd auto). Step 3
|
|
2083
|
+
// already chdir'd here, but defending the success-return contract makes
|
|
2084
|
+
// future maintainers safe against intervening chdir's between step 3 and
|
|
2085
|
+
// here.
|
|
2086
|
+
try {
|
|
2087
|
+
// process.cwd() can throw ENOENT when cwd was removed, so attempt
|
|
2088
|
+
// recovery directly.
|
|
2089
|
+
process.chdir(originalBasePath_);
|
|
2090
|
+
}
|
|
2091
|
+
catch (err) {
|
|
2092
|
+
logWarning("worktree", `chdir to project root after merge failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2093
|
+
}
|
|
2079
2094
|
return { commitMessage, pushed, prCreated, codeFilesChanged };
|
|
2080
2095
|
}
|
|
@@ -1441,15 +1441,18 @@ export function ensurePreconditions(unitType, unitId, base, state) {
|
|
|
1441
1441
|
}
|
|
1442
1442
|
}
|
|
1443
1443
|
export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, triggerUnitId, hookPrompt, hookModel, targetBasePath) {
|
|
1444
|
+
const wasActive = s.active;
|
|
1445
|
+
const previousBasePath = s.basePath;
|
|
1446
|
+
const previousCurrentUnit = s.currentUnit ? { ...s.currentUnit } : null;
|
|
1444
1447
|
if (!s.active) {
|
|
1445
1448
|
s.active = true;
|
|
1446
1449
|
s.stepMode = true;
|
|
1447
1450
|
s.cmdCtx = ctx;
|
|
1448
|
-
s.basePath = targetBasePath;
|
|
1449
1451
|
s.autoStartTime = Date.now();
|
|
1450
1452
|
s.currentUnit = null;
|
|
1451
1453
|
s.pendingQuickTasks = [];
|
|
1452
1454
|
}
|
|
1455
|
+
s.basePath = targetBasePath;
|
|
1453
1456
|
const hookUnitType = `hook/${hookName}`;
|
|
1454
1457
|
const hookStartedAt = Date.now();
|
|
1455
1458
|
s.currentUnit = {
|
|
@@ -1457,6 +1460,27 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
|
|
|
1457
1460
|
id: triggerUnitId,
|
|
1458
1461
|
startedAt: hookStartedAt,
|
|
1459
1462
|
};
|
|
1463
|
+
// Ensure cwd matches basePath BEFORE newSession() captures it (#1389).
|
|
1464
|
+
// newSession() snapshots process.cwd() during construction; chdir-ing
|
|
1465
|
+
// afterward leaves the session rooted to whatever cwd was when the call
|
|
1466
|
+
// was made. Must be synchronous — no awaits between chdir and newSession.
|
|
1467
|
+
try {
|
|
1468
|
+
if (process.cwd() !== s.basePath)
|
|
1469
|
+
process.chdir(s.basePath);
|
|
1470
|
+
}
|
|
1471
|
+
catch (err) {
|
|
1472
|
+
const msg = `Failed to chdir before hook newSession (basePath: ${s.basePath}): ${err instanceof Error ? err.message : String(err)}`;
|
|
1473
|
+
logWarning("engine", msg, { file: "auto.ts", basePath: s.basePath, error: err instanceof Error ? err.message : String(err) });
|
|
1474
|
+
ctx.ui.notify(`${msg}. Cancelling hook dispatch to avoid running in the wrong directory.`, "error");
|
|
1475
|
+
if (wasActive) {
|
|
1476
|
+
s.basePath = previousBasePath;
|
|
1477
|
+
s.currentUnit = previousCurrentUnit;
|
|
1478
|
+
}
|
|
1479
|
+
else {
|
|
1480
|
+
s.reset();
|
|
1481
|
+
}
|
|
1482
|
+
return false;
|
|
1483
|
+
}
|
|
1460
1484
|
const result = await s.cmdCtx.newSession();
|
|
1461
1485
|
if (result.cancelled) {
|
|
1462
1486
|
await stopAuto(ctx, pi);
|
|
@@ -1499,14 +1523,6 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
|
|
|
1499
1523
|
}, hookHardTimeoutMs);
|
|
1500
1524
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
1501
1525
|
ctx.ui.notify(`Running post-unit hook: ${hookName}`, "info");
|
|
1502
|
-
// Ensure cwd matches basePath before hook dispatch (#1389)
|
|
1503
|
-
try {
|
|
1504
|
-
if (process.cwd() !== s.basePath)
|
|
1505
|
-
process.chdir(s.basePath);
|
|
1506
|
-
}
|
|
1507
|
-
catch (err) {
|
|
1508
|
-
logWarning("engine", `chdir failed before hook dispatch: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
1509
|
-
}
|
|
1510
1526
|
debugLog("dispatchHookUnit", {
|
|
1511
1527
|
phase: "send-message",
|
|
1512
1528
|
promptLength: hookPrompt.length,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
**Working directory:** `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory.
|
|
2
|
+
|
|
1
3
|
Discuss milestone {{milestoneId}} ("{{milestoneTitle}}"). Identify gray areas, ask the user about them, and write `{{milestoneId}}-CONTEXT.md` in the milestone directory with the decisions. Use the **Context** output template below. If a `GSD Skill Preferences` block is present in system context, use it to decide which skills to load and follow; do not override required artifact rules.
|
|
2
4
|
|
|
3
5
|
**Structured questions available: {{structuredQuestionsAvailable}}**
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Parallel Slice Research
|
|
2
2
|
|
|
3
|
+
**Working directory:** `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory.
|
|
4
|
+
|
|
3
5
|
You are dispatching parallel research agents for **{{sliceCount}} slices** in milestone **{{mid}} — {{midTitle}}**.
|
|
4
6
|
|
|
5
7
|
## Slices to Research
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
You are executing GSD auto-mode.
|
|
2
2
|
|
|
3
|
+
**Working directory:** `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory.
|
|
4
|
+
|
|
3
5
|
## UNIT: Rewrite Documents — Apply Override(s) for Milestone {{milestoneId}} ("{{milestoneTitle}}")
|
|
4
6
|
|
|
5
7
|
An override was issued by the user that changes a fundamental decision or approach. Your job is to propagate this change across all active planning documents so they are internally consistent and future tasks execute correctly.
|
|
@@ -297,6 +297,30 @@ export class WorktreeResolver {
|
|
|
297
297
|
*/
|
|
298
298
|
mergeAndExit(milestoneId, ctx) {
|
|
299
299
|
this.validateMilestoneId(milestoneId);
|
|
300
|
+
// Anchor cwd at the project root before any merge work. Some merge code
|
|
301
|
+
// paths (mergeMilestoneToMain, slice-cadence) chdir explicitly; others
|
|
302
|
+
// (branch-mode, isolation-degraded skip, missing-original-base skip)
|
|
303
|
+
// do not. If the worktree dir is later torn down while cwd still points
|
|
304
|
+
// into it, every subsequent process.cwd() throws ENOENT — and after
|
|
305
|
+
// de73fb43d that surfaces as a session-failed cancel and (in headless
|
|
306
|
+
// mode) terminates the whole gsd process. Best-effort: silent on
|
|
307
|
+
// failure so existing test fixtures that use synthetic paths still pass.
|
|
308
|
+
if (this.s.originalBasePath) {
|
|
309
|
+
try {
|
|
310
|
+
// process.cwd() can throw ENOENT when cwd was removed, so attempt
|
|
311
|
+
// recovery directly.
|
|
312
|
+
process.chdir(this.s.originalBasePath);
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
debugLog("WorktreeResolver", {
|
|
316
|
+
action: "mergeAndExit",
|
|
317
|
+
phase: "pre-merge-chdir-failed",
|
|
318
|
+
milestoneId,
|
|
319
|
+
originalBasePath: this.s.originalBasePath,
|
|
320
|
+
error: err instanceof Error ? err.message : String(err),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
300
324
|
// #4764 — telemetry: record start timestamp so we can emit merge duration.
|
|
301
325
|
const mergeStartedAt = new Date().toISOString();
|
|
302
326
|
const mergeStartMs = Date.now();
|
|
@@ -7,6 +7,10 @@ description: Lint and format code. Auto-detects ESLint, Biome, Prettier, or lang
|
|
|
7
7
|
Lint and format code in the current project. Auto-detect the project's linter and formatter toolchain, run them against the target files, and report results grouped by severity with actionable fix suggestions.
|
|
8
8
|
</objective>
|
|
9
9
|
|
|
10
|
+
<working_directory_awareness>
|
|
11
|
+
**Before running any `git` or build command:** check whether your dispatch context specifies a working directory (look for "Working directory:" in your initial prompt). If it does and `pwd` does not match it, prefix every git invocation with `-C <that path>` (e.g. `git -C /path/to/worktree diff --name-only`) and run linters/formatters with the explicit path argument. Linting the wrong directory is a silent failure mode.
|
|
12
|
+
</working_directory_awareness>
|
|
13
|
+
|
|
10
14
|
<arguments>
|
|
11
15
|
This skill accepts optional arguments after `/lint`:
|
|
12
16
|
|
|
@@ -23,6 +23,10 @@ The reviewer reads both the diff and the surrounding source files to understand
|
|
|
23
23
|
The purpose is to review and report findings. Making changes during review conflates the reviewer and author roles. Present findings and let the user decide what to act on.
|
|
24
24
|
</analysis_only_rule>
|
|
25
25
|
|
|
26
|
+
<working_directory_awareness>
|
|
27
|
+
**Before running any `git` command:** check whether your dispatch context specifies a working directory (look for "Working directory:" in your initial prompt). If it does and `pwd` does not match it, prefix every git invocation with `-C <that path>` (e.g. `git -C /path/to/worktree diff --cached`). Reviewing the wrong directory's diff is a silent failure mode — the review will look correct but cover the wrong code.
|
|
28
|
+
</working_directory_awareness>
|
|
29
|
+
|
|
26
30
|
<quick_start>
|
|
27
31
|
|
|
28
32
|
<determine_review_scope>
|
|
@@ -151,6 +151,9 @@ Failures:
|
|
|
151
151
|
**Suggest what to test when no arguments are given.**
|
|
152
152
|
|
|
153
153
|
**A. Check recent changes:**
|
|
154
|
+
|
|
155
|
+
> **Working directory check:** if your dispatch context specifies a working directory and `pwd` does not match it, prefix the git commands below with `-C <that path>` (e.g. `git -C /path/to/worktree diff --name-only HEAD~5`).
|
|
156
|
+
|
|
154
157
|
- Run `git diff --name-only HEAD~5` to find recently changed files
|
|
155
158
|
- Run `git diff --name-only --cached` for staged files
|
|
156
159
|
- Filter to source files (exclude configs, docs, lockfiles)
|