diff --git a/docs/agent/followups.md b/docs/agent/followups.md index d77665c..ddaa142 100644 --- a/docs/agent/followups.md +++ b/docs/agent/followups.md @@ -36,4 +36,6 @@ - [ ] FU-10 · C · src=PR#45 第八轮 [Low] · 2026-08-06 · **注释与文档漂移(收口时未清)**:safe-cleanup.sh 里若干注释仍在引用已删掉的东西 —— :150-151 / :247 / :317 提到 `update-ref` 的 expected-old-value(那个函数已删)、:501 附近说 §3「deletes on the SERVER」(已改成完全不处理)。另外 run.md:76 写「不加 --delete-branch,远程分支删除统一交给 safe-cleanup」,但 safe-cleanup 已不处理远程、git-guard 又硬拒 --delete-branch,于是**远程 head 分支的清理在文档流程里没有主人**,只剩 GitHub 的 auto-delete-on-merge,而 skill 既不检查该设置是否打开、也没在任何地方教人去开。改法:清一遍过时注释;在 doctor 里加一条只读检查「本仓库是否开了 auto-delete-on-merge」并在关闭时提示 - [ ] FU-11 · C · src=PR#45 第八轮 [Low] · 2026-08-06 · §1b 现在是个纯报告,但计价没变:每个候选分支一次 gh API 调用(本仓库约 25 次 / 20.6 秒),而 status.md 让它在**每次** `pilot status` 都跑。改法二选一:① 按 tip sha 做本地缓存(tip 没动就不重查);② 在 status.md 里把 `--squash-merged` 改成显式 opt-in,默认不带 - [ ] FU-12 · C · src=PR#45 第八轮 [Low] · 2026-08-06 · §1b 会把「某个活着的 worktree 正在 rebase/bisect 的分支」当成游离分支列出来并打印 `git branch -D `,而同一次运行里 §2 对那个 worktree 打的是 KEEP —— **同一份输出自相矛盾**。已验证无害(git 2.50 会拒绝 `cannot delete branch 'x' used by worktree`,分支完好,rebase --continue 正常),所以只是输出问题。顺带记一句:这也说明删掉 `ref_in_use_by_worktree` 是对的 —— `git branch -D` 自带那个拒绝,而 `update-ref -d` 没有 +- [ ] FU-13 · C · src=PR#45 第九轮 APPROVE 附带 [Low] · 2026-08-06 · **`--remote` / `--remote-name` 已成摆设**:两个 flag 仍被解析、仍出现在 `--help` 的 Usage 和未知参数提示里,但 `--remote-name` 现在只喂给 `## Remote branches ($remote_name)` 这个标题,`--remote` 只开关一段说明文字。而且那段说明文字**从文档流程里已经不可达** —— status.md/run.md 都不再传 `--remote`,所以它只会对「传了一个文档已不再提及的 flag」的调用者打印。改法:把「去开 GitHub auto-delete-on-merge」这句折进默认输出或 doctor(FU-10 已记 doctor 那半),然后把两个 flag 连同 `.pilot.yml` 的 `remote:` 键一起考虑是否移除 +- [ ] FU-14 · C · src=PR#45 第九轮 APPROVE 附带 [Low] · 2026-08-06 · `--integration X` 只决定**候选列表**,而真正执行的 `git branch -d` 判的是「合进 **HEAD**」。站在非 integration 分支上时,每一个真正已合并的候选都会打印 `SKIP (not safely merged)`。方向是 fail-safe(只会漏删,不会误删),但 status.md 要求模型把输出**原样**转达给用户,于是它会如实转达一个**假的「未合并」**。改法:执行前切到 integration,或改用 `git branch -d` 之外的等价判断,或在这一行的输出里说明「判据是 HEAD,不是 --integration」 diff --git a/plugins/pilot/skills/pilot/README.md b/plugins/pilot/skills/pilot/README.md index a7b3404..1684044 100644 --- a/plugins/pilot/skills/pilot/README.md +++ b/plugins/pilot/skills/pilot/README.md @@ -87,7 +87,6 @@ base_branch: main integration_branch: preview # PR 合并进这里,不是主干 protect_patterns: [release, hotfix] remote: origin -allow_remote_cleanup: false docs_dir: docs/agent ``` diff --git a/plugins/pilot/skills/pilot/SKILL.md b/plugins/pilot/skills/pilot/SKILL.md index 54701a6..8b2a352 100644 --- a/plugins/pilot/skills/pilot/SKILL.md +++ b/plugins/pilot/skills/pilot/SKILL.md @@ -62,7 +62,7 @@ base_branch: main # 主干,受保护,禁止直推 integration_branch: preview # PR 合并进这里;不存在则回退到 base_branch protect_patterns: [release, hotfix] # 额外保护的分支前缀 remote: origin -allow_remote_cleanup: false # 删除远程已合并分支需显式置 true +# allow_remote_cleanup 已废弃:脚本不再删远程分支,没有任何代码读这个键(用 GitHub auto-delete-on-merge) docs_dir: docs/agent # 规划/运行态文档目录 planning_source: docs # docs(默认)=查上面那七个文件 | external=规划在别处,门禁不查(见下) ``` diff --git a/plugins/pilot/skills/pilot/phases/status.md b/plugins/pilot/skills/pilot/phases/status.md index 12fe1a5..cd2d5e4 100644 --- a/plugins/pilot/skills/pilot/phases/status.md +++ b/plugins/pilot/skills/pilot/phases/status.md @@ -39,8 +39,11 @@ ``` bash /scripts/safe-cleanup.sh --integration [--squash-merged] --apply ``` - - 要连带删远程已合并分支:仅当 `.pilot.yml` 的 `allow_remote_cleanup: true`,且用户明确同意,才加 `--remote`。 - - 无人值守模式(由 /loop 调用且用户已预先授权清理):可直接 `--apply`,但**永远不加 `--remote`** 除非配置显式开启。 + - **远程分支脚本不处理**(`--remote` 只会打印一段说明)。让用户去仓库设置里开 GitHub 的 + **auto-delete-on-merge** —— 它在合并真正发生的那一侧判定,而从 clone 里判断要靠可能过期的 + remote-tracking ref,且 bare 仓库不留 reflog。 + - 无人值守模式(由 /loop 调用且用户已预先授权清理):可直接 `--apply`。脚本此时唯一会执行的 + 删除仍是 `git branch -d`,其余照样只列。 6. **建议下一步**:基于 progress.md 指出「下一个该做的 READY task」;若没有规划文档,建议 `pilot plan`。 diff --git a/plugins/pilot/skills/pilot/scripts/safe-cleanup.sh b/plugins/pilot/skills/pilot/scripts/safe-cleanup.sh index 8c8d384..861f268 100755 --- a/plugins/pilot/skills/pilot/scripts/safe-cleanup.sh +++ b/plugins/pilot/skills/pilot/scripts/safe-cleanup.sh @@ -408,7 +408,16 @@ echo # ---- 2. Worktrees: clean + merged only -------------------------------------- echo "## Worktrees (clean + merged only)" -main_root="$(git rev-parse --show-toplevel)" +# The PRIMARY worktree, not "whichever worktree you are standing in". `--show-toplevel` returns +# the CURRENT worktree, so running from a linked one — which this skill's own doctrine (one task = +# one worktree) makes the normal case, and status.md runs on every `pilot status` — made §2 skip +# the linked worktree it was standing in and instead print a command to remove the MAIN checkout: +# remove with: git worktree remove /path/to/repo && git branch -d primaryfeat +# Harmless as printed output (git refuses both halves), but it is a wrong command handed to a human +# to paste, and on the parent commit — where §2 still executed the removal — it was an attempt to +# delete the main checkout. The first block of `git worktree list --porcelain` is always primary. +main_root="$(git worktree list --porcelain 2>/dev/null | sed -n 's/^worktree //p' | head -1)" +[ -n "$main_root" ] || main_root="$(git rev-parse --show-toplevel 2>/dev/null || true)" # `git worktree list --porcelain` emits blocks separated by blank lines. wt_path=""; wt_branch="" handle_wt() { @@ -489,7 +498,17 @@ handle_wt() { # This also disposes of the mid-bisect case (`git bisect start` with HEAD still on a branch reports # porcelain-clean, and removing the worktree takes BISECT_LOG/BISECT_START with it). printf ' %s [%s] (%s)\n' "$path" "$short" "$via" - printf ' remove with: git worktree remove %q && git branch -d %q\n' "$path" "$short" + # `-d` for git-native evidence, `-D` for squash evidence — the same split §1b uses, and for the + # same reason: after a squash the original tip is not an ancestor, so `git branch -d` CANNOT + # succeed. Printing `-d` for a squash-merged worktree produced a half-completed paste — the + # directory removed, then `error: the branch 'x' is not fully merged` — leaving an orphan branch. + # In a squash repo that was every single line of this section, i.e. the only repo shape this + # feature exists for. + if [ "$via" = "git" ]; then + printf ' remove with: git worktree remove %q && git branch -d %q\n' "$path" "$short" + else + printf ' remove with: git worktree remove %q && git branch -D %q\n' "$path" "$short" + fi printf ' (check for ignored files first — `git worktree remove` deletes the directory:\n' printf ' git -C %q status --porcelain --ignored=matching)\n' "$path" } diff --git a/plugins/pilot/skills/pilot/templates/pilot.example.yml b/plugins/pilot/skills/pilot/templates/pilot.example.yml index fb68a81..b6692a6 100644 --- a/plugins/pilot/skills/pilot/templates/pilot.example.yml +++ b/plugins/pilot/skills/pilot/templates/pilot.example.yml @@ -7,7 +7,10 @@ protect_patterns: # 额外保护的分支前缀(如 release/1.2 会 - release - hotfix remote: origin -allow_remote_cleanup: false # 删除远程已合并分支需显式置 true(无人值守默认不删远程) +# allow_remote_cleanup 已废弃 —— 脚本不再删远程分支,没有任何代码读这个键。 +# 远程 head 分支请用 GitHub 的 auto-delete-on-merge(仓库设置里开),它在合并真正 +# 发生的那一侧判定;从 clone 里判断要靠可能过期的 remote-tracking ref,而且 bare +# 仓库不留 reflog,是所有删除里唯一完全没有后悔药的。 docs_dir: docs/agent # 规划/运行态文档目录 # 规划在哪里。docs(默认)= 起跑门禁检查 docs_dir 下那七个文件;