/** * 审计[485]③ / 深挖 G4 — the `EnterWorktree` / `ExitWorktree` tool pair: SESSION-level git-worktree * isolation (CC 2.1.198 parity). EnterWorktree creates a managed worktree under `.sema-worktrees/` * (the same {@link WORKTREE_PARENT} the subagent `isolation:"worktree"` lane uses) and REALLY switches * the task's working directory into it; ExitWorktree returns to the original directory and cleans up. * * CC-parity semantics (198 bundle, verbatim-checked): * ① worktrees live in a managed parent dir inside the repo (CC: `.claude/worktrees/`; sema: * `.sema-worktrees/`) — never scattered; * ② baseRef is two-state: `"head"` = the current local HEAD (unpushed commits present), `"fresh"` = * the default branch tip (CC: `origin/` for a clean tree); * ③ a second CREATING EnterWorktree (`name`) while one is active is REFUSED ("Already in a worktree * session" — CC errorCode 2); switching into an EXISTING worktree via `path` is allowed (深对比残差 * M22 RESOLVED, live-CC anchor 2026-07-09: the previous worktree is left on disk untouched, only the * new one is tracked for exit-time cleanup, and a path-entered tree is NEVER removed by ExitWorktree); * ④ ExitWorktree outside a session is an honest NO-OP (CC errorCode 1), and a worktree with * uncommitted files or commits is NOT deleted by default (CC refuses `remove` without * `discard_changes`; sema keeps it and reports the path — inspect/merge in userland); * ⑤ outside a git repository CC delegates to WorktreeCreate/WorktreeRemove hooks — sema has no such * shell extension point, so the tool says so honestly and refuses (no silent degrade). * * cwd architecture (the load-bearing conclusion): the task's working directory IS mutable state — the * hands band shares one {@link CwdRef} (`handsCwdRef` in prepare-task): Bash runs every command in * `cwdRef.current` (and `cd` moves it), Read/Edit/Write resolve relative paths against it, Monitor * spawns there. EnterWorktree therefore performs a REAL switch by assigning `cwdRef.current`; the * Runner's per-tool cwd read reports it as `workspace_changed`, same as an observed `cd`. Path * containment is untouched: the worktree lives UNDER the task root, so resolveKey's canonical-within * check still holds for every subsequent fs op. * * ⚠️ Containment ruling (codex 终审 1.255 F4, deliberate): entering a worktree is a **cwd switch, NOT a * sandbox**. resolveKey's containment anchor stays the ORIGINAL task root — it is deliberately NOT * re-anchored/tightened to the worktree dir, so `../../` from inside the worktree can still reach the * rest of the repository (which is WITHIN the task root and was reachable before Enter too), while * anything outside the task root stays fenced exactly as before. This matches CC 2.1.198 semantics * (CC worktrees live beside the repo with no containment tightening either); tightening here would buy * no security (Bash/`cd` already roam the root) and would break legitimate repo-relative access. The * tool description + Enter reply state this honestly so the model never assumes isolation. * * Security posture: both tools only ever run `git` against the task root / managed worktree (no * arbitrary command surface), but they create/delete on-disk state ⇒ effect:"write" (plan mode gates * them like every other write tool; read-only hands never mount them — see prepare-task). * * Durability (codex 终审 1.255 F2): the active session lives in a SHARED, ALL-SERIALIZABLE * {@link WorktreeSessionRef} owned by prepare-task (never a closure-only var, and no captured destroy * closure — Exit re-derives removal as a plain `git worktree remove`). A durable suspend stamps it into * `CheckpointState.activeWorktree` (+ the cwd into `handsCwd`); resume re-seeds both, so * Enter→suspend→resume→Exit behaves exactly like an unsuspended session. */ import type { AgentTool, ExecutionEnv } from "../internal/harness.js"; import type { CwdRef } from "./fs/index.js"; /** codex 终审 1.255 F2 — the ACTIVE worktree session, ALL-SERIALIZABLE (plain strings, no closures): * prepare-task owns the ref, stamps `current` into `CheckpointState.activeWorktree` at a durable * suspend, and re-seeds it on resume, so ExitWorktree works across the suspend/resume boundary (the * tree survives on disk; removal is re-derived as a plain `git worktree remove`). */ export interface ActiveWorktreeSession { worktreeDir: string; originalCwd: string; baseSha: string; /** M22 (live-CC anchor): true when the session ENTERED a pre-existing worktree via `path` instead of * creating one — ExitWorktree then never removes it ("ExitWorktree will not remove a worktree entered * this way"). Absent/false = created by EnterWorktree (removable per the normal rules). */ entered?: boolean; } /** The shared mutable holder for {@link ActiveWorktreeSession} — the worktree twin of {@link CwdRef}. */ export interface WorktreeSessionRef { current?: ActiveWorktreeSession; } export interface WorktreeToolsOptions { /** The task root (the repo root the managed worktrees nest under; `git` runs `-C` here). */ repoRoot: string; /** The task's shared mutable working directory (the hands band's `handsCwdRef`) — EnterWorktree * assigns it to the worktree path (a REAL cwd switch: Bash/Read/Edit/Write/Monitor all follow), * ExitWorktree restores it. */ cwdRef: CwdRef; /** codex 终审 1.255 F2: the shared session-state ref (checkpointable — prepare-task serializes it into * the durable checkpoint and re-seeds it on resume). Omitted ⇒ a private per-mount ref (same live * behavior, minus durable-suspend survival — prepare-task always passes one). */ session?: WorktreeSessionRef; } /** * Build the EnterWorktree/ExitWorktree pair. One session-state closure per mount (= per task): the * active worktree handle lives here, never in a model argument. Mounted by prepare-task only when the * task has real write hands over a tracked cwd (`handsCwdRef` exists — same premise family as the * Monitor mount); git-ness is probed honestly at Enter time (same posture as the subagent * worktree-isolation helper — no per-task prepare-time git exec). */ export declare function createWorktreeTools(env: ExecutionEnv, opts: WorktreeToolsOptions): AgentTool[]; //# sourceMappingURL=worktree.d.ts.map