Claude Code's (and Copilot's) native plugin installer has zero awareness of .apm/ nesting -- it convention-scans only flat skills/, agents/, commands/, hooks.json at each plugin's root. Confirmed via strings on the installed claude binary and live installs of git@holocron/gitea@holocron/kyberforge@ holocron, all reporting Skills(0) Agents(0) Hooks(0) post ADR-0015's apm conversion. Root cause (apm_cli/core/plugin_manifest.py): apm's plugin.json compiler deliberately strips skills/agents/commands keys, assuming the host already auto-discovers those convention directories -- it has no model of .apm/ being host-visible at all. Separately, apm's own bundle exporter (apm_cli/bundle/plugin_exporter.py, behind `apm pack --format plugin`) implements the correct .apm/ -> flat mapping, but only ever targeted build/<name>-<version>/, a path nothing in marketplace.json's source: points at. scripts/sync-plugin-content.sh wraps that bundle exporter and copies its agents/, skills/, commands/, instructions/, extensions/, and merged hooks.json back into each plugin's own root as a second tracked compiled-output category -- same governance status as .claude-plugin/plugin.json: generated from .apm/, never hand-edited. tests/ subdirectories are excluded from the mirror (dev fixtures, not host-visible runtime content; several hardcode a relative repo-root walk-up sized for the .apm/-nested depth, which breaks when duplicated one level shallower). Applied for real across all 6 plugins and verified two ways: `claude plugin validate --strict` passes on every real plugin directory, and a live `claude --plugin-dir <path> -p "list skills/agents"` behavioral test confirms content is now actually discovered. Also, from the same issue #90 review round: - scripts/check-manifests.sh pointed at each plugin's root-level plugin.json (checking skills/hooks/mcpServers/agents pointer fields) -- that file was a stale near-duplicate of .claude-plugin/plugin.json nothing else read or wrote, now deleted across all 6 plugins. check-manifests.sh is rewritten to validate .claude-plugin/plugin.json instead, and drops the pointer-field checks entirely (nothing to check -- those fields are correctly absent by design). Content-presence drift is now check-plugin-content-sync's job, a new pre-push hook wired in .pre-commit-config.yaml. docs/adr/0017 records the root cause and decision in full, including two rejected alternatives (patching plugin.json's path fields directly -- apm's compiler strips them on every run; pointing marketplace.json at apm pack's build/ output -- a version-suffixed non-source directory nothing can install from without an extra build step). ADR-0015 and CONTEXT.md are updated to point at it. Refs: #90
126 lines
6.0 KiB
Markdown
126 lines
6.0 KiB
Markdown
---
|
|
name: git-worktrees
|
|
|
|
description: >
|
|
Manage Git worktrees to enable multi-branch parallel development across isolated directories.
|
|
Use when the user needs to work on multiple branches simultaneously without stashing, switch between feature/hotfix/experimental work, or coordinate code reviews alongside ongoing development.
|
|
Handles creation, listing, locking, moving, removal, pruning, and repair of worktrees.
|
|
Provides structured results (paths, branches, lock status) for agent composition in git orchestration workflows.
|
|
Do not use when only inspecting a single branch or when the user needs standard checkout/stash workflows.
|
|
|
|
metadata:
|
|
category: git
|
|
source_keys:
|
|
- git-scm-worktree-docs
|
|
---
|
|
|
|
## Concept
|
|
|
|
A worktree lets you check out multiple branches simultaneously from one repository, each in its own directory. All worktrees share the same objects, config, and most refs (`refs/`). Each worktree has its own `HEAD`, index, and per-worktree metadata (`ORIG_HEAD`, `MERGE_HEAD`, `refs/bisect/`, `refs/worktree/`, `refs/rewritten/`) stored at `$GIT_DIR/worktrees/<name>/`. The **main worktree** (from `git init`/`git clone`) is exactly one per repo and cannot be removed; **linked worktrees** are the additional ones created via `git worktree add`.
|
|
|
|
## Gotchas
|
|
|
|
- **A branch can only be checked out in one worktree at a time.** Attempting `git worktree add` for an already-checked-out branch fails unless you pass `--force`. Use `--force` only when intentional.
|
|
- **Submodules are unsupported and block operations.** Repos with submodules have incomplete worktree support. Worktrees containing submodules cannot be moved and require `--force` to remove.
|
|
- **Never manually `rm -rf` a worktree directory.** This leaves stale metadata in `$GIT_DIR/worktrees/`. Always use `git worktree remove`. If already deleted, run `git worktree prune` to clean up.
|
|
- **Manual moves break bidirectional pointers.** If a worktree directory is moved outside of `git worktree move`, run `git worktree repair` to fix connections.
|
|
- **Force-flag escalation with locks.** Removing or moving a locked worktree requires `-ff` (two flags), not just `-f`.
|
|
- **Worktree identification is by full path, unique basename, or unique partial path.** Ambiguous names error. Use `git worktree list` to see available identifiers.
|
|
- **`--lock` on `add` is atomic; create-then-lock has a race window.** Use `--lock` directly on `git worktree add` when consistency matters.
|
|
- **`extensions.worktreeConfig = true` is a one-way door.** It enables per-worktree config (`git config --worktree ...`) but makes the repo refuse to open in older Git versions. Once set, `core.bare`/`core.worktree` must live in `config.worktree`, not `config`. Don't enable it unless per-worktree config is actually needed.
|
|
|
|
## Common Operations
|
|
|
|
**Create and switch to a new worktree** — default approach:
|
|
```bash
|
|
git worktree add -b <new-branch> <path>
|
|
cd <path>
|
|
```
|
|
This creates a new branch and checks it out in a new directory. Other branches cannot be checked out elsewhere simultaneously.
|
|
|
|
**Create-or-reset a branch**: `git worktree add -B <branch> <path>` — like `-b` but resets the branch to HEAD if it already exists.
|
|
|
|
**Create a worktree for an existing remote branch**:
|
|
```bash
|
|
git worktree add <path> <remote>/<branch>
|
|
```
|
|
For ambiguous names across remotes, disambiguate via `checkout.defaultRemote` config or `--guess-remote`. Full flag table and detail: `references/worktrees.md`.
|
|
|
|
**Throwaway experiment in detached HEAD**:
|
|
```bash
|
|
git worktree add -d ../experiment # or --detach
|
|
# experiment freely, no branch created
|
|
git worktree remove ../experiment
|
|
```
|
|
|
|
**List all worktrees with state**:
|
|
```bash
|
|
git worktree list -v # human-readable with lock/prune reasons
|
|
git worktree list --porcelain -z # machine-readable, NUL-terminated
|
|
```
|
|
|
|
**Move a worktree to a new path**:
|
|
```bash
|
|
git worktree move <current-path> <new-path>
|
|
# Cannot move: main worktree, worktrees with submodules
|
|
# To override safeguards: -f; to override locked state too: -ff
|
|
```
|
|
|
|
**Remove a worktree**:
|
|
```bash
|
|
git worktree remove <path> # only if clean
|
|
git worktree remove -f <path> # force-remove unclean
|
|
git worktree remove -ff <path> # force-remove even if locked
|
|
```
|
|
|
|
**Prune stale metadata**:
|
|
```bash
|
|
git worktree prune --dry-run # preview what would be removed
|
|
git worktree prune # clean up orphaned metadata
|
|
```
|
|
Also triggered by `git gc`, controlled by `gc.worktreePruneExpire` config.
|
|
|
|
**Repair broken connections** (after a manual move):
|
|
```bash
|
|
git worktree repair # from main worktree or after it was moved
|
|
git worktree repair <path> # reconnect a specific linked worktree
|
|
```
|
|
|
|
Sparse-checkout worktrees, locking for removable media, the full `add` flag table, and the config key reference: `references/worktrees.md`.
|
|
|
|
## Worked Examples
|
|
|
|
**Emergency fix without disrupting current work** — no stashing needed, ongoing work in the main worktree is untouched:
|
|
```bash
|
|
git worktree add -b emergency-fix ../temp main
|
|
cd ../temp
|
|
# fix, commit
|
|
git commit -a -m "fix: critical production bug"
|
|
cd -
|
|
git worktree remove ../temp
|
|
```
|
|
|
|
**Review a PR branch alongside your current work** — no context switch, both branches stay checked out:
|
|
```bash
|
|
git worktree add ../review-pr-123 origin/feature-xyz
|
|
# open ../review-pr-123 in a second editor window or terminal
|
|
```
|
|
|
|
## Return Format for Agents
|
|
|
|
When invoking worktree operations, return structured results:
|
|
```yaml
|
|
worktrees:
|
|
- path: <directory-path>
|
|
branch: <branch-name>
|
|
commit: <short-hash>
|
|
locked: <true/false>
|
|
lock_reason: <reason or empty>
|
|
- ...
|
|
```
|
|
Derive these fields from `git worktree list --porcelain -z` — its `worktree`/`branch`/`HEAD`/`locked` lines map directly to `path`/`branch`/`commit`/`locked`+`lock_reason`.
|
|
|
|
For single operations, include the operation result (e.g., `created: true`, `removed: true`, `moved: true`).
|
|
|
|
For multi-step flows spanning branch strategy plus worktree setup, compose with the `git-workflow` skill — it handles the broader orchestration, this skill handles the worktree mechanics.
|