Claude Skill

worktree-agent

Mandatory rules for agents in git worktree isolation.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download notque-vexjoy-agent-skills_process_worktree-agent-8ad6845.zip · 5 KB
Part of notque/vexjoy-agent — 69 skills

Install

skills CLI npx skills add https://github.com/notque/vexjoy-agent/tree/main/skills/process/worktree-agent
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install notque-vexjoy-agent@llmmart
Git git clone https://github.com/notque/vexjoy-agent.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole notque/vexjoy-agent collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Worktree Agent Rules

Mandatory rules for any agent dispatched with isolation: "worktree".

Rule 1: Verify Your Working Directory

On start, run pwd. Your path MUST contain .claude/worktrees/. If your CWD is the main repo path, STOP and report the error.

Rule 2: Create Feature Branch First

git checkout -b <branch-name>

Never commit on the default worktree-agent-* branch. Create your feature branch FIRST.

If git checkout -b <branch-name> fails with "a branch named X already exists":

# Option A: the branch has no commits beyond main — safe to reset and reuse
git branch -D <branch-name>
git checkout -b <branch-name>

# Option B: the branch is checked out in another active worktree — use a unique name
git checkout -b <branch-name>-2   # or append timestamp: $(date +%s)

If git checkout -b <branch-name> fails with "X is already used by worktree at Y":

# Branch is live in another worktree — use a unique suffix
git checkout -b <branch-name>-$(date +%s)

To update a branch held by another worktree (e.g. an existing PR branch): work detached from origin/<branch> and push with git push origin HEAD:<branch>. gh pr merge's post-merge local-checkout errors are harmless.

Rule 3: Use Worktree-Relative Paths

Never hardcode absolute paths from the main repo. Use $(git rev-parse --show-toplevel)/path. Exception: Reading gitignored ADR files requires the main repo absolute path.

Rule 4: Ignore Auto-Plan Hooks

Keep planning inline instead of creating task_plan.md. If the auto-plan hook fires, continue with the current task and keep your attention on implementation.

Rule 5: Stage Specific Files Only

git add path/to/specific/file.py

Never git add ., git add -A, or git add --all. Verify with git diff --cached --stat.

Rule 6: Do Not Touch the Main Worktree

Never write to paths outside your worktree directory. Never run git checkout in the main repo.

Rule 7: Commit with Conventional Format

Use the commit message specified in your prompt. No attribution lines.

Rule 8: Run Both ruff Checks Before Declaring CI-Ready

For any Python code changes, run both checks before pushing or creating a PR:

ruff check . --config pyproject.toml
ruff format --check . --config pyproject.toml

Running only ruff check misses formatting violations. The Tests / lint CI job runs both — if you skip ruff format --check, the PR will fail CI and cannot merge due to branch protection.

Rule 9: Run Preflight Check on Start

Run the preflight script at the start of any worktree task to confirm clean state:

bash scripts/worktree-preflight.sh <intended-branch-name>

If it exits 1, fix the reported issue before proceeding.

Rule 10: Reserve disk capacity before creating a checkout

The dispatcher runs this before each implementation worktree:

python3 ~/.claude/skills/process/worktree-agent/scripts/worktree_capacity.py \
  --repo "$(git rev-parse --show-toplevel)" --strict

The JSON report has three states:

State Dispatcher action
ready (<80% used) Create the one implementation checkout for the candidate.
cleanup-soon (80–<85%) Reclaim accepted clean checkouts before adding another. Use root read-only review work where possible.
blocked (≥85%) Integrate, deploy, verify, or reclaim; create no new checkout.

The report lists clean candidates only. The dispatcher confirms their task is inactive before removal because Git cleanliness alone does not prove that fact.

Rule 11: Assign checkout roles deliberately

Task Checkout policy
Source implementation or repair One writable task worktree, reused through review corrections.
Code review, test-plan review, or read-only investigation Read the candidate through git diff or git show from the repository root; allocate no checkout.
Large repository implementation Create a sparse worktree containing declared source/test/config scopes; include whole-repository content only when the task requires it.

