gsd-pi 2.78.1-dev.9d08d820b → 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/README.md +1 -0
- 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/google-search/index.js +2 -6
- 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/bootstrap/register-hooks.js +12 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +11 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +8 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +8 -0
- package/dist/resources/extensions/gsd/commands-worktree.js +309 -0
- 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/extensions/mcp-client/index.js +0 -6
- 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 +12 -12
- 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 +12 -12
- 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/dist/welcome-screen.js +27 -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/google-search/index.ts +2 -9
- 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/bootstrap/register-hooks.ts +13 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +8 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +10 -0
- package/src/resources/extensions/gsd/commands-worktree.ts +383 -0
- 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/bundled-skill-triggers.test.ts +50 -27
- package/src/resources/extensions/gsd/tests/commands-worktree-clean.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +25 -65
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +34 -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/extensions/mcp-client/index.ts +0 -7
- 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/{-Ukk6_YxRd4GY4iUOnRUE → GlYncvckBGG33CSoJaSnB}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{-Ukk6_YxRd4GY4iUOnRUE → GlYncvckBGG33CSoJaSnB}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -471,6 +471,7 @@ On first run, GSD launches a branded setup wizard that walks you through LLM pro
|
|
|
471
471
|
| `/gsd logs` | Browse activity, debug, and metrics logs |
|
|
472
472
|
| `/gsd export --html` | Generate HTML report for current or completed milestone |
|
|
473
473
|
| `/worktree` (`/wt`) | Git worktree lifecycle — create, switch, merge, remove |
|
|
474
|
+
| `/gsd worktree` (`/gsd wt`) | TUI worktree management — list, merge, clean, remove with safety checks |
|
|
474
475
|
| `/voice` | Toggle real-time speech-to-text (macOS, Linux) |
|
|
475
476
|
| `/exit` | Graceful shutdown — saves session state before exiting |
|
|
476
477
|
| `/kill` | Kill GSD process immediately |
|
|
@@ -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
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
export default function (
|
|
2
|
-
|
|
3
|
-
ctx.ui.notify("google_search is being extracted to @gsd-extensions/google-search " +
|
|
4
|
-
"(not yet published to npm). This stub will be replaced once the " +
|
|
5
|
-
"package is available. No action needed for now.", "warning");
|
|
6
|
-
});
|
|
1
|
+
export default function (_pi) {
|
|
2
|
+
// Deprecation notice intentionally suppressed until @gsd-extensions/google-search ships.
|
|
7
3
|
}
|
|
@@ -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,
|
|
@@ -407,6 +407,18 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
407
407
|
return;
|
|
408
408
|
markToolStart(event.toolCallId, event.toolName);
|
|
409
409
|
safetyRecordToolCall(event.toolCallId, event.toolName, event.input);
|
|
410
|
+
// Persist immediately at dispatch so a mid-unit re-dispatch — which calls
|
|
411
|
+
// resetEvidence() + loadEvidenceFromDisk() in runUnitPhase — cannot wipe
|
|
412
|
+
// the entry between tool_call and tool_execution_end. Without this, the
|
|
413
|
+
// race window equals the tool's runtime, producing the "no bash calls"
|
|
414
|
+
// false positive when the LLM clearly ran a verification command.
|
|
415
|
+
const callDash = getAutoRuntimeSnapshot();
|
|
416
|
+
if (callDash.basePath && callDash.currentUnit?.type === "execute-task") {
|
|
417
|
+
const { milestone: cMid, slice: cSid, task: cTid } = parseUnitId(callDash.currentUnit.id);
|
|
418
|
+
if (cMid && cSid && cTid) {
|
|
419
|
+
saveEvidenceToDisk(callDash.basePath, cMid, cSid, cTid);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
410
422
|
// Destructive command classification (warn only, never block)
|
|
411
423
|
if (isToolCallEventType("bash", event)) {
|
|
412
424
|
const classification = classifyCommand(event.input.command);
|
|
@@ -47,6 +47,17 @@ export const BUNDLED_SKILL_TRIGGERS = [
|
|
|
47
47
|
{ trigger: "HTTP/REST/GraphQL API design — verbs, status codes, pagination, errors, idempotency, versioning", skill: "api-design" },
|
|
48
48
|
{ trigger: "Dependency upgrades — risk-batched, verified between batches, one major per commit", skill: "dependency-upgrade" },
|
|
49
49
|
{ trigger: "Agent-first observability — structured logs, persisted failure state, health surfaces, explicit failure modes", skill: "observability" },
|
|
50
|
+
{ trigger: "React/Next.js performance — components, data fetching, bundle optimization, rendering patterns from Vercel Engineering", skill: "react-best-practices" },
|
|
51
|
+
{ trigger: "Core Web Vitals — fix LCP, CLS, INP; layout shifts; page experience optimization", skill: "core-web-vitals" },
|
|
52
|
+
{ trigger: "GitHub Actions CI/CD — write, run, and debug workflow files; live syntax and run monitoring", skill: "github-workflows" },
|
|
53
|
+
{ trigger: "Comprehensive web quality audit — performance, accessibility, SEO, and best-practices (Lighthouse-style)", skill: "web-quality-audit" },
|
|
54
|
+
{ trigger: "Browser automation — open sites, fill forms, click, screenshot, scrape, or test web apps programmatically", skill: "agent-browser" },
|
|
55
|
+
{ trigger: "Review UI code for Web Interface Guidelines compliance — UX, design, and accessibility patterns", skill: "web-design-guidelines" },
|
|
56
|
+
{ trigger: "UI/UX patterns reference — animations, CSS, typography, prefetching, icons (file:line findings)", skill: "userinterface-wiki" },
|
|
57
|
+
{ trigger: "Author or refine a GSD skill — SKILL.md structure, frontmatter, and best practices", skill: "create-skill" },
|
|
58
|
+
{ trigger: "Create or debug a GSD extension — tools, commands, event hooks, custom TUI, providers", skill: "create-gsd-extension" },
|
|
59
|
+
{ trigger: "Author a YAML workflow definition — steps, triggers, and templates", skill: "create-workflow" },
|
|
60
|
+
{ trigger: "Deep code optimization audit — perf anti-patterns, memory leaks, algorithmic complexity, bundle size, I/O, caching, dead code (parallel pattern-based hunt)", skill: "code-optimizer" },
|
|
50
61
|
];
|
|
51
62
|
function buildBundledSkillsTable() {
|
|
52
63
|
const cwd = process.cwd();
|
|
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { loadRegistry } from "../workflow-templates.js";
|
|
5
5
|
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
6
|
-
export const GSD_COMMAND_DESCRIPTION = "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|model|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|debug|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|onboarding|inspect|extensions|update|fast|mcp|rethink|workflow|codebase|notifications|ship|do|session-report|backlog|pr-branch|add-tests|scan|language";
|
|
6
|
+
export const GSD_COMMAND_DESCRIPTION = "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|model|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|debug|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|onboarding|inspect|extensions|update|fast|mcp|rethink|workflow|codebase|notifications|ship|do|session-report|backlog|pr-branch|add-tests|scan|language|worktree";
|
|
7
7
|
export const TOP_LEVEL_SUBCOMMANDS = [
|
|
8
8
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
9
9
|
{ cmd: "next", desc: "Explicit step mode (same as /gsd)" },
|
|
@@ -71,6 +71,7 @@ export const TOP_LEVEL_SUBCOMMANDS = [
|
|
|
71
71
|
{ cmd: "add-tests", desc: "Generate tests for completed slices" },
|
|
72
72
|
{ cmd: "scan", desc: "Rapid codebase assessment — lightweight alternative to full map (--focus tech|arch|quality|concerns|tech+arch)" },
|
|
73
73
|
{ cmd: "language", desc: "Set or clear the global response language (e.g. /gsd language Chinese)" },
|
|
74
|
+
{ cmd: "worktree", desc: "Manage worktrees from the TUI (list, merge, clean, remove)" },
|
|
74
75
|
];
|
|
75
76
|
const NESTED_COMPLETIONS = {
|
|
76
77
|
auto: [
|
|
@@ -286,6 +287,12 @@ const NESTED_COMPLETIONS = {
|
|
|
286
287
|
{ cmd: "off", desc: "Clear the language preference (revert to default)" },
|
|
287
288
|
{ cmd: "clear", desc: "Alias for off — clear the language preference" },
|
|
288
289
|
],
|
|
290
|
+
worktree: [
|
|
291
|
+
{ cmd: "list", desc: "Show all worktrees with status" },
|
|
292
|
+
{ cmd: "merge", desc: "Merge a worktree into main and clean up" },
|
|
293
|
+
{ cmd: "clean", desc: "Remove all merged/empty worktrees" },
|
|
294
|
+
{ cmd: "remove", desc: "Remove a worktree (--force to skip safety checks)" },
|
|
295
|
+
],
|
|
289
296
|
};
|
|
290
297
|
function filterOptions(partial, options, prefix = "") {
|
|
291
298
|
const normalizedPrefix = prefix ? `${prefix} ` : "";
|
|
@@ -124,6 +124,7 @@ export function showHelp(ctx, args = "") {
|
|
|
124
124
|
" /gsd forensics Examine execution logs and post-mortem analysis",
|
|
125
125
|
" /gsd export Export milestone/slice results [--json|--markdown|--html] [--all]",
|
|
126
126
|
" /gsd cleanup Remove merged branches or snapshots [branches|snapshots]",
|
|
127
|
+
" /gsd worktree Manage worktrees from the TUI [list|merge|clean|remove]",
|
|
127
128
|
" /gsd migrate Migrate .planning/ (v1) to .gsd/ (v2) format",
|
|
128
129
|
" /gsd remote Control remote auto-mode [slack|discord|status|disconnect]",
|
|
129
130
|
" /gsd inspect Show SQLite DB diagnostics (schema, row counts, recent entries)",
|
|
@@ -259,5 +259,13 @@ Examples:
|
|
|
259
259
|
await handleScan(trimmed.replace(/^scan\s*/, "").trim(), ctx, pi);
|
|
260
260
|
return true;
|
|
261
261
|
}
|
|
262
|
+
if (trimmed === "worktree" ||
|
|
263
|
+
trimmed.startsWith("worktree ") ||
|
|
264
|
+
trimmed === "wt" ||
|
|
265
|
+
trimmed.startsWith("wt ")) {
|
|
266
|
+
const { handleWorktree } = await import("../../commands-worktree.js");
|
|
267
|
+
await handleWorktree(trimmed.replace(/^(worktree|wt)\s*/, "").trim(), ctx);
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
262
270
|
return false;
|
|
263
271
|
}
|