gsd-pi 2.78.1-dev.b6a389b66 → 2.78.1-dev.d8826a445
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/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/phases.js +7 -2
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +3 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +7 -1
- package/dist/resources/extensions/gsd/auto-worktree.js +185 -40
- package/dist/resources/extensions/gsd/auto.js +62 -1
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -16
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
- package/dist/resources/extensions/gsd/db-writer.js +96 -16
- package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
- package/dist/resources/extensions/gsd/gsd-db.js +194 -0
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +117 -25
- package/dist/resources/extensions/gsd/metrics.js +287 -1
- package/dist/resources/extensions/gsd/paths.js +79 -8
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/dist/resources/extensions/gsd/templates/project.md +10 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
- package/dist/resources/extensions/gsd/workspace.js +59 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +15 -2
- package/dist/resources/extensions/gsd/write-intercept.js +3 -3
- 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 +10 -10
- 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/required-server-files.json +1 -1
- 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 +10 -10
- 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/server.js +1 -1
- package/package.json +1 -1
- package/packages/mcp-server/README.md +2 -11
- package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
- package/packages/mcp-server/dist/remote-questions.js +28 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +28 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +94 -4
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +226 -0
- package/packages/mcp-server/src/remote-questions.test.ts +103 -0
- package/packages/mcp-server/src/remote-questions.ts +35 -0
- package/packages/mcp-server/src/server.ts +129 -6
- package/packages/mcp-server/src/workflow-tools.ts +1 -1
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/phases.ts +8 -2
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +10 -2
- package/src/resources/extensions/gsd/auto-post-unit.ts +8 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +225 -47
- package/src/resources/extensions/gsd/auto.ts +79 -1
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +17 -17
- package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
- package/src/resources/extensions/gsd/db-writer.ts +113 -17
- package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
- package/src/resources/extensions/gsd/gsd-db.ts +184 -0
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +154 -25
- package/src/resources/extensions/gsd/metrics.ts +321 -1
- package/src/resources/extensions/gsd/paths.ts +67 -8
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/src/resources/extensions/gsd/templates/project.md +10 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
- package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
- package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
- package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +371 -0
- package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
- package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
- package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
- package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
- package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +74 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +28 -16
- package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +453 -0
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/workspace.test.ts +190 -0
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +67 -52
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
- package/src/resources/extensions/gsd/workspace.ts +95 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +16 -2
- package/src/resources/extensions/gsd/write-intercept.ts +3 -3
- /package/dist/web/standalone/.next/static/{HahrZrc_Xn4wumj0O1Ydp → AT5qi39nKXkdmQIOIoh0f}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{HahrZrc_Xn4wumj0O1Ydp → AT5qi39nKXkdmQIOIoh0f}/_ssgManifest.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
3cb2810818585c65
|
|
@@ -12,7 +12,7 @@ import { MAX_RECOVERY_CHARS, BUDGET_THRESHOLDS, MAX_FINALIZE_TIMEOUTS, } from ".
|
|
|
12
12
|
import { detectStuck } from "./detect-stuck.js";
|
|
13
13
|
import { runUnit } from "./run-unit.js";
|
|
14
14
|
import { debugLog } from "../debug-logger.js";
|
|
15
|
-
import { resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
15
|
+
import { resolveWorktreeProjectRoot, normalizeWorktreePathForCompare } from "../worktree-root.js";
|
|
16
16
|
import { PROJECT_FILES, hasProjectFileInAncestor } from "../detection.js";
|
|
17
17
|
import { MergeConflictError } from "../git-service.js";
|
|
18
18
|
import { setCurrentPhase, clearCurrentPhase } from "../../shared/gsd-phase-state.js";
|
|
@@ -37,6 +37,11 @@ import { parseUnitId } from "../unit-id.js";
|
|
|
37
37
|
import { createCheckpoint, cleanupCheckpoint, rollbackToCheckpoint } from "../safety/git-checkpoint.js";
|
|
38
38
|
import { resolveSafetyHarnessConfig } from "../safety/safety-harness.js";
|
|
39
39
|
import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForAutoUnit, supportsStructuredQuestions, } from "../workflow-mcp.js";
|
|
40
|
+
// ─── Path Comparison Helper ───────────────────────────────────────────────
|
|
41
|
+
/** Compare two paths for physical identity, tolerating trailing slashes and symlinks. */
|
|
42
|
+
function isSamePathLocal(a, b) {
|
|
43
|
+
return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
|
|
44
|
+
}
|
|
40
45
|
// ─── Session timeout auto-resume state ────────────────────────────────────────
|
|
41
46
|
let consecutiveSessionTimeouts = 0;
|
|
42
47
|
const MAX_SESSION_TIMEOUT_AUTO_RESUMES = 3;
|
|
@@ -280,7 +285,7 @@ export async function runPreDispatch(ic, loopState) {
|
|
|
280
285
|
}
|
|
281
286
|
// Sync project root artifacts into worktree
|
|
282
287
|
if (s.originalBasePath &&
|
|
283
|
-
s.basePath
|
|
288
|
+
!isSamePathLocal(s.basePath, s.originalBasePath) &&
|
|
284
289
|
s.currentMilestoneId) {
|
|
285
290
|
deps.syncProjectRootToWorktree(s.originalBasePath, s.basePath, s.currentMilestoneId);
|
|
286
291
|
}
|
|
@@ -32,6 +32,8 @@ export class AutoSession {
|
|
|
32
32
|
// ── Paths ────────────────────────────────────────────────────────────────
|
|
33
33
|
basePath = "";
|
|
34
34
|
originalBasePath = "";
|
|
35
|
+
// TODO(C8): remove basePath/originalBasePath once all readers use s.scope
|
|
36
|
+
scope = null;
|
|
35
37
|
previousProjectRootEnv = null;
|
|
36
38
|
hadProjectRootEnv = false;
|
|
37
39
|
projectRootEnvCaptured = false;
|
|
@@ -173,6 +175,7 @@ export class AutoSession {
|
|
|
173
175
|
// Paths
|
|
174
176
|
this.basePath = "";
|
|
175
177
|
this.originalBasePath = "";
|
|
178
|
+
this.scope = null;
|
|
176
179
|
this.previousProjectRootEnv = null;
|
|
177
180
|
this.hadProjectRootEnv = false;
|
|
178
181
|
this.projectRootEnvCaptured = false;
|
|
@@ -31,6 +31,7 @@ import { ensureWorkflowPreferencesCaptured } from "./planning-depth.js";
|
|
|
31
31
|
import { MILESTONE_ID_RE } from "./milestone-ids.js";
|
|
32
32
|
import { PROJECT_RESEARCH_INFLIGHT_MARKER, } from "./project-research-policy.js";
|
|
33
33
|
import { isWorkflowPrefsCaptured, resolveDeepProjectSetupState, } from "./deep-project-setup-policy.js";
|
|
34
|
+
import { annotateBackgroundable } from "./delegation-policy.js";
|
|
34
35
|
let reassessmentChecker = checkNeedsReassessment;
|
|
35
36
|
let researchProjectPromptBuilder = buildResearchProjectPrompt;
|
|
36
37
|
function shouldBypassMilestoneDepthGateInAuto(prefs) {
|
|
@@ -1152,7 +1153,7 @@ export async function resolveDispatch(ctx) {
|
|
|
1152
1153
|
// Delegate to registry when available
|
|
1153
1154
|
try {
|
|
1154
1155
|
const registry = getRegistry();
|
|
1155
|
-
return await registry.evaluateDispatch(ctx);
|
|
1156
|
+
return annotateBackgroundable(await registry.evaluateDispatch(ctx));
|
|
1156
1157
|
}
|
|
1157
1158
|
catch (err) {
|
|
1158
1159
|
// Registry not initialized — fall back to inline loop
|
|
@@ -1163,7 +1164,7 @@ export async function resolveDispatch(ctx) {
|
|
|
1163
1164
|
if (result) {
|
|
1164
1165
|
if (result.action !== "skip")
|
|
1165
1166
|
result.matchedRule = rule.name;
|
|
1166
|
-
return result;
|
|
1167
|
+
return annotateBackgroundable(result);
|
|
1167
1168
|
}
|
|
1168
1169
|
}
|
|
1169
1170
|
// No rule matched — unhandled phase.
|
|
@@ -24,6 +24,7 @@ import { runTurnGitAction, } from "./git-service.js";
|
|
|
24
24
|
import { verifyExpectedArtifact, resolveExpectedArtifactPath, writeBlockerPlaceholder, diagnoseExpectedArtifact, } from "./auto-recovery.js";
|
|
25
25
|
import { regenerateIfMissing } from "./workflow-projections.js";
|
|
26
26
|
import { syncStateToProjectRoot } from "./auto-worktree.js";
|
|
27
|
+
import { normalizeWorktreePathForCompare } from "./worktree-root.js";
|
|
27
28
|
import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
|
|
28
29
|
import { renderPlanCheckboxes } from "./markdown-renderer.js";
|
|
29
30
|
import { consumeSignal } from "./session-status-io.js";
|
|
@@ -51,6 +52,11 @@ import { detectAbandonMilestone } from "./abandon-detect.js";
|
|
|
51
52
|
import { isDeterministicPolicyError } from "./auto-tool-tracking.js";
|
|
52
53
|
import { clearProjectResearchInflightMarker, finalizeProjectResearchTimeout, } from "./project-research-policy.js";
|
|
53
54
|
import { validateArtifact } from "./schemas/validate.js";
|
|
55
|
+
// ─── Path Comparison Helper ───────────────────────────────────────────────
|
|
56
|
+
/** Compare two paths for physical identity, tolerating trailing slashes and symlinks. */
|
|
57
|
+
function isSamePathLocal(a, b) {
|
|
58
|
+
return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
|
|
59
|
+
}
|
|
54
60
|
/** Maximum verification retry attempts before escalating to blocker placeholder (#2653). */
|
|
55
61
|
const MAX_VERIFICATION_RETRIES = 3;
|
|
56
62
|
/** Keep failure toasts short while still showing concrete examples. */
|
|
@@ -510,7 +516,7 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
510
516
|
await rebuildState(s.basePath);
|
|
511
517
|
});
|
|
512
518
|
// Sync worktree state back to project root (skipped for lightweight sidecars)
|
|
513
|
-
if (!opts?.skipWorktreeSync && s.originalBasePath && s.originalBasePath
|
|
519
|
+
if (!opts?.skipWorktreeSync && s.originalBasePath && !isSamePathLocal(s.originalBasePath, s.basePath)) {
|
|
514
520
|
await runSafely("postUnit", "worktree-sync", () => {
|
|
515
521
|
syncStateToProjectRoot(s.basePath, s.originalBasePath, s.currentMilestoneId);
|
|
516
522
|
});
|
|
@@ -23,6 +23,7 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
|
23
23
|
import { MILESTONE_ID_RE } from "./milestone-ids.js";
|
|
24
24
|
import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeWorkingTreeStatus, nativeAddAllWithExclusions, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeConflictFiles, nativeCheckoutTheirs, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchForceReset, nativeBranchExists, nativeDiffNumstat, nativeUpdateRef, nativeIsAncestor, nativeMergeAbort, } from "./native-git-bridge.js";
|
|
25
25
|
import { gsdHome } from "./gsd-home.js";
|
|
26
|
+
import { createWorkspace } from "./workspace.js";
|
|
26
27
|
const PROJECT_PREFERENCES_FILE = "PREFERENCES.md";
|
|
27
28
|
const LEGACY_PROJECT_PREFERENCES_FILE = "preferences.md";
|
|
28
29
|
const LEGACY_DEEP_SETUP_RUNTIME_UNIT_FILES = new Set([
|
|
@@ -195,8 +196,14 @@ function forceOverwriteAssessmentsWithVerdict(srcMilestoneDir, dstMilestoneDir)
|
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
// ─── Module State ──────────────────────────────────────────────────────────
|
|
198
|
-
/**
|
|
199
|
-
let
|
|
199
|
+
/** Active workspace registry — replaces the legacy `originalBase` singleton. */
|
|
200
|
+
let activeWorkspace = null;
|
|
201
|
+
function setActiveWorkspace(ws) {
|
|
202
|
+
activeWorkspace = ws;
|
|
203
|
+
}
|
|
204
|
+
function getActiveWorkspace() {
|
|
205
|
+
return activeWorkspace;
|
|
206
|
+
}
|
|
200
207
|
function clearProjectRootStateFiles(basePath, milestoneId) {
|
|
201
208
|
const gsdDir = gsdRoot(basePath);
|
|
202
209
|
const transientFiles = [
|
|
@@ -275,6 +282,33 @@ export const isSafeToAutoResolve = (filePath) => filePath.startsWith(".gsd/") ||
|
|
|
275
282
|
* gsd.db in the worktree so it rebuilds from fresh disk state (#853).
|
|
276
283
|
* Non-fatal — sync failure should never block dispatch.
|
|
277
284
|
*/
|
|
285
|
+
/**
|
|
286
|
+
* Scope-typed variant of syncProjectRootToWorktree.
|
|
287
|
+
*
|
|
288
|
+
* Takes an explicit (rootScope, worktreeScope) pair where rootScope is the
|
|
289
|
+
* project root and worktreeScope is the auto-worktree. Direction is encoded
|
|
290
|
+
* in argument order. Asserts both scopes belong to the same workspace identity
|
|
291
|
+
* to prevent silent mismatch bugs.
|
|
292
|
+
*/
|
|
293
|
+
export function syncProjectRootToWorktreeByScope(rootScope, worktreeScope) {
|
|
294
|
+
if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
|
|
295
|
+
throw new Error(`syncProjectRootToWorktreeByScope: scope identity mismatch — ` +
|
|
296
|
+
`rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
|
|
297
|
+
`worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`);
|
|
298
|
+
}
|
|
299
|
+
if (rootScope.milestoneId !== worktreeScope.milestoneId) {
|
|
300
|
+
throw new Error(`syncProjectRootToWorktreeByScope: milestoneId mismatch — ` +
|
|
301
|
+
`rootScope.milestoneId="${rootScope.milestoneId}" worktreeScope.milestoneId="${worktreeScope.milestoneId}"`);
|
|
302
|
+
}
|
|
303
|
+
const projectRoot = rootScope.workspace.projectRoot;
|
|
304
|
+
const worktreePath_ = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
|
|
305
|
+
const milestoneId = rootScope.milestoneId;
|
|
306
|
+
syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneId);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* @deprecated Use syncProjectRootToWorktreeByScope instead.
|
|
310
|
+
* TODO(C-future): remove once all callers migrated.
|
|
311
|
+
*/
|
|
278
312
|
export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneId) {
|
|
279
313
|
if (!worktreePath_ || !projectRoot || worktreePath_ === projectRoot)
|
|
280
314
|
return;
|
|
@@ -343,12 +377,36 @@ export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneI
|
|
|
343
377
|
logWarning("worktree", `worktree DB cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
344
378
|
}
|
|
345
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Scope-typed variant of syncStateToProjectRoot.
|
|
382
|
+
*
|
|
383
|
+
* Takes an explicit (worktreeScope, rootScope) pair. Direction is encoded in
|
|
384
|
+
* argument order (worktree → root). Asserts both scopes belong to the same
|
|
385
|
+
* workspace identity to prevent silent mismatch bugs.
|
|
386
|
+
*/
|
|
387
|
+
export function syncStateToProjectRootByScope(worktreeScope, rootScope) {
|
|
388
|
+
if (worktreeScope.workspace.identityKey !== rootScope.workspace.identityKey) {
|
|
389
|
+
throw new Error(`syncStateToProjectRootByScope: scope identity mismatch — ` +
|
|
390
|
+
`worktreeScope.identityKey="${worktreeScope.workspace.identityKey}" ` +
|
|
391
|
+
`rootScope.identityKey="${rootScope.workspace.identityKey}"`);
|
|
392
|
+
}
|
|
393
|
+
if (worktreeScope.milestoneId !== rootScope.milestoneId) {
|
|
394
|
+
throw new Error(`syncStateToProjectRootByScope: milestoneId mismatch — ` +
|
|
395
|
+
`worktreeScope.milestoneId="${worktreeScope.milestoneId}" rootScope.milestoneId="${rootScope.milestoneId}"`);
|
|
396
|
+
}
|
|
397
|
+
const worktreePath_ = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
|
|
398
|
+
const projectRoot = rootScope.workspace.projectRoot;
|
|
399
|
+
const milestoneId = worktreeScope.milestoneId;
|
|
400
|
+
syncStateToProjectRoot(worktreePath_, projectRoot, milestoneId);
|
|
401
|
+
}
|
|
346
402
|
/**
|
|
347
403
|
* Sync worktree diagnostics from worktree to project root.
|
|
348
404
|
* Only runs when inside an auto-worktree (worktreePath differs from projectRoot).
|
|
349
405
|
* DB/project-root state remains authoritative; markdown projections are not
|
|
350
406
|
* copied from the worktree back to the project root.
|
|
351
407
|
* Non-fatal — sync failure should never block dispatch.
|
|
408
|
+
* @deprecated Use syncStateToProjectRootByScope instead.
|
|
409
|
+
* TODO(C-future): remove once all callers migrated.
|
|
352
410
|
*/
|
|
353
411
|
export function syncStateToProjectRoot(worktreePath_, projectRoot, milestoneId) {
|
|
354
412
|
if (!worktreePath_ || !projectRoot || worktreePath_ === projectRoot)
|
|
@@ -520,6 +578,24 @@ export function cleanStaleRuntimeUnits(gsdRootPath, hasMilestoneSummary) {
|
|
|
520
578
|
return cleaned;
|
|
521
579
|
}
|
|
522
580
|
// ─── Worktree ↔ Main Repo Sync (#1311) ──────────────────────────────────────
|
|
581
|
+
/**
|
|
582
|
+
* Scope-typed variant of syncGsdStateToWorktree.
|
|
583
|
+
*
|
|
584
|
+
* Takes an explicit (rootScope, worktreeScope) pair. Note: milestoneId is not
|
|
585
|
+
* used by syncGsdStateToWorktree — this variant only requires workspace
|
|
586
|
+
* identity. Asserts both scopes belong to the same workspace identity to
|
|
587
|
+
* prevent silent mismatch bugs.
|
|
588
|
+
*/
|
|
589
|
+
export function syncGsdStateToWorktreeByScope(rootScope, worktreeScope) {
|
|
590
|
+
if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
|
|
591
|
+
throw new Error(`syncGsdStateToWorktreeByScope: scope identity mismatch — ` +
|
|
592
|
+
`rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
|
|
593
|
+
`worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`);
|
|
594
|
+
}
|
|
595
|
+
const mainBasePath = rootScope.workspace.projectRoot;
|
|
596
|
+
const worktreePath_ = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
|
|
597
|
+
return syncGsdStateToWorktree(mainBasePath, worktreePath_);
|
|
598
|
+
}
|
|
523
599
|
/**
|
|
524
600
|
* Sync .gsd/ state from the main repo into the worktree.
|
|
525
601
|
*
|
|
@@ -534,6 +610,8 @@ export function cleanStaleRuntimeUnits(gsdRootPath, hasMilestoneSummary) {
|
|
|
534
610
|
* Only adds missing content — never overwrites existing files in the worktree.
|
|
535
611
|
* Worktree files are compatibility projections; DB/project root remains
|
|
536
612
|
* authoritative for runtime state.
|
|
613
|
+
* @deprecated Use syncGsdStateToWorktreeByScope instead.
|
|
614
|
+
* TODO(C-future): remove once all callers migrated.
|
|
537
615
|
*/
|
|
538
616
|
export function syncGsdStateToWorktree(mainBasePath, worktreePath_) {
|
|
539
617
|
const contract = resolveGsdPathContract(worktreePath_, mainBasePath);
|
|
@@ -906,6 +984,32 @@ export function enterBranchModeForMilestone(basePath, milestoneId) {
|
|
|
906
984
|
* remains authoritative; this never downgrades checked boxes in a local
|
|
907
985
|
* worktree projection.
|
|
908
986
|
*/
|
|
987
|
+
/**
|
|
988
|
+
* Scope-typed variant of reconcilePlanCheckboxes.
|
|
989
|
+
*
|
|
990
|
+
* Takes an explicit (rootScope, worktreeScope) pair. milestoneId is taken
|
|
991
|
+
* from rootScope. Asserts both scopes belong to the same workspace identity
|
|
992
|
+
* to prevent silent mismatch bugs.
|
|
993
|
+
*/
|
|
994
|
+
export function reconcilePlanCheckboxesByScope(rootScope, worktreeScope) {
|
|
995
|
+
if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
|
|
996
|
+
throw new Error(`reconcilePlanCheckboxesByScope: scope identity mismatch — ` +
|
|
997
|
+
`rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
|
|
998
|
+
`worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`);
|
|
999
|
+
}
|
|
1000
|
+
if (rootScope.milestoneId !== worktreeScope.milestoneId) {
|
|
1001
|
+
throw new Error(`reconcilePlanCheckboxesByScope: milestoneId mismatch — ` +
|
|
1002
|
+
`rootScope.milestoneId="${rootScope.milestoneId}" worktreeScope.milestoneId="${worktreeScope.milestoneId}"`);
|
|
1003
|
+
}
|
|
1004
|
+
const projectRoot = rootScope.workspace.projectRoot;
|
|
1005
|
+
const wtPath = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
|
|
1006
|
+
const milestoneId = rootScope.milestoneId;
|
|
1007
|
+
reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId);
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* @deprecated Use reconcilePlanCheckboxesByScope instead.
|
|
1011
|
+
* TODO(C-future): remove once all callers migrated.
|
|
1012
|
+
*/
|
|
909
1013
|
function reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId) {
|
|
910
1014
|
const srcMilestone = join(projectRoot, ".gsd", "milestones", milestoneId);
|
|
911
1015
|
const dstMilestone = join(wtPath, ".gsd", "milestones", milestoneId);
|
|
@@ -1059,11 +1163,11 @@ export function createAutoWorktree(basePath, milestoneId) {
|
|
|
1059
1163
|
const previousCwd = process.cwd();
|
|
1060
1164
|
try {
|
|
1061
1165
|
process.chdir(info.path);
|
|
1062
|
-
|
|
1166
|
+
setActiveWorkspace(createWorkspace(basePath));
|
|
1063
1167
|
}
|
|
1064
1168
|
catch (err) {
|
|
1065
1169
|
// If chdir fails, the worktree was created but we couldn't enter it.
|
|
1066
|
-
// Don't
|
|
1170
|
+
// Don't set activeWorkspace -- caller can retry or clean up.
|
|
1067
1171
|
throw new GSDError(GSD_IO_ERROR, `Auto-worktree created at ${info.path} but chdir failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1068
1172
|
}
|
|
1069
1173
|
nudgeGitBranchCache(previousCwd);
|
|
@@ -1123,41 +1227,79 @@ export function teardownAutoWorktree(originalBasePath, milestoneId, opts = {}) {
|
|
|
1123
1227
|
const branch = autoWorktreeBranch(milestoneId);
|
|
1124
1228
|
const { preserveBranch = false } = opts;
|
|
1125
1229
|
const previousCwd = process.cwd();
|
|
1230
|
+
// Wrap the entire teardown body in a single try/finally so activeWorkspace
|
|
1231
|
+
// is ALWAYS cleared — even if process.chdir throws (e.g. originalBasePath
|
|
1232
|
+
// was deleted before teardown ran). Previously the finally only covered
|
|
1233
|
+
// removeWorktree, leaving the registry stale on a chdir failure (H3 fix).
|
|
1126
1234
|
try {
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
`Remove it manually with: rm -rf "${wtDir.replaceAll("\\", "/")}"`, { worktree: milestoneId });
|
|
1146
|
-
// Attempt a direct filesystem removal as a fallback — but ONLY if the
|
|
1147
|
-
// path is safely inside .gsd/worktrees/ to prevent #2365 data loss.
|
|
1148
|
-
if (isInsideWorktreesDir(originalBasePath, wtDir)) {
|
|
1235
|
+
try {
|
|
1236
|
+
process.chdir(originalBasePath);
|
|
1237
|
+
}
|
|
1238
|
+
catch (err) {
|
|
1239
|
+
throw new GSDError(GSD_IO_ERROR, `Failed to chdir back to ${originalBasePath} during teardown: ${err instanceof Error ? err.message : String(err)}`);
|
|
1240
|
+
}
|
|
1241
|
+
// Mirror cleanup steps from mergeMilestoneToMain abort path:
|
|
1242
|
+
// 1. Remove transient state files (STATE.md, auto.lock, {MID}-META.json).
|
|
1243
|
+
// Non-fatal — must not block teardown.
|
|
1244
|
+
try {
|
|
1245
|
+
clearProjectRootStateFiles(originalBasePath, milestoneId);
|
|
1246
|
+
}
|
|
1247
|
+
catch (err) {
|
|
1248
|
+
logWarning("worktree", `clearProjectRootStateFiles failed during teardown: ${err instanceof Error ? err.message : String(err)}`);
|
|
1249
|
+
}
|
|
1250
|
+
// 2. Reconcile worktree-local gsd.db into project root DB if both exist.
|
|
1251
|
+
// Non-fatal — handles legacy worktrees that have a local copy.
|
|
1252
|
+
if (isDbAvailable()) {
|
|
1149
1253
|
try {
|
|
1150
|
-
|
|
1254
|
+
const contract = resolveGsdPathContract(previousCwd, originalBasePath);
|
|
1255
|
+
const worktreeDbPath = join(contract.worktreeGsd ?? join(previousCwd, ".gsd"), "gsd.db");
|
|
1256
|
+
const mainDbPath = contract.projectDb;
|
|
1257
|
+
if (existsSync(worktreeDbPath) && !isSamePath(worktreeDbPath, mainDbPath)) {
|
|
1258
|
+
reconcileWorktreeDb(mainDbPath, worktreeDbPath);
|
|
1259
|
+
}
|
|
1151
1260
|
}
|
|
1152
1261
|
catch (err) {
|
|
1153
|
-
|
|
1154
|
-
|
|
1262
|
+
/* non-fatal */
|
|
1263
|
+
logError("worktree", `DB reconciliation failed during teardown: ${err instanceof Error ? err.message : String(err)}`);
|
|
1155
1264
|
}
|
|
1156
1265
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1266
|
+
nudgeGitBranchCache(previousCwd);
|
|
1267
|
+
// 3. Remove the worktree. Errors propagate naturally — the outer finally
|
|
1268
|
+
// ensures activeWorkspace is cleared regardless.
|
|
1269
|
+
removeWorktree(originalBasePath, milestoneId, {
|
|
1270
|
+
branch,
|
|
1271
|
+
deleteBranch: !preserveBranch,
|
|
1272
|
+
});
|
|
1273
|
+
// Verify cleanup succeeded — warn if the worktree directory is still on disk.
|
|
1274
|
+
// On Windows, bash-based cleanup can silently fail when paths contain
|
|
1275
|
+
// backslashes (#1436), leaving ~1 GB+ orphaned directories.
|
|
1276
|
+
const wtDir = worktreePath(originalBasePath, milestoneId);
|
|
1277
|
+
if (existsSync(wtDir)) {
|
|
1278
|
+
logWarning("reconcile", `Worktree directory still exists after teardown: ${wtDir}. ` +
|
|
1279
|
+
`This is likely an orphaned directory consuming disk space. ` +
|
|
1280
|
+
`Remove it manually with: rm -rf "${wtDir.replaceAll("\\", "/")}"`, { worktree: milestoneId });
|
|
1281
|
+
// Attempt a direct filesystem removal as a fallback — but ONLY if the
|
|
1282
|
+
// path is safely inside .gsd/worktrees/ to prevent #2365 data loss.
|
|
1283
|
+
if (isInsideWorktreesDir(originalBasePath, wtDir)) {
|
|
1284
|
+
try {
|
|
1285
|
+
rmSync(wtDir, { recursive: true, force: true });
|
|
1286
|
+
}
|
|
1287
|
+
catch (err) {
|
|
1288
|
+
// Non-fatal — the warning above tells the user how to clean up
|
|
1289
|
+
logWarning("worktree", `worktree directory removal failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
else {
|
|
1293
|
+
console.error(`[GSD] REFUSING fallback rmSync — path is outside .gsd/worktrees/: ${wtDir}`);
|
|
1294
|
+
}
|
|
1159
1295
|
}
|
|
1160
1296
|
}
|
|
1297
|
+
finally {
|
|
1298
|
+
// Clear module state unconditionally — regardless of which step above
|
|
1299
|
+
// failed. A stale activeWorkspace causes getActiveAutoWorktreeContext()
|
|
1300
|
+
// to return wrong data for subsequent operations.
|
|
1301
|
+
setActiveWorkspace(null);
|
|
1302
|
+
}
|
|
1161
1303
|
}
|
|
1162
1304
|
/**
|
|
1163
1305
|
* Detect if the process is currently inside an auto-worktree.
|
|
@@ -1168,8 +1310,9 @@ export function isInAutoWorktree(basePath) {
|
|
|
1168
1310
|
const targetPath = isGsdWorktreePath(basePath) ? basePath : process.cwd();
|
|
1169
1311
|
if (!isGsdWorktreePath(targetPath))
|
|
1170
1312
|
return false;
|
|
1171
|
-
const
|
|
1172
|
-
const
|
|
1313
|
+
const storedBase = getAutoWorktreeOriginalBase();
|
|
1314
|
+
const projectRoot = resolveWorktreeProjectRoot(basePath, storedBase);
|
|
1315
|
+
const targetProjectRoot = resolveWorktreeProjectRoot(targetPath, storedBase);
|
|
1173
1316
|
if (normalizeWorktreePathForCompare(projectRoot) !==
|
|
1174
1317
|
normalizeWorktreePathForCompare(targetProjectRoot)) {
|
|
1175
1318
|
return false;
|
|
@@ -1242,7 +1385,7 @@ export function enterAutoWorktree(basePath, milestoneId) {
|
|
|
1242
1385
|
const previousCwd = process.cwd();
|
|
1243
1386
|
try {
|
|
1244
1387
|
process.chdir(p);
|
|
1245
|
-
|
|
1388
|
+
setActiveWorkspace(createWorkspace(basePath));
|
|
1246
1389
|
}
|
|
1247
1390
|
catch (err) {
|
|
1248
1391
|
throw new GSDError(GSD_IO_ERROR, `Failed to enter auto-worktree at ${p}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1255,14 +1398,16 @@ export function enterAutoWorktree(basePath, milestoneId) {
|
|
|
1255
1398
|
* Returns null if not currently in an auto-worktree.
|
|
1256
1399
|
*/
|
|
1257
1400
|
export function getAutoWorktreeOriginalBase() {
|
|
1258
|
-
return
|
|
1401
|
+
return getActiveWorkspace()?.projectRoot ?? null;
|
|
1259
1402
|
}
|
|
1260
1403
|
export function _resetAutoWorktreeOriginalBaseForTests() {
|
|
1261
|
-
|
|
1404
|
+
setActiveWorkspace(null);
|
|
1262
1405
|
}
|
|
1263
1406
|
export function getActiveAutoWorktreeContext() {
|
|
1264
|
-
|
|
1407
|
+
const ws = getActiveWorkspace();
|
|
1408
|
+
if (!ws)
|
|
1265
1409
|
return null;
|
|
1410
|
+
const originalBase = ws.projectRoot;
|
|
1266
1411
|
const cwd = process.cwd();
|
|
1267
1412
|
if (!isGsdWorktreePath(cwd))
|
|
1268
1413
|
return null;
|
|
@@ -1332,11 +1477,11 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
|
|
|
1332
1477
|
// integration branch captures dirty files from OTHER milestones under a
|
|
1333
1478
|
// misleading commit message, contaminating the main branch (#2929).
|
|
1334
1479
|
//
|
|
1335
|
-
// When
|
|
1480
|
+
// When activeWorkspace is null (branch mode, no worktree), autoCommitDirtyState
|
|
1336
1481
|
// runs unconditionally — the caller is responsible for cwd placement.
|
|
1337
1482
|
{
|
|
1338
1483
|
let shouldAutoCommit = true;
|
|
1339
|
-
if (
|
|
1484
|
+
if (getActiveWorkspace() !== null) {
|
|
1340
1485
|
try {
|
|
1341
1486
|
const currentBranch = nativeGetCurrentBranch(worktreeCwd);
|
|
1342
1487
|
shouldAutoCommit = currentBranch === milestoneBranch;
|
|
@@ -2020,7 +2165,7 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
|
|
|
2020
2165
|
logWarning("worktree", `git branch-delete failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2021
2166
|
}
|
|
2022
2167
|
// 14. Clear module state
|
|
2023
|
-
|
|
2168
|
+
setActiveWorkspace(null);
|
|
2024
2169
|
nudgeGitBranchCache(previousCwd);
|
|
2025
2170
|
// 15. Anchor cwd at the project root on success-return. Step 12 removed
|
|
2026
2171
|
// the worktree dir; if cwd was inside it, every subsequent process.cwd()
|
|
@@ -86,6 +86,7 @@ import { reorderForCaching } from "./prompt-ordering.js";
|
|
|
86
86
|
export { STUB_RECOVERY_THRESHOLD, NEW_SESSION_TIMEOUT_MS, } from "./auto/session.js";
|
|
87
87
|
import { autoSession as s } from "./auto-runtime-state.js";
|
|
88
88
|
import { gsdHome } from "./gsd-home.js";
|
|
89
|
+
import { createWorkspace, scopeMilestone } from "./workspace.js";
|
|
89
90
|
// ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
|
|
90
91
|
// ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
|
|
91
92
|
// This file must NOT declare module-level `let` or `var` variables for state.
|
|
@@ -147,6 +148,32 @@ function restoreMilestoneLockEnv() {
|
|
|
147
148
|
s.hadMilestoneLockEnv = false;
|
|
148
149
|
s.milestoneLockEnvCaptured = false;
|
|
149
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Rebuild s.scope from the current s.basePath / s.originalBasePath / s.currentMilestoneId.
|
|
153
|
+
*
|
|
154
|
+
* Pass the worktree path as rawPath when entering a worktree so createWorkspace
|
|
155
|
+
* can detect the worktree layout and set mode="worktree". When no worktree is
|
|
156
|
+
* active, rawPath should equal the project root.
|
|
157
|
+
*
|
|
158
|
+
* Clears s.scope when milestoneId is absent — scope is only meaningful when a
|
|
159
|
+
* milestone is active.
|
|
160
|
+
*
|
|
161
|
+
* TODO(C8): remove basePath/originalBasePath once all readers use s.scope.
|
|
162
|
+
*/
|
|
163
|
+
function rebuildScope(rawPath, milestoneId) {
|
|
164
|
+
if (!milestoneId) {
|
|
165
|
+
s.scope = null;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const workspace = createWorkspace(rawPath);
|
|
170
|
+
s.scope = scopeMilestone(workspace, milestoneId);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Non-fatal — scope is additive. Existing readers still use basePath.
|
|
174
|
+
s.scope = null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
150
177
|
function normalizeSessionFilePath(raw) {
|
|
151
178
|
if (typeof raw !== "string")
|
|
152
179
|
return null;
|
|
@@ -284,6 +311,18 @@ export function isAutoActive() {
|
|
|
284
311
|
export function _setAutoActiveForTest(active) {
|
|
285
312
|
s.active = active;
|
|
286
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Test-only seam: emit the missing-worktree warning exactly as the resume path
|
|
316
|
+
* does. Allows unit tests to verify the warning is produced without
|
|
317
|
+
* bootstrapping the full auto-mode entry point. Do not use in production code.
|
|
318
|
+
*/
|
|
319
|
+
export function _warnIfWorktreeMissingForTest(worktreePath, milestoneId) {
|
|
320
|
+
if (worktreePath && !existsSync(worktreePath)) {
|
|
321
|
+
logWarning("session", `Worktree was expected at ${worktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`, { file: "auto.ts", milestoneId });
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
287
326
|
export function isAutoPaused() {
|
|
288
327
|
return s.paused;
|
|
289
328
|
}
|
|
@@ -1204,6 +1243,18 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1204
1243
|
s.autoStartTime = meta.autoStartTime || Date.now();
|
|
1205
1244
|
s.sessionMilestoneLock = meta.milestoneLock ?? null;
|
|
1206
1245
|
s.paused = true;
|
|
1246
|
+
// Build scope from persisted state. Use worktreePath when present and
|
|
1247
|
+
// still on disk so mode is detected correctly; fall back to project root.
|
|
1248
|
+
{
|
|
1249
|
+
const persistedWorktreePath = meta.worktreePath ?? null;
|
|
1250
|
+
if (persistedWorktreePath && !existsSync(persistedWorktreePath)) {
|
|
1251
|
+
logWarning("session", `Worktree was expected at ${persistedWorktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`, { file: "auto.ts", milestoneId: meta.milestoneId ?? "" });
|
|
1252
|
+
}
|
|
1253
|
+
const rawForScope = (persistedWorktreePath && existsSync(persistedWorktreePath))
|
|
1254
|
+
? persistedWorktreePath
|
|
1255
|
+
: (s.originalBasePath || base);
|
|
1256
|
+
rebuildScope(rawForScope, s.currentMilestoneId);
|
|
1257
|
+
}
|
|
1207
1258
|
try {
|
|
1208
1259
|
unlinkSync(pausedPath);
|
|
1209
1260
|
}
|
|
@@ -1285,10 +1336,15 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1285
1336
|
// session (e.g. isolation mode changed, detectWorktreeName differs across
|
|
1286
1337
|
// process restarts). We guard with existsSync so a stale or deleted
|
|
1287
1338
|
// worktree directory safely falls back to the project root.
|
|
1288
|
-
const resumeWorktreePath = freshStartAssessment.pausedSession?.worktreePath;
|
|
1339
|
+
const resumeWorktreePath = freshStartAssessment.pausedSession?.worktreePath ?? null;
|
|
1340
|
+
if (resumeWorktreePath && !existsSync(resumeWorktreePath)) {
|
|
1341
|
+
logWarning("session", `Worktree was expected at ${resumeWorktreePath} but is missing. Continuing in project-root mode. To restart with a fresh worktree, run /gsd-debug or recreate the milestone.`, { file: "auto.ts", milestoneId: s.currentMilestoneId ?? "" });
|
|
1342
|
+
}
|
|
1289
1343
|
if (resumeWorktreePath && existsSync(resumeWorktreePath)) {
|
|
1290
1344
|
s.basePath = resumeWorktreePath;
|
|
1291
1345
|
}
|
|
1346
|
+
// Rebuild scope now that s.basePath reflects the actual worktree (or project root).
|
|
1347
|
+
rebuildScope(s.basePath, s.currentMilestoneId);
|
|
1292
1348
|
// Ensure the workflow-logger audit log is pinned to the project root
|
|
1293
1349
|
// even when auto-mode is entered via a path that bypasses the
|
|
1294
1350
|
// bootstrap/dynamic-tools ensureDbOpen() → setLogBasePath() chain
|
|
@@ -1315,6 +1371,8 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1315
1371
|
buildResolver().enterMilestone(s.currentMilestoneId, {
|
|
1316
1372
|
notify: ctx.ui.notify.bind(ctx.ui),
|
|
1317
1373
|
});
|
|
1374
|
+
// s.basePath may have been updated to a worktree path by enterMilestone.
|
|
1375
|
+
rebuildScope(s.basePath, s.currentMilestoneId);
|
|
1318
1376
|
}
|
|
1319
1377
|
registerSigtermHandler(lockBase());
|
|
1320
1378
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
@@ -1393,6 +1451,9 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
|
|
|
1393
1451
|
const ready = await bootstrapAutoSession(s, ctx, pi, base, verboseMode, requestedStepMode, bootstrapDeps, freshStartAssessment);
|
|
1394
1452
|
if (!ready)
|
|
1395
1453
|
return;
|
|
1454
|
+
// Build scope after bootstrap has populated s.basePath / s.originalBasePath /
|
|
1455
|
+
// s.currentMilestoneId (including worktree setup inside bootstrapAutoSession).
|
|
1456
|
+
rebuildScope(s.basePath, s.currentMilestoneId);
|
|
1396
1457
|
captureProjectRootEnv(s.originalBasePath || s.basePath);
|
|
1397
1458
|
try {
|
|
1398
1459
|
pi.events.emit(CMUX_CHANNELS.SIDEBAR, { action: "sync", preferences: loadEffectiveGSDPreferences(s.basePath || undefined)?.preferences, state: await deriveState(s.basePath) });
|
|
@@ -86,7 +86,7 @@ export async function handleAgentEnd(pi, event, ctx) {
|
|
|
86
86
|
logWarning("bootstrap", `checkDeepProjectSetupAfterTurn failed: ${message}`);
|
|
87
87
|
}
|
|
88
88
|
if (checkAutoStartAfterDiscuss()) {
|
|
89
|
-
clearDiscussionFlowState();
|
|
89
|
+
clearDiscussionFlowState(resolveAgentEndBasePath() ?? process.cwd());
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
// #4573 — When the LLM emits "Milestone X ready." but the required files
|