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
|
@@ -40,6 +40,29 @@ export async function runUnit(
|
|
|
40
40
|
): Promise<UnitResult> {
|
|
41
41
|
debugLog("runUnit", { phase: "start", unitType, unitId });
|
|
42
42
|
|
|
43
|
+
// Ensure cwd matches basePath BEFORE newSession() captures it. The new
|
|
44
|
+
// session reads process.cwd() during construction to anchor its tool
|
|
45
|
+
// runtime and system prompt; if cwd has drifted (async_bash, background
|
|
46
|
+
// jobs, prior unit cleanup), the session would otherwise be rooted to
|
|
47
|
+
// the wrong directory. Must be synchronous — no awaits between chdir
|
|
48
|
+
// and newSession (#1389, #4762 follow-up).
|
|
49
|
+
try {
|
|
50
|
+
if (process.cwd() !== s.basePath) {
|
|
51
|
+
process.chdir(s.basePath);
|
|
52
|
+
}
|
|
53
|
+
} catch (e) {
|
|
54
|
+
const msg = `Failed to chdir to basePath before newSession (basePath: ${s.basePath}): ${String(e)}`;
|
|
55
|
+
logWarning("engine", msg, { basePath: s.basePath, error: String(e) });
|
|
56
|
+
return {
|
|
57
|
+
status: "cancelled",
|
|
58
|
+
errorContext: {
|
|
59
|
+
message: msg,
|
|
60
|
+
category: "session-failed",
|
|
61
|
+
isTransient: true,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
43
66
|
// ── Session creation with timeout ──
|
|
44
67
|
debugLog("runUnit", { phase: "session-create", unitType, unitId });
|
|
45
68
|
|
|
@@ -120,17 +143,6 @@ export async function runUnit(
|
|
|
120
143
|
_setCurrentResolve(resolve);
|
|
121
144
|
});
|
|
122
145
|
|
|
123
|
-
// Ensure cwd matches basePath before dispatch (#1389).
|
|
124
|
-
// async_bash and background jobs can drift cwd away from the worktree.
|
|
125
|
-
// Realigning here prevents commits from landing on the wrong branch.
|
|
126
|
-
try {
|
|
127
|
-
if (process.cwd() !== s.basePath) {
|
|
128
|
-
process.chdir(s.basePath);
|
|
129
|
-
}
|
|
130
|
-
} catch (e) {
|
|
131
|
-
logWarning("engine", "Failed to chdir to basePath before dispatch", { basePath: s.basePath, error: String(e) });
|
|
132
|
-
}
|
|
133
|
-
|
|
134
146
|
// ── Provider request-readiness pre-check (#4555) ──
|
|
135
147
|
// Verify the provider can accept requests before dispatching. If the token
|
|
136
148
|
// has expired since bootstrap, return cancelled immediately so the unit is
|
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
31
31
|
import type { MinimalModelRegistry } from "./context-budget.js";
|
|
32
32
|
import { pauseAuto } from "./auto.js";
|
|
33
|
+
import { resolveCanonicalMilestoneRoot } from "./worktree-manager.js";
|
|
34
|
+
import { logWarning } from "./workflow-logger.js";
|
|
33
35
|
import {
|
|
34
36
|
getWorkflowTransportSupportError,
|
|
35
37
|
getRequiredWorkflowToolsForAutoUnit,
|
|
@@ -50,6 +52,14 @@ export async function dispatchDirectPhase(
|
|
|
50
52
|
return;
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
const projectRoot = base;
|
|
56
|
+
|
|
57
|
+
// Switch the dispatch base to the canonical milestone worktree if one
|
|
58
|
+
// exists. Without this, /gsd dispatch invoked from the project root would
|
|
59
|
+
// build prompts and create a session anchored to the project root even
|
|
60
|
+
// though the milestone's actual code lives in the worktree.
|
|
61
|
+
const dispatchBase = resolveCanonicalMilestoneRoot(base, mid);
|
|
62
|
+
|
|
53
63
|
const normalized = phase.toLowerCase();
|
|
54
64
|
let unitType: string;
|
|
55
65
|
let unitId: string;
|
|
@@ -70,7 +80,7 @@ export async function dispatchDirectPhase(
|
|
|
70
80
|
|
|
71
81
|
// When require_slice_discussion is enabled, pause auto-mode before
|
|
72
82
|
// each new slice so the user can discuss requirements first (#789).
|
|
73
|
-
const sliceContextFile = resolveSliceFile(
|
|
83
|
+
const sliceContextFile = resolveSliceFile(dispatchBase, mid, sid, "CONTEXT");
|
|
74
84
|
const requireDiscussion = loadEffectiveGSDPreferences()?.preferences?.phases?.require_slice_discussion;
|
|
75
85
|
if (requireDiscussion && !sliceContextFile) {
|
|
76
86
|
ctx.ui.notify(
|
|
@@ -83,11 +93,11 @@ export async function dispatchDirectPhase(
|
|
|
83
93
|
|
|
84
94
|
unitType = "research-slice";
|
|
85
95
|
unitId = `${mid}/${sid}`;
|
|
86
|
-
prompt = await buildResearchSlicePrompt(mid, midTitle, sid, sTitle,
|
|
96
|
+
prompt = await buildResearchSlicePrompt(mid, midTitle, sid, sTitle, dispatchBase);
|
|
87
97
|
} else {
|
|
88
98
|
unitType = "research-milestone";
|
|
89
99
|
unitId = mid;
|
|
90
|
-
prompt = await buildResearchMilestonePrompt(mid, midTitle,
|
|
100
|
+
prompt = await buildResearchMilestonePrompt(mid, midTitle, dispatchBase);
|
|
91
101
|
}
|
|
92
102
|
break;
|
|
93
103
|
}
|
|
@@ -106,7 +116,7 @@ export async function dispatchDirectPhase(
|
|
|
106
116
|
unitType = "plan-slice";
|
|
107
117
|
unitId = `${mid}/${sid}`;
|
|
108
118
|
prompt = await buildPlanSlicePrompt(
|
|
109
|
-
mid, midTitle, sid, sTitle,
|
|
119
|
+
mid, midTitle, sid, sTitle, dispatchBase, undefined,
|
|
110
120
|
{
|
|
111
121
|
sessionContextWindow: ctx.model?.contextWindow,
|
|
112
122
|
modelRegistry: ctx.modelRegistry as MinimalModelRegistry | undefined,
|
|
@@ -115,7 +125,7 @@ export async function dispatchDirectPhase(
|
|
|
115
125
|
} else {
|
|
116
126
|
unitType = "plan-milestone";
|
|
117
127
|
unitId = mid;
|
|
118
|
-
prompt = await buildPlanMilestonePrompt(mid, midTitle,
|
|
128
|
+
prompt = await buildPlanMilestonePrompt(mid, midTitle, dispatchBase);
|
|
119
129
|
}
|
|
120
130
|
break;
|
|
121
131
|
}
|
|
@@ -137,7 +147,7 @@ export async function dispatchDirectPhase(
|
|
|
137
147
|
unitType = "execute-task";
|
|
138
148
|
unitId = `${mid}/${sid}/${tid}`;
|
|
139
149
|
prompt = await buildExecuteTaskPrompt(
|
|
140
|
-
mid, sid, sTitle, tid, tTitle,
|
|
150
|
+
mid, sid, sTitle, tid, tTitle, dispatchBase,
|
|
141
151
|
{
|
|
142
152
|
sessionContextWindow: ctx.model?.contextWindow,
|
|
143
153
|
modelRegistry: ctx.modelRegistry as MinimalModelRegistry | undefined,
|
|
@@ -159,11 +169,11 @@ export async function dispatchDirectPhase(
|
|
|
159
169
|
}
|
|
160
170
|
unitType = "complete-slice";
|
|
161
171
|
unitId = `${mid}/${sid}`;
|
|
162
|
-
prompt = await buildCompleteSlicePrompt(mid, midTitle, sid, sTitle,
|
|
172
|
+
prompt = await buildCompleteSlicePrompt(mid, midTitle, sid, sTitle, dispatchBase);
|
|
163
173
|
} else {
|
|
164
174
|
unitType = "complete-milestone";
|
|
165
175
|
unitId = mid;
|
|
166
|
-
prompt = await buildCompleteMilestonePrompt(mid, midTitle,
|
|
176
|
+
prompt = await buildCompleteMilestonePrompt(mid, midTitle, dispatchBase);
|
|
167
177
|
}
|
|
168
178
|
break;
|
|
169
179
|
}
|
|
@@ -177,7 +187,7 @@ export async function dispatchDirectPhase(
|
|
|
177
187
|
}
|
|
178
188
|
if (completedSliceIds.length === 0) {
|
|
179
189
|
// File-based fallback: parse roadmap checkboxes
|
|
180
|
-
const roadmapPath = resolveMilestoneFile(
|
|
190
|
+
const roadmapPath = resolveMilestoneFile(dispatchBase, mid, "ROADMAP");
|
|
181
191
|
if (roadmapPath) {
|
|
182
192
|
const roadmapContent = await loadFile(roadmapPath);
|
|
183
193
|
if (roadmapContent) {
|
|
@@ -192,7 +202,7 @@ export async function dispatchDirectPhase(
|
|
|
192
202
|
const completedSliceId = completedSliceIds[completedSliceIds.length - 1];
|
|
193
203
|
unitType = "reassess-roadmap";
|
|
194
204
|
unitId = `${mid}/${completedSliceId}`;
|
|
195
|
-
prompt = await buildReassessRoadmapPrompt(mid, midTitle, completedSliceId,
|
|
205
|
+
prompt = await buildReassessRoadmapPrompt(mid, midTitle, completedSliceId, dispatchBase);
|
|
196
206
|
break;
|
|
197
207
|
}
|
|
198
208
|
|
|
@@ -208,7 +218,7 @@ export async function dispatchDirectPhase(
|
|
|
208
218
|
}
|
|
209
219
|
if (uatCompletedSliceIds.length === 0) {
|
|
210
220
|
// File-based fallback: parse roadmap checkboxes
|
|
211
|
-
const roadmapPath = resolveMilestoneFile(
|
|
221
|
+
const roadmapPath = resolveMilestoneFile(dispatchBase, mid, "ROADMAP");
|
|
212
222
|
if (roadmapPath) {
|
|
213
223
|
const roadmapContent = await loadFile(roadmapPath);
|
|
214
224
|
if (roadmapContent) {
|
|
@@ -221,7 +231,7 @@ export async function dispatchDirectPhase(
|
|
|
221
231
|
return;
|
|
222
232
|
}
|
|
223
233
|
const sid = uatCompletedSliceIds[uatCompletedSliceIds.length - 1];
|
|
224
|
-
const uatFile = resolveSliceFile(
|
|
234
|
+
const uatFile = resolveSliceFile(dispatchBase, mid, sid, "UAT");
|
|
225
235
|
if (!uatFile) {
|
|
226
236
|
ctx.ui.notify("Cannot dispatch run-uat: no UAT file found.", "warning");
|
|
227
237
|
return;
|
|
@@ -231,10 +241,10 @@ export async function dispatchDirectPhase(
|
|
|
231
241
|
ctx.ui.notify("Cannot dispatch run-uat: UAT file is empty.", "warning");
|
|
232
242
|
return;
|
|
233
243
|
}
|
|
234
|
-
const uatPath = relSliceFile(
|
|
244
|
+
const uatPath = relSliceFile(dispatchBase, mid, sid, "UAT");
|
|
235
245
|
unitType = "run-uat";
|
|
236
246
|
unitId = `${mid}/${sid}`;
|
|
237
|
-
prompt = await buildRunUatPrompt(mid, sid, uatPath, uatContent,
|
|
247
|
+
prompt = await buildRunUatPrompt(mid, sid, uatPath, uatContent, dispatchBase);
|
|
238
248
|
break;
|
|
239
249
|
}
|
|
240
250
|
|
|
@@ -248,7 +258,7 @@ export async function dispatchDirectPhase(
|
|
|
248
258
|
}
|
|
249
259
|
unitType = "replan-slice";
|
|
250
260
|
unitId = `${mid}/${sid}`;
|
|
251
|
-
prompt = await buildReplanSlicePrompt(mid, midTitle, sid, sTitle,
|
|
261
|
+
prompt = await buildReplanSlicePrompt(mid, midTitle, sid, sTitle, dispatchBase);
|
|
252
262
|
break;
|
|
253
263
|
}
|
|
254
264
|
|
|
@@ -264,7 +274,7 @@ export async function dispatchDirectPhase(
|
|
|
264
274
|
ctx.model?.provider,
|
|
265
275
|
getRequiredWorkflowToolsForAutoUnit(unitType),
|
|
266
276
|
{
|
|
267
|
-
projectRoot
|
|
277
|
+
projectRoot,
|
|
268
278
|
surface: "direct phase dispatch",
|
|
269
279
|
unitType,
|
|
270
280
|
authMode: ctx.model?.provider ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider) : undefined,
|
|
@@ -277,13 +287,39 @@ export async function dispatchDirectPhase(
|
|
|
277
287
|
}
|
|
278
288
|
|
|
279
289
|
ctx.ui.notify(`Dispatching ${unitType} for ${unitId}...`, "info");
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
290
|
+
|
|
291
|
+
const originalCwd = process.cwd();
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
// Ensure cwd matches dispatchBase BEFORE newSession() captures it. Synchronous —
|
|
295
|
+
// no awaits between chdir and newSession.
|
|
296
|
+
try {
|
|
297
|
+
if (process.cwd() !== dispatchBase) {
|
|
298
|
+
process.chdir(dispatchBase);
|
|
299
|
+
}
|
|
300
|
+
} catch (err) {
|
|
301
|
+
const msg = `Failed to chdir before direct-dispatch newSession (basePath: ${dispatchBase}): ${err instanceof Error ? err.message : String(err)}`;
|
|
302
|
+
logWarning("engine", msg, { file: "auto-direct-dispatch.ts", basePath: dispatchBase, error: err instanceof Error ? err.message : String(err) });
|
|
303
|
+
ctx.ui.notify(`${msg}. Cancelling dispatch to avoid running in the wrong directory.`, "error");
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const result = await ctx.newSession();
|
|
308
|
+
if (result.cancelled) {
|
|
309
|
+
ctx.ui.notify("Session creation cancelled.", "warning");
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
pi.sendMessage(
|
|
313
|
+
{ customType: "gsd-dispatch", content: prompt, display: false },
|
|
314
|
+
{ triggerTurn: true },
|
|
315
|
+
);
|
|
316
|
+
} finally {
|
|
317
|
+
try {
|
|
318
|
+
if (process.cwd() !== originalCwd) {
|
|
319
|
+
process.chdir(originalCwd);
|
|
320
|
+
}
|
|
321
|
+
} catch (err) {
|
|
322
|
+
logWarning("engine", `Failed to restore cwd after direct dispatch: ${err instanceof Error ? err.message : String(err)}`, { file: "auto-direct-dispatch.ts", basePath: originalCwd });
|
|
323
|
+
}
|
|
284
324
|
}
|
|
285
|
-
pi.sendMessage(
|
|
286
|
-
{ customType: "gsd-dispatch", content: prompt, display: false },
|
|
287
|
-
{ triggerTurn: true },
|
|
288
|
-
);
|
|
289
325
|
}
|
|
@@ -1267,6 +1267,7 @@ export async function buildDiscussMilestonePrompt(
|
|
|
1267
1267
|
const discussTemplates = inlineTemplate("context", "Context");
|
|
1268
1268
|
|
|
1269
1269
|
const basePrompt = loadPrompt("guided-discuss-milestone", {
|
|
1270
|
+
workingDirectory: base,
|
|
1270
1271
|
milestoneId: mid,
|
|
1271
1272
|
milestoneTitle: midTitle,
|
|
1272
1273
|
inlinedTemplates: discussTemplates,
|
|
@@ -2645,6 +2646,7 @@ export async function buildParallelResearchSlicesPrompt(
|
|
|
2645
2646
|
}
|
|
2646
2647
|
|
|
2647
2648
|
return loadPrompt("parallel-research-slices", {
|
|
2649
|
+
workingDirectory: basePath,
|
|
2648
2650
|
mid,
|
|
2649
2651
|
midTitle,
|
|
2650
2652
|
sliceCount: String(slices.length),
|
|
@@ -2681,6 +2683,7 @@ export async function buildGateEvaluatePrompt(
|
|
|
2681
2683
|
|
|
2682
2684
|
const subagentSections: string[] = [];
|
|
2683
2685
|
const gateListLines: string[] = [];
|
|
2686
|
+
const normalizedBase = base.replaceAll("\\", "/");
|
|
2684
2687
|
|
|
2685
2688
|
for (const def of gateDefs) {
|
|
2686
2689
|
gateListLines.push(`- **${def.id}**: ${def.question}`);
|
|
@@ -2688,6 +2691,8 @@ export async function buildGateEvaluatePrompt(
|
|
|
2688
2691
|
const subPrompt = [
|
|
2689
2692
|
`You are evaluating quality gate **${def.id}** for slice ${sid} (${sTitle}).`,
|
|
2690
2693
|
"",
|
|
2694
|
+
`**Working directory:** \`${normalizedBase}\`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT \`cd\` to any other directory.`,
|
|
2695
|
+
"",
|
|
2691
2696
|
`## Question: ${def.question}`,
|
|
2692
2697
|
"",
|
|
2693
2698
|
def.guidance,
|
|
@@ -2804,6 +2809,7 @@ export async function buildRewriteDocsPrompt(
|
|
|
2804
2809
|
const documentList = docList.length > 0 ? docList.join("\n") : "- No active plan documents found.";
|
|
2805
2810
|
|
|
2806
2811
|
return loadPrompt("rewrite-docs", {
|
|
2812
|
+
workingDirectory: base,
|
|
2807
2813
|
milestoneId: mid,
|
|
2808
2814
|
milestoneTitle: midTitle,
|
|
2809
2815
|
sliceId: sid ?? "none",
|
|
@@ -2372,5 +2372,20 @@ export function mergeMilestoneToMain(
|
|
|
2372
2372
|
originalBase = null;
|
|
2373
2373
|
nudgeGitBranchCache(previousCwd);
|
|
2374
2374
|
|
|
2375
|
+
// 15. Anchor cwd at the project root on success-return. Step 12 removed
|
|
2376
|
+
// the worktree dir; if cwd was inside it, every subsequent process.cwd()
|
|
2377
|
+
// would throw ENOENT and trip auto/run-unit.ts:50's session-failed cancel
|
|
2378
|
+
// path (the de73fb43d regression that closes headless gsd auto). Step 3
|
|
2379
|
+
// already chdir'd here, but defending the success-return contract makes
|
|
2380
|
+
// future maintainers safe against intervening chdir's between step 3 and
|
|
2381
|
+
// here.
|
|
2382
|
+
try {
|
|
2383
|
+
// process.cwd() can throw ENOENT when cwd was removed, so attempt
|
|
2384
|
+
// recovery directly.
|
|
2385
|
+
process.chdir(originalBasePath_);
|
|
2386
|
+
} catch (err) {
|
|
2387
|
+
logWarning("worktree", `chdir to project root after merge failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2375
2390
|
return { commitMessage, pushed, prCreated, codeFilesChanged };
|
|
2376
2391
|
}
|
|
@@ -1864,16 +1864,21 @@ export async function dispatchHookUnit(
|
|
|
1864
1864
|
hookModel: string | undefined,
|
|
1865
1865
|
targetBasePath: string,
|
|
1866
1866
|
): Promise<boolean> {
|
|
1867
|
+
const wasActive = s.active;
|
|
1868
|
+
const previousBasePath = s.basePath;
|
|
1869
|
+
const previousCurrentUnit = s.currentUnit ? { ...s.currentUnit } : null;
|
|
1870
|
+
|
|
1867
1871
|
if (!s.active) {
|
|
1868
1872
|
s.active = true;
|
|
1869
1873
|
s.stepMode = true;
|
|
1870
1874
|
s.cmdCtx = ctx as ExtensionCommandContext;
|
|
1871
|
-
s.basePath = targetBasePath;
|
|
1872
1875
|
s.autoStartTime = Date.now();
|
|
1873
1876
|
s.currentUnit = null;
|
|
1874
1877
|
s.pendingQuickTasks = [];
|
|
1875
1878
|
}
|
|
1876
1879
|
|
|
1880
|
+
s.basePath = targetBasePath;
|
|
1881
|
+
|
|
1877
1882
|
const hookUnitType = `hook/${hookName}`;
|
|
1878
1883
|
const hookStartedAt = Date.now();
|
|
1879
1884
|
|
|
@@ -1883,6 +1888,23 @@ export async function dispatchHookUnit(
|
|
|
1883
1888
|
startedAt: hookStartedAt,
|
|
1884
1889
|
};
|
|
1885
1890
|
|
|
1891
|
+
// Ensure cwd matches basePath BEFORE newSession() captures it (#1389).
|
|
1892
|
+
// newSession() snapshots process.cwd() during construction; chdir-ing
|
|
1893
|
+
// afterward leaves the session rooted to whatever cwd was when the call
|
|
1894
|
+
// was made. Must be synchronous — no awaits between chdir and newSession.
|
|
1895
|
+
try { if (process.cwd() !== s.basePath) process.chdir(s.basePath); } catch (err) {
|
|
1896
|
+
const msg = `Failed to chdir before hook newSession (basePath: ${s.basePath}): ${err instanceof Error ? err.message : String(err)}`;
|
|
1897
|
+
logWarning("engine", msg, { file: "auto.ts", basePath: s.basePath, error: err instanceof Error ? err.message : String(err) });
|
|
1898
|
+
ctx.ui.notify(`${msg}. Cancelling hook dispatch to avoid running in the wrong directory.`, "error");
|
|
1899
|
+
if (wasActive) {
|
|
1900
|
+
s.basePath = previousBasePath;
|
|
1901
|
+
s.currentUnit = previousCurrentUnit;
|
|
1902
|
+
} else {
|
|
1903
|
+
s.reset();
|
|
1904
|
+
}
|
|
1905
|
+
return false;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1886
1908
|
const result = await s.cmdCtx!.newSession();
|
|
1887
1909
|
if (result.cancelled) {
|
|
1888
1910
|
await stopAuto(ctx, pi);
|
|
@@ -1939,11 +1961,6 @@ export async function dispatchHookUnit(
|
|
|
1939
1961
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
1940
1962
|
ctx.ui.notify(`Running post-unit hook: ${hookName}`, "info");
|
|
1941
1963
|
|
|
1942
|
-
// Ensure cwd matches basePath before hook dispatch (#1389)
|
|
1943
|
-
try { if (process.cwd() !== s.basePath) process.chdir(s.basePath); } catch (err) {
|
|
1944
|
-
logWarning("engine", `chdir failed before hook dispatch: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
1964
|
debugLog("dispatchHookUnit", {
|
|
1948
1965
|
phase: "send-message",
|
|
1949
1966
|
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.
|
|
@@ -21,6 +21,7 @@ import { _clearGsdRootCache } from "../paths.ts";
|
|
|
21
21
|
// Isolate from user's global preferences (which may have git.main_branch set)
|
|
22
22
|
let originalHome: string | undefined;
|
|
23
23
|
let fakeHome: string;
|
|
24
|
+
const testCwd = process.cwd();
|
|
24
25
|
|
|
25
26
|
test.before(() => {
|
|
26
27
|
originalHome = process.env.HOME;
|
|
@@ -37,6 +38,11 @@ test.after(() => {
|
|
|
37
38
|
rmSync(fakeHome, { recursive: true, force: true });
|
|
38
39
|
});
|
|
39
40
|
|
|
41
|
+
function cleanupTempRepo(repo: string): void {
|
|
42
|
+
try { process.chdir(testCwd); } catch { /* best-effort */ }
|
|
43
|
+
try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* cleanup best-effort */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
40
46
|
function run(cmd: string, cwd: string): string {
|
|
41
47
|
return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
42
48
|
}
|
|
@@ -106,7 +112,7 @@ test("#2766: stash pop conflict on .gsd/ files is auto-resolved", () => {
|
|
|
106
112
|
try { stashList = run("git stash list", repo); } catch { /* empty stash */ }
|
|
107
113
|
assert.strictEqual(stashList, "", "stash is empty after .gsd/ conflict auto-resolution");
|
|
108
114
|
} finally {
|
|
109
|
-
|
|
115
|
+
cleanupTempRepo(repo);
|
|
110
116
|
}
|
|
111
117
|
});
|
|
112
118
|
|
|
@@ -141,6 +147,6 @@ test("#2766: stash pop conflict on non-.gsd files preserves stash for manual res
|
|
|
141
147
|
"merge succeeds even with non-.gsd stash pop conflict",
|
|
142
148
|
);
|
|
143
149
|
} finally {
|
|
144
|
-
|
|
150
|
+
cleanupTempRepo(repo);
|
|
145
151
|
}
|
|
146
152
|
});
|
|
@@ -35,6 +35,7 @@ import { _clearGsdRootCache } from "../paths.ts";
|
|
|
35
35
|
// Isolate from user's global preferences (which may have git.main_branch set)
|
|
36
36
|
let originalHome: string | undefined;
|
|
37
37
|
let fakeHome: string;
|
|
38
|
+
const testCwd = process.cwd();
|
|
38
39
|
|
|
39
40
|
test.before(() => {
|
|
40
41
|
originalHome = process.env.HOME;
|
|
@@ -51,6 +52,13 @@ test.after(() => {
|
|
|
51
52
|
rmSync(fakeHome, { recursive: true, force: true });
|
|
52
53
|
});
|
|
53
54
|
|
|
55
|
+
function cleanupTempPaths(...paths: string[]): void {
|
|
56
|
+
try { process.chdir(testCwd); } catch { /* best-effort */ }
|
|
57
|
+
for (const p of paths) {
|
|
58
|
+
rmSync(p, { recursive: true, force: true });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
function run(cmd: string, cwd: string): string {
|
|
55
63
|
return execSync(cmd, {
|
|
56
64
|
cwd,
|
|
@@ -271,7 +279,7 @@ test("#2505: mergeMilestoneToMain preserves queued CONTEXT files (not swept into
|
|
|
271
279
|
}
|
|
272
280
|
}
|
|
273
281
|
} finally {
|
|
274
|
-
|
|
282
|
+
cleanupTempPaths(repo);
|
|
275
283
|
}
|
|
276
284
|
});
|
|
277
285
|
|
|
@@ -308,8 +316,7 @@ test("#2505: pre-merge stash handles symlinked .gsd without traversing it", () =
|
|
|
308
316
|
assert.equal(readFileSync(join(repo, "README.md"), "utf-8").replace(/\r\n/g, "\n"), "# test\n\nDirty change.\n");
|
|
309
317
|
assert.equal(readFileSync(join(repo, "local-note.txt"), "utf-8"), "local scratch\n");
|
|
310
318
|
} finally {
|
|
311
|
-
|
|
312
|
-
rmSync(stateDir, { recursive: true, force: true });
|
|
319
|
+
cleanupTempPaths(repo, stateDir);
|
|
313
320
|
}
|
|
314
321
|
});
|
|
315
322
|
|
|
@@ -375,7 +382,7 @@ test("#2505: back-to-back merges preserve queued CONTEXT files", () => {
|
|
|
375
382
|
"M013 context content preserved after back-to-back merges",
|
|
376
383
|
);
|
|
377
384
|
} finally {
|
|
378
|
-
|
|
385
|
+
cleanupTempPaths(repo);
|
|
379
386
|
}
|
|
380
387
|
});
|
|
381
388
|
|
|
@@ -430,7 +437,6 @@ test("#4573: gitignored .gsd symlink does not break pre-merge stash", () => {
|
|
|
430
437
|
".gsd symlink remains in place",
|
|
431
438
|
);
|
|
432
439
|
} finally {
|
|
433
|
-
|
|
434
|
-
rmSync(stateDir, { recursive: true, force: true });
|
|
440
|
+
cleanupTempPaths(repo, stateDir);
|
|
435
441
|
}
|
|
436
442
|
});
|