Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 67 additions & 7 deletions .agents/skills/git-branch/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,52 @@ When an issue ID is provided, use one `gh issue view <issueID> --json
title,body,number` call for naming context. If it fails, continue only when the
request is enough and report that the issue was not verified.

## Base

This section is the single source of truth for base selection. Other skills
(`git-worktree`) reference it and copy the snippet below instead of restating a
second rule set.

Resolve the base in exactly this order, validating every candidate with a ref
check and falling through to the next one when it does not resolve:

1. the repository's documented base — the default branch the repository itself
states (for example in `CONTRIBUTING`, `README`, or `AGENTS`); skip this step
when nothing is documented;
2. `origin/HEAD` — the remote's default branch;
3. the branch the user explicitly asked for (`--base <branch>` or a branch named
in the request);
4. `main` as the last resort.

Never continue with a base that does not resolve. If no candidate resolves, list
the available remote branches, ask the user to pass `--base <branch>`, and exit
non-zero — do not silently fall back to a `main` that does not exist.

```bash
ref_exists() {
git rev-parse --verify --quiet "origin/$1" >/dev/null 2>&1 \
|| git rev-parse --verify --quiet "refs/heads/$1" >/dev/null 2>&1
}

resolve_base() {
local documented="$1" user_base="$2" origin_head cand
# 1) documented base
if [ -n "$documented" ] && ref_exists "$documented"; then echo "$documented"; return 0; fi
# 2) origin/HEAD
origin_head=$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##')
[ -n "$origin_head" ] || origin_head=$(git remote show origin 2>/dev/null | sed -n 's/^ *HEAD branch: *//p')
# 3) user choice 4) main fallback
for cand in "$origin_head" "$user_base" main; do
if [ -n "$cand" ] && ref_exists "$cand"; then echo "$cand"; return 0; fi
done
return 1
}
```

The order is deliberate: `origin/HEAD` is checked before the user choice, so an
explicit `--base` does not override a resolvable `origin/HEAD`. Surface that
conflict to the user instead of silently reordering the rule.

## Create

Check the current worktree, branch, and target branch first. Stop if the
Expand All @@ -31,17 +77,31 @@ branch is unsafe. Validate the name with:
git check-ref-format --branch <branch-name>
```

Use the repository's documented base, otherwise `main`. In the same repository
prefer `origin/<base>` over `upstream/<base>`; use `upstream` only for a fork or
explicit guidance. Fetch or compare freshness only when it can change the
choice of base. If creating from a stale local base, confirm before updating or
use a freshly fetched remote base and report that the local base was unchanged.
Resolve the base with the snippet from the Base section, then prefer the remote
ref and use the local ref only when the remote one is missing:

```bash
git switch -c <branch-name> <base>
git switch --no-track -c <branch-name> origin/<base>
base=$(resolve_base "$documented_base" "$user_base") || {
echo "No usable base branch. Candidates:" >&2
git branch -r >&2
echo "Pass --base <branch> to choose one." >&2
exit 1
}
if git rev-parse --verify --quiet "origin/$base" >/dev/null; then
base_ref="origin/$base"
else
base_ref="$base"
fi

git switch --no-track -c <branch-name> "$base_ref"
```

In the same repository prefer `origin/<base>` over `upstream/<base>`; use
`upstream` only for a fork or explicit guidance. Fetch or compare freshness only
when it can change the choice of base. If creating from a stale local base,
confirm before updating or use a freshly fetched remote base and report that the
local base was unchanged.

Verify the resulting branch. Do not switch to an existing branch, overwrite,
reset, stash, delete, or force anything without explicit user intent. Do not
create protected/shared base branches.
122 changes: 107 additions & 15 deletions .agents/skills/git-worktree/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,127 @@ Use this when work should not disturb the current worktree. The new worktree
does not copy uncommitted changes, and it does not change the user's shell
directory.

## Naming and base
## Repository root

Resolve the repository root first and build every path from it. Never rely on
the caller's working directory: in a monorepo this skill is invoked from
arbitrary subdirectories, and a relative path would nest `.worktrees/` under the
subdirectory instead of the repository root.

```bash
repo_root=$(git rev-parse --show-toplevel) || {
echo "Not inside a Git repository; run this from a repository checkout." >&2
exit 1
}
target="$repo_root/.worktrees/<branch-name>"
```

Stop with a non-zero exit when `git rev-parse --show-toplevel` fails — do not
invent a relative path as a fallback.

## Naming

Use the `git-branch` naming rules: issue work is
`<type>/<short-desc>-<issueID>`, other work uses a user-provided name, and no
issue ID is invented. When an issue ID is provided, use one `gh issue view`
call for naming context and continue from the request if it fails. Store the
worktree at `.worktrees/<branch-name>` so branch path segments are preserved.
The repository should ignore `.worktrees/`.
worktree at `"$repo_root/.worktrees/<branch-name>"` so branch path segments are
preserved and a subdirectory call still lands under the repository root. The
repository should ignore `.worktrees/`.

## Base

Base selection has exactly one source of truth: the `## Base` section of the
`git-branch` skill. Do not restate or re-order it here — call `resolve_base`
from that section and use the value it returns. For reference, the order is
documented base → `origin/HEAD` → user choice → `main` fallback, and every
candidate is validated with a ref check before it is used.

