@opengsd/gsd-pi 1.2.0-dev.4c756166 → 1.2.0-dev.955e4da0
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/bg-shell/utilities.js +2 -2
- package/dist/resources/extensions/claude-code-cli/models.js +9 -0
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +8 -2
- package/dist/resources/extensions/gsd/auto/orchestrator.js +33 -4
- package/dist/resources/extensions/gsd/auto/phases.js +6 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +8 -6
- package/dist/resources/extensions/gsd/auto-start.js +8 -13
- package/dist/resources/extensions/gsd/auto-worktree-repair.js +10 -2
- package/dist/resources/extensions/gsd/auto-worktree.js +13 -270
- package/dist/resources/extensions/gsd/auto.js +4 -7
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +9 -6
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -3
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +26 -4
- package/dist/resources/extensions/gsd/captures.js +5 -13
- package/dist/resources/extensions/gsd/closeout-recovery.js +3 -2
- package/dist/resources/extensions/gsd/commands/catalog.js +6 -62
- package/dist/resources/extensions/gsd/db/engine.js +755 -0
- package/dist/resources/extensions/gsd/db/queries.js +372 -0
- package/dist/resources/extensions/gsd/db/sql-constants.js +11 -0
- package/dist/resources/extensions/gsd/db/writers/cascades.js +194 -0
- package/dist/resources/extensions/gsd/db/writers/import-restore.js +182 -0
- package/dist/resources/extensions/gsd/db/writers/memory.js +149 -0
- package/dist/resources/extensions/gsd/db/writers/reconcile.js +458 -0
- package/dist/resources/extensions/gsd/db/writers/status.js +70 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +8 -10
- package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -3
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +9 -2
- package/dist/resources/extensions/gsd/git-service.js +1 -0
- package/dist/resources/extensions/gsd/gitignore.js +3 -0
- package/dist/resources/extensions/gsd/gsd-db.js +171 -2048
- package/dist/resources/extensions/gsd/guided-flow.js +34 -3
- package/dist/resources/extensions/gsd/migrate/safety.js +17 -9
- package/dist/resources/extensions/gsd/migration-auto-check.js +24 -3
- package/dist/resources/extensions/gsd/model-cost-table.js +1 -0
- package/dist/resources/extensions/gsd/model-router.js +3 -0
- package/dist/resources/extensions/gsd/parallel-merge.js +14 -11
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +7 -5
- package/dist/resources/extensions/gsd/paths.js +10 -24
- package/dist/resources/extensions/gsd/preferences.js +14 -0
- package/dist/resources/extensions/gsd/recovery-classification.js +12 -1
- package/dist/resources/extensions/gsd/safety/evidence-collector.js +37 -4
- package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +7 -2
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +10 -0
- package/dist/resources/extensions/gsd/state-transition-matrix.js +38 -0
- package/dist/resources/extensions/gsd/status-guards.js +56 -8
- package/dist/resources/extensions/gsd/tools/complete-slice.js +24 -43
- package/dist/resources/extensions/gsd/tools/exec-tool.js +5 -5
- package/dist/resources/extensions/gsd/tools/reopen-milestone.js +11 -29
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +14 -33
- package/dist/resources/extensions/gsd/tools/skip-slice.js +18 -36
- package/dist/resources/extensions/gsd/undo.js +8 -7
- package/dist/resources/extensions/gsd/worktree-git-recovery.js +287 -0
- package/dist/resources/extensions/gsd/worktree-lifecycle.js +9 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +45 -28
- package/dist/resources/extensions/gsd/worktree-placement.js +59 -0
- package/dist/resources/extensions/gsd/worktree-reentry.js +12 -8
- package/dist/resources/extensions/gsd/worktree-root.js +17 -6
- package/dist/resources/extensions/gsd/worktree-safety.js +8 -5
- package/dist/resources/extensions/gsd/worktree-session-state.js +12 -10
- package/dist/resources/skills/gsd-browser/SKILL.md +1 -1
- 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 +13 -13
- 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.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +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 +13 -13
- package/dist/web/standalone/.next/server/chunks/{5047.js → 5942.js} +2 -2
- package/dist/web/standalone/.next/server/chunks/8357.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/worktree-status-banner.js +7 -3
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- 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/package.json +7 -7
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +30 -21
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +266 -35
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +235 -46
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js +3 -1
- package/packages/pi-coding-agent/dist/core/capability-patches.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/bg-shell/utilities.ts +2 -2
- package/src/resources/extensions/claude-code-cli/models.ts +9 -0
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +6 -0
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +28 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -1
- package/src/resources/extensions/gsd/auto/orchestrator.ts +39 -5
- package/src/resources/extensions/gsd/auto/phases.ts +10 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +12 -5
- package/src/resources/extensions/gsd/auto-start.ts +8 -14
- package/src/resources/extensions/gsd/auto-worktree-repair.ts +13 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +20 -280
- package/src/resources/extensions/gsd/auto.ts +12 -9
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +10 -6
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +32 -3
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +25 -3
- package/src/resources/extensions/gsd/captures.ts +5 -14
- package/src/resources/extensions/gsd/closeout-recovery.ts +2 -1
- package/src/resources/extensions/gsd/commands/catalog.ts +6 -68
- package/src/resources/extensions/gsd/db/engine.ts +809 -0
- package/src/resources/extensions/gsd/db/queries.ts +453 -0
- package/src/resources/extensions/gsd/db/sql-constants.ts +12 -0
- package/src/resources/extensions/gsd/db/writers/cascades.ts +237 -0
- package/src/resources/extensions/gsd/db/writers/import-restore.ts +310 -0
- package/src/resources/extensions/gsd/db/writers/memory.ts +220 -0
- package/src/resources/extensions/gsd/db/writers/reconcile.ts +500 -0
- package/src/resources/extensions/gsd/db/writers/status.ts +88 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +8 -11
- package/src/resources/extensions/gsd/doctor-git-checks.ts +3 -3
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +10 -3
- package/src/resources/extensions/gsd/git-service.ts +1 -0
- package/src/resources/extensions/gsd/gitignore.ts +3 -0
- package/src/resources/extensions/gsd/gsd-db.ts +173 -2373
- package/src/resources/extensions/gsd/guided-flow.ts +34 -3
- package/src/resources/extensions/gsd/migrate/safety.ts +15 -7
- package/src/resources/extensions/gsd/migration-auto-check.ts +28 -3
- package/src/resources/extensions/gsd/model-cost-table.ts +1 -0
- package/src/resources/extensions/gsd/model-router.ts +3 -0
- package/src/resources/extensions/gsd/parallel-merge.ts +12 -9
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +6 -5
- package/src/resources/extensions/gsd/paths.ts +9 -22
- package/src/resources/extensions/gsd/preferences.ts +18 -0
- package/src/resources/extensions/gsd/recovery-classification.ts +14 -1
- package/src/resources/extensions/gsd/safety/evidence-collector.ts +36 -4
- package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +7 -2
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +14 -0
- package/src/resources/extensions/gsd/state-transition-matrix.ts +42 -0
- package/src/resources/extensions/gsd/status-guards.ts +59 -8
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +123 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/auto-post-unit-evidence-crossref-4909.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-worktree-repair.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/clear-stale-autostart.test.ts +22 -0
- package/src/resources/extensions/gsd/tests/evidence-xref-gsd-exec.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +33 -1
- package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +5 -4
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +85 -1
- package/src/resources/extensions/gsd/tests/recovery-classification-illegal-transition.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +91 -1
- package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/session-switch-clears-pending-autostart.test.ts +108 -0
- package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +43 -6
- package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +41 -4
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/worktree-placement.test.ts +113 -0
- package/src/resources/extensions/gsd/tests/worktree-reentry.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +12 -6
- package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +42 -0
- package/src/resources/extensions/gsd/tools/complete-slice.ts +23 -58
- package/src/resources/extensions/gsd/tools/exec-tool.ts +5 -5
- package/src/resources/extensions/gsd/tools/reopen-milestone.ts +11 -38
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +14 -42
- package/src/resources/extensions/gsd/tools/skip-slice.ts +18 -44
- package/src/resources/extensions/gsd/undo.ts +9 -8
- package/src/resources/extensions/gsd/worktree-git-recovery.ts +308 -0
- package/src/resources/extensions/gsd/worktree-lifecycle.ts +10 -1
- package/src/resources/extensions/gsd/worktree-manager.ts +47 -28
- package/src/resources/extensions/gsd/worktree-placement.ts +63 -0
- package/src/resources/extensions/gsd/worktree-reentry.ts +10 -7
- package/src/resources/extensions/gsd/worktree-root.ts +17 -6
- package/src/resources/extensions/gsd/worktree-safety.ts +8 -5
- package/src/resources/extensions/gsd/worktree-session-state.ts +12 -10
- package/src/resources/skills/gsd-browser/SKILL.md +1 -1
- /package/dist/web/standalone/.next/static/{DUFWcMFRH3iXh7d2fbrOF → C24pqUd-aru-l0Dp0gLZP}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{DUFWcMFRH3iXh7d2fbrOF → C24pqUd-aru-l0Dp0gLZP}/_ssgManifest.js +0 -0
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
unlinkSync,
|
|
21
21
|
lstatSync as lstatSyncFn,
|
|
22
22
|
} from "node:fs";
|
|
23
|
-
import { dirname, isAbsolute, join, relative, resolve
|
|
23
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
24
24
|
import { GSDError, GSD_IO_ERROR, GSD_GIT_ERROR } from "./errors.js";
|
|
25
25
|
import {
|
|
26
26
|
reconcileWorktreeDb,
|
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
nudgeGitBranchCache,
|
|
51
51
|
} from "./worktree.js";
|
|
52
52
|
import {
|
|
53
|
+
findWorktreeSegment,
|
|
53
54
|
isGsdWorktreePath,
|
|
54
55
|
normalizeWorktreePathForCompare,
|
|
55
56
|
resolveWorktreeProjectRoot,
|
|
@@ -62,6 +63,19 @@ import {
|
|
|
62
63
|
} from "./pull-request-process.js";
|
|
63
64
|
import { debugLog } from "./debug-logger.js";
|
|
64
65
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
66
|
+
import {
|
|
67
|
+
checkoutBranchWithStashGuard,
|
|
68
|
+
cleanupConflictState,
|
|
69
|
+
gsdJsonlFilesWithConflictMarkers,
|
|
70
|
+
hasConflictMarkers,
|
|
71
|
+
popStashByRef,
|
|
72
|
+
removeMergeStateFiles,
|
|
73
|
+
stashAlreadyExistsFilesFromError,
|
|
74
|
+
stashRefFromError,
|
|
75
|
+
} from "./worktree-git-recovery.js";
|
|
76
|
+
|
|
77
|
+
// Re-export for existing callers/tests (auto-start.ts, checkout-branch-stash-guard.test.ts).
|
|
78
|
+
export { checkoutBranchWithStashGuard } from "./worktree-git-recovery.js";
|
|
65
79
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
66
80
|
import { MILESTONE_ID_RE } from "./milestone-ids.js";
|
|
67
81
|
import { runWorktreePostCreateHook } from "./worktree-post-create-hook.js";
|
|
@@ -85,7 +99,6 @@ import {
|
|
|
85
99
|
nativeDiffNumstat,
|
|
86
100
|
nativeUpdateRef,
|
|
87
101
|
nativeIsAncestor,
|
|
88
|
-
nativeMergeAbort,
|
|
89
102
|
nativeWorktreeList,
|
|
90
103
|
nativeLsFiles,
|
|
91
104
|
} from "./native-git-bridge.js";
|
|
@@ -132,111 +145,6 @@ const ROOT_STATE_FILES = [
|
|
|
132
145
|
// because the project root is authoritative for preferences (#2684).
|
|
133
146
|
] as const;
|
|
134
147
|
|
|
135
|
-
/**
|
|
136
|
-
* Pop a stash entry by tracking the unique marker embedded in its message so
|
|
137
|
-
* concurrent stash operations against the same project root cannot cause us to
|
|
138
|
-
* pop the wrong entry.
|
|
139
|
-
*
|
|
140
|
-
* If `stashMarker` is null or no longer present in the stash list (e.g. a
|
|
141
|
-
* concurrent process popped/dropped it), leaves the stash list untouched and
|
|
142
|
-
* returns null.
|
|
143
|
-
*
|
|
144
|
-
* Throws on pop failure so callers can handle conflict cases the same way
|
|
145
|
-
* they would with the prior `git stash pop` form. When throwing after a
|
|
146
|
-
* targeted pop attempt, the error is annotated with the targeted stash ref.
|
|
147
|
-
*
|
|
148
|
-
* (Issue #4980 HIGH-6)
|
|
149
|
-
*/
|
|
150
|
-
function popStashByRef(basePath: string, stashMarker: string | null): string | null {
|
|
151
|
-
let popArg: string | null = null;
|
|
152
|
-
if (stashMarker) {
|
|
153
|
-
try {
|
|
154
|
-
const list = execFileSync("git", ["stash", "list", "--format=%gd%x00%s"], {
|
|
155
|
-
cwd: basePath,
|
|
156
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
157
|
-
encoding: "utf-8",
|
|
158
|
-
}).trim().split("\n").filter(Boolean);
|
|
159
|
-
for (const entry of list) {
|
|
160
|
-
const [ref, subject] = entry.split("\0");
|
|
161
|
-
if (ref && subject?.includes(stashMarker)) {
|
|
162
|
-
popArg = ref;
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
} catch (err) {
|
|
167
|
-
logWarning("worktree", `stash list lookup failed; leaving stash untouched: ${err instanceof Error ? err.message : String(err)}`);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
if (!popArg) {
|
|
171
|
-
logWarning("worktree", "recorded stash entry could not be resolved; skipping automatic pop");
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
try {
|
|
175
|
-
execFileSync("git", ["stash", "pop", popArg], {
|
|
176
|
-
cwd: basePath,
|
|
177
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
178
|
-
encoding: "utf-8",
|
|
179
|
-
});
|
|
180
|
-
} catch (err) {
|
|
181
|
-
if (err && typeof err === "object") {
|
|
182
|
-
(err as { stashRef?: string }).stashRef = popArg;
|
|
183
|
-
}
|
|
184
|
-
throw err;
|
|
185
|
-
}
|
|
186
|
-
return popArg;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Extract a stash ref annotation injected by popStashByRef() when git stash
|
|
191
|
-
* pop fails and we need to conditionally drop the exact stash entry later.
|
|
192
|
-
*/
|
|
193
|
-
function stashRefFromError(err: unknown): string | null {
|
|
194
|
-
if (!err || typeof err !== "object") return null;
|
|
195
|
-
const stashRef = (err as { stashRef?: unknown }).stashRef;
|
|
196
|
-
return typeof stashRef === "string" && stashRef.length > 0 ? stashRef : null;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function stashAlreadyExistsFilesFromError(err: unknown): string[] {
|
|
200
|
-
if (!err || typeof err !== "object") return [];
|
|
201
|
-
const stderr = (err as { stderr?: unknown }).stderr;
|
|
202
|
-
const stderrText = typeof stderr === "string"
|
|
203
|
-
? stderr
|
|
204
|
-
: stderr instanceof Uint8Array
|
|
205
|
-
? Buffer.from(stderr).toString("utf-8")
|
|
206
|
-
: "";
|
|
207
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
208
|
-
const text = `${stderrText}\n${message}`;
|
|
209
|
-
const files = new Set<string>();
|
|
210
|
-
for (const line of text.split("\n")) {
|
|
211
|
-
const m = line.match(/^(.*?)\s+already exists, no checkout\s*$/i);
|
|
212
|
-
if (!m) continue;
|
|
213
|
-
const filePath = m[1]?.trim();
|
|
214
|
-
if (filePath) files.add(filePath);
|
|
215
|
-
}
|
|
216
|
-
return [...files];
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Detect whether an on-disk file still contains unresolved merge conflict
|
|
221
|
-
* markers from a failed stash-pop or merge attempt.
|
|
222
|
-
*
|
|
223
|
-
* Returns false when the file cannot be read.
|
|
224
|
-
*/
|
|
225
|
-
function hasConflictMarkers(filePath: string): boolean {
|
|
226
|
-
try {
|
|
227
|
-
const content = readFileSync(filePath, "utf-8");
|
|
228
|
-
return content.includes("<<<<<<<") && content.includes("=======") && content.includes(">>>>>>>");
|
|
229
|
-
} catch {
|
|
230
|
-
return false;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function gsdJsonlFilesWithConflictMarkers(basePath: string): string[] {
|
|
235
|
-
return nativeLsFiles(basePath, ".gsd/*.jsonl").filter((f) =>
|
|
236
|
-
hasConflictMarkers(join(basePath, f)),
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
148
|
/**
|
|
241
149
|
* Check if two filesystem paths resolve to the same real location.
|
|
242
150
|
* Returns false if either path cannot be resolved (e.g. doesn't exist).
|
|
@@ -524,49 +432,6 @@ export const isSafeToAutoResolve = (filePath: string): boolean =>
|
|
|
524
432
|
filePath.startsWith(".gsd/") ||
|
|
525
433
|
SAFE_AUTO_RESOLVE_PATTERNS.some((re) => re.test(filePath));
|
|
526
434
|
|
|
527
|
-
function removeMergeStateFiles(basePath: string, contextLabel: string): void {
|
|
528
|
-
try {
|
|
529
|
-
for (const f of ["SQUASH_MSG", "MERGE_MSG", "MERGE_MODE", "MERGE_HEAD", "AUTO_MERGE"]) {
|
|
530
|
-
const rawPath = execFileSync("git", ["rev-parse", "--git-path", f], {
|
|
531
|
-
cwd: basePath,
|
|
532
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
533
|
-
encoding: "utf-8",
|
|
534
|
-
}).trim();
|
|
535
|
-
const p = rawPath.length > 0
|
|
536
|
-
? (isAbsolute(rawPath) ? rawPath : resolve(basePath, rawPath))
|
|
537
|
-
: join(resolveGitDir(basePath), f);
|
|
538
|
-
if (existsSync(p)) unlinkSync(p);
|
|
539
|
-
}
|
|
540
|
-
} catch (err) {
|
|
541
|
-
logError("worktree", `${contextLabel} merge state cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function cleanupConflictState(basePath: string): void {
|
|
546
|
-
// Merge conflicts can leave unmerged index entries; merge-abort alone is not
|
|
547
|
-
// enough for squash merges (MERGE_HEAD is never written). Reset the merge
|
|
548
|
-
// index, then remove merge message files that native/libgit2 paths may have
|
|
549
|
-
// created.
|
|
550
|
-
try {
|
|
551
|
-
nativeMergeAbort(basePath);
|
|
552
|
-
} catch (err) {
|
|
553
|
-
// MERGE_HEAD absent (squash merge path) — abort is a no-op, which is fine.
|
|
554
|
-
debugLog("conflict-cleanup:merge-abort-skipped", {
|
|
555
|
-
error: err instanceof Error ? err.message : String(err),
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
try {
|
|
559
|
-
execFileSync("git", ["reset", "--merge"], {
|
|
560
|
-
cwd: basePath,
|
|
561
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
562
|
-
encoding: "utf-8",
|
|
563
|
-
});
|
|
564
|
-
} catch (err) {
|
|
565
|
-
logError("worktree", `git reset --merge failed after merge conflict: ${err instanceof Error ? err.message : String(err)}`);
|
|
566
|
-
}
|
|
567
|
-
removeMergeStateFiles(basePath, "conflict");
|
|
568
|
-
}
|
|
569
|
-
|
|
570
435
|
// ─── Dispatch-Level Sync (project root ↔ worktree) ──────────────────────────
|
|
571
436
|
|
|
572
437
|
/**
|
|
@@ -655,21 +520,12 @@ export function checkResourcesStale(
|
|
|
655
520
|
* Returns the corrected base path.
|
|
656
521
|
*/
|
|
657
522
|
export function escapeStaleWorktree(base: string): string {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
let idx = base.indexOf(directMarker);
|
|
661
|
-
if (idx === -1) {
|
|
662
|
-
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
663
|
-
const symlinkRe = new RegExp(
|
|
664
|
-
`\\${pathSep}\\.gsd\\${pathSep}projects\\${pathSep}[a-f0-9]+\\${pathSep}worktrees\\${pathSep}`,
|
|
665
|
-
);
|
|
666
|
-
const match = base.match(symlinkRe);
|
|
667
|
-
if (!match || match.index === undefined) return base;
|
|
668
|
-
idx = match.index;
|
|
669
|
-
}
|
|
523
|
+
const segment = findWorktreeSegment(base.replaceAll("\\", "/"));
|
|
524
|
+
if (!segment) return base;
|
|
670
525
|
|
|
671
|
-
// base is inside .gsd/worktrees/<something> — extract the project root
|
|
672
|
-
|
|
526
|
+
// base is inside .gsd/worktrees/<something> — extract the project root.
|
|
527
|
+
// Normalization is 1:1 on characters, so the segment index is valid in `base`.
|
|
528
|
+
const projectRoot = base.slice(0, segment.gsdIdx);
|
|
673
529
|
|
|
674
530
|
// Guard: If the candidate project root's .gsd IS the user-level ~/.gsd,
|
|
675
531
|
// the string-slice heuristic matched the wrong /.gsd/ boundary. This happens
|
|
@@ -1066,122 +922,6 @@ export function enterBranchModeForMilestone(
|
|
|
1066
922
|
checkoutBranchWithStashGuard(basePath, branch, `enter-branch-mode:${milestoneId}`);
|
|
1067
923
|
}
|
|
1068
924
|
|
|
1069
|
-
export function checkoutBranchWithStashGuard(
|
|
1070
|
-
basePath: string,
|
|
1071
|
-
branch: string,
|
|
1072
|
-
reason: string,
|
|
1073
|
-
): void {
|
|
1074
|
-
let stashMarker: string | null = null;
|
|
1075
|
-
let stashed = false;
|
|
1076
|
-
|
|
1077
|
-
const status = nativeWorkingTreeStatus(basePath).trim();
|
|
1078
|
-
if (status.length > 0) {
|
|
1079
|
-
stashMarker = `gsd-checkout-stash:${reason}:${process.pid}:${Date.now()}:${process.hrtime.bigint().toString(36)}`;
|
|
1080
|
-
const stashListBefore = execFileSync("git", ["stash", "list"], {
|
|
1081
|
-
cwd: basePath,
|
|
1082
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1083
|
-
encoding: "utf-8",
|
|
1084
|
-
});
|
|
1085
|
-
execFileSync(
|
|
1086
|
-
"git",
|
|
1087
|
-
["stash", "push", "--include-untracked", "-m", `gsd: checkout stash [${stashMarker}]`],
|
|
1088
|
-
{
|
|
1089
|
-
cwd: basePath,
|
|
1090
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1091
|
-
encoding: "utf-8",
|
|
1092
|
-
},
|
|
1093
|
-
);
|
|
1094
|
-
const stashListAfter = execFileSync("git", ["stash", "list"], {
|
|
1095
|
-
cwd: basePath,
|
|
1096
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1097
|
-
encoding: "utf-8",
|
|
1098
|
-
});
|
|
1099
|
-
stashed = stashListAfter !== stashListBefore;
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
// Checkout and stash-restore are split so we can distinguish two failure
|
|
1103
|
-
// modes: (a) checkout failed → HEAD did not move, restore stash and rethrow;
|
|
1104
|
-
// (b) checkout succeeded but stash pop failed → HEAD moved to `branch` but
|
|
1105
|
-
// the working-tree changes remain in the stash list. We surface a distinct
|
|
1106
|
-
// error in case (b) so callers don't assume the branch switch was rolled back.
|
|
1107
|
-
try {
|
|
1108
|
-
nativeCheckoutBranch(basePath, branch);
|
|
1109
|
-
} catch (checkoutErr) {
|
|
1110
|
-
if (stashed) {
|
|
1111
|
-
try {
|
|
1112
|
-
popStashByRef(basePath, stashMarker);
|
|
1113
|
-
} catch (restoreErr) {
|
|
1114
|
-
logWarning("worktree", `git stash pop failed during checkout restore: ${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)}`);
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
throw checkoutErr;
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
if (stashed) {
|
|
1121
|
-
try {
|
|
1122
|
-
popStashByRef(basePath, stashMarker);
|
|
1123
|
-
} catch (popErr) {
|
|
1124
|
-
const msg = popErr instanceof Error ? popErr.message : String(popErr);
|
|
1125
|
-
const stderr = popErr && typeof popErr === "object"
|
|
1126
|
-
? (popErr as { stderr?: unknown }).stderr
|
|
1127
|
-
: undefined;
|
|
1128
|
-
const stderrText = typeof stderr === "string"
|
|
1129
|
-
? stderr
|
|
1130
|
-
: stderr instanceof Uint8Array
|
|
1131
|
-
? Buffer.from(stderr).toString("utf-8")
|
|
1132
|
-
: "";
|
|
1133
|
-
const stashPopMessage = `${stderrText}\n${msg}`.trim();
|
|
1134
|
-
const alreadyExists = stashAlreadyExistsFilesFromError(popErr);
|
|
1135
|
-
const gsdAlreadyExists = alreadyExists.filter((f) => f.startsWith(".gsd/"));
|
|
1136
|
-
const nonGsdAlreadyExists = alreadyExists.filter((f) => !f.startsWith(".gsd/"));
|
|
1137
|
-
const isUntrackedRestoreFailure = stashPopMessage.includes("could not restore untracked files from stash");
|
|
1138
|
-
const stashRefForDrop = stashRefFromError(popErr);
|
|
1139
|
-
const nonGsdUnmerged = nativeConflictFiles(basePath).filter((f) => !f.startsWith(".gsd/"));
|
|
1140
|
-
const gsdContentConflicts = isUntrackedRestoreFailure
|
|
1141
|
-
? gsdJsonlFilesWithConflictMarkers(basePath)
|
|
1142
|
-
: [];
|
|
1143
|
-
const gsdConflictFiles = [...new Set([...gsdAlreadyExists, ...gsdContentConflicts])];
|
|
1144
|
-
|
|
1145
|
-
if (
|
|
1146
|
-
isUntrackedRestoreFailure &&
|
|
1147
|
-
gsdConflictFiles.length > 0 &&
|
|
1148
|
-
nonGsdAlreadyExists.length === 0 &&
|
|
1149
|
-
nonGsdUnmerged.length === 0
|
|
1150
|
-
) {
|
|
1151
|
-
for (const f of gsdConflictFiles) {
|
|
1152
|
-
execFileSync("git", ["checkout", "HEAD", "--", f], {
|
|
1153
|
-
cwd: basePath,
|
|
1154
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1155
|
-
encoding: "utf-8",
|
|
1156
|
-
});
|
|
1157
|
-
nativeAddPaths(basePath, [f]);
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
if (stashRefForDrop) {
|
|
1161
|
-
try {
|
|
1162
|
-
execFileSync("git", ["stash", "drop", stashRefForDrop], {
|
|
1163
|
-
cwd: basePath,
|
|
1164
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1165
|
-
encoding: "utf-8",
|
|
1166
|
-
});
|
|
1167
|
-
} catch (err) { /* stash may already be consumed */
|
|
1168
|
-
logWarning("worktree", `git stash drop failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1169
|
-
}
|
|
1170
|
-
} else {
|
|
1171
|
-
logWarning("worktree", "recorded stash entry could not be resolved; skipping automatic drop");
|
|
1172
|
-
}
|
|
1173
|
-
return;
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
const wrapped = new Error(
|
|
1177
|
-
`checkout to '${branch}' succeeded but stash restore failed; working tree changes remain in the stash list. Original error: ${msg}`,
|
|
1178
|
-
);
|
|
1179
|
-
if (stashRefForDrop) (wrapped as { stashRef?: string }).stashRef = stashRefForDrop;
|
|
1180
|
-
throw wrapped;
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
925
|
// ─── Public API ────────────────────────────────────────────────────────────
|
|
1186
926
|
|
|
1187
927
|
/**
|
|
@@ -81,6 +81,7 @@ import {
|
|
|
81
81
|
resolveAutoSupervisorConfig,
|
|
82
82
|
loadEffectiveGSDPreferences,
|
|
83
83
|
getIsolationMode,
|
|
84
|
+
resolveEffectiveUnitIsolationMode,
|
|
84
85
|
} from "./preferences.js";
|
|
85
86
|
import { playNotificationBell, sendDesktopNotification } from "./notifications.js";
|
|
86
87
|
import type { GSDPreferences } from "./preferences.js";
|
|
@@ -634,22 +635,24 @@ export function shouldUseWorktreeIsolation(basePath?: string): boolean {
|
|
|
634
635
|
|
|
635
636
|
type AutoIsolationMode = ReturnType<typeof getIsolationMode>;
|
|
636
637
|
|
|
637
|
-
function resolveEffectiveUnitIsolationMode(
|
|
638
|
-
configuredMode: AutoIsolationMode,
|
|
639
|
-
isolationDegraded: boolean,
|
|
640
|
-
): AutoIsolationMode {
|
|
641
|
-
return configuredMode === "worktree" && isolationDegraded ? "branch" : configuredMode;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
638
|
export function _resolveEffectiveUnitIsolationModeForTest(
|
|
645
639
|
configuredMode: AutoIsolationMode,
|
|
646
640
|
isolationDegraded: boolean,
|
|
641
|
+
strandedRecoveryIsolationMode: "worktree" | "branch" | null = null,
|
|
647
642
|
): AutoIsolationMode {
|
|
648
|
-
return resolveEffectiveUnitIsolationMode(
|
|
643
|
+
return resolveEffectiveUnitIsolationMode(
|
|
644
|
+
configuredMode,
|
|
645
|
+
isolationDegraded,
|
|
646
|
+
strandedRecoveryIsolationMode,
|
|
647
|
+
);
|
|
649
648
|
}
|
|
650
649
|
|
|
651
650
|
function getEffectiveUnitIsolationMode(basePath: string): AutoIsolationMode {
|
|
652
|
-
return resolveEffectiveUnitIsolationMode(
|
|
651
|
+
return resolveEffectiveUnitIsolationMode(
|
|
652
|
+
getIsolationMode(basePath),
|
|
653
|
+
s.isolationDegraded,
|
|
654
|
+
s.strandedRecoveryIsolationMode,
|
|
655
|
+
);
|
|
653
656
|
}
|
|
654
657
|
|
|
655
658
|
/** Crash recovery prompt — set by startAuto, consumed by the main loop */
|
|
@@ -12,6 +12,7 @@ import { logWarning } from "../workflow-logger.js";
|
|
|
12
12
|
import { openWorkflowDatabase } from "../db-workspace.js";
|
|
13
13
|
import { getAutoWorktreePath } from "../auto-worktree.js";
|
|
14
14
|
import { resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
15
|
+
import { worktreesDirs } from "../worktree-placement.js";
|
|
15
16
|
|
|
16
17
|
export function safeWorkspaceCwd(): string {
|
|
17
18
|
try {
|
|
@@ -46,13 +47,15 @@ export function resolveWorkflowToolBasePath(
|
|
|
46
47
|
const worktree = getAutoWorktreePath(projectRoot, milestoneId);
|
|
47
48
|
if (worktree) return worktree;
|
|
48
49
|
} else {
|
|
49
|
-
const
|
|
50
|
-
|
|
50
|
+
const live: string[] = [];
|
|
51
|
+
for (const worktreesDir of worktreesDirs(projectRoot)) {
|
|
52
|
+
if (!existsSync(worktreesDir)) continue;
|
|
51
53
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
live.push(
|
|
55
|
+
...readdirSync(worktreesDir)
|
|
56
|
+
.map((name) => join(worktreesDir, name))
|
|
57
|
+
.filter((p) => existsSync(join(p, ".git"))),
|
|
58
|
+
);
|
|
56
59
|
} catch (err) {
|
|
57
60
|
logWarning(
|
|
58
61
|
"bootstrap",
|
|
@@ -60,6 +63,7 @@ export function resolveWorkflowToolBasePath(
|
|
|
60
63
|
);
|
|
61
64
|
}
|
|
62
65
|
}
|
|
66
|
+
if (live.length === 1) return live[0]!;
|
|
63
67
|
}
|
|
64
68
|
return cwd;
|
|
65
69
|
}
|
|
@@ -13,7 +13,7 @@ import type { GSDEcosystemBeforeAgentStartHandler } from "../ecosystem/gsd-exten
|
|
|
13
13
|
import { updateSnapshot } from "../ecosystem/gsd-extension-api.js";
|
|
14
14
|
|
|
15
15
|
import { buildMilestoneFileName, clearPathCache, milestonesDir, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
|
|
16
|
-
import { applyAskUserQuestionsGateResult, canonicalToolName, clearDiscussionFlowState, formatPendingAskUserQuestionsGateMessage, isMilestoneDepthVerified, isQueuePhaseActive, markApprovalGateVerified, markDepthVerified, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, shouldBlockWorktreeWrite, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
|
|
16
|
+
import { applyAskUserQuestionsGateResult, canonicalToolName, clearDiscussionFlowState, formatPendingAskUserQuestionsGateMessage, isApprovalGateVerifiedInSnapshot, isMilestoneDepthVerified, isMilestoneDepthVerifiedInSnapshot, isQueuePhaseActive, loadWriteGateSnapshot, markApprovalGateVerified, markDepthVerified, refreshWriteGateStateFromDisk, resetWriteGateState, shouldBlockContextWrite, shouldBlockPlanningUnit, shouldBlockQueueExecution, shouldBlockWorktreeWrite, isGateQuestionId, setPendingGate, clearPendingGate, getPendingGate, shouldBlockPendingGate, shouldBlockPendingGateBash, extractDepthVerificationMilestoneId } from "./write-gate.js";
|
|
17
17
|
import { resolveManifest } from "../unit-context-manifest.js";
|
|
18
18
|
import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
|
|
19
19
|
import { loadFile, saveFile, formatContinue } from "../files.js";
|
|
@@ -61,6 +61,7 @@ import { filterToolsForProvider } from "../model-router.js";
|
|
|
61
61
|
import { mcpToolMatchesBaseName } from "../mcp-tool-name.js";
|
|
62
62
|
import { RUN_UAT_READ_ONLY_TOOL_NAMES, RUN_UAT_WORKFLOW_TOOL_NAMES } from "../tool-presentation-plan.js";
|
|
63
63
|
import { supportsSourceObservationsForUnit } from "../source-observations.js";
|
|
64
|
+
import { clearPendingAutoStart } from "../pending-auto-start.js";
|
|
64
65
|
|
|
65
66
|
let approvalQuestionAbortInFlight = false;
|
|
66
67
|
|
|
@@ -572,8 +573,14 @@ function isShellExecutionTool(canonicalName: string): boolean {
|
|
|
572
573
|
|
|
573
574
|
function activateDeferredApprovalGate(basePath: string): void {
|
|
574
575
|
if (deferredApprovalGate?.basePath !== basePath) return;
|
|
575
|
-
|
|
576
|
+
const gateId = deferredApprovalGate.gateId;
|
|
576
577
|
deferredApprovalGate = null;
|
|
578
|
+
refreshWriteGateStateFromDisk(basePath);
|
|
579
|
+
const snapshot = loadWriteGateSnapshot(basePath);
|
|
580
|
+
const milestoneId = extractDepthVerificationMilestoneId(gateId);
|
|
581
|
+
if (isApprovalGateVerifiedInSnapshot(snapshot, gateId)) return;
|
|
582
|
+
if (milestoneId && isMilestoneDepthVerifiedInSnapshot(snapshot, milestoneId)) return;
|
|
583
|
+
setPendingGate(gateId, basePath);
|
|
577
584
|
}
|
|
578
585
|
|
|
579
586
|
function extractGateQuestionId(input: unknown): string | undefined {
|
|
@@ -802,7 +809,7 @@ export function registerHooks(
|
|
|
802
809
|
}
|
|
803
810
|
});
|
|
804
811
|
|
|
805
|
-
pi.on("session_switch", async (
|
|
812
|
+
pi.on("session_switch", async (event, ctx) => {
|
|
806
813
|
const basePath = contextBasePath(ctx);
|
|
807
814
|
const preserveCloseoutSurface = isAutoCompletionStopInProgress();
|
|
808
815
|
initSessionNotifications(ctx);
|
|
@@ -811,6 +818,13 @@ export function registerHooks(
|
|
|
811
818
|
clearDeferredApprovalGate();
|
|
812
819
|
await resetAskUserQuestionsTurnCache();
|
|
813
820
|
clearDiscussionFlowState(basePath);
|
|
821
|
+
// /clear or /new destroys the conversation holding a discuss interview, so
|
|
822
|
+
// its pending discuss→auto handoff can never be answered — clear it. Resume
|
|
823
|
+
// restores the interview transcript, so the entry survives. Auto-mode's own
|
|
824
|
+
// newSession() calls are safe: the handoff consumes the entry on agent_end.
|
|
825
|
+
if (event.reason === "new") {
|
|
826
|
+
clearPendingAutoStart(basePath);
|
|
827
|
+
}
|
|
814
828
|
await syncServiceTierStatus(ctx);
|
|
815
829
|
await applyDisabledModelProviderPolicy(ctx);
|
|
816
830
|
await applyCompactionThresholdOverride(ctx);
|
|
@@ -1419,6 +1433,21 @@ export function registerHooks(
|
|
|
1419
1433
|
clearDeferredApprovalGate(basePath);
|
|
1420
1434
|
}
|
|
1421
1435
|
}
|
|
1436
|
+
|
|
1437
|
+
// Safety harness: record evidence here, not only in tool_call. External
|
|
1438
|
+
// engines (claude-code-cli) pre-execute tools, so the agent loop skips
|
|
1439
|
+
// beforeToolCall/tool_call for them — tool_execution_start is the only
|
|
1440
|
+
// event that fires for every tool call. recordToolCall dedupes by
|
|
1441
|
+
// toolCallId, so native tools (which hit both events) record once.
|
|
1442
|
+
safetyRecordToolCall(event.toolCallId, event.toolName, (event.args ?? {}) as Record<string, unknown>);
|
|
1443
|
+
const execDash = getAutoRuntimeSnapshot();
|
|
1444
|
+
if (execDash.basePath && execDash.currentUnit?.type === "execute-task") {
|
|
1445
|
+
const { milestone: xMid, slice: xSid, task: xTid } = parseUnitId(execDash.currentUnit.id);
|
|
1446
|
+
if (xMid && xSid && xTid) {
|
|
1447
|
+
saveEvidenceToDisk(execDash.basePath, xMid, xSid, xTid);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1422
1451
|
if (!isAutoActive()) return;
|
|
1423
1452
|
markToolStart(event.toolCallId, event.toolName);
|
|
1424
1453
|
});
|
|
@@ -10,6 +10,7 @@ import { getIsolationMode } from "../preferences.js";
|
|
|
10
10
|
import { compileSubagentPermissionContract, type ToolsPolicy } from "../unit-context-manifest.js";
|
|
11
11
|
import { logWarning } from "../workflow-logger.js";
|
|
12
12
|
import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
13
|
+
import { worktreesDirs } from "../worktree-placement.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Regex matching milestone CONTEXT.md file names in both legacy M001
|
|
@@ -244,6 +245,23 @@ export function loadWriteGateSnapshot(basePath: string): WriteGateSnapshot {
|
|
|
244
245
|
}
|
|
245
246
|
}
|
|
246
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Merge the persisted write-gate snapshot into the in-process Map entry.
|
|
250
|
+
* The workflow MCP server runs in a child process and records depth
|
|
251
|
+
* verification there; without this refresh the extension host keeps stale
|
|
252
|
+
* pending-gate memory and `activateDeferredApprovalGate` can re-arm a gate
|
|
253
|
+
* that the subprocess already cleared on disk.
|
|
254
|
+
*/
|
|
255
|
+
export function refreshWriteGateStateFromDisk(basePath: string): void {
|
|
256
|
+
if (!shouldPersistWriteGateSnapshot()) return;
|
|
257
|
+
const snapshot = loadWriteGateSnapshot(basePath);
|
|
258
|
+
const state = getWriteGateState(basePath);
|
|
259
|
+
state.pendingGateId = snapshot.pendingGateId;
|
|
260
|
+
state.activeQueuePhase = snapshot.activeQueuePhase;
|
|
261
|
+
state.verifiedDepthMilestones = new Set(snapshot.verifiedDepthMilestones);
|
|
262
|
+
state.verifiedApprovalGates = new Set(snapshot.verifiedApprovalGates ?? []);
|
|
263
|
+
}
|
|
264
|
+
|
|
247
265
|
export function isDepthVerified(basePath: string = process.cwd()): boolean {
|
|
248
266
|
return getWriteGateState(basePath).verifiedDepthMilestones.size > 0;
|
|
249
267
|
}
|
|
@@ -256,6 +274,7 @@ export function isMilestoneDepthVerified(
|
|
|
256
274
|
basePath: string = process.cwd(),
|
|
257
275
|
): boolean {
|
|
258
276
|
if (!milestoneId) return false;
|
|
277
|
+
refreshWriteGateStateFromDisk(basePath);
|
|
259
278
|
return getWriteGateState(basePath).verifiedDepthMilestones.has(milestoneId);
|
|
260
279
|
}
|
|
261
280
|
|
|
@@ -357,6 +376,7 @@ export function clearPendingGate(basePath: string): void {
|
|
|
357
376
|
* Get the currently pending gate, if any.
|
|
358
377
|
*/
|
|
359
378
|
export function getPendingGate(basePath: string = process.cwd()): string | null {
|
|
379
|
+
refreshWriteGateStateFromDisk(basePath);
|
|
360
380
|
return getWriteGateState(basePath).pendingGateId;
|
|
361
381
|
}
|
|
362
382
|
|
|
@@ -1138,10 +1158,12 @@ export function shouldBlockWorktreeWrite(
|
|
|
1138
1158
|
const realTarget = realpathOrResolve(absTarget);
|
|
1139
1159
|
const realRoot = realpathOrResolve(projectRoot);
|
|
1140
1160
|
const realGsd = realpathOrResolve(join(projectRoot, ".gsd"));
|
|
1141
|
-
const realWorktreesDir = realpathOrResolve(join(projectRoot, ".gsd", "worktrees"));
|
|
1142
1161
|
|
|
1143
|
-
// Allow writes inside
|
|
1144
|
-
|
|
1162
|
+
// Allow writes inside a legitimate worktrees subtree (canonical
|
|
1163
|
+
// .gsd-worktrees/ or legacy .gsd/worktrees/).
|
|
1164
|
+
for (const container of worktreesDirs(projectRoot)) {
|
|
1165
|
+
if (isPathContained(realTarget, realpathOrResolve(container))) return { block: false };
|
|
1166
|
+
}
|
|
1145
1167
|
|
|
1146
1168
|
// Allow writes to .gsd/ planning artifacts, but reject siblings whose name
|
|
1147
1169
|
// starts with "worktrees" (the worktrees-extra prefix trick — case 4).
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
12
|
-
import { join, resolve
|
|
12
|
+
import { join, resolve } from "node:path";
|
|
13
13
|
import { randomUUID } from "node:crypto";
|
|
14
14
|
import { gsdRoot } from "./paths.js";
|
|
15
|
+
import { findWorktreeSegment } from "./worktree-root.js";
|
|
15
16
|
|
|
16
17
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
17
18
|
|
|
@@ -60,20 +61,10 @@ const VALID_CLASSIFICATIONS: readonly string[] = [
|
|
|
60
61
|
*/
|
|
61
62
|
export function resolveCapturesPath(basePath: string): string {
|
|
62
63
|
const resolved = resolve(basePath);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
let idx = resolved.indexOf(worktreeMarker);
|
|
66
|
-
if (idx === -1) {
|
|
67
|
-
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
68
|
-
const symlinkRe = new RegExp(
|
|
69
|
-
`\\${sep}\\.gsd\\${sep}projects\\${sep}[a-f0-9]+\\${sep}worktrees\\${sep}`,
|
|
70
|
-
);
|
|
71
|
-
const match = resolved.match(symlinkRe);
|
|
72
|
-
if (match && match.index !== undefined) idx = match.index;
|
|
73
|
-
}
|
|
74
|
-
if (idx !== -1) {
|
|
64
|
+
const segment = findWorktreeSegment(resolved.replaceAll("\\", "/"));
|
|
65
|
+
if (segment) {
|
|
75
66
|
// basePath is inside a worktree — resolve to project root
|
|
76
|
-
const projectRoot = resolved.slice(0,
|
|
67
|
+
const projectRoot = resolved.slice(0, segment.gsdIdx);
|
|
77
68
|
return join(projectRoot, ".gsd", CAPTURES_FILENAME);
|
|
78
69
|
}
|
|
79
70
|
return join(gsdRoot(basePath), CAPTURES_FILENAME);
|
|
@@ -10,6 +10,7 @@ import { runTurnGitAction, type TurnGitActionMode, type TurnGitActionResult } fr
|
|
|
10
10
|
import { _getAdapter, upsertTurnGitTransaction } from "./gsd-db.js";
|
|
11
11
|
import { probeGitConflictState } from "./git-conflict-state.js";
|
|
12
12
|
import { parseUnitId } from "./unit-id.js";
|
|
13
|
+
import { worktreePathFor } from "./worktree-placement.js";
|
|
13
14
|
|
|
14
15
|
export interface CloseoutFailureRecord {
|
|
15
16
|
traceId: string;
|
|
@@ -156,7 +157,7 @@ export function resolveCloseoutRecoveryBasePath(projectRoot: string, record: Clo
|
|
|
156
157
|
const parsed = parseUnitId(record.unitId);
|
|
157
158
|
const milestoneId = parsed.milestone ?? (/^M\d+(?:-[a-z0-9]{6})?/.exec(record.unitId)?.[0] ?? "");
|
|
158
159
|
if (milestoneId) {
|
|
159
|
-
const worktreePath = existingRealPath(
|
|
160
|
+
const worktreePath = existingRealPath(worktreePathFor(projectRoot, milestoneId));
|
|
160
161
|
if (worktreePath) return worktreePath;
|
|
161
162
|
}
|
|
162
163
|
|