@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
|
@@ -8,25 +8,28 @@
|
|
|
8
8
|
* manages create, enter, detect, and teardown for auto-mode worktrees.
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync, cpSync, readFileSync, readdirSync, mkdirSync, realpathSync, rmSync, unlinkSync, lstatSync as lstatSyncFn, } from "node:fs";
|
|
11
|
-
import { dirname, isAbsolute, join, relative
|
|
11
|
+
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
12
12
|
import { GSDError, GSD_IO_ERROR, GSD_GIT_ERROR } from "./errors.js";
|
|
13
13
|
import { reconcileWorktreeDb, isDbAvailable, getMilestone, getMilestoneSlices, getSliceTasks, } from "./gsd-db.js";
|
|
14
14
|
import { closeWorkflowDatabase, getWorkflowDatabasePath, openWorkflowDatabasePath, } from "./db-workspace.js";
|
|
15
15
|
import { execFileSync } from "node:child_process";
|
|
16
16
|
import { gsdRoot, resolveGsdPathContract } from "./paths.js";
|
|
17
|
-
import { createWorktree, removeWorktree,
|
|
17
|
+
import { createWorktree, removeWorktree, worktreePath, isInsideWorktreesDir, } from "./worktree-manager.js";
|
|
18
18
|
import { detectWorktreeName, nudgeGitBranchCache, } from "./worktree.js";
|
|
19
|
-
import { isGsdWorktreePath, normalizeWorktreePathForCompare, resolveWorktreeProjectRoot, } from "./worktree-root.js";
|
|
19
|
+
import { findWorktreeSegment, isGsdWorktreePath, normalizeWorktreePathForCompare, resolveWorktreeProjectRoot, } from "./worktree-root.js";
|
|
20
20
|
import { autoResolveSafeConflictPaths } from "./git-conflict-resolve.js";
|
|
21
21
|
import { MergeConflictError, readIntegrationBranch, resolveMilestoneIntegrationBranch, RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
|
|
22
22
|
import { buildPullRequestEvidence, createDraftPullRequestFromEvidence, } from "./pull-request-process.js";
|
|
23
23
|
import { debugLog } from "./debug-logger.js";
|
|
24
24
|
import { logWarning, logError } from "./workflow-logger.js";
|
|
25
|
+
import { checkoutBranchWithStashGuard, cleanupConflictState, gsdJsonlFilesWithConflictMarkers, hasConflictMarkers, popStashByRef, removeMergeStateFiles, stashAlreadyExistsFilesFromError, stashRefFromError, } from "./worktree-git-recovery.js";
|
|
26
|
+
// Re-export for existing callers/tests (auto-start.ts, checkout-branch-stash-guard.test.ts).
|
|
27
|
+
export { checkoutBranchWithStashGuard } from "./worktree-git-recovery.js";
|
|
25
28
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
26
29
|
import { MILESTONE_ID_RE } from "./milestone-ids.js";
|
|
27
30
|
import { runWorktreePostCreateHook } from "./worktree-post-create-hook.js";
|
|
28
31
|
import { classifyProject } from "./detection.js";
|
|
29
|
-
import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeWorkingTreeStatus, nativeAddAllWithExclusions, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeMergeRegular, nativeConflictFiles, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchForceReset, nativeBranchExists, nativeDiffNumstat, nativeUpdateRef, nativeIsAncestor,
|
|
32
|
+
import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeWorkingTreeStatus, nativeAddAllWithExclusions, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeMergeRegular, nativeConflictFiles, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchForceReset, nativeBranchExists, nativeDiffNumstat, nativeUpdateRef, nativeIsAncestor, nativeWorktreeList, nativeLsFiles, } from "./native-git-bridge.js";
|
|
30
33
|
import { CLOSEOUT_CONSISTENCY_BLOCKED_REASON, } from "./closeout-consistency-gate.js";
|
|
31
34
|
import { formatCloseoutProofBlock, proveMilestoneCloseout, } from "./milestone-closeout-proof.js";
|
|
32
35
|
import { gsdHome } from "./gsd-home.js";
|
|
@@ -61,111 +64,6 @@ const ROOT_STATE_FILES = [
|
|
|
61
64
|
// Back-sync (worktree → main) must NEVER overwrite the project root's copy
|
|
62
65
|
// because the project root is authoritative for preferences (#2684).
|
|
63
66
|
];
|
|
64
|
-
/**
|
|
65
|
-
* Pop a stash entry by tracking the unique marker embedded in its message so
|
|
66
|
-
* concurrent stash operations against the same project root cannot cause us to
|
|
67
|
-
* pop the wrong entry.
|
|
68
|
-
*
|
|
69
|
-
* If `stashMarker` is null or no longer present in the stash list (e.g. a
|
|
70
|
-
* concurrent process popped/dropped it), leaves the stash list untouched and
|
|
71
|
-
* returns null.
|
|
72
|
-
*
|
|
73
|
-
* Throws on pop failure so callers can handle conflict cases the same way
|
|
74
|
-
* they would with the prior `git stash pop` form. When throwing after a
|
|
75
|
-
* targeted pop attempt, the error is annotated with the targeted stash ref.
|
|
76
|
-
*
|
|
77
|
-
* (Issue #4980 HIGH-6)
|
|
78
|
-
*/
|
|
79
|
-
function popStashByRef(basePath, stashMarker) {
|
|
80
|
-
let popArg = null;
|
|
81
|
-
if (stashMarker) {
|
|
82
|
-
try {
|
|
83
|
-
const list = execFileSync("git", ["stash", "list", "--format=%gd%x00%s"], {
|
|
84
|
-
cwd: basePath,
|
|
85
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
86
|
-
encoding: "utf-8",
|
|
87
|
-
}).trim().split("\n").filter(Boolean);
|
|
88
|
-
for (const entry of list) {
|
|
89
|
-
const [ref, subject] = entry.split("\0");
|
|
90
|
-
if (ref && subject?.includes(stashMarker)) {
|
|
91
|
-
popArg = ref;
|
|
92
|
-
break;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
catch (err) {
|
|
97
|
-
logWarning("worktree", `stash list lookup failed; leaving stash untouched: ${err instanceof Error ? err.message : String(err)}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
if (!popArg) {
|
|
101
|
-
logWarning("worktree", "recorded stash entry could not be resolved; skipping automatic pop");
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
try {
|
|
105
|
-
execFileSync("git", ["stash", "pop", popArg], {
|
|
106
|
-
cwd: basePath,
|
|
107
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
108
|
-
encoding: "utf-8",
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
catch (err) {
|
|
112
|
-
if (err && typeof err === "object") {
|
|
113
|
-
err.stashRef = popArg;
|
|
114
|
-
}
|
|
115
|
-
throw err;
|
|
116
|
-
}
|
|
117
|
-
return popArg;
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Extract a stash ref annotation injected by popStashByRef() when git stash
|
|
121
|
-
* pop fails and we need to conditionally drop the exact stash entry later.
|
|
122
|
-
*/
|
|
123
|
-
function stashRefFromError(err) {
|
|
124
|
-
if (!err || typeof err !== "object")
|
|
125
|
-
return null;
|
|
126
|
-
const stashRef = err.stashRef;
|
|
127
|
-
return typeof stashRef === "string" && stashRef.length > 0 ? stashRef : null;
|
|
128
|
-
}
|
|
129
|
-
function stashAlreadyExistsFilesFromError(err) {
|
|
130
|
-
if (!err || typeof err !== "object")
|
|
131
|
-
return [];
|
|
132
|
-
const stderr = err.stderr;
|
|
133
|
-
const stderrText = typeof stderr === "string"
|
|
134
|
-
? stderr
|
|
135
|
-
: stderr instanceof Uint8Array
|
|
136
|
-
? Buffer.from(stderr).toString("utf-8")
|
|
137
|
-
: "";
|
|
138
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
139
|
-
const text = `${stderrText}\n${message}`;
|
|
140
|
-
const files = new Set();
|
|
141
|
-
for (const line of text.split("\n")) {
|
|
142
|
-
const m = line.match(/^(.*?)\s+already exists, no checkout\s*$/i);
|
|
143
|
-
if (!m)
|
|
144
|
-
continue;
|
|
145
|
-
const filePath = m[1]?.trim();
|
|
146
|
-
if (filePath)
|
|
147
|
-
files.add(filePath);
|
|
148
|
-
}
|
|
149
|
-
return [...files];
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Detect whether an on-disk file still contains unresolved merge conflict
|
|
153
|
-
* markers from a failed stash-pop or merge attempt.
|
|
154
|
-
*
|
|
155
|
-
* Returns false when the file cannot be read.
|
|
156
|
-
*/
|
|
157
|
-
function hasConflictMarkers(filePath) {
|
|
158
|
-
try {
|
|
159
|
-
const content = readFileSync(filePath, "utf-8");
|
|
160
|
-
return content.includes("<<<<<<<") && content.includes("=======") && content.includes(">>>>>>>");
|
|
161
|
-
}
|
|
162
|
-
catch {
|
|
163
|
-
return false;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
function gsdJsonlFilesWithConflictMarkers(basePath) {
|
|
167
|
-
return nativeLsFiles(basePath, ".gsd/*.jsonl").filter((f) => hasConflictMarkers(join(basePath, f)));
|
|
168
|
-
}
|
|
169
67
|
/**
|
|
170
68
|
* Check if two filesystem paths resolve to the same real location.
|
|
171
69
|
* Returns false if either path cannot be resolved (e.g. doesn't exist).
|
|
@@ -421,51 +319,6 @@ export const SAFE_AUTO_RESOLVE_PATTERNS = [
|
|
|
421
319
|
* Covers `.gsd/` state files and common build artifacts. */
|
|
422
320
|
export const isSafeToAutoResolve = (filePath) => filePath.startsWith(".gsd/") ||
|
|
423
321
|
SAFE_AUTO_RESOLVE_PATTERNS.some((re) => re.test(filePath));
|
|
424
|
-
function removeMergeStateFiles(basePath, contextLabel) {
|
|
425
|
-
try {
|
|
426
|
-
for (const f of ["SQUASH_MSG", "MERGE_MSG", "MERGE_MODE", "MERGE_HEAD", "AUTO_MERGE"]) {
|
|
427
|
-
const rawPath = execFileSync("git", ["rev-parse", "--git-path", f], {
|
|
428
|
-
cwd: basePath,
|
|
429
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
430
|
-
encoding: "utf-8",
|
|
431
|
-
}).trim();
|
|
432
|
-
const p = rawPath.length > 0
|
|
433
|
-
? (isAbsolute(rawPath) ? rawPath : resolve(basePath, rawPath))
|
|
434
|
-
: join(resolveGitDir(basePath), f);
|
|
435
|
-
if (existsSync(p))
|
|
436
|
-
unlinkSync(p);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
catch (err) {
|
|
440
|
-
logError("worktree", `${contextLabel} merge state cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
function cleanupConflictState(basePath) {
|
|
444
|
-
// Merge conflicts can leave unmerged index entries; merge-abort alone is not
|
|
445
|
-
// enough for squash merges (MERGE_HEAD is never written). Reset the merge
|
|
446
|
-
// index, then remove merge message files that native/libgit2 paths may have
|
|
447
|
-
// created.
|
|
448
|
-
try {
|
|
449
|
-
nativeMergeAbort(basePath);
|
|
450
|
-
}
|
|
451
|
-
catch (err) {
|
|
452
|
-
// MERGE_HEAD absent (squash merge path) — abort is a no-op, which is fine.
|
|
453
|
-
debugLog("conflict-cleanup:merge-abort-skipped", {
|
|
454
|
-
error: err instanceof Error ? err.message : String(err),
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
try {
|
|
458
|
-
execFileSync("git", ["reset", "--merge"], {
|
|
459
|
-
cwd: basePath,
|
|
460
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
461
|
-
encoding: "utf-8",
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
catch (err) {
|
|
465
|
-
logError("worktree", `git reset --merge failed after merge conflict: ${err instanceof Error ? err.message : String(err)}`);
|
|
466
|
-
}
|
|
467
|
-
removeMergeStateFiles(basePath, "conflict");
|
|
468
|
-
}
|
|
469
322
|
// ─── Dispatch-Level Sync (project root ↔ worktree) ──────────────────────────
|
|
470
323
|
/**
|
|
471
324
|
* Sync milestone artifacts from project root INTO worktree before deriveState.
|
|
@@ -539,19 +392,12 @@ export function checkResourcesStale(versionOnStart) {
|
|
|
539
392
|
* Returns the corrected base path.
|
|
540
393
|
*/
|
|
541
394
|
export function escapeStaleWorktree(base) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const match = base.match(symlinkRe);
|
|
549
|
-
if (!match || match.index === undefined)
|
|
550
|
-
return base;
|
|
551
|
-
idx = match.index;
|
|
552
|
-
}
|
|
553
|
-
// base is inside .gsd/worktrees/<something> — extract the project root
|
|
554
|
-
const projectRoot = base.slice(0, idx);
|
|
395
|
+
const segment = findWorktreeSegment(base.replaceAll("\\", "/"));
|
|
396
|
+
if (!segment)
|
|
397
|
+
return base;
|
|
398
|
+
// base is inside .gsd/worktrees/<something> — extract the project root.
|
|
399
|
+
// Normalization is 1:1 on characters, so the segment index is valid in `base`.
|
|
400
|
+
const projectRoot = base.slice(0, segment.gsdIdx);
|
|
555
401
|
// Guard: If the candidate project root's .gsd IS the user-level ~/.gsd,
|
|
556
402
|
// the string-slice heuristic matched the wrong /.gsd/ boundary. This happens
|
|
557
403
|
// when .gsd is a symlink into ~/.gsd/projects/<hash> and process.cwd()
|
|
@@ -908,109 +754,6 @@ export function enterBranchModeForMilestone(basePath, milestoneId) {
|
|
|
908
754
|
}
|
|
909
755
|
checkoutBranchWithStashGuard(basePath, branch, `enter-branch-mode:${milestoneId}`);
|
|
910
756
|
}
|
|
911
|
-
export function checkoutBranchWithStashGuard(basePath, branch, reason) {
|
|
912
|
-
let stashMarker = null;
|
|
913
|
-
let stashed = false;
|
|
914
|
-
const status = nativeWorkingTreeStatus(basePath).trim();
|
|
915
|
-
if (status.length > 0) {
|
|
916
|
-
stashMarker = `gsd-checkout-stash:${reason}:${process.pid}:${Date.now()}:${process.hrtime.bigint().toString(36)}`;
|
|
917
|
-
const stashListBefore = execFileSync("git", ["stash", "list"], {
|
|
918
|
-
cwd: basePath,
|
|
919
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
920
|
-
encoding: "utf-8",
|
|
921
|
-
});
|
|
922
|
-
execFileSync("git", ["stash", "push", "--include-untracked", "-m", `gsd: checkout stash [${stashMarker}]`], {
|
|
923
|
-
cwd: basePath,
|
|
924
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
925
|
-
encoding: "utf-8",
|
|
926
|
-
});
|
|
927
|
-
const stashListAfter = execFileSync("git", ["stash", "list"], {
|
|
928
|
-
cwd: basePath,
|
|
929
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
930
|
-
encoding: "utf-8",
|
|
931
|
-
});
|
|
932
|
-
stashed = stashListAfter !== stashListBefore;
|
|
933
|
-
}
|
|
934
|
-
// Checkout and stash-restore are split so we can distinguish two failure
|
|
935
|
-
// modes: (a) checkout failed → HEAD did not move, restore stash and rethrow;
|
|
936
|
-
// (b) checkout succeeded but stash pop failed → HEAD moved to `branch` but
|
|
937
|
-
// the working-tree changes remain in the stash list. We surface a distinct
|
|
938
|
-
// error in case (b) so callers don't assume the branch switch was rolled back.
|
|
939
|
-
try {
|
|
940
|
-
nativeCheckoutBranch(basePath, branch);
|
|
941
|
-
}
|
|
942
|
-
catch (checkoutErr) {
|
|
943
|
-
if (stashed) {
|
|
944
|
-
try {
|
|
945
|
-
popStashByRef(basePath, stashMarker);
|
|
946
|
-
}
|
|
947
|
-
catch (restoreErr) {
|
|
948
|
-
logWarning("worktree", `git stash pop failed during checkout restore: ${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)}`);
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
throw checkoutErr;
|
|
952
|
-
}
|
|
953
|
-
if (stashed) {
|
|
954
|
-
try {
|
|
955
|
-
popStashByRef(basePath, stashMarker);
|
|
956
|
-
}
|
|
957
|
-
catch (popErr) {
|
|
958
|
-
const msg = popErr instanceof Error ? popErr.message : String(popErr);
|
|
959
|
-
const stderr = popErr && typeof popErr === "object"
|
|
960
|
-
? popErr.stderr
|
|
961
|
-
: undefined;
|
|
962
|
-
const stderrText = typeof stderr === "string"
|
|
963
|
-
? stderr
|
|
964
|
-
: stderr instanceof Uint8Array
|
|
965
|
-
? Buffer.from(stderr).toString("utf-8")
|
|
966
|
-
: "";
|
|
967
|
-
const stashPopMessage = `${stderrText}\n${msg}`.trim();
|
|
968
|
-
const alreadyExists = stashAlreadyExistsFilesFromError(popErr);
|
|
969
|
-
const gsdAlreadyExists = alreadyExists.filter((f) => f.startsWith(".gsd/"));
|
|
970
|
-
const nonGsdAlreadyExists = alreadyExists.filter((f) => !f.startsWith(".gsd/"));
|
|
971
|
-
const isUntrackedRestoreFailure = stashPopMessage.includes("could not restore untracked files from stash");
|
|
972
|
-
const stashRefForDrop = stashRefFromError(popErr);
|
|
973
|
-
const nonGsdUnmerged = nativeConflictFiles(basePath).filter((f) => !f.startsWith(".gsd/"));
|
|
974
|
-
const gsdContentConflicts = isUntrackedRestoreFailure
|
|
975
|
-
? gsdJsonlFilesWithConflictMarkers(basePath)
|
|
976
|
-
: [];
|
|
977
|
-
const gsdConflictFiles = [...new Set([...gsdAlreadyExists, ...gsdContentConflicts])];
|
|
978
|
-
if (isUntrackedRestoreFailure &&
|
|
979
|
-
gsdConflictFiles.length > 0 &&
|
|
980
|
-
nonGsdAlreadyExists.length === 0 &&
|
|
981
|
-
nonGsdUnmerged.length === 0) {
|
|
982
|
-
for (const f of gsdConflictFiles) {
|
|
983
|
-
execFileSync("git", ["checkout", "HEAD", "--", f], {
|
|
984
|
-
cwd: basePath,
|
|
985
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
986
|
-
encoding: "utf-8",
|
|
987
|
-
});
|
|
988
|
-
nativeAddPaths(basePath, [f]);
|
|
989
|
-
}
|
|
990
|
-
if (stashRefForDrop) {
|
|
991
|
-
try {
|
|
992
|
-
execFileSync("git", ["stash", "drop", stashRefForDrop], {
|
|
993
|
-
cwd: basePath,
|
|
994
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
995
|
-
encoding: "utf-8",
|
|
996
|
-
});
|
|
997
|
-
}
|
|
998
|
-
catch (err) { /* stash may already be consumed */
|
|
999
|
-
logWarning("worktree", `git stash drop failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
else {
|
|
1003
|
-
logWarning("worktree", "recorded stash entry could not be resolved; skipping automatic drop");
|
|
1004
|
-
}
|
|
1005
|
-
return;
|
|
1006
|
-
}
|
|
1007
|
-
const wrapped = new Error(`checkout to '${branch}' succeeded but stash restore failed; working tree changes remain in the stash list. Original error: ${msg}`);
|
|
1008
|
-
if (stashRefForDrop)
|
|
1009
|
-
wrapped.stashRef = stashRefForDrop;
|
|
1010
|
-
throw wrapped;
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
757
|
// ─── Public API ────────────────────────────────────────────────────────────
|
|
1015
758
|
/**
|
|
1016
759
|
* Create a new auto-worktree for a milestone, chdir into it, and store
|
|
@@ -25,7 +25,7 @@ import { clearActivityLogState } from "./activity-log.js";
|
|
|
25
25
|
import { synthesizeCrashRecovery, getDeepDiagnostic, readActiveMilestoneId, } from "./session-forensics.js";
|
|
26
26
|
import { writeLock, clearLock, clearStaleWorkerLock, readCrashLock, isLockProcessAlive, formatCrashInfo, emitCrashRecoveredUnitEnd, emitOpenUnitEndForUnit, } from "./crash-recovery.js";
|
|
27
27
|
import { acquireSessionLock, getSessionLockStatus, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
|
|
28
|
-
import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences, getIsolationMode, } from "./preferences.js";
|
|
28
|
+
import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences, getIsolationMode, resolveEffectiveUnitIsolationMode, } from "./preferences.js";
|
|
29
29
|
import { playNotificationBell, sendDesktopNotification } from "./notifications.js";
|
|
30
30
|
import { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction, resolveCompactionThresholdPercent, shouldRerootStepSessionForContext, } from "./auto-budget.js";
|
|
31
31
|
import { markToolStart as _markToolStart, markToolEnd as _markToolEnd, getOldestInFlightToolAgeMs as _getOldestInFlightToolAgeMs, clearInFlightTools, isToolInvocationError, isQueuedUserMessageSkip, isDeterministicPolicyError, } from "./auto-tool-tracking.js";
|
|
@@ -346,14 +346,11 @@ export function startAutoDetached(ctx, pi, base, verboseMode, options) {
|
|
|
346
346
|
export function shouldUseWorktreeIsolation(basePath) {
|
|
347
347
|
return getIsolationMode(basePath) === "worktree";
|
|
348
348
|
}
|
|
349
|
-
function
|
|
350
|
-
return configuredMode
|
|
351
|
-
}
|
|
352
|
-
export function _resolveEffectiveUnitIsolationModeForTest(configuredMode, isolationDegraded) {
|
|
353
|
-
return resolveEffectiveUnitIsolationMode(configuredMode, isolationDegraded);
|
|
349
|
+
export function _resolveEffectiveUnitIsolationModeForTest(configuredMode, isolationDegraded, strandedRecoveryIsolationMode = null) {
|
|
350
|
+
return resolveEffectiveUnitIsolationMode(configuredMode, isolationDegraded, strandedRecoveryIsolationMode);
|
|
354
351
|
}
|
|
355
352
|
function getEffectiveUnitIsolationMode(basePath) {
|
|
356
|
-
return resolveEffectiveUnitIsolationMode(getIsolationMode(basePath), s.isolationDegraded);
|
|
353
|
+
return resolveEffectiveUnitIsolationMode(getIsolationMode(basePath), s.isolationDegraded, s.strandedRecoveryIsolationMode);
|
|
357
354
|
}
|
|
358
355
|
/** Crash recovery prompt — set by startAuto, consumed by the main loop */
|
|
359
356
|
/** Pending verification retry — set when gate fails with retries remaining, consumed by autoLoop */
|
|
@@ -9,6 +9,7 @@ import { logWarning } from "../workflow-logger.js";
|
|
|
9
9
|
import { openWorkflowDatabase } from "../db-workspace.js";
|
|
10
10
|
import { getAutoWorktreePath } from "../auto-worktree.js";
|
|
11
11
|
import { resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
12
|
+
import { worktreesDirs } from "../worktree-placement.js";
|
|
12
13
|
export function safeWorkspaceCwd() {
|
|
13
14
|
try {
|
|
14
15
|
return process.cwd();
|
|
@@ -42,19 +43,21 @@ export function resolveWorkflowToolBasePath(ctx, scope) {
|
|
|
42
43
|
return worktree;
|
|
43
44
|
}
|
|
44
45
|
else {
|
|
45
|
-
const
|
|
46
|
-
|
|
46
|
+
const live = [];
|
|
47
|
+
for (const worktreesDir of worktreesDirs(projectRoot)) {
|
|
48
|
+
if (!existsSync(worktreesDir))
|
|
49
|
+
continue;
|
|
47
50
|
try {
|
|
48
|
-
|
|
51
|
+
live.push(...readdirSync(worktreesDir)
|
|
49
52
|
.map((name) => join(worktreesDir, name))
|
|
50
|
-
.filter((p) => existsSync(join(p, ".git")));
|
|
51
|
-
if (live.length === 1)
|
|
52
|
-
return live[0];
|
|
53
|
+
.filter((p) => existsSync(join(p, ".git"))));
|
|
53
54
|
}
|
|
54
55
|
catch (err) {
|
|
55
56
|
logWarning("bootstrap", `resolveWorkflowToolBasePath: failed to scan worktrees: ${err instanceof Error ? err.message : String(err)}`);
|
|
56
57
|
}
|
|
57
58
|
}
|
|
59
|
+
if (live.length === 1)
|
|
60
|
+
return live[0];
|
|
58
61
|
}
|
|
59
62
|
return cwd;
|
|
60
63
|
}
|
|
@@ -7,7 +7,7 @@ import { isToolCallEventType } from "@gsd/pi-coding-agent";
|
|
|
7
7
|
import { ALWAYS_PRESERVED_SHIM_TOOL_NAMES } from "@gsd/pi-ai";
|
|
8
8
|
import { updateSnapshot } from "../ecosystem/gsd-extension-api.js";
|
|
9
9
|
import { buildMilestoneFileName, clearPathCache, milestonesDir, resolveMilestonePath, resolveSliceFile, resolveSlicePath } from "../paths.js";
|
|
10
|
-
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";
|
|
10
|
+
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";
|
|
11
11
|
import { resolveManifest } from "../unit-context-manifest.js";
|
|
12
12
|
import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
|
|
13
13
|
import { loadFile, saveFile, formatContinue } from "../files.js";
|
|
@@ -35,6 +35,7 @@ import { filterToolsForProvider } from "../model-router.js";
|
|
|
35
35
|
import { mcpToolMatchesBaseName } from "../mcp-tool-name.js";
|
|
36
36
|
import { RUN_UAT_READ_ONLY_TOOL_NAMES, RUN_UAT_WORKFLOW_TOOL_NAMES } from "../tool-presentation-plan.js";
|
|
37
37
|
import { supportsSourceObservationsForUnit } from "../source-observations.js";
|
|
38
|
+
import { clearPendingAutoStart } from "../pending-auto-start.js";
|
|
38
39
|
let approvalQuestionAbortInFlight = false;
|
|
39
40
|
async function loadWelcomeScreenModule() {
|
|
40
41
|
const candidates = [];
|
|
@@ -444,8 +445,16 @@ function isShellExecutionTool(canonicalName) {
|
|
|
444
445
|
function activateDeferredApprovalGate(basePath) {
|
|
445
446
|
if (deferredApprovalGate?.basePath !== basePath)
|
|
446
447
|
return;
|
|
447
|
-
|
|
448
|
+
const gateId = deferredApprovalGate.gateId;
|
|
448
449
|
deferredApprovalGate = null;
|
|
450
|
+
refreshWriteGateStateFromDisk(basePath);
|
|
451
|
+
const snapshot = loadWriteGateSnapshot(basePath);
|
|
452
|
+
const milestoneId = extractDepthVerificationMilestoneId(gateId);
|
|
453
|
+
if (isApprovalGateVerifiedInSnapshot(snapshot, gateId))
|
|
454
|
+
return;
|
|
455
|
+
if (milestoneId && isMilestoneDepthVerifiedInSnapshot(snapshot, milestoneId))
|
|
456
|
+
return;
|
|
457
|
+
setPendingGate(gateId, basePath);
|
|
449
458
|
}
|
|
450
459
|
function extractGateQuestionId(input) {
|
|
451
460
|
const questions = input?.questions ?? [];
|
|
@@ -626,7 +635,7 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
626
635
|
catch { /* non-fatal */ }
|
|
627
636
|
}
|
|
628
637
|
});
|
|
629
|
-
pi.on("session_switch", async (
|
|
638
|
+
pi.on("session_switch", async (event, ctx) => {
|
|
630
639
|
const basePath = contextBasePath(ctx);
|
|
631
640
|
const preserveCloseoutSurface = isAutoCompletionStopInProgress();
|
|
632
641
|
initSessionNotifications(ctx);
|
|
@@ -635,6 +644,13 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
635
644
|
clearDeferredApprovalGate();
|
|
636
645
|
await resetAskUserQuestionsTurnCache();
|
|
637
646
|
clearDiscussionFlowState(basePath);
|
|
647
|
+
// /clear or /new destroys the conversation holding a discuss interview, so
|
|
648
|
+
// its pending discuss→auto handoff can never be answered — clear it. Resume
|
|
649
|
+
// restores the interview transcript, so the entry survives. Auto-mode's own
|
|
650
|
+
// newSession() calls are safe: the handoff consumes the entry on agent_end.
|
|
651
|
+
if (event.reason === "new") {
|
|
652
|
+
clearPendingAutoStart(basePath);
|
|
653
|
+
}
|
|
638
654
|
await syncServiceTierStatus(ctx);
|
|
639
655
|
await applyDisabledModelProviderPolicy(ctx);
|
|
640
656
|
await applyCompactionThresholdOverride(ctx);
|
|
@@ -1150,6 +1166,19 @@ export function registerHooks(pi, ecosystemHandlers) {
|
|
|
1150
1166
|
clearDeferredApprovalGate(basePath);
|
|
1151
1167
|
}
|
|
1152
1168
|
}
|
|
1169
|
+
// Safety harness: record evidence here, not only in tool_call. External
|
|
1170
|
+
// engines (claude-code-cli) pre-execute tools, so the agent loop skips
|
|
1171
|
+
// beforeToolCall/tool_call for them — tool_execution_start is the only
|
|
1172
|
+
// event that fires for every tool call. recordToolCall dedupes by
|
|
1173
|
+
// toolCallId, so native tools (which hit both events) record once.
|
|
1174
|
+
safetyRecordToolCall(event.toolCallId, event.toolName, (event.args ?? {}));
|
|
1175
|
+
const execDash = getAutoRuntimeSnapshot();
|
|
1176
|
+
if (execDash.basePath && execDash.currentUnit?.type === "execute-task") {
|
|
1177
|
+
const { milestone: xMid, slice: xSid, task: xTid } = parseUnitId(execDash.currentUnit.id);
|
|
1178
|
+
if (xMid && xSid && xTid) {
|
|
1179
|
+
saveEvidenceToDisk(execDash.basePath, xMid, xSid, xTid);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1153
1182
|
if (!isAutoActive())
|
|
1154
1183
|
return;
|
|
1155
1184
|
markToolStart(event.toolCallId, event.toolName);
|
|
@@ -8,6 +8,7 @@ import { getIsolationMode } from "../preferences.js";
|
|
|
8
8
|
import { compileSubagentPermissionContract } from "../unit-context-manifest.js";
|
|
9
9
|
import { logWarning } from "../workflow-logger.js";
|
|
10
10
|
import { isGsdWorktreePath, resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
11
|
+
import { worktreesDirs } from "../worktree-placement.js";
|
|
11
12
|
/**
|
|
12
13
|
* Regex matching milestone CONTEXT.md file names in both legacy M001
|
|
13
14
|
* and unique M001-abc123 formats. Exported so regex-hardening tests
|
|
@@ -214,6 +215,23 @@ export function loadWriteGateSnapshot(basePath) {
|
|
|
214
215
|
return currentWriteGateSnapshot(basePath);
|
|
215
216
|
}
|
|
216
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Merge the persisted write-gate snapshot into the in-process Map entry.
|
|
220
|
+
* The workflow MCP server runs in a child process and records depth
|
|
221
|
+
* verification there; without this refresh the extension host keeps stale
|
|
222
|
+
* pending-gate memory and `activateDeferredApprovalGate` can re-arm a gate
|
|
223
|
+
* that the subprocess already cleared on disk.
|
|
224
|
+
*/
|
|
225
|
+
export function refreshWriteGateStateFromDisk(basePath) {
|
|
226
|
+
if (!shouldPersistWriteGateSnapshot())
|
|
227
|
+
return;
|
|
228
|
+
const snapshot = loadWriteGateSnapshot(basePath);
|
|
229
|
+
const state = getWriteGateState(basePath);
|
|
230
|
+
state.pendingGateId = snapshot.pendingGateId;
|
|
231
|
+
state.activeQueuePhase = snapshot.activeQueuePhase;
|
|
232
|
+
state.verifiedDepthMilestones = new Set(snapshot.verifiedDepthMilestones);
|
|
233
|
+
state.verifiedApprovalGates = new Set(snapshot.verifiedApprovalGates ?? []);
|
|
234
|
+
}
|
|
217
235
|
export function isDepthVerified(basePath = process.cwd()) {
|
|
218
236
|
return getWriteGateState(basePath).verifiedDepthMilestones.size > 0;
|
|
219
237
|
}
|
|
@@ -223,6 +241,7 @@ export function isDepthVerified(basePath = process.cwd()) {
|
|
|
223
241
|
export function isMilestoneDepthVerified(milestoneId, basePath = process.cwd()) {
|
|
224
242
|
if (!milestoneId)
|
|
225
243
|
return false;
|
|
244
|
+
refreshWriteGateStateFromDisk(basePath);
|
|
226
245
|
return getWriteGateState(basePath).verifiedDepthMilestones.has(milestoneId);
|
|
227
246
|
}
|
|
228
247
|
export function isMilestoneDepthVerifiedInSnapshot(snapshot, milestoneId) {
|
|
@@ -309,6 +328,7 @@ export function clearPendingGate(basePath) {
|
|
|
309
328
|
* Get the currently pending gate, if any.
|
|
310
329
|
*/
|
|
311
330
|
export function getPendingGate(basePath = process.cwd()) {
|
|
331
|
+
refreshWriteGateStateFromDisk(basePath);
|
|
312
332
|
return getWriteGateState(basePath).pendingGateId;
|
|
313
333
|
}
|
|
314
334
|
/**
|
|
@@ -919,10 +939,12 @@ export function shouldBlockWorktreeWrite(toolName, targetPath, effectiveBasePath
|
|
|
919
939
|
const realTarget = realpathOrResolve(absTarget);
|
|
920
940
|
const realRoot = realpathOrResolve(projectRoot);
|
|
921
941
|
const realGsd = realpathOrResolve(join(projectRoot, ".gsd"));
|
|
922
|
-
|
|
923
|
-
//
|
|
924
|
-
|
|
925
|
-
|
|
942
|
+
// Allow writes inside a legitimate worktrees subtree (canonical
|
|
943
|
+
// .gsd-worktrees/ or legacy .gsd/worktrees/).
|
|
944
|
+
for (const container of worktreesDirs(projectRoot)) {
|
|
945
|
+
if (isPathContained(realTarget, realpathOrResolve(container)))
|
|
946
|
+
return { block: false };
|
|
947
|
+
}
|
|
926
948
|
// Allow writes to .gsd/ planning artifacts, but reject siblings whose name
|
|
927
949
|
// starts with "worktrees" (the worktrees-extra prefix trick — case 4).
|
|
928
950
|
if (isPathContained(realTarget, realGsd)) {
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
* `.gsd/CAPTURES.md`, not the worktree's local `.gsd/`.
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
11
|
-
import { join, resolve
|
|
11
|
+
import { join, resolve } from "node:path";
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
13
|
import { gsdRoot } from "./paths.js";
|
|
14
|
+
import { findWorktreeSegment } from "./worktree-root.js";
|
|
14
15
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
15
16
|
const CAPTURES_FILENAME = "CAPTURES.md";
|
|
16
17
|
const VALID_CLASSIFICATIONS = [
|
|
@@ -30,19 +31,10 @@ const VALID_CLASSIFICATIONS = [
|
|
|
30
31
|
*/
|
|
31
32
|
export function resolveCapturesPath(basePath) {
|
|
32
33
|
const resolved = resolve(basePath);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
let idx = resolved.indexOf(worktreeMarker);
|
|
36
|
-
if (idx === -1) {
|
|
37
|
-
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
38
|
-
const symlinkRe = new RegExp(`\\${sep}\\.gsd\\${sep}projects\\${sep}[a-f0-9]+\\${sep}worktrees\\${sep}`);
|
|
39
|
-
const match = resolved.match(symlinkRe);
|
|
40
|
-
if (match && match.index !== undefined)
|
|
41
|
-
idx = match.index;
|
|
42
|
-
}
|
|
43
|
-
if (idx !== -1) {
|
|
34
|
+
const segment = findWorktreeSegment(resolved.replaceAll("\\", "/"));
|
|
35
|
+
if (segment) {
|
|
44
36
|
// basePath is inside a worktree — resolve to project root
|
|
45
|
-
const projectRoot = resolved.slice(0,
|
|
37
|
+
const projectRoot = resolved.slice(0, segment.gsdIdx);
|
|
46
38
|
return join(projectRoot, ".gsd", CAPTURES_FILENAME);
|
|
47
39
|
}
|
|
48
40
|
return join(gsdRoot(basePath), CAPTURES_FILENAME);
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
// File Purpose: Closeout git failure discovery, retry, and manual resolution helpers.
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
4
|
import { existsSync, realpathSync } from "node:fs";
|
|
5
|
-
import { isAbsolute,
|
|
5
|
+
import { isAbsolute, resolve } from "node:path";
|
|
6
6
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
7
7
|
import { runTurnGitAction } from "./git-service.js";
|
|
8
8
|
import { _getAdapter, upsertTurnGitTransaction } from "./gsd-db.js";
|
|
9
9
|
import { probeGitConflictState } from "./git-conflict-state.js";
|
|
10
10
|
import { parseUnitId } from "./unit-id.js";
|
|
11
|
+
import { worktreePathFor } from "./worktree-placement.js";
|
|
11
12
|
function parseMetadata(value) {
|
|
12
13
|
if (typeof value !== "string" || !value.trim())
|
|
13
14
|
return {};
|
|
@@ -121,7 +122,7 @@ export function resolveCloseoutRecoveryBasePath(projectRoot, record) {
|
|
|
121
122
|
const parsed = parseUnitId(record.unitId);
|
|
122
123
|
const milestoneId = parsed.milestone ?? (/^M\d+(?:-[a-z0-9]{6})?/.exec(record.unitId)?.[0] ?? "");
|
|
123
124
|
if (milestoneId) {
|
|
124
|
-
const worktreePath = existingRealPath(
|
|
125
|
+
const worktreePath = existingRealPath(worktreePathFor(projectRoot, milestoneId));
|
|
125
126
|
if (worktreePath)
|
|
126
127
|
return worktreePath;
|
|
127
128
|
}
|