mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
feat(worktrees): create, list and remove git worktrees of a local case
Sessions in one case share its folder, so several agents on one repository edit, build and commit in the same checkout and step on each other. A case whose folder is a git repository can now keep worktrees under <repo>/.claude/worktrees/<name> on a new branch (default worktree-<name>), which is Claude Code's own --worktree layout (measured in Claude Code 2.1.296) and the one Codeman's worktree pill already recognizes. New module src/git-worktrees.ts (pure name/branch gates, worktree list parsing, exclude text; IO through an injectable runner) and routes GET/POST /api/cases/:name/worktrees and DELETE /api/cases/:name/worktrees/:worktree: - git runs as argv with -- before paths, non-interactive env, process group timeouts, --no-optional-locks on reads; names are gated before git and confirmed with git check-ref-format --branch. - Only <repo>/.claude/worktrees/<valid-name> is created or removed; every path component is lstat'ed and realpath-checked, so a symlinked .claude cannot redirect a create or remove outside the repository. - /.claude/worktrees/ (and the per-workspace hooks file Codeman writes, which otherwise blocks git worktree remove) go to the repository's local info/exclude, never a tracked .gitignore. - Removal is git worktree remove WITHOUT --force, refused while a live session runs inside; branch delete is git branch -d, never -D. - Docker and remote cases are refused with a 400, and git never runs for a case at or inside a Docker case workspace. Starting a session reuses the existing create paths: quick-start takes a worktree name (validated, local cases only) and runs hooks, the skill, the model override and the session itself in the worktree; POST /api/sessions takes the worktree as workingDir. The unified session list names the worktree of a live session from its folder layout, so a codex or shell session in one gets the worktree pill too. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a8a8bb71a3
commit
517e12fe1d
10 files changed
+1963
-9
No files matched your search
@@ -167,6 +167,18 @@ function overwrite<K extends keyof UnifiedSessionItem>(
|
||||
if (value !== undefined) item[key] = value;
|
||||
}
|
||||
|
||||
/** `<repo>/.claude/worktrees/<name>`: Claude Code's `--worktree` layout, which Codeman's own worktrees use too. */
|
||||
const WORKTREE_PATH = /^(.+)\/\.claude\/worktrees\/([^/]+)\/?$/;
|
||||
|
||||
/**
|
||||
* The worktree a working directory is, by its layout alone: `{ repo, name }` for
|
||||
* `<repo>/.claude/worktrees/<name>` (the folder itself, not a subfolder of it), else null.
|
||||
*/
|
||||
export function worktreeFromPath(workingDir: string): { repo: string; name: string } | null {
|
||||
const m = WORKTREE_PATH.exec(workingDir);
|
||||
return m ? { repo: m[1], name: m[2] } : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge all source views into one list, applying precedence
|
||||
* (history → lifecycle → persisted → live) and the meaningfulness floor.
|
||||
@@ -326,6 +338,17 @@ export function mergeUnifiedSessions(sources: UnifiedSources): UnifiedSessionIte
|
||||
if (byDir) item.lastPrompt = byDir.prompt;
|
||||
}
|
||||
}
|
||||
// A live or persisted session started in a worktree Codeman created (or Claude Code's own
|
||||
// `--worktree`) has no transcript record naming it until claude writes one, and a codex or
|
||||
// shell session never gets one, so its row read like any other session of the repo. The
|
||||
// folder layout says which worktree it is; a transcript record, when there is one, still wins.
|
||||
if (!item.worktreeName && item.workingDir) {
|
||||
const wt = worktreeFromPath(item.workingDir);
|
||||
if (wt) {
|
||||
item.worktreeName = wt.name;
|
||||
item.worktreeRepo ??= wt.repo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Meaningfulness floor: keep real rows, drop bare lifecycle/mux-only noise.
|
||||
|
||||
Reference in new issue
Block a user