gsd-pi 2.78.1-dev.e9d88a536 → 2.78.1-dev.eccf86e27
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 +5 -7
- package/dist/help-text.js +1 -1
- package/dist/resource-loader.js +6 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +41 -5
- package/dist/resources/extensions/gsd/auto/loop.js +235 -36
- package/dist/resources/extensions/gsd/auto/phases.js +14 -7
- package/dist/resources/extensions/gsd/auto/session.js +36 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +49 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +26 -12
- package/dist/resources/extensions/gsd/auto-worktree.js +185 -201
- package/dist/resources/extensions/gsd/auto.js +139 -49
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +26 -20
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
- package/dist/resources/extensions/gsd/crash-recovery.js +160 -47
- package/dist/resources/extensions/gsd/db/auto-workers.js +227 -0
- package/dist/resources/extensions/gsd/db/command-queue.js +105 -0
- package/dist/resources/extensions/gsd/db/milestone-leases.js +210 -0
- package/dist/resources/extensions/gsd/db/runtime-kv.js +91 -0
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +322 -0
- 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/docs/COORDINATION.md +42 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +4 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +22 -6
- package/dist/resources/extensions/gsd/doctor.js +12 -2
- package/dist/resources/extensions/gsd/gsd-db.js +355 -3
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +116 -26
- package/dist/resources/extensions/gsd/interrupted-session.js +18 -15
- 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/state.js +21 -6
- 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 +79 -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 +14 -14
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/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 +14 -14
- 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/detect-stuck.ts +37 -5
- package/src/resources/extensions/gsd/auto/loop.ts +263 -41
- package/src/resources/extensions/gsd/auto/phases.ts +15 -7
- package/src/resources/extensions/gsd/auto/session.ts +40 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +63 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +27 -12
- package/src/resources/extensions/gsd/auto-worktree.ts +218 -225
- package/src/resources/extensions/gsd/auto.ts +166 -43
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +26 -21
- 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/crash-recovery.ts +177 -43
- package/src/resources/extensions/gsd/db/auto-workers.ts +273 -0
- package/src/resources/extensions/gsd/db/command-queue.ts +149 -0
- package/src/resources/extensions/gsd/db/milestone-leases.ts +274 -0
- package/src/resources/extensions/gsd/db/runtime-kv.ts +127 -0
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +446 -0
- 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/docs/COORDINATION.md +42 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +4 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +24 -6
- package/src/resources/extensions/gsd/doctor.ts +10 -2
- package/src/resources/extensions/gsd/gsd-db.ts +354 -3
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +152 -26
- package/src/resources/extensions/gsd/interrupted-session.ts +19 -12
- 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/state.ts +44 -6
- 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-loop-no-copy-artifacts.test.ts +72 -0
- package/src/resources/extensions/gsd/tests/auto-loop-symlink-worktree.test.ts +190 -0
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
- package/src/resources/extensions/gsd/tests/auto-workers.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/command-queue.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +169 -59
- 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/detect-stuck-respects-retry.test.ts +173 -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/auto-worktree.test.ts +22 -12
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +24 -10
- package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +35 -23
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +369 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +72 -25
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +72 -25
- package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +9 -6
- 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/milestone-leases.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
- package/src/resources/extensions/gsd/tests/parallel-milestone-isolation.test.ts +106 -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/paused-session-via-db.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +3 -17
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +138 -16
- package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/runtime-kv.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +133 -28
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +434 -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 +98 -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/unit-dispatches.test.ts +247 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +41 -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 +196 -0
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +94 -71
- 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 +78 -2
- package/src/resources/extensions/gsd/write-intercept.ts +3 -3
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +0 -213
- package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +0 -87
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +0 -159
- /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_ssgManifest.js +0 -0
|
@@ -9,7 +9,6 @@ import { existsSync, cpSync, readFileSync, readdirSync, mkdirSync, realpathSync,
|
|
|
9
9
|
import { isAbsolute, join, sep as pathSep } from "node:path";
|
|
10
10
|
import { GSDError, GSD_IO_ERROR, GSD_GIT_ERROR } from "./errors.js";
|
|
11
11
|
import { reconcileWorktreeDb, isDbAvailable, getMilestone, getMilestoneSlices, closeDatabase, openDatabase, getDbPath, } from "./gsd-db.js";
|
|
12
|
-
import { atomicWriteSync } from "./atomic-write.js";
|
|
13
12
|
import { execFileSync } from "node:child_process";
|
|
14
13
|
import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
15
14
|
import { gsdRoot, resolveGsdPathContract } from "./paths.js";
|
|
@@ -23,6 +22,7 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
|
23
22
|
import { MILESTONE_ID_RE } from "./milestone-ids.js";
|
|
24
23
|
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
24
|
import { gsdHome } from "./gsd-home.js";
|
|
25
|
+
import { createWorkspace } from "./workspace.js";
|
|
26
26
|
const PROJECT_PREFERENCES_FILE = "PREFERENCES.md";
|
|
27
27
|
const LEGACY_PROJECT_PREFERENCES_FILE = "preferences.md";
|
|
28
28
|
const LEGACY_DEEP_SETUP_RUNTIME_UNIT_FILES = new Set([
|
|
@@ -195,13 +195,22 @@ function forceOverwriteAssessmentsWithVerdict(srcMilestoneDir, dstMilestoneDir)
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
// ─── Module State ──────────────────────────────────────────────────────────
|
|
198
|
-
/**
|
|
199
|
-
let
|
|
198
|
+
/** Active workspace registry — replaces the legacy `originalBase` singleton. */
|
|
199
|
+
let activeWorkspace = null;
|
|
200
|
+
function setActiveWorkspace(ws) {
|
|
201
|
+
activeWorkspace = ws;
|
|
202
|
+
}
|
|
203
|
+
function getActiveWorkspace() {
|
|
204
|
+
return activeWorkspace;
|
|
205
|
+
}
|
|
200
206
|
function clearProjectRootStateFiles(basePath, milestoneId) {
|
|
201
207
|
const gsdDir = gsdRoot(basePath);
|
|
208
|
+
// Phase C pt 2: auto.lock removed from this list — the file is gone
|
|
209
|
+
// (migrated to the workers + unit_dispatches + runtime_kv tables). The
|
|
210
|
+
// remaining transient files (STATE.md, {MID}-META.json) are still
|
|
211
|
+
// worth removing on teardown.
|
|
202
212
|
const transientFiles = [
|
|
203
213
|
join(gsdDir, "STATE.md"),
|
|
204
|
-
join(gsdDir, "auto.lock"),
|
|
205
214
|
join(gsdDir, "milestones", milestoneId, `${milestoneId}-META.json`),
|
|
206
215
|
];
|
|
207
216
|
for (const file of transientFiles) {
|
|
@@ -275,6 +284,33 @@ export const isSafeToAutoResolve = (filePath) => filePath.startsWith(".gsd/") ||
|
|
|
275
284
|
* gsd.db in the worktree so it rebuilds from fresh disk state (#853).
|
|
276
285
|
* Non-fatal — sync failure should never block dispatch.
|
|
277
286
|
*/
|
|
287
|
+
/**
|
|
288
|
+
* Scope-typed variant of syncProjectRootToWorktree.
|
|
289
|
+
*
|
|
290
|
+
* Takes an explicit (rootScope, worktreeScope) pair where rootScope is the
|
|
291
|
+
* project root and worktreeScope is the auto-worktree. Direction is encoded
|
|
292
|
+
* in argument order. Asserts both scopes belong to the same workspace identity
|
|
293
|
+
* to prevent silent mismatch bugs.
|
|
294
|
+
*/
|
|
295
|
+
export function syncProjectRootToWorktreeByScope(rootScope, worktreeScope) {
|
|
296
|
+
if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
|
|
297
|
+
throw new Error(`syncProjectRootToWorktreeByScope: scope identity mismatch — ` +
|
|
298
|
+
`rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
|
|
299
|
+
`worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`);
|
|
300
|
+
}
|
|
301
|
+
if (rootScope.milestoneId !== worktreeScope.milestoneId) {
|
|
302
|
+
throw new Error(`syncProjectRootToWorktreeByScope: milestoneId mismatch — ` +
|
|
303
|
+
`rootScope.milestoneId="${rootScope.milestoneId}" worktreeScope.milestoneId="${worktreeScope.milestoneId}"`);
|
|
304
|
+
}
|
|
305
|
+
const projectRoot = rootScope.workspace.projectRoot;
|
|
306
|
+
const worktreePath_ = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
|
|
307
|
+
const milestoneId = rootScope.milestoneId;
|
|
308
|
+
syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneId);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* @deprecated Use syncProjectRootToWorktreeByScope instead.
|
|
312
|
+
* TODO(C-future): remove once all callers migrated.
|
|
313
|
+
*/
|
|
278
314
|
export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneId) {
|
|
279
315
|
if (!worktreePath_ || !projectRoot || worktreePath_ === projectRoot)
|
|
280
316
|
return;
|
|
@@ -343,12 +379,36 @@ export function syncProjectRootToWorktree(projectRoot, worktreePath_, milestoneI
|
|
|
343
379
|
logWarning("worktree", `worktree DB cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
344
380
|
}
|
|
345
381
|
}
|
|
382
|
+
/**
|
|
383
|
+
* Scope-typed variant of syncStateToProjectRoot.
|
|
384
|
+
*
|
|
385
|
+
* Takes an explicit (worktreeScope, rootScope) pair. Direction is encoded in
|
|
386
|
+
* argument order (worktree → root). Asserts both scopes belong to the same
|
|
387
|
+
* workspace identity to prevent silent mismatch bugs.
|
|
388
|
+
*/
|
|
389
|
+
export function syncStateToProjectRootByScope(worktreeScope, rootScope) {
|
|
390
|
+
if (worktreeScope.workspace.identityKey !== rootScope.workspace.identityKey) {
|
|
391
|
+
throw new Error(`syncStateToProjectRootByScope: scope identity mismatch — ` +
|
|
392
|
+
`worktreeScope.identityKey="${worktreeScope.workspace.identityKey}" ` +
|
|
393
|
+
`rootScope.identityKey="${rootScope.workspace.identityKey}"`);
|
|
394
|
+
}
|
|
395
|
+
if (worktreeScope.milestoneId !== rootScope.milestoneId) {
|
|
396
|
+
throw new Error(`syncStateToProjectRootByScope: milestoneId mismatch — ` +
|
|
397
|
+
`worktreeScope.milestoneId="${worktreeScope.milestoneId}" rootScope.milestoneId="${rootScope.milestoneId}"`);
|
|
398
|
+
}
|
|
399
|
+
const worktreePath_ = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
|
|
400
|
+
const projectRoot = rootScope.workspace.projectRoot;
|
|
401
|
+
const milestoneId = worktreeScope.milestoneId;
|
|
402
|
+
syncStateToProjectRoot(worktreePath_, projectRoot, milestoneId);
|
|
403
|
+
}
|
|
346
404
|
/**
|
|
347
405
|
* Sync worktree diagnostics from worktree to project root.
|
|
348
406
|
* Only runs when inside an auto-worktree (worktreePath differs from projectRoot).
|
|
349
407
|
* DB/project-root state remains authoritative; markdown projections are not
|
|
350
408
|
* copied from the worktree back to the project root.
|
|
351
409
|
* Non-fatal — sync failure should never block dispatch.
|
|
410
|
+
* @deprecated Use syncStateToProjectRootByScope instead.
|
|
411
|
+
* TODO(C-future): remove once all callers migrated.
|
|
352
412
|
*/
|
|
353
413
|
export function syncStateToProjectRoot(worktreePath_, projectRoot, milestoneId) {
|
|
354
414
|
if (!worktreePath_ || !projectRoot || worktreePath_ === projectRoot)
|
|
@@ -520,6 +580,24 @@ export function cleanStaleRuntimeUnits(gsdRootPath, hasMilestoneSummary) {
|
|
|
520
580
|
return cleaned;
|
|
521
581
|
}
|
|
522
582
|
// ─── Worktree ↔ Main Repo Sync (#1311) ──────────────────────────────────────
|
|
583
|
+
/**
|
|
584
|
+
* Scope-typed variant of syncGsdStateToWorktree.
|
|
585
|
+
*
|
|
586
|
+
* Takes an explicit (rootScope, worktreeScope) pair. Note: milestoneId is not
|
|
587
|
+
* used by syncGsdStateToWorktree — this variant only requires workspace
|
|
588
|
+
* identity. Asserts both scopes belong to the same workspace identity to
|
|
589
|
+
* prevent silent mismatch bugs.
|
|
590
|
+
*/
|
|
591
|
+
export function syncGsdStateToWorktreeByScope(rootScope, worktreeScope) {
|
|
592
|
+
if (rootScope.workspace.identityKey !== worktreeScope.workspace.identityKey) {
|
|
593
|
+
throw new Error(`syncGsdStateToWorktreeByScope: scope identity mismatch — ` +
|
|
594
|
+
`rootScope.identityKey="${rootScope.workspace.identityKey}" ` +
|
|
595
|
+
`worktreeScope.identityKey="${worktreeScope.workspace.identityKey}"`);
|
|
596
|
+
}
|
|
597
|
+
const mainBasePath = rootScope.workspace.projectRoot;
|
|
598
|
+
const worktreePath_ = worktreeScope.workspace.worktreeRoot ?? worktreeScope.workspace.projectRoot;
|
|
599
|
+
return syncGsdStateToWorktree(mainBasePath, worktreePath_);
|
|
600
|
+
}
|
|
523
601
|
/**
|
|
524
602
|
* Sync .gsd/ state from the main repo into the worktree.
|
|
525
603
|
*
|
|
@@ -534,6 +612,8 @@ export function cleanStaleRuntimeUnits(gsdRootPath, hasMilestoneSummary) {
|
|
|
534
612
|
* Only adds missing content — never overwrites existing files in the worktree.
|
|
535
613
|
* Worktree files are compatibility projections; DB/project root remains
|
|
536
614
|
* authoritative for runtime state.
|
|
615
|
+
* @deprecated Use syncGsdStateToWorktreeByScope instead.
|
|
616
|
+
* TODO(C-future): remove once all callers migrated.
|
|
537
617
|
*/
|
|
538
618
|
export function syncGsdStateToWorktree(mainBasePath, worktreePath_) {
|
|
539
619
|
const contract = resolveGsdPathContract(worktreePath_, mainBasePath);
|
|
@@ -890,94 +970,13 @@ export function enterBranchModeForMilestone(basePath, milestoneId) {
|
|
|
890
970
|
* Forward-merge plan checkbox state from the project root into a freshly
|
|
891
971
|
* re-attached worktree (#778).
|
|
892
972
|
*
|
|
893
|
-
*
|
|
894
|
-
*
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
*
|
|
898
|
-
*
|
|
899
|
-
* dispatch/skip loop.
|
|
900
|
-
*
|
|
901
|
-
* Fix: after re-attaching, read every *.md plan file in the milestone
|
|
902
|
-
* directory at the project root and apply any [x] checkbox states that are
|
|
903
|
-
* ahead of the worktree version (forward-only: never downgrade [x] → [ ]).
|
|
904
|
-
*
|
|
905
|
-
* This is forward-only compatibility for legacy projection copies. The DB
|
|
906
|
-
* remains authoritative; this never downgrades checked boxes in a local
|
|
907
|
-
* worktree projection.
|
|
973
|
+
* Phase C: deleted. Writers in workflow-projections.ts, triage-resolution.ts,
|
|
974
|
+
* rule-registry.ts, and auto-post-unit.ts now route through
|
|
975
|
+
* s.canonicalProjectRoot, so non-symlinked worktrees no longer need a local
|
|
976
|
+
* .gsd/ projection — the project-root .gsd/ is the only authoritative source
|
|
977
|
+
* for both reads and writes. copyPlanningArtifacts and reconcilePlanCheckboxes
|
|
978
|
+
* (both formerly here) became dead.
|
|
908
979
|
*/
|
|
909
|
-
function reconcilePlanCheckboxes(projectRoot, wtPath, milestoneId) {
|
|
910
|
-
const srcMilestone = join(projectRoot, ".gsd", "milestones", milestoneId);
|
|
911
|
-
const dstMilestone = join(wtPath, ".gsd", "milestones", milestoneId);
|
|
912
|
-
if (!existsSync(srcMilestone) || !existsSync(dstMilestone))
|
|
913
|
-
return;
|
|
914
|
-
// Walk all markdown files in the milestone directory (plans, summaries, etc.)
|
|
915
|
-
function walkMd(dir) {
|
|
916
|
-
const results = [];
|
|
917
|
-
try {
|
|
918
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
919
|
-
const full = join(dir, entry.name);
|
|
920
|
-
if (entry.isDirectory()) {
|
|
921
|
-
results.push(...walkMd(full));
|
|
922
|
-
}
|
|
923
|
-
else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
924
|
-
results.push(full);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
catch (err) {
|
|
929
|
-
/* non-fatal */
|
|
930
|
-
logWarning("worktree", `walkMd directory read failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
931
|
-
}
|
|
932
|
-
return results;
|
|
933
|
-
}
|
|
934
|
-
for (const srcFile of walkMd(srcMilestone)) {
|
|
935
|
-
const rel = srcFile.slice(srcMilestone.length);
|
|
936
|
-
const dstFile = dstMilestone + rel;
|
|
937
|
-
if (!existsSync(dstFile))
|
|
938
|
-
continue; // only reconcile existing files
|
|
939
|
-
let srcContent;
|
|
940
|
-
let dstContent;
|
|
941
|
-
try {
|
|
942
|
-
srcContent = readFileSync(srcFile, "utf-8");
|
|
943
|
-
dstContent = readFileSync(dstFile, "utf-8");
|
|
944
|
-
}
|
|
945
|
-
catch (e) {
|
|
946
|
-
logWarning("worktree", `reconcilePlanCheckboxes read failed: ${e.message}`);
|
|
947
|
-
continue;
|
|
948
|
-
}
|
|
949
|
-
if (srcContent === dstContent)
|
|
950
|
-
continue;
|
|
951
|
-
// Extract all checked task IDs from the source (project root)
|
|
952
|
-
// Pattern: - [x] **T<id>: or - [x] **S<id>: (case-insensitive x)
|
|
953
|
-
const checkedRe = /^- \[[xX]\] \*\*([TS]\d+):/gm;
|
|
954
|
-
const srcChecked = new Set();
|
|
955
|
-
for (const m of srcContent.matchAll(checkedRe))
|
|
956
|
-
srcChecked.add(m[1]);
|
|
957
|
-
if (srcChecked.size === 0)
|
|
958
|
-
continue;
|
|
959
|
-
// Forward-apply: replace [ ] → [x] for any IDs that are checked in src
|
|
960
|
-
let updated = dstContent;
|
|
961
|
-
let changed = false;
|
|
962
|
-
for (const id of srcChecked) {
|
|
963
|
-
const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
964
|
-
const uncheckedRe = new RegExp(`^(- )\\[ \\]( \\*\\*${escapedId}:)`, "gm");
|
|
965
|
-
if (uncheckedRe.test(updated)) {
|
|
966
|
-
updated = updated.replace(new RegExp(`^(- )\\[ \\]( \\*\\*${escapedId}:)`, "gm"), "$1[x]$2");
|
|
967
|
-
changed = true;
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
if (changed) {
|
|
971
|
-
try {
|
|
972
|
-
atomicWriteSync(dstFile, updated, "utf-8");
|
|
973
|
-
}
|
|
974
|
-
catch (err) {
|
|
975
|
-
/* non-fatal */
|
|
976
|
-
logWarning("worktree", `plan checkbox reconcile write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
980
|
export function createAutoWorktree(basePath, milestoneId) {
|
|
982
981
|
basePath = resolveWorktreeProjectRoot(basePath);
|
|
983
982
|
// Check if repo has commits — git worktree requires a valid HEAD
|
|
@@ -1023,33 +1022,15 @@ export function createAutoWorktree(basePath, milestoneId) {
|
|
|
1023
1022
|
startPoint,
|
|
1024
1023
|
});
|
|
1025
1024
|
}
|
|
1026
|
-
//
|
|
1027
|
-
//
|
|
1028
|
-
//
|
|
1029
|
-
//
|
|
1030
|
-
//
|
|
1031
|
-
//
|
|
1032
|
-
//
|
|
1033
|
-
// The
|
|
1034
|
-
//
|
|
1035
|
-
// overwrite them with stale data ([ ] checkboxes) because the root is
|
|
1036
|
-
// not always fully synced.
|
|
1037
|
-
if (!branchExists) {
|
|
1038
|
-
copyPlanningArtifacts(basePath, info.path);
|
|
1039
|
-
}
|
|
1040
|
-
else {
|
|
1041
|
-
// Re-attaching to an existing branch: forward-merge any plan checkpoint
|
|
1042
|
-
// state from the project root into the worktree (#778).
|
|
1043
|
-
//
|
|
1044
|
-
// If auto-mode stopped via crash, the milestone branch HEAD may lag behind
|
|
1045
|
-
// the project root filesystem because syncStateToProjectRoot() ran after
|
|
1046
|
-
// task completion but the auto-commit never fired. On restart the worktree
|
|
1047
|
-
// is re-created from the branch HEAD (which has [ ] for the crashed task),
|
|
1048
|
-
// causing verifyExpectedArtifact() to return false → stale-key eviction →
|
|
1049
|
-
// infinite dispatch/skip loop. Reconciling here ensures the worktree sees
|
|
1050
|
-
// the same [x] state that syncStateToProjectRoot() wrote to the root.
|
|
1051
|
-
reconcilePlanCheckboxes(basePath, info.path, milestoneId);
|
|
1052
|
-
}
|
|
1025
|
+
// Phase C: copyPlanningArtifacts and reconcilePlanCheckboxes were
|
|
1026
|
+
// deleted. Both addressed the same problem (worktree-local .gsd/
|
|
1027
|
+
// projection lagging behind project-root state) by maintaining a stale
|
|
1028
|
+
// copy. Now that auto-mode writers in workflow-projections.ts,
|
|
1029
|
+
// triage-resolution.ts, rule-registry.ts, and auto-post-unit.ts route
|
|
1030
|
+
// through s.canonicalProjectRoot, the worktree never needs a local
|
|
1031
|
+
// .gsd/ — both reads and writes converge on the project-root .gsd/.
|
|
1032
|
+
// The original concerns (#759, #778) no longer apply because there is
|
|
1033
|
+
// no second copy to drift.
|
|
1053
1034
|
// Run user-configured post-create hook (#597) — e.g. copy .env, symlink assets
|
|
1054
1035
|
const hookError = runWorktreePostCreateHook(basePath, info.path);
|
|
1055
1036
|
if (hookError) {
|
|
@@ -1059,61 +1040,23 @@ export function createAutoWorktree(basePath, milestoneId) {
|
|
|
1059
1040
|
const previousCwd = process.cwd();
|
|
1060
1041
|
try {
|
|
1061
1042
|
process.chdir(info.path);
|
|
1062
|
-
|
|
1043
|
+
setActiveWorkspace(createWorkspace(basePath));
|
|
1063
1044
|
}
|
|
1064
1045
|
catch (err) {
|
|
1065
1046
|
// If chdir fails, the worktree was created but we couldn't enter it.
|
|
1066
|
-
// Don't
|
|
1047
|
+
// Don't set activeWorkspace -- caller can retry or clean up.
|
|
1067
1048
|
throw new GSDError(GSD_IO_ERROR, `Auto-worktree created at ${info.path} but chdir failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1068
1049
|
}
|
|
1069
1050
|
nudgeGitBranchCache(previousCwd);
|
|
1070
1051
|
return info.path;
|
|
1071
1052
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
function copyPlanningArtifacts(srcBase, wtPath) {
|
|
1080
|
-
const srcGsd = join(srcBase, ".gsd");
|
|
1081
|
-
const dstGsd = join(wtPath, ".gsd");
|
|
1082
|
-
if (!existsSync(srcGsd))
|
|
1083
|
-
return;
|
|
1084
|
-
if (isSamePath(srcGsd, dstGsd))
|
|
1085
|
-
return;
|
|
1086
|
-
// Copy milestones/ directory (planning files, roadmaps, plans, research)
|
|
1087
|
-
safeCopyRecursive(join(srcGsd, "milestones"), join(dstGsd, "milestones"), {
|
|
1088
|
-
force: true,
|
|
1089
|
-
filter: (src) => !src.endsWith("-META.json"),
|
|
1090
|
-
});
|
|
1091
|
-
// Copy top-level planning files
|
|
1092
|
-
for (const file of [
|
|
1093
|
-
"DECISIONS.md",
|
|
1094
|
-
"REQUIREMENTS.md",
|
|
1095
|
-
"PROJECT.md",
|
|
1096
|
-
"QUEUE.md",
|
|
1097
|
-
"STATE.md",
|
|
1098
|
-
"KNOWLEDGE.md",
|
|
1099
|
-
"OVERRIDES.md",
|
|
1100
|
-
"mcp.json",
|
|
1101
|
-
]) {
|
|
1102
|
-
safeCopy(join(srcGsd, file), join(dstGsd, file), { force: true });
|
|
1103
|
-
}
|
|
1104
|
-
// Seed canonical PREFERENCES.md when available; fall back to legacy lowercase.
|
|
1105
|
-
if (existsSync(join(srcGsd, PROJECT_PREFERENCES_FILE))) {
|
|
1106
|
-
safeCopy(join(srcGsd, PROJECT_PREFERENCES_FILE), join(dstGsd, PROJECT_PREFERENCES_FILE), { force: true });
|
|
1107
|
-
}
|
|
1108
|
-
else if (existsSync(join(srcGsd, LEGACY_PROJECT_PREFERENCES_FILE))) {
|
|
1109
|
-
safeCopy(join(srcGsd, LEGACY_PROJECT_PREFERENCES_FILE), join(dstGsd, LEGACY_PROJECT_PREFERENCES_FILE), { force: true });
|
|
1110
|
-
}
|
|
1111
|
-
// Shared WAL (R012): worktrees use the project root's DB directly.
|
|
1112
|
-
// No longer copy gsd.db into the worktree — the DB path resolver in
|
|
1113
|
-
// ensureDbOpen() detects the worktree location and opens the root DB.
|
|
1114
|
-
// Compat note: reconcileWorktreeDb() in mergeMilestoneToMain handles
|
|
1115
|
-
// worktrees that already have a local gsd.db from before this change.
|
|
1116
|
-
}
|
|
1053
|
+
// Phase C: copyPlanningArtifacts removed. Planning artifacts now live
|
|
1054
|
+
// only at the project root .gsd/; auto-mode writers (workflow-projections,
|
|
1055
|
+
// triage-resolution, rule-registry, regenerateIfMissing,
|
|
1056
|
+
// resolveHookArtifactPath) all route through s.canonicalProjectRoot.
|
|
1057
|
+
// Worktrees are pure git checkouts — they no longer maintain a parallel
|
|
1058
|
+
// .gsd/ projection. The gsd.db has always lived at the project root via
|
|
1059
|
+
// the shared-WAL R012 contract; that is unchanged.
|
|
1117
1060
|
/**
|
|
1118
1061
|
* Teardown an auto-worktree: chdir back to original base, then remove
|
|
1119
1062
|
* the worktree and its branch.
|
|
@@ -1123,41 +1066,79 @@ export function teardownAutoWorktree(originalBasePath, milestoneId, opts = {}) {
|
|
|
1123
1066
|
const branch = autoWorktreeBranch(milestoneId);
|
|
1124
1067
|
const { preserveBranch = false } = opts;
|
|
1125
1068
|
const previousCwd = process.cwd();
|
|
1069
|
+
// Wrap the entire teardown body in a single try/finally so activeWorkspace
|
|
1070
|
+
// is ALWAYS cleared — even if process.chdir throws (e.g. originalBasePath
|
|
1071
|
+
// was deleted before teardown ran). Previously the finally only covered
|
|
1072
|
+
// removeWorktree, leaving the registry stale on a chdir failure (H3 fix).
|
|
1126
1073
|
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)) {
|
|
1074
|
+
try {
|
|
1075
|
+
process.chdir(originalBasePath);
|
|
1076
|
+
}
|
|
1077
|
+
catch (err) {
|
|
1078
|
+
throw new GSDError(GSD_IO_ERROR, `Failed to chdir back to ${originalBasePath} during teardown: ${err instanceof Error ? err.message : String(err)}`);
|
|
1079
|
+
}
|
|
1080
|
+
// Mirror cleanup steps from mergeMilestoneToMain abort path:
|
|
1081
|
+
// 1. Remove transient state files (STATE.md, auto.lock, {MID}-META.json).
|
|
1082
|
+
// Non-fatal — must not block teardown.
|
|
1083
|
+
try {
|
|
1084
|
+
clearProjectRootStateFiles(originalBasePath, milestoneId);
|
|
1085
|
+
}
|
|
1086
|
+
catch (err) {
|
|
1087
|
+
logWarning("worktree", `clearProjectRootStateFiles failed during teardown: ${err instanceof Error ? err.message : String(err)}`);
|
|
1088
|
+
}
|
|
1089
|
+
// 2. Reconcile worktree-local gsd.db into project root DB if both exist.
|
|
1090
|
+
// Non-fatal — handles legacy worktrees that have a local copy.
|
|
1091
|
+
if (isDbAvailable()) {
|
|
1149
1092
|
try {
|
|
1150
|
-
|
|
1093
|
+
const contract = resolveGsdPathContract(previousCwd, originalBasePath);
|
|
1094
|
+
const worktreeDbPath = join(contract.worktreeGsd ?? join(previousCwd, ".gsd"), "gsd.db");
|
|
1095
|
+
const mainDbPath = contract.projectDb;
|
|
1096
|
+
if (existsSync(worktreeDbPath) && !isSamePath(worktreeDbPath, mainDbPath)) {
|
|
1097
|
+
reconcileWorktreeDb(mainDbPath, worktreeDbPath);
|
|
1098
|
+
}
|
|
1151
1099
|
}
|
|
1152
1100
|
catch (err) {
|
|
1153
|
-
|
|
1154
|
-
|
|
1101
|
+
/* non-fatal */
|
|
1102
|
+
logError("worktree", `DB reconciliation failed during teardown: ${err instanceof Error ? err.message : String(err)}`);
|
|
1155
1103
|
}
|
|
1156
1104
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1105
|
+
nudgeGitBranchCache(previousCwd);
|
|
1106
|
+
// 3. Remove the worktree. Errors propagate naturally — the outer finally
|
|
1107
|
+
// ensures activeWorkspace is cleared regardless.
|
|
1108
|
+
removeWorktree(originalBasePath, milestoneId, {
|
|
1109
|
+
branch,
|
|
1110
|
+
deleteBranch: !preserveBranch,
|
|
1111
|
+
});
|
|
1112
|
+
// Verify cleanup succeeded — warn if the worktree directory is still on disk.
|
|
1113
|
+
// On Windows, bash-based cleanup can silently fail when paths contain
|
|
1114
|
+
// backslashes (#1436), leaving ~1 GB+ orphaned directories.
|
|
1115
|
+
const wtDir = worktreePath(originalBasePath, milestoneId);
|
|
1116
|
+
if (existsSync(wtDir)) {
|
|
1117
|
+
logWarning("reconcile", `Worktree directory still exists after teardown: ${wtDir}. ` +
|
|
1118
|
+
`This is likely an orphaned directory consuming disk space. ` +
|
|
1119
|
+
`Remove it manually with: rm -rf "${wtDir.replaceAll("\\", "/")}"`, { worktree: milestoneId });
|
|
1120
|
+
// Attempt a direct filesystem removal as a fallback — but ONLY if the
|
|
1121
|
+
// path is safely inside .gsd/worktrees/ to prevent #2365 data loss.
|
|
1122
|
+
if (isInsideWorktreesDir(originalBasePath, wtDir)) {
|
|
1123
|
+
try {
|
|
1124
|
+
rmSync(wtDir, { recursive: true, force: true });
|
|
1125
|
+
}
|
|
1126
|
+
catch (err) {
|
|
1127
|
+
// Non-fatal — the warning above tells the user how to clean up
|
|
1128
|
+
logWarning("worktree", `worktree directory removal failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
else {
|
|
1132
|
+
console.error(`[GSD] REFUSING fallback rmSync — path is outside .gsd/worktrees/: ${wtDir}`);
|
|
1133
|
+
}
|
|
1159
1134
|
}
|
|
1160
1135
|
}
|
|
1136
|
+
finally {
|
|
1137
|
+
// Clear module state unconditionally — regardless of which step above
|
|
1138
|
+
// failed. A stale activeWorkspace causes getActiveAutoWorktreeContext()
|
|
1139
|
+
// to return wrong data for subsequent operations.
|
|
1140
|
+
setActiveWorkspace(null);
|
|
1141
|
+
}
|
|
1161
1142
|
}
|
|
1162
1143
|
/**
|
|
1163
1144
|
* Detect if the process is currently inside an auto-worktree.
|
|
@@ -1168,8 +1149,9 @@ export function isInAutoWorktree(basePath) {
|
|
|
1168
1149
|
const targetPath = isGsdWorktreePath(basePath) ? basePath : process.cwd();
|
|
1169
1150
|
if (!isGsdWorktreePath(targetPath))
|
|
1170
1151
|
return false;
|
|
1171
|
-
const
|
|
1172
|
-
const
|
|
1152
|
+
const storedBase = getAutoWorktreeOriginalBase();
|
|
1153
|
+
const projectRoot = resolveWorktreeProjectRoot(basePath, storedBase);
|
|
1154
|
+
const targetProjectRoot = resolveWorktreeProjectRoot(targetPath, storedBase);
|
|
1173
1155
|
if (normalizeWorktreePathForCompare(projectRoot) !==
|
|
1174
1156
|
normalizeWorktreePathForCompare(targetProjectRoot)) {
|
|
1175
1157
|
return false;
|
|
@@ -1242,7 +1224,7 @@ export function enterAutoWorktree(basePath, milestoneId) {
|
|
|
1242
1224
|
const previousCwd = process.cwd();
|
|
1243
1225
|
try {
|
|
1244
1226
|
process.chdir(p);
|
|
1245
|
-
|
|
1227
|
+
setActiveWorkspace(createWorkspace(basePath));
|
|
1246
1228
|
}
|
|
1247
1229
|
catch (err) {
|
|
1248
1230
|
throw new GSDError(GSD_IO_ERROR, `Failed to enter auto-worktree at ${p}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1255,14 +1237,16 @@ export function enterAutoWorktree(basePath, milestoneId) {
|
|
|
1255
1237
|
* Returns null if not currently in an auto-worktree.
|
|
1256
1238
|
*/
|
|
1257
1239
|
export function getAutoWorktreeOriginalBase() {
|
|
1258
|
-
return
|
|
1240
|
+
return getActiveWorkspace()?.projectRoot ?? null;
|
|
1259
1241
|
}
|
|
1260
1242
|
export function _resetAutoWorktreeOriginalBaseForTests() {
|
|
1261
|
-
|
|
1243
|
+
setActiveWorkspace(null);
|
|
1262
1244
|
}
|
|
1263
1245
|
export function getActiveAutoWorktreeContext() {
|
|
1264
|
-
|
|
1246
|
+
const ws = getActiveWorkspace();
|
|
1247
|
+
if (!ws)
|
|
1265
1248
|
return null;
|
|
1249
|
+
const originalBase = ws.projectRoot;
|
|
1266
1250
|
const cwd = process.cwd();
|
|
1267
1251
|
if (!isGsdWorktreePath(cwd))
|
|
1268
1252
|
return null;
|
|
@@ -1332,11 +1316,11 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
|
|
|
1332
1316
|
// integration branch captures dirty files from OTHER milestones under a
|
|
1333
1317
|
// misleading commit message, contaminating the main branch (#2929).
|
|
1334
1318
|
//
|
|
1335
|
-
// When
|
|
1319
|
+
// When activeWorkspace is null (branch mode, no worktree), autoCommitDirtyState
|
|
1336
1320
|
// runs unconditionally — the caller is responsible for cwd placement.
|
|
1337
1321
|
{
|
|
1338
1322
|
let shouldAutoCommit = true;
|
|
1339
|
-
if (
|
|
1323
|
+
if (getActiveWorkspace() !== null) {
|
|
1340
1324
|
try {
|
|
1341
1325
|
const currentBranch = nativeGetCurrentBranch(worktreeCwd);
|
|
1342
1326
|
shouldAutoCommit = currentBranch === milestoneBranch;
|
|
@@ -2020,7 +2004,7 @@ export function mergeMilestoneToMain(originalBasePath_, milestoneId, roadmapCont
|
|
|
2020
2004
|
logWarning("worktree", `git branch-delete failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2021
2005
|
}
|
|
2022
2006
|
// 14. Clear module state
|
|
2023
|
-
|
|
2007
|
+
setActiveWorkspace(null);
|
|
2024
2008
|
nudgeGitBranchCache(previousCwd);
|
|
2025
2009
|
// 15. Anchor cwd at the project root on success-return. Step 12 removed
|
|
2026
2010
|
// the worktree dir; if cwd was inside it, every subsequent process.cwd()
|