Copy both helpers from `git-branch` verbatim (keep them identical when that
section changes):

```bash
ref_exists() {
git rev-parse --verify --quiet "origin/$1" >/dev/null 2>&1 \
|| git rev-parse --verify --quiet "refs/heads/$1" >/dev/null 2>&1
}

resolve_base() {
local documented="$1" user_base="$2" origin_head cand
# 1) documented base
if [ -n "$documented" ] && ref_exists "$documented"; then echo "$documented"; return 0; fi
# 2) origin/HEAD
origin_head=$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's#^origin/##')
[ -n "$origin_head" ] || origin_head=$(git remote show origin 2>/dev/null | sed -n 's/^ *HEAD branch: *//p')
# 3) user choice 4) main fallback
for cand in "$origin_head" "$user_base" main; do
if [ -n "$cand" ] && ref_exists "$cand"; then echo "$cand"; return 0; fi
done
return 1
}
```

Default the base to `main`; same-repository work prefers `origin/<base>`, then
local `<base>`, and uses `upstream` only for fork workflows or explicit
guidance. Fetch/check freshness only when it affects the result. If the local
base is stale, confirm before using it or create from a fetched remote base and
report that the local base was unchanged.
Create from the remote ref first and fall back to the local ref only when the
remote one is missing:

```bash
base=$(resolve_base "$documented_base" "$user_base") || {
echo "No usable base branch. Candidates:" >&2
git branch -r >&2
echo "Pass --base <branch> to choose one." >&2
exit 1
}
if git rev-parse --verify --quiet "origin/$base" >/dev/null; then
base_ref="origin/$base"
else
base_ref="$base"
fi
```

Same-repository work prefers `origin/<base>`; use `upstream` only for fork
workflows or explicit guidance. Fetch or compare freshness only when it can
change the choice of base. If the local base is stale, confirm before using it
or create from a fetched remote base and report that the local base was
unchanged. Never continue with a base that does not resolve, and never fall back
to a `main` branch that does not exist.

## Create

Before creating, check current status, existing worktrees/branches, and the
target path. Stop if any target already exists or dirty-work intent is unclear.
Validate the branch with `git check-ref-format --branch <branch-name>`, create
parent directories as needed, then run the equivalent of:
target path. Stop if the target already exists: never overwrite, reset, or
reuse an existing worktree directory. Validate the branch with
`git check-ref-format --branch <branch-name>`, resolve the base as shown above,
then run the equivalent of:

```bash
if [ -e "$target" ]; then
echo "Worktree target already exists: $target" >&2
echo "Pick another branch name, or remove the existing worktree yourself; this skill never overwrites." >&2
exit 1
fi

if ! git check-ignore -q "$target"; then
echo "warning: $target is not covered by .gitignore" >&2
echo "Add '.worktrees/' to the repository root .gitignore before creating the worktree." >&2
fi

mkdir -p "$(dirname "$target")"
git worktree add --no-track -b <branch-name> "$target" "$base_ref"
```

The ignore check only reports: this skill does not edit `.gitignore` itself.

## Report

Verify the new worktree's branch, status, and `pwd`. Report its absolute path,
the base, whether dirty changes were excluded, and the user's next step:

```bash
git worktree add --no-track -b <branch-name> .worktrees/<branch-name> <base-ref>
cd "$repo_root/.worktrees/<branch-name>"
```

Verify the new worktree's branch, status, and `pwd`. Report its path, base,
whether dirty changes were excluded, and the user's next step:
`cd .worktrees/<branch-name>`.
The worktree always lives at the repository root, no matter which subdirectory
of the repository the skill was invoked from.

Do not remove, prune, overwrite, reset, stash, push, or force anything unless
explicitly asked. Do not create protected/shared base branches as targets.
Expand Down
Loading