For a sparse implementation checkout:

git worktree add --no-checkout <worktree-path> -b <branch> <base-sha>
git -C <worktree-path> sparse-checkout init --no-cone
git -C <worktree-path> sparse-checkout set --no-cone <declared-path>...
git -C <worktree-path> checkout

Record any full-checkout reason in the dispatch handoff.

Post-Merge Cleanup

After integration or a PR merge, the dispatcher first confirms that the task is inactive and the checkout is clean, then runs:

git worktree remove -- <accepted-worktree-path>
bash scripts/worktree-cleanup.sh --force

git worktree remove frees the materialized checkout while preserving its branch for recovery. The cleanup script then prunes stale .git/worktrees entries and removes merged harness branches.

Failure Modes This Prevents

Failure Rule Without It
Agent edits main repo files 1, 6 Changes leak to main, get stashed/lost
Context wasted on task_plan.md 4 Implementation budget consumed by planning
Commit on wrong branch 2 Orchestrator merges wrong content
PR has changes from 2 ADRs 5, 6 Cross-contamination between agents
Branch locked by worktree 2 Fatal error on checkout
PR fails CI on format 8 Merge blocked; ruff format --check was skipped
New task fails to create worktree 9 Branch name collision from prior stale run
Files (vexjoy-agent)
  • scripts
    • tests
      • test_worktree_capacity.py 2.6 KB
        """Regression tests for the deterministic worktree-capacity guard."""
        
        from __future__ import annotations
        
        import importlib.util
        from pathlib import Path
        from types import SimpleNamespace
        
        SCRIPT_PATH = Path(__file__).resolve().parents[1] / "worktree_capacity.py"
        TOOLKIT_ROOT = Path(__file__).resolve().parents[5]
        SPEC = importlib.util.spec_from_file_location("worktree_capacity", SCRIPT_PATH)
        assert SPEC is not None and SPEC.loader is not None
        worktree_capacity = importlib.util.module_from_spec(SPEC)
        SPEC.loader.exec_module(worktree_capacity)
        
        
        def test_capacity_status_reserves_hard_stop_for_new_worktrees():
            assert worktree_capacity.capacity_status(79, soft_percent=80, hard_percent=85) == "ready"
            assert worktree_capacity.capacity_status(80, soft_percent=80, hard_percent=85) == "cleanup-soon"
            assert worktree_capacity.capacity_status(85, soft_percent=80, hard_percent=85) == "blocked"
        
        
        def test_usable_disk_percent_counts_filesystem_reserved_space_as_unavailable():
            stats = SimpleNamespace(f_blocks=100, f_bfree=30, f_bavail=20)
        
            assert worktree_capacity.usable_disk_percent(stats) == 77.8
        
        
        def test_parse_worktree_porcelain_keeps_only_paths_under_agent_root(tmp_path):
            agent_root = tmp_path / ".claude" / "worktrees"
            kept = agent_root / "active"
            outside = tmp_path / "manual-checkout"
            payload = (
                f"worktree {tmp_path}\nHEAD abc\nbranch refs/heads/main\n\n"
                f"worktree {kept}\nHEAD def\nbranch refs/heads/fix/active\n\n"
                f"worktree {outside}\nHEAD ghi\ndetached\n"
            )
        
            assert worktree_capacity.agent_worktree_paths(payload, agent_root) == [kept]
        
        
        def test_capacity_report_marks_clean_checkouts_for_dispatcher_review(tmp_path):
            report = worktree_capacity.capacity_report(
                used_percent=81,
                paths=[tmp_path / "clean", tmp_path / "dirty"],
                clean_paths={tmp_path / "clean"},
                soft_percent=80,
                hard_percent=85,
            )
        
            assert report["status"] == "cleanup-soon"
            assert report["worktrees"] == {"count": 2, "clean_candidates": [str(tmp_path / "clean")]}
        
        
        def test_dispatch_rules_require_capacity_preflight_and_checkout_roles():
            canonical_rules = (TOOLKIT_ROOT / "skills/meta/do/references/worktree-rules.md").read_text()
            quality_loop = (TOOLKIT_ROOT / "skills/meta/do/references/quality-loop.md").read_text()
        
            assert "worktree_capacity.py" in canonical_rules
            assert "allocate no checkout" in canonical_rules
            assert "sparse-checkout" in canonical_rules
            assert "git worktree remove -- <accepted-worktree-path>" in canonical_rules
            assert "read-only reviewers" in quality_loop
        
    • worktree_capacity.py 4.8 KB
      #!/usr/bin/env python3
      """Report task-worktree capacity without deleting any checkout or branch."""
      
      from __future__ import annotations
      
      import argparse
      import json
      import os
      import subprocess
      import sys
      from pathlib import Path
      from typing import Iterable
      
      DEFAULT_SOFT_PERCENT = 80
      DEFAULT_HARD_PERCENT = 85
      
      
      def capacity_status(used_percent: float, *, soft_percent: int, hard_percent: int) -> str:
          """Classify disk pressure for task-worktree creation."""
          if not 0 <= soft_percent < hard_percent <= 100:
              raise ValueError("thresholds require 0 <= soft < hard <= 100")
          if used_percent >= hard_percent:
              return "blocked"
          if used_percent >= soft_percent:
              return "cleanup-soon"
          return "ready"
      
      
      def usable_disk_percent(stats: os.statvfs_result) -> float:
          """Match `df` capacity by excluding filesystem-reserved blocks."""
          used_blocks = stats.f_blocks - stats.f_bfree
          usable_blocks = used_blocks + stats.f_bavail
          if usable_blocks <= 0:
              return 100.0
          return round(used_blocks * 100 / usable_blocks, 1)
      
      
      def _porcelain_worktree_paths(porcelain: str) -> Iterable[Path]:
          for record in porcelain.split("\n\n"):
              for line in record.splitlines():
                  if line.startswith("worktree "):
                      yield Path(line.removeprefix("worktree "))
                      break
      
      
      def agent_worktree_paths(porcelain: str, agent_root: Path) -> list[Path]:
          """Return only registered worktrees below the designated agent root."""
          root = agent_root.resolve()
          result: list[Path] = []
          for path in _porcelain_worktree_paths(porcelain):
              resolved = path.resolve()
              try:
                  resolved.relative_to(root)
              except ValueError:
                  continue
              result.append(resolved)
          return sorted(result)
      
      
      def _git_output(repo: Path, *args: str) -> str:
          completed = subprocess.run(
              ["git", "-C", str(repo), *args],
              check=True,
              capture_output=True,
              text=True,
              timeout=10,
          )
          return completed.stdout
      
      
      def clean_worktree_paths(paths: Iterable[Path]) -> set[Path]:
          """Return checkouts with no tracked or untracked changes.
      
          This does not establish whether a task is still active. Dispatchers review
          these candidates before calling `git worktree remove`.
          """
          clean: set[Path] = set()
          for path in paths:
              try:
                  if not _git_output(path, "status", "--porcelain"):
                      clean.add(path)
              except (OSError, subprocess.CalledProcessError, subprocess.TimeoutExpired):
                  continue
          return clean
      
      
      def capacity_report(
          *,
          used_percent: float,
          paths: Iterable[Path],
          clean_paths: set[Path],
          soft_percent: int,
          hard_percent: int,
      ) -> dict[str, object]:
          """Build the stable, machine-readable capacity report."""
          ordered_paths = sorted(paths)
          return {
              "status": capacity_status(used_percent, soft_percent=soft_percent, hard_percent=hard_percent),
              "disk": {"used_percent": round(used_percent, 1), "soft_percent": soft_percent, "hard_percent": hard_percent},
              "worktrees": {
                  "count": len(ordered_paths),
                  "clean_candidates": [str(path) for path in ordered_paths if path in clean_paths],
              },
          }
      
      
      def parse_args() -> argparse.Namespace:
          parser = argparse.ArgumentParser(description=__doc__)
          parser.add_argument("--repo", type=Path, required=True, help="repository root")
          parser.add_argument(
              "--worktree-root",
              type=Path,
              default=Path(".claude/worktrees"),
              help="agent checkout root, relative to --repo unless absolute",
          )
          parser.add_argument("--soft-percent", type=int, default=DEFAULT_SOFT_PERCENT)
          parser.add_argument("--hard-percent", type=int, default=DEFAULT_HARD_PERCENT)
          parser.add_argument(
              "--strict",
              action="store_true",
              help="exit 2 when hard capacity is reached; default mode reports only",
          )
          return parser.parse_args()
      
      
      def main() -> int:
          args = parse_args()
          repo = args.repo.resolve()
          agent_root = args.worktree_root if args.worktree_root.is_absolute() else repo / args.worktree_root
          used_percent = usable_disk_percent(os.statvfs(repo))
          try:
              paths = agent_worktree_paths(_git_output(repo, "worktree", "list", "--porcelain"), agent_root)
          except (OSError, subprocess.CalledProcessError, subprocess.TimeoutExpired) as exc:
              print(f"worktree-capacity: cannot inspect {repo}: {exc}", file=sys.stderr)
              return 1
      
          report = capacity_report(
              used_percent=used_percent,
              paths=paths,
              clean_paths=clean_worktree_paths(paths),
              soft_percent=args.soft_percent,
              hard_percent=args.hard_percent,
          )
          print(json.dumps(report, sort_keys=True))
          return 2 if args.strict and report["status"] == "blocked" else 0
      
      
      if __name__ == "__main__":
          raise SystemExit(main())
      
  • SKILL.md 5.7 KB
    ---
    name: worktree-agent
    promoted_to: do
    description: "Mandatory rules for agents in git worktree isolation."
    user-invocable: false
    context: fork
    tags: [worktree, isolation, parallel, agent]
    routing:
      triggers:
        - "worktree agent"
        - "git worktree"
        - "git worktree rules"
        - "isolated agent"
      category: git-workflow
    ---
    
    # Worktree Agent Rules
    
    Mandatory rules for any agent dispatched with `isolation: "worktree"`.
    
    ## Rule 1: Verify Your Working Directory
    
    On start, run `pwd`. Your path MUST contain `.claude/worktrees/`.
    If your CWD is the main repo path, **STOP** and report the error.
    
    ## Rule 2: Create Feature Branch First
    
    ```bash
    git checkout -b <branch-name>
    ```
    
    Never commit on the default `worktree-agent-*` branch. Create your feature branch FIRST.
    
    If `git checkout -b <branch-name>` fails with "a branch named X already exists":
    
    ```bash
    # Option A: the branch has no commits beyond main — safe to reset and reuse
    git branch -D <branch-name>
    git checkout -b <branch-name>
    
    # Option B: the branch is checked out in another active worktree — use a unique name
    git checkout -b <branch-name>-2   # or append timestamp: $(date +%s)
    ```
    
    If `git checkout -b <branch-name>` fails with "X is already used by worktree at Y":
    
    ```bash
    # Branch is live in another worktree — use a unique suffix
    git checkout -b <branch-name>-$(date +%s)
    ```
    
    To update a branch held by another worktree (e.g. an existing PR branch): work detached from `origin/<branch>` and push with `git push origin HEAD:<branch>`. `gh pr merge`'s post-merge local-checkout errors are harmless.
    
    ## Rule 3: Use Worktree-Relative Paths
    
    Never hardcode absolute paths from the main repo. Use `$(git rev-parse --show-toplevel)/path`.
    **Exception**: Reading gitignored ADR files requires the main repo absolute path.
    
    ## Rule 4: Ignore Auto-Plan Hooks
    
    Keep planning inline instead of creating `task_plan.md`. If the auto-plan hook fires, continue with the current task and keep your attention on implementation.
    
    ## Rule 5: Stage Specific Files Only
    
    ```bash
    git add path/to/specific/file.py
    ```
    
    Never `git add .`, `git add -A`, or `git add --all`. Verify with `git diff --cached --stat`.
    
    ## Rule 6: Do Not Touch the Main Worktree
    
    Never write to paths outside your worktree directory. Never run `git checkout` in the main repo.
    
    ## Rule 7: Commit with Conventional Format
    
    Use the commit message specified in your prompt. No attribution lines.
    
    ## Rule 8: Run Both ruff Checks Before Declaring CI-Ready
    
    For any Python code changes, run both checks before pushing or creating a PR:
    
    ```bash
    ruff check . --config pyproject.toml
    ruff format --check . --config pyproject.toml
    ```
    
    Running only `ruff check` misses formatting violations. The `Tests / lint` CI job runs both — if you skip `ruff format --check`, the PR will fail CI and cannot merge due to branch protection.
    
    ## Rule 9: Run Preflight Check on Start
    
    Run the preflight script at the start of any worktree task to confirm clean state:
    
    ```bash
    bash scripts/worktree-preflight.sh <intended-branch-name>
    ```
    
    If it exits 1, fix the reported issue before proceeding.
    
    ## Rule 10: Reserve disk capacity before creating a checkout
    
    The dispatcher runs this before each implementation worktree:
    
    ```bash
    python3 ~/.claude/skills/process/worktree-agent/scripts/worktree_capacity.py \
      --repo "$(git rev-parse --show-toplevel)" --strict
    ```
    
    The JSON report has three states:
    
    | State | Dispatcher action |
    |---|---|
    | `ready` (<80% used) | Create the one implementation checkout for the candidate. |
    | `cleanup-soon` (80–<85%) | Reclaim accepted clean checkouts before adding another. Use root read-only review work where possible. |
    | `blocked` (≥85%) | Integrate, deploy, verify, or reclaim; create no new checkout. |
    
    The report lists clean candidates only. The dispatcher confirms their task is inactive before removal because Git cleanliness alone does not prove that fact.
    
    ## Rule 11: Assign checkout roles deliberately
    
    | Task | Checkout policy |
    |---|---|
    | Source implementation or repair | One writable task worktree, reused through review corrections. |
    | Code review, test-plan review, or read-only investigation | Read the candidate through `git diff` or `git show` from the repository root; allocate no checkout. |
    | Large repository implementation | Create a sparse worktree containing declared source/test/config scopes; include whole-repository content only when the task requires it. |
    
    For a sparse implementation checkout:
    
    ```bash
    git worktree add --no-checkout <worktree-path> -b <branch> <base-sha>
    git -C <worktree-path> sparse-checkout init --no-cone
    git -C <worktree-path> sparse-checkout set --no-cone <declared-path>...
    git -C <worktree-path> checkout
    ```
    
    Record any full-checkout reason in the dispatch handoff.
    
    ## Post-Merge Cleanup
    
    After integration or a PR merge, the dispatcher first confirms that the task is inactive and the checkout is clean, then runs:
    
    ```bash
    git worktree remove -- <accepted-worktree-path>
    bash scripts/worktree-cleanup.sh --force
    ```
    
    `git worktree remove` frees the materialized checkout while preserving its branch for recovery. The cleanup script then prunes stale `.git/worktrees` entries and removes merged harness branches.
    
    ## Failure Modes This Prevents
    
    | Failure | Rule | Without It |
    |---------|------|-----------|
    | Agent edits main repo files | 1, 6 | Changes leak to main, get stashed/lost |
    | Context wasted on task_plan.md | 4 | Implementation budget consumed by planning |
    | Commit on wrong branch | 2 | Orchestrator merges wrong content |
    | PR has changes from 2 ADRs | 5, 6 | Cross-contamination between agents |
    | Branch locked by worktree | 2 | Fatal error on checkout |
    | PR fails CI on format | 8 | Merge blocked; `ruff format --check` was skipped |
    | New task fails to create worktree | 9 | Branch name collision from prior stale run |
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related