@opengsd/gsd-pi 1.0.2-dev.235ebf3 → 1.0.2-dev.2c204d3
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 +63 -12
- package/dist/resource-loader.d.ts +7 -0
- package/dist/resource-loader.js +42 -9
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/context7/index.js +12 -2
- package/dist/resources/extensions/gsd/auto/loop.js +19 -0
- package/dist/resources/extensions/gsd/auto/phases.js +1 -1
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-start.js +232 -49
- package/dist/resources/extensions/gsd/auto-worktree.js +2 -54
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +4 -3
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -15
- package/dist/resources/extensions/gsd/closeout-recovery.js +7 -1
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +9 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +3 -0
- package/dist/resources/extensions/gsd/git-conflict-state.js +26 -1
- package/dist/resources/extensions/gsd/tools/complete-task.js +9 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +40 -1
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -3
- package/dist/resources/extensions/gsd/worktree-post-create-hook.js +117 -0
- package/dist/resources/extensions/search-the-web/native-search.js +57 -8
- package/dist/resources/shared/package-manager-detection.js +36 -0
- package/dist/update-check.d.ts +6 -2
- package/dist/update-check.js +7 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
- 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/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +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 +7 -7
- package/dist/web/standalone/.next/server/chunks/1834.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- 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/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/dist/web/standalone/package.json +0 -1
- package/dist/worktree-cli.d.ts +0 -2
- package/dist/worktree-cli.js +21 -9
- package/package.json +5 -2
- package/packages/cloud-mcp-gateway/bin/gsd-cloud-mcp-gateway.js +14 -0
- package/packages/cloud-mcp-gateway/package.json +4 -3
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +3 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +2 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/bin/gsd-mcp-server.js +14 -0
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +5 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +13 -13
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/bin/pi-ai.js +14 -0
- package/packages/pi-ai/dist/models.generated.d.ts +40 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +49 -30
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +50 -0
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -0
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +3 -2
- package/packages/pi-coding-agent/dist/core/tools/read.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/read.js +5 -3
- package/packages/pi-coding-agent/dist/core/tools/read.js.map +1 -1
- package/packages/pi-coding-agent/package.json +8 -8
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/scripts/install/deps.js +10 -0
- package/scripts/install/detect-existing.js +17 -3
- package/scripts/install/npm-global.js +103 -33
- package/scripts/install.js +1 -0
- package/src/resources/extensions/context7/index.ts +15 -2
- package/src/resources/extensions/gsd/auto/loop.ts +22 -0
- package/src/resources/extensions/gsd/auto/phases.ts +1 -1
- package/src/resources/extensions/gsd/auto/session.ts +3 -0
- package/src/resources/extensions/gsd/auto-start.ts +307 -56
- package/src/resources/extensions/gsd/auto-worktree.ts +2 -56
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +4 -3
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -15
- package/src/resources/extensions/gsd/closeout-recovery.ts +6 -1
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +2 -0
- package/src/resources/extensions/gsd/git-conflict-state.ts +25 -1
- package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +436 -0
- package/src/resources/extensions/gsd/tests/closeout-recovery.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/commands-dispatcher-workspace-git.test.ts +15 -2
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +64 -0
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +70 -10
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +13 -2
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +24 -1
- package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +16 -1
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +141 -1
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +45 -1
- package/src/resources/extensions/gsd/tools/complete-task.ts +9 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +56 -4
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +37 -2
- package/src/resources/extensions/gsd/worktree-post-create-hook.ts +127 -0
- package/src/resources/extensions/search-the-web/native-search.ts +60 -8
- package/src/resources/shared/package-manager-detection.ts +39 -0
- package/dist/tsconfig.extensions.tsbuildinfo +0 -1
- /package/dist/web/standalone/.next/static/{-P554bKh56nzavKUmvFM2 → mijI90BL1BdUcMUnhC0HU}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{-P554bKh56nzavKUmvFM2 → mijI90BL1BdUcMUnhC0HU}/_ssgManifest.js +0 -0
|
@@ -21,7 +21,7 @@ import { invalidateAllCaches } from "./cache.js";
|
|
|
21
21
|
import { writeLock, clearLock, readCrashLock, isLockProcessAlive } from "./crash-recovery.js";
|
|
22
22
|
import { acquireSessionLock, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
|
|
23
23
|
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|
24
|
-
import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit, nativeGetCurrentBranch, nativeDetectMainBranch, nativeBranchList, nativeBranchExists, nativeBranchListMerged, nativeBranchDelete, nativeWorktreeRemove, nativeCommitCountBetween, } from "./native-git-bridge.js";
|
|
24
|
+
import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit, nativeGetCurrentBranch, nativeDetectMainBranch, nativeBranchList, nativeBranchExists, nativeBranchListMerged, nativeBranchDelete, nativeWorktreeRemove, nativeCommitCountBetween, nativeHasChanges, } from "./native-git-bridge.js";
|
|
25
25
|
import { GitServiceImpl } from "./git-service.js";
|
|
26
26
|
import { captureIntegrationBranch, detectWorktreeName, setActiveMilestoneId, } from "./worktree.js";
|
|
27
27
|
import { getAutoWorktreePath, checkoutBranchWithStashGuard } from "./auto-worktree.js";
|
|
@@ -126,17 +126,68 @@ export function resolveSurvivorRecoveryIsolationMode(isolationMode, phase) {
|
|
|
126
126
|
return "branch";
|
|
127
127
|
return isolationMode;
|
|
128
128
|
}
|
|
129
|
-
|
|
129
|
+
function isBlockingStrandedWorkAction(action) {
|
|
130
|
+
return action.kind === "in-progress-stranded-work" && action.blocksAuto;
|
|
131
|
+
}
|
|
132
|
+
function detectWorktreeEvidence(basePath, milestoneId, hasChanges) {
|
|
133
|
+
const wtDir = getWorktreeDir(basePath, milestoneId);
|
|
134
|
+
const wtPath = getAutoWorktreePath(basePath, milestoneId);
|
|
135
|
+
let dirty = false;
|
|
136
|
+
if (wtPath) {
|
|
137
|
+
try {
|
|
138
|
+
dirty = hasChanges(wtPath);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
dirty = false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
path: wtPath,
|
|
146
|
+
dirExists: existsSync(wtDir),
|
|
147
|
+
dirty,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function strandedWorkMessage(args) {
|
|
151
|
+
const evidence = [];
|
|
152
|
+
if (args.branch && args.commitsAhead > 0) {
|
|
153
|
+
evidence.push(`branch ${args.branch} has ${args.commitsAhead} commit(s) ahead of ${args.mainBranch}`);
|
|
154
|
+
}
|
|
155
|
+
if (args.dirtyWorktree) {
|
|
156
|
+
evidence.push("the worktree has uncommitted changes");
|
|
157
|
+
}
|
|
158
|
+
if (evidence.length === 0) {
|
|
159
|
+
evidence.push("physical git evidence exists");
|
|
160
|
+
}
|
|
161
|
+
const wtSuffix = args.worktreeDirExists
|
|
162
|
+
? ` Worktree directory at .gsd/worktrees/${args.milestoneId}/ holds live work.`
|
|
163
|
+
: "";
|
|
164
|
+
const recovery = args.recoveryMode === "worktree"
|
|
165
|
+
? "Recovering will adopt the existing worktree."
|
|
166
|
+
: "Recovering will adopt the milestone branch.";
|
|
167
|
+
return (`Stranded work for in-progress milestone ${args.milestoneId}: ${evidence.join("; ")}.` +
|
|
168
|
+
wtSuffix +
|
|
169
|
+
` ${recovery} Park or discard explicitly if abandoning.`);
|
|
170
|
+
}
|
|
171
|
+
export function auditOrphanedMilestoneBranches(basePath, _isolationMode, gitDeps = {}) {
|
|
130
172
|
const recovered = [];
|
|
131
173
|
const warnings = [];
|
|
174
|
+
const actions = [];
|
|
132
175
|
const branchList = gitDeps.branchList ?? nativeBranchList;
|
|
133
176
|
const branchExists = gitDeps.branchExists ?? nativeBranchExists;
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
177
|
+
const hasChanges = gitDeps.hasChanges ?? nativeHasChanges;
|
|
178
|
+
const pushAction = (action) => {
|
|
179
|
+
actions.push(action);
|
|
180
|
+
if (action.severity === "info") {
|
|
181
|
+
recovered.push(action.message);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
warnings.push(action.message);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
137
187
|
// Skip if DB not available — can't determine completion status
|
|
138
|
-
if (!isDbAvailable())
|
|
139
|
-
return { recovered, warnings };
|
|
188
|
+
if (!isDbAvailable()) {
|
|
189
|
+
return { recovered, warnings, actions, blockingStrandedWork: null };
|
|
190
|
+
}
|
|
140
191
|
let milestoneBranches;
|
|
141
192
|
let milestoneBranchListAvailable = true;
|
|
142
193
|
try {
|
|
@@ -170,6 +221,7 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode, gitDeps
|
|
|
170
221
|
if (!milestone)
|
|
171
222
|
continue;
|
|
172
223
|
const isMerged = mergedBranches.has(branch);
|
|
224
|
+
const worktreeEvidence = detectWorktreeEvidence(basePath, milestoneId, hasChanges);
|
|
173
225
|
// #4762 — in-progress milestone branch with unmerged commits ahead of
|
|
174
226
|
// main. This is the pre-completion orphan case: auto-mode exited without
|
|
175
227
|
// completing the milestone (pause, stop, crash, merge error, blocker) and
|
|
@@ -181,32 +233,45 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode, gitDeps
|
|
|
181
233
|
// Parked/other closed statuses go through the legacy complete/unmerged
|
|
182
234
|
// path below where appropriate.
|
|
183
235
|
if (!isClosedStatus(milestone.status)) {
|
|
184
|
-
if (isMerged)
|
|
185
|
-
continue; // nothing to recover
|
|
186
236
|
let commitsAhead = 0;
|
|
187
237
|
try {
|
|
188
238
|
commitsAhead = nativeCommitCountBetween(basePath, mainBranch, branch);
|
|
189
239
|
}
|
|
190
240
|
catch {
|
|
191
|
-
|
|
192
|
-
continue;
|
|
241
|
+
commitsAhead = 0;
|
|
193
242
|
}
|
|
194
|
-
if (commitsAhead === 0)
|
|
243
|
+
if ((isMerged || commitsAhead === 0) && !worktreeEvidence.dirty)
|
|
195
244
|
continue;
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
245
|
+
const recoveryMode = worktreeEvidence.path
|
|
246
|
+
? "worktree"
|
|
247
|
+
: "branch";
|
|
248
|
+
const message = strandedWorkMessage({
|
|
249
|
+
milestoneId,
|
|
250
|
+
branch,
|
|
251
|
+
commitsAhead,
|
|
252
|
+
mainBranch,
|
|
253
|
+
dirtyWorktree: worktreeEvidence.dirty,
|
|
254
|
+
worktreeDirExists: worktreeEvidence.dirExists,
|
|
255
|
+
recoveryMode,
|
|
256
|
+
});
|
|
257
|
+
pushAction({
|
|
258
|
+
kind: "in-progress-stranded-work",
|
|
259
|
+
milestoneId,
|
|
260
|
+
branch,
|
|
261
|
+
commitsAhead,
|
|
262
|
+
dirtyWorktree: worktreeEvidence.dirty,
|
|
263
|
+
worktreeDirExists: worktreeEvidence.dirExists,
|
|
264
|
+
recoveryMode,
|
|
265
|
+
message,
|
|
266
|
+
severity: "warning",
|
|
267
|
+
blocksAuto: true,
|
|
268
|
+
});
|
|
204
269
|
// #4764 telemetry
|
|
205
270
|
try {
|
|
206
271
|
emitWorktreeOrphaned(basePath, milestoneId, {
|
|
207
272
|
reason: "in-progress-unmerged",
|
|
208
273
|
commitsAhead,
|
|
209
|
-
worktreeDirExists:
|
|
274
|
+
worktreeDirExists: worktreeEvidence.dirExists,
|
|
210
275
|
});
|
|
211
276
|
}
|
|
212
277
|
catch (err) {
|
|
@@ -223,7 +288,14 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode, gitDeps
|
|
|
223
288
|
// Branch is merged — safe to delete branch and clean up worktree dir
|
|
224
289
|
try {
|
|
225
290
|
nativeBranchDelete(basePath, branch, true);
|
|
226
|
-
|
|
291
|
+
pushAction({
|
|
292
|
+
kind: "complete-merged-branch",
|
|
293
|
+
milestoneId,
|
|
294
|
+
branch,
|
|
295
|
+
message: `Deleted merged branch ${branch} for completed milestone ${milestoneId}.`,
|
|
296
|
+
severity: "info",
|
|
297
|
+
blocksAuto: false,
|
|
298
|
+
});
|
|
227
299
|
}
|
|
228
300
|
catch (err) {
|
|
229
301
|
warnings.push(`Failed to delete merged branch ${branch}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -246,7 +318,15 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode, gitDeps
|
|
|
246
318
|
if (isInsideWorktreesDir(basePath, wtDir)) {
|
|
247
319
|
try {
|
|
248
320
|
rmSync(wtDir, { recursive: true, force: true });
|
|
249
|
-
|
|
321
|
+
pushAction({
|
|
322
|
+
kind: "complete-merged-worktree",
|
|
323
|
+
milestoneId,
|
|
324
|
+
branch,
|
|
325
|
+
worktreeDirExists: true,
|
|
326
|
+
message: `Removed orphaned worktree directory for ${milestoneId}.`,
|
|
327
|
+
severity: "info",
|
|
328
|
+
blocksAuto: false,
|
|
329
|
+
});
|
|
250
330
|
}
|
|
251
331
|
catch (err2) {
|
|
252
332
|
warnings.push(`Failed to remove worktree directory for ${milestoneId}: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
@@ -257,14 +337,30 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode, gitDeps
|
|
|
257
337
|
}
|
|
258
338
|
}
|
|
259
339
|
else {
|
|
260
|
-
|
|
340
|
+
pushAction({
|
|
341
|
+
kind: "complete-merged-worktree",
|
|
342
|
+
milestoneId,
|
|
343
|
+
branch,
|
|
344
|
+
worktreeDirExists: true,
|
|
345
|
+
message: `Removed orphaned worktree directory for ${milestoneId}.`,
|
|
346
|
+
severity: "info",
|
|
347
|
+
blocksAuto: false,
|
|
348
|
+
});
|
|
261
349
|
}
|
|
262
350
|
}
|
|
263
351
|
}
|
|
264
352
|
else {
|
|
265
353
|
// Branch is NOT merged — preserve for safety, warn the user
|
|
266
|
-
|
|
267
|
-
|
|
354
|
+
pushAction({
|
|
355
|
+
kind: "complete-unmerged-branch",
|
|
356
|
+
milestoneId,
|
|
357
|
+
branch,
|
|
358
|
+
worktreeDirExists: worktreeEvidence.dirExists,
|
|
359
|
+
message: `Branch ${branch} exists for completed milestone ${milestoneId} but is NOT merged into ${mainBranch}. ` +
|
|
360
|
+
`This may contain unmerged work. Merge manually or run \`/gsd doctor fix\` to resolve.`,
|
|
361
|
+
severity: "warning",
|
|
362
|
+
blocksAuto: false,
|
|
363
|
+
});
|
|
268
364
|
// #4764 telemetry
|
|
269
365
|
try {
|
|
270
366
|
emitWorktreeOrphaned(basePath, milestoneId, {
|
|
@@ -300,6 +396,43 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode, gitDeps
|
|
|
300
396
|
completedMilestones = [];
|
|
301
397
|
}
|
|
302
398
|
for (const m of completedMilestones) {
|
|
399
|
+
if (!isClosedStatus(m.status)) {
|
|
400
|
+
if (seenMilestoneIds.has(m.id))
|
|
401
|
+
continue;
|
|
402
|
+
const worktreeEvidence = detectWorktreeEvidence(basePath, m.id, hasChanges);
|
|
403
|
+
if (!worktreeEvidence.dirty)
|
|
404
|
+
continue;
|
|
405
|
+
const message = strandedWorkMessage({
|
|
406
|
+
milestoneId: m.id,
|
|
407
|
+
commitsAhead: 0,
|
|
408
|
+
mainBranch,
|
|
409
|
+
dirtyWorktree: true,
|
|
410
|
+
worktreeDirExists: worktreeEvidence.dirExists,
|
|
411
|
+
recoveryMode: "worktree",
|
|
412
|
+
});
|
|
413
|
+
pushAction({
|
|
414
|
+
kind: "in-progress-stranded-work",
|
|
415
|
+
milestoneId: m.id,
|
|
416
|
+
commitsAhead: 0,
|
|
417
|
+
dirtyWorktree: true,
|
|
418
|
+
worktreeDirExists: worktreeEvidence.dirExists,
|
|
419
|
+
recoveryMode: "worktree",
|
|
420
|
+
message,
|
|
421
|
+
severity: "warning",
|
|
422
|
+
blocksAuto: true,
|
|
423
|
+
});
|
|
424
|
+
try {
|
|
425
|
+
emitWorktreeOrphaned(basePath, m.id, {
|
|
426
|
+
reason: "in-progress-unmerged",
|
|
427
|
+
commitsAhead: 0,
|
|
428
|
+
worktreeDirExists: worktreeEvidence.dirExists,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
catch (err) {
|
|
432
|
+
logWarning("engine", `worktree-orphaned telemetry failed for ${m.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
433
|
+
}
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
303
436
|
if (m.status !== "complete")
|
|
304
437
|
continue;
|
|
305
438
|
if (seenMilestoneIds.has(m.id))
|
|
@@ -332,17 +465,36 @@ export function auditOrphanedMilestoneBranches(basePath, isolationMode, gitDeps
|
|
|
332
465
|
if (existsSync(wtDir)) {
|
|
333
466
|
try {
|
|
334
467
|
rmSync(wtDir, { recursive: true, force: true });
|
|
335
|
-
|
|
468
|
+
pushAction({
|
|
469
|
+
kind: "complete-branchless-worktree",
|
|
470
|
+
milestoneId: m.id,
|
|
471
|
+
worktreeDirExists: true,
|
|
472
|
+
message: `Removed orphaned worktree directory for ${m.id} (branch already deleted).`,
|
|
473
|
+
severity: "info",
|
|
474
|
+
blocksAuto: false,
|
|
475
|
+
});
|
|
336
476
|
}
|
|
337
477
|
catch (err) {
|
|
338
478
|
warnings.push(`Failed to remove orphaned worktree directory for ${m.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
339
479
|
}
|
|
340
480
|
}
|
|
341
481
|
else {
|
|
342
|
-
|
|
482
|
+
pushAction({
|
|
483
|
+
kind: "complete-branchless-worktree",
|
|
484
|
+
milestoneId: m.id,
|
|
485
|
+
worktreeDirExists: true,
|
|
486
|
+
message: `Removed orphaned worktree directory for ${m.id} (branch already deleted).`,
|
|
487
|
+
severity: "info",
|
|
488
|
+
blocksAuto: false,
|
|
489
|
+
});
|
|
343
490
|
}
|
|
344
491
|
}
|
|
345
|
-
return {
|
|
492
|
+
return {
|
|
493
|
+
recovered,
|
|
494
|
+
warnings,
|
|
495
|
+
actions,
|
|
496
|
+
blockingStrandedWork: actions.find(isBlockingStrandedWorkAction) ?? null,
|
|
497
|
+
};
|
|
346
498
|
}
|
|
347
499
|
/**
|
|
348
500
|
* Pure decision function for picking which orphan milestone the auto-loop
|
|
@@ -679,17 +831,27 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
679
831
|
// was lost due to session ending between completion and teardown.
|
|
680
832
|
// Must run after DB open and before worktree entry.
|
|
681
833
|
let orphanAuditRecovered = false;
|
|
834
|
+
let strandedRecoveryActions = [];
|
|
835
|
+
let strandedRecoveryAction = null;
|
|
682
836
|
try {
|
|
683
837
|
const auditResult = auditOrphanedMilestoneBranches(base, getIsolationMode(base));
|
|
838
|
+
strandedRecoveryActions = auditResult.actions.filter(isBlockingStrandedWorkAction);
|
|
839
|
+
strandedRecoveryAction = strandedRecoveryActions[0] ?? null;
|
|
684
840
|
for (const msg of auditResult.recovered) {
|
|
685
841
|
ctx.ui.notify(`Orphan audit: ${msg}`, "info");
|
|
686
842
|
}
|
|
687
843
|
for (const msg of auditResult.warnings) {
|
|
688
|
-
|
|
844
|
+
const prefix = msg.startsWith("Stranded work") ? "" : "Orphan audit: ";
|
|
845
|
+
ctx.ui.notify(`${prefix}${msg}`, "warning");
|
|
689
846
|
}
|
|
690
847
|
if (auditResult.recovered.length > 0) {
|
|
691
848
|
orphanAuditRecovered = true;
|
|
692
|
-
debugLog("orphan-audit", {
|
|
849
|
+
debugLog("orphan-audit", {
|
|
850
|
+
recovered: auditResult.recovered,
|
|
851
|
+
warnings: auditResult.warnings,
|
|
852
|
+
strandedRecoveryAction,
|
|
853
|
+
strandedRecoveryActions,
|
|
854
|
+
});
|
|
693
855
|
}
|
|
694
856
|
}
|
|
695
857
|
catch (err) {
|
|
@@ -724,13 +886,6 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
724
886
|
logWarning("bootstrap", `orphaned preflight-stash audit failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
725
887
|
}
|
|
726
888
|
let state = await deriveState(base);
|
|
727
|
-
if (process.env.GSD_HEADLESS === "1" &&
|
|
728
|
-
orphanAuditRecovered &&
|
|
729
|
-
!state.activeMilestone &&
|
|
730
|
-
state.phase === "complete") {
|
|
731
|
-
ctx.ui.notify("Auto-mode stopped (Recovered completed milestone cleanup; all milestones complete).", "info");
|
|
732
|
-
return releaseLockAndReturn();
|
|
733
|
-
}
|
|
734
889
|
// Stale worktree state recovery (#654)
|
|
735
890
|
if (state.activeMilestone &&
|
|
736
891
|
shouldUseWorktreeIsolation(base) &&
|
|
@@ -740,6 +895,28 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
740
895
|
state = await deriveState(wtPath);
|
|
741
896
|
}
|
|
742
897
|
}
|
|
898
|
+
const blockingStrandedRecoveryAction = state.activeMilestone
|
|
899
|
+
? strandedRecoveryActions.find((action) => action.milestoneId !== state.activeMilestone?.id) ?? strandedRecoveryAction
|
|
900
|
+
: strandedRecoveryAction;
|
|
901
|
+
if (blockingStrandedRecoveryAction) {
|
|
902
|
+
if (!state.activeMilestone) {
|
|
903
|
+
ctx.ui.notify(`Stranded work for ${blockingStrandedRecoveryAction.milestoneId} blocks auto-mode, but that milestone is not active in project state. Park or discard it explicitly before continuing.`, "error");
|
|
904
|
+
return releaseLockAndReturn();
|
|
905
|
+
}
|
|
906
|
+
if (state.activeMilestone.id !== blockingStrandedRecoveryAction.milestoneId) {
|
|
907
|
+
ctx.ui.notify(`Stranded work for ${blockingStrandedRecoveryAction.milestoneId} blocks auto-mode before ${state.activeMilestone.id}. Recover, park, or discard ${blockingStrandedRecoveryAction.milestoneId} explicitly before continuing.`, "error");
|
|
908
|
+
return releaseLockAndReturn();
|
|
909
|
+
}
|
|
910
|
+
strandedRecoveryAction = blockingStrandedRecoveryAction;
|
|
911
|
+
ctx.ui.notify(`Recovering stranded work for ${strandedRecoveryAction.milestoneId} before dispatching new units.`, "info");
|
|
912
|
+
}
|
|
913
|
+
if (process.env.GSD_HEADLESS === "1" &&
|
|
914
|
+
orphanAuditRecovered &&
|
|
915
|
+
!state.activeMilestone &&
|
|
916
|
+
state.phase === "complete") {
|
|
917
|
+
ctx.ui.notify("Auto-mode stopped (Recovered completed milestone cleanup; all milestones complete).", "info");
|
|
918
|
+
return releaseLockAndReturn();
|
|
919
|
+
}
|
|
743
920
|
// Milestone branch recovery (#601, #2358)
|
|
744
921
|
// Detect survivor milestone branches in both pre-planning and complete phases.
|
|
745
922
|
// In phase=complete, the milestone artifacts exist but finalization (merge,
|
|
@@ -768,7 +945,8 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
768
945
|
// The worktree/branch was created but the milestone only has CONTEXT-DRAFT.md.
|
|
769
946
|
// Route to the interactive discussion handler instead of falling through to
|
|
770
947
|
// auto-mode, which would immediately stop with "needs discussion".
|
|
771
|
-
if (
|
|
948
|
+
if (!strandedRecoveryAction &&
|
|
949
|
+
decideSurvivorAction(hasSurvivorBranch, state.phase) === "discuss") {
|
|
772
950
|
const { showSmartEntry } = await import("./guided-flow.js");
|
|
773
951
|
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
774
952
|
invalidateAllCaches();
|
|
@@ -848,14 +1026,14 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
848
1026
|
const effectivePrefs = loadEffectiveGSDPreferences(base)?.preferences;
|
|
849
1027
|
const { shouldRunDeepProjectSetup } = await import("./auto-dispatch.js");
|
|
850
1028
|
const deepProjectStagePending = shouldRunDeepProjectSetup(state, effectivePrefs, base, { hasSurvivorBranch });
|
|
851
|
-
if (deepProjectStagePending) {
|
|
1029
|
+
if (deepProjectStagePending && !strandedRecoveryAction) {
|
|
852
1030
|
// Deep project-level setup runs before the first milestone exists. Let
|
|
853
1031
|
// the auto loop dispatch workflow-preferences / project / requirements
|
|
854
1032
|
// units instead of recursing back through showSmartEntry while this
|
|
855
1033
|
// bootstrap still holds the session lock.
|
|
856
1034
|
s.currentMilestoneId = null;
|
|
857
1035
|
}
|
|
858
|
-
if (!hasSurvivorBranch && !deepProjectStagePending) {
|
|
1036
|
+
if (!hasSurvivorBranch && !deepProjectStagePending && !strandedRecoveryAction) {
|
|
859
1037
|
// No active work — start a new milestone via discuss flow
|
|
860
1038
|
if (!state.activeMilestone || state.phase === "complete") {
|
|
861
1039
|
// Guard against recursive dialog loop (#1348):
|
|
@@ -917,7 +1095,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
917
1095
|
}
|
|
918
1096
|
}
|
|
919
1097
|
// Unreachable safety check
|
|
920
|
-
if (!state.activeMilestone && !deepProjectStagePending) {
|
|
1098
|
+
if (!state.activeMilestone && !deepProjectStagePending && !strandedRecoveryAction) {
|
|
921
1099
|
const { showSmartEntry } = await import("./guided-flow.js");
|
|
922
1100
|
await showSmartEntry(ctx, pi, base, { step: requestedStepMode });
|
|
923
1101
|
return releaseLockAndReturn();
|
|
@@ -952,7 +1130,9 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
952
1130
|
s.resourceVersionOnStart = readResourceVersion();
|
|
953
1131
|
s.pendingQuickTasks = [];
|
|
954
1132
|
s.currentUnit = null;
|
|
955
|
-
s.currentMilestoneId ??=
|
|
1133
|
+
s.currentMilestoneId ??=
|
|
1134
|
+
strandedRecoveryAction?.milestoneId ??
|
|
1135
|
+
(deepProjectStagePending ? null : state.activeMilestone?.id ?? null);
|
|
956
1136
|
s.originalModelId = startModelSnapshot?.id ?? ctx.model?.id ?? null;
|
|
957
1137
|
s.originalModelProvider = startModelSnapshot?.provider ?? ctx.model?.provider ?? null;
|
|
958
1138
|
s.originalThinkingLevel = startThinkingSnapshot ?? null;
|
|
@@ -960,7 +1140,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
960
1140
|
registerSigtermHandler(base);
|
|
961
1141
|
// Capture integration branch
|
|
962
1142
|
if (s.currentMilestoneId) {
|
|
963
|
-
if (getIsolationMode(base) !== "none") {
|
|
1143
|
+
if (getIsolationMode(base) !== "none" || strandedRecoveryAction) {
|
|
964
1144
|
captureIntegrationBranch(base, s.currentMilestoneId);
|
|
965
1145
|
}
|
|
966
1146
|
setActiveMilestoneId(base, s.currentMilestoneId);
|
|
@@ -970,7 +1150,7 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
970
1150
|
// milestone/<MID>. Auto-checkout back to the integration branch.
|
|
971
1151
|
const isolationMode = getIsolationMode(base);
|
|
972
1152
|
const isRepo = nativeIsRepo(base);
|
|
973
|
-
if (isolationMode === "none" && isRepo) {
|
|
1153
|
+
if (isolationMode === "none" && isRepo && !strandedRecoveryAction) {
|
|
974
1154
|
try {
|
|
975
1155
|
const currentBranch = nativeGetCurrentBranch(base);
|
|
976
1156
|
const integrationBranch = nativeDetectMainBranch(base);
|
|
@@ -1001,12 +1181,15 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
|
|
|
1001
1181
|
return symlinkRe.test(p);
|
|
1002
1182
|
};
|
|
1003
1183
|
if (s.currentMilestoneId &&
|
|
1004
|
-
getIsolationMode(base) !== "none" &&
|
|
1184
|
+
(getIsolationMode(base) !== "none" || strandedRecoveryAction?.recoveryMode) &&
|
|
1005
1185
|
!detectWorktreeName(base) &&
|
|
1006
1186
|
!isUnderGsdWorktrees(base)) {
|
|
1007
|
-
const
|
|
1008
|
-
|
|
1009
|
-
|
|
1187
|
+
const lifecycle = buildLifecycle();
|
|
1188
|
+
const enterResult = strandedRecoveryAction?.recoveryMode
|
|
1189
|
+
? lifecycle.adoptStrandedMilestone(s.currentMilestoneId, base, { notify: ctx.ui.notify.bind(ctx.ui) }, { mode: strandedRecoveryAction.recoveryMode })
|
|
1190
|
+
: lifecycle.enterMilestone(s.currentMilestoneId, {
|
|
1191
|
+
notify: ctx.ui.notify.bind(ctx.ui),
|
|
1192
|
+
});
|
|
1010
1193
|
if (!enterResult.ok) {
|
|
1011
1194
|
s.active = false;
|
|
1012
1195
|
if (enterResult.reason === "lease-conflict") {
|
|
@@ -23,6 +23,7 @@ import { debugLog } from "./debug-logger.js";
|
|
|
23
23
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
24
24
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
25
25
|
import { MILESTONE_ID_RE } from "./milestone-ids.js";
|
|
26
|
+
import { runWorktreePostCreateHook } from "./worktree-post-create-hook.js";
|
|
26
27
|
import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeWorkingTreeStatus, nativeAddAllWithExclusions, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeConflictFiles, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchForceReset, nativeBranchExists, nativeDiffNumstat, nativeUpdateRef, nativeIsAncestor, nativeMergeAbort, nativeWorktreeList, nativeLsFiles, } from "./native-git-bridge.js";
|
|
27
28
|
import { gsdHome } from "./gsd-home.js";
|
|
28
29
|
import { createWorkspace } from "./workspace.js";
|
|
@@ -796,60 +797,7 @@ export function syncGsdStateToWorktree(mainBasePath, worktreePath_) {
|
|
|
796
797
|
export function syncWorktreeStateBack(mainBasePath, worktreePath, milestoneId) {
|
|
797
798
|
return _finalizeProjectionForMergeImpl(mainBasePath, worktreePath, milestoneId);
|
|
798
799
|
}
|
|
799
|
-
|
|
800
|
-
/**
|
|
801
|
-
* Run the user-configured post-create hook script after worktree creation.
|
|
802
|
-
* The script receives SOURCE_DIR and WORKTREE_DIR as environment variables.
|
|
803
|
-
* Failure is non-fatal — returns the error message or null on success.
|
|
804
|
-
*
|
|
805
|
-
* Reads the hook path from git.worktree_post_create in preferences.
|
|
806
|
-
* Pass hookPath directly to bypass preference loading (useful for testing).
|
|
807
|
-
*/
|
|
808
|
-
export function runWorktreePostCreateHook(sourceDir, worktreeDir, hookPath) {
|
|
809
|
-
if (hookPath === undefined) {
|
|
810
|
-
const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
811
|
-
hookPath = prefs?.worktree_post_create;
|
|
812
|
-
}
|
|
813
|
-
if (!hookPath)
|
|
814
|
-
return null;
|
|
815
|
-
// Resolve relative paths against the source project root.
|
|
816
|
-
// On Windows, convert 8.3 short paths (e.g. RUNNER~1) to long paths
|
|
817
|
-
// so execFileSync can locate the file correctly.
|
|
818
|
-
let resolved = isAbsolute(hookPath) ? hookPath : join(sourceDir, hookPath);
|
|
819
|
-
if (!existsSync(resolved)) {
|
|
820
|
-
return `Worktree post-create hook not found: ${resolved}`;
|
|
821
|
-
}
|
|
822
|
-
if (process.platform === "win32") {
|
|
823
|
-
try {
|
|
824
|
-
resolved = realpathSync.native(resolved);
|
|
825
|
-
}
|
|
826
|
-
catch (err) { /* keep original */
|
|
827
|
-
logWarning("worktree", `realpath failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
try {
|
|
831
|
-
// .bat/.cmd files on Windows require shell mode — execFileSync cannot
|
|
832
|
-
// spawn them directly (EINVAL).
|
|
833
|
-
const needsShell = process.platform === "win32" && /\.(bat|cmd)$/i.test(resolved);
|
|
834
|
-
execFileSync(resolved, [], {
|
|
835
|
-
cwd: worktreeDir,
|
|
836
|
-
env: {
|
|
837
|
-
...process.env,
|
|
838
|
-
SOURCE_DIR: sourceDir,
|
|
839
|
-
WORKTREE_DIR: worktreeDir,
|
|
840
|
-
},
|
|
841
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
842
|
-
encoding: "utf-8",
|
|
843
|
-
timeout: 30_000, // 30 second timeout
|
|
844
|
-
shell: needsShell,
|
|
845
|
-
});
|
|
846
|
-
return null;
|
|
847
|
-
}
|
|
848
|
-
catch (err) {
|
|
849
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
850
|
-
return `Worktree post-create hook failed: ${msg}`;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
800
|
+
export { runWorktreePostCreateHook } from "./worktree-post-create-hook.js";
|
|
853
801
|
// ─── Auto-Worktree Branch Naming ───────────────────────────────────────────
|
|
854
802
|
/** Returns the git branch name for a milestone worktree (`milestone/<MID>`). */
|
|
855
803
|
export function autoWorktreeBranch(milestoneId) {
|
|
@@ -678,8 +678,9 @@ export function registerDbTools(pi) {
|
|
|
678
678
|
promptSnippet: "Complete a GSD task (DB write + summary render + checkbox toggle)",
|
|
679
679
|
promptGuidelines: [
|
|
680
680
|
"Use gsd_task_complete (or gsd_complete_task) when a task is finished and needs to be recorded.",
|
|
681
|
-
"
|
|
682
|
-
"
|
|
681
|
+
"Include verification whenever possible. If verification is omitted, the executor derives it from verificationEvidence when possible.",
|
|
682
|
+
"verificationEvidence is an array of objects with command, exitCode, verdict, durationMs.",
|
|
683
|
+
"The tool validates required fields and returns an error message if verification cannot be derived.",
|
|
683
684
|
"On success, returns the summaryPath where the SUMMARY.md was written.",
|
|
684
685
|
"Idempotent — calling with the same params twice will upsert (INSERT OR REPLACE) without error.",
|
|
685
686
|
],
|
|
@@ -690,7 +691,7 @@ export function registerDbTools(pi) {
|
|
|
690
691
|
milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
|
|
691
692
|
oneLiner: Type.String({ description: "One-line summary of what was accomplished" }),
|
|
692
693
|
narrative: Type.String({ description: "Detailed narrative of what happened during the task" }),
|
|
693
|
-
verification: Type.String({ description: "What was verified and how — commands run, tests passed, behavior confirmed" }),
|
|
694
|
+
verification: Type.Optional(Type.String({ description: "What was verified and how — commands run, tests passed, behavior confirmed. If omitted, derived from verificationEvidence when possible." })),
|
|
694
695
|
// ── Enrichment metadata (optional — defaults to empty) ────────────
|
|
695
696
|
deviations: Type.Optional(Type.String({ description: "Deviations from the task plan, or 'None.'" })),
|
|
696
697
|
knownIssues: Type.Optional(Type.String({ description: "Known issues discovered but not fixed, or 'None.'" })),
|
|
@@ -418,6 +418,17 @@ function initSessionNotifications(ctx) {
|
|
|
418
418
|
installNotifyInterceptor(ctx);
|
|
419
419
|
initNotificationWidget(ctx);
|
|
420
420
|
}
|
|
421
|
+
async function prepareWorkflowMcpForHookContext(ctx, basePath) {
|
|
422
|
+
// Skip MCP auto-prep when running inside an auto-worktree. The worktree
|
|
423
|
+
// already has .mcp.json from createAutoWorktree, and re-running the writer
|
|
424
|
+
// post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
|
|
425
|
+
// CLI path resolution), dirtying the tree and breaking the milestone merge.
|
|
426
|
+
const { isInAutoWorktree } = await import("../auto-worktree.js");
|
|
427
|
+
if (isInAutoWorktree(basePath))
|
|
428
|
+
return;
|
|
429
|
+
const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
|
|
430
|
+
prepareWorkflowMcpForProject(ctx, basePath);
|
|
431
|
+
}
|
|
421
432
|
export function registerHooks(pi, ecosystemHandlers) {
|
|
422
433
|
// ADR-005 Phase 3b: surface pi-ai ProviderSwitchReport via audit, notification, and counter.
|
|
423
434
|
// Idempotent — only the first registerHooks call installs.
|
|
@@ -438,12 +449,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
438
449
|
await syncServiceTierStatus(ctx);
|
|
439
450
|
await applyDisabledModelProviderPolicy(ctx);
|
|
440
451
|
await applyCompactionThresholdOverride(ctx);
|
|
441
|
-
|
|
442
|
-
const { isInAutoWorktree } = await import("../auto-worktree.js");
|
|
443
|
-
if (!isInAutoWorktree(basePath)) {
|
|
444
|
-
const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
|
|
445
|
-
prepareWorkflowMcpForProject(ctx, basePath);
|
|
446
|
-
}
|
|
452
|
+
await prepareWorkflowMcpForHookContext(ctx, basePath);
|
|
447
453
|
// Apply show_token_cost preference (#1515)
|
|
448
454
|
try {
|
|
449
455
|
const { loadEffectiveGSDPreferences } = await import("../preferences.js");
|
|
@@ -468,15 +474,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
468
474
|
await syncServiceTierStatus(ctx);
|
|
469
475
|
await applyDisabledModelProviderPolicy(ctx);
|
|
470
476
|
await applyCompactionThresholdOverride(ctx);
|
|
471
|
-
|
|
472
|
-
// already has .mcp.json from createAutoWorktree, and re-running the writer
|
|
473
|
-
// post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
|
|
474
|
-
// CLI path resolution), dirtying the tree and breaking the milestone merge.
|
|
475
|
-
const { isInAutoWorktree } = await import("../auto-worktree.js");
|
|
476
|
-
if (!isInAutoWorktree(basePath)) {
|
|
477
|
-
const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
|
|
478
|
-
prepareWorkflowMcpForProject(ctx, basePath);
|
|
479
|
-
}
|
|
477
|
+
await prepareWorkflowMcpForHookContext(ctx, basePath);
|
|
480
478
|
await loadToolApiKeysForSession();
|
|
481
479
|
if (!isAutoActive()) {
|
|
482
480
|
ctx.ui.setWidget("gsd-progress", undefined);
|
|
@@ -511,6 +509,10 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
511
509
|
}
|
|
512
510
|
}
|
|
513
511
|
clearDeferredApprovalGate(beforeAgentBasePath);
|
|
512
|
+
// session_start can fire before the active provider has settled. By
|
|
513
|
+
// before_agent_start, Claude Code CLI sessions should get the same
|
|
514
|
+
// project MCP config that /gsd mcp init would write.
|
|
515
|
+
await prepareWorkflowMcpForHookContext(ctx, beforeAgentBasePath);
|
|
514
516
|
// GSD's own context injection (existing behavior — unchanged).
|
|
515
517
|
const { buildBeforeAgentStartResult } = await import("./system-context.js");
|
|
516
518
|
const gsdResult = await buildBeforeAgentStartResult(event, ctx);
|
|
@@ -167,7 +167,13 @@ export function getCloseoutManualResolveBlocker(basePath) {
|
|
|
167
167
|
if (conflictProbe.status === "dirty" && conflictProbe.unmerged.length > 0) {
|
|
168
168
|
return `Unmerged paths remain in ${basePath}: ${conflictProbe.unmerged.slice(0, 5).join(", ")}`;
|
|
169
169
|
}
|
|
170
|
-
|
|
170
|
+
let status;
|
|
171
|
+
try {
|
|
172
|
+
status = runGit(basePath, ["status", "--porcelain"]);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return `Could not inspect git status in ${basePath}.`;
|
|
176
|
+
}
|
|
171
177
|
if (status) {
|
|
172
178
|
return `Working tree still has uncommitted changes in ${basePath}. Commit, stash, or run /gsd closeout retry first.`;
|
|
173
179
|
}
|
|
@@ -197,7 +197,15 @@ export async function handleAutoCommand(trimmed, ctx, pi) {
|
|
|
197
197
|
if (trimmed === "") {
|
|
198
198
|
if (!(await guardRemoteSession(ctx, pi)))
|
|
199
199
|
return true;
|
|
200
|
-
|
|
200
|
+
const basePath = projectRoot();
|
|
201
|
+
const { hasGsdBootstrapArtifacts } = await import("../../detection.js");
|
|
202
|
+
const { gsdRoot } = await import("../../paths.js");
|
|
203
|
+
if (!hasGsdBootstrapArtifacts(gsdRoot(basePath))) {
|
|
204
|
+
const { showSmartEntry } = await import("../../guided-flow.js");
|
|
205
|
+
await showSmartEntry(ctx, pi, basePath, { step: true });
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
if (await hasUnresolvedCloseoutBlocker(ctx, basePath))
|
|
201
209
|
return true;
|
|
202
210
|
const { showGsdHome } = await import("../../gsd-command-home.js");
|
|
203
211
|
await showGsdHome(ctx, pi, projectRoot());
|