From 7a909ca3a21f64711a7ed7f8be8ddb2677760639 Mon Sep 17 00:00:00 2001 From: echuan-agent Date: Wed, 4 Nov 2026 05:08:11 +0000 Subject: [PATCH 1/2] =?UTF-8?q?docs(skills):=20=E7=BB=9F=E4=B8=80=20git-br?= =?UTF-8?q?anch=20/=20git-worktree=20=E7=9A=84=20base=20=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=20(#335)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原因:git-worktree 把 base 硬编码为 main,默认分支是 master/trunk 的仓库 要么选错(陈旧 main)要么选不到(无 main);两处各自表述也必然漂移。 改动: - git-branch 新增 ## Base 段作为唯一事实源:documented base → origin/HEAD → 用户指定 → main 兜底,每级先用 git rev-parse --verify --quiet 校验 ref - 给出可执行片段 ref_exists / resolve_base - git-worktree base 段改为引用 git-branch 规则,片段逐字节复制; 创建时 origin/ 优先、其次本地 ;解析失败列候选、提示 --base 、非零退出,禁止静默按不存在的 main 继续 验证:fixture A(master 默认分支 + origin/HEAD + 无 main)worktree HEAD == origin/master;陈旧 main 不误选;无可用 base 非零退出;两文件片段 diff 为空; .github 两个 unittest 目录 533 + 14 条全绿 --- .agents/skills/git-branch/SKILL.md | 74 +++++++++++++++++++++++++--- .agents/skills/git-worktree/SKILL.md | 67 ++++++++++++++++++++++--- 2 files changed, 126 insertions(+), 15 deletions(-) diff --git a/.agents/skills/git-branch/SKILL.md b/.agents/skills/git-branch/SKILL.md index 85ab3e0..fa6fb7c 100644 --- a/.agents/skills/git-branch/SKILL.md +++ b/.agents/skills/git-branch/SKILL.md @@ -21,6 +21,52 @@ When an issue ID is provided, use one `gh issue view --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 ` 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 `, 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 @@ -31,17 +77,31 @@ branch is unsafe. Validate the name with: git check-ref-format --branch ``` -Use the repository's documented base, otherwise `main`. In the same repository -prefer `origin/` over `upstream/`; 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 -git switch --no-track -c origin/ +base=$(resolve_base "$documented_base" "$user_base") || { + echo "No usable base branch. Candidates:" >&2 + git branch -r >&2 + echo "Pass --base 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 "$base_ref" ``` +In the same repository prefer `origin/` over `upstream/`; 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. diff --git a/.agents/skills/git-worktree/SKILL.md b/.agents/skills/git-worktree/SKILL.md index 5f7b113..8f1394f 100644 --- a/.agents/skills/git-worktree/SKILL.md +++ b/.agents/skills/git-worktree/SKILL.md @@ -9,7 +9,7 @@ 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 +## Naming Use the `git-branch` naming rules: issue work is `/-`, other work uses a user-provided name, and no @@ -18,21 +18,72 @@ call for naming context and continue from the request if it fails. Store the worktree at `.worktrees/` so branch path segments are preserved. The repository should ignore `.worktrees/`. -Default the base to `main`; same-repository work prefers `origin/`, then -local ``, 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. +## 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 +} +``` + +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 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/`; 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 `, create -parent directories as needed, then run the equivalent of: +parent directories as needed, resolve the base as shown above, then run the +equivalent of: ```bash -git worktree add --no-track -b .worktrees/ +git worktree add --no-track -b .worktrees/ "$base_ref" ``` Verify the new worktree's branch, status, and `pwd`. Report its path, base, From bd417c3a9a9471933c061fdea6199d137294a8a0 Mon Sep 17 00:00:00 2001 From: echuan-agent Date: Wed, 4 Nov 2026 06:45:10 +0000 Subject: [PATCH 2/2] =?UTF-8?q?docs(skills):=20worktree=20=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E6=94=B9=E4=B8=BA=E5=9F=BA=E4=BA=8E=E4=BB=93=E5=BA=93?= =?UTF-8?q?=E6=A0=B9=E7=9A=84=E7=BB=9D=E5=AF=B9=E8=B7=AF=E5=BE=84=20(#336)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原因:SKILL 把路径写成相对 `.worktrees/`,在 monorepo 子目录 (如 packages/app)调用时会建到 `/.worktrees/`:根目录 ignore 覆盖不到, 产生未跟踪的嵌套 worktree,且报告里的 `cd .worktrees/...` 指引失效。 改动: - 新增 ## Repository root 段:先 `git rev-parse --show-toplevel` 取根, 非仓库内明确报错并非零退出,不回落到相对路径 - Naming / Create 的目标改为绝对路径 "$repo_root/.worktrees/" - 边界:目标已存在不覆盖并非零退出;`git check-ignore -q` 只提示不改 .gitignore - Report 段 cd 指引改为 `cd "$repo_root/.worktrees/"` 验证:fixture B(packages/app 内执行)根目录建出 worktree、 子目录无 .worktrees、git status --porcelain 为空、worktree HEAD == origin/main、 cd 指引可用;非仓库内报错退出;重复创建拒绝覆盖;未忽略时只警告 --- .agents/skills/git-worktree/SKILL.md | 61 +++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/.agents/skills/git-worktree/SKILL.md b/.agents/skills/git-worktree/SKILL.md index 8f1394f..8da9036 100644 --- a/.agents/skills/git-worktree/SKILL.md +++ b/.agents/skills/git-worktree/SKILL.md @@ -9,14 +9,33 @@ 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. +## 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/" +``` + +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 `/-`, 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/` so branch path segments are preserved. -The repository should ignore `.worktrees/`. +worktree at `"$repo_root/.worktrees/"` so branch path segments are +preserved and a subdirectory call still lands under the repository root. The +repository should ignore `.worktrees/`. ## Base @@ -77,18 +96,40 @@ 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 `, create -parent directories as needed, resolve the base as shown above, 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 `, 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 "$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 .worktrees/ "$base_ref" +cd "$repo_root/.worktrees/" ``` -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/`. +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.