1实现原理 · 为什么它能做到
整体定位是『编排层 SKILL.md + 6 个自带 bash 取证脚本』,且所有脚本在破坏动作前一律非破坏性/增量式——恢复动作本身绝不能让损失更大。
until a step is explicitly labeled destructive — recovery must never make the loss worse.
『我会丢掉什么』的权威检查由 git_loss_audit.sh 实现:先 fetch 刷新远端跟踪 ref,再枚举每个 linked worktree 的 HEAD 与 status、本地分支/标签、stash、dangling commit,任何本地独有 commit 或脏/不可读 worktree 会 exit 1。
# It refreshes remote-tracking refs with `fetch`, then only inspects repository/worktree state. It
防『审计干净但东西其实在另一个 clone 里』:git_find_all_checkouts.sh 做机器级 checkout 发现,靠规范化 remote URL(SSH/HTTPS 等价)或『共享任一 commit 历史』来识别同一仓库的其他 checkout,从不按目录名匹配。
# not `git worktree list`, not `git branch -a`, not `git fsck`, not `git stash list`,
合并判定不数 commit,而是做内容级试验合并:`git merge-tree --write-tree <base> <branch>` 在内存里把分支合回 base,若结果树 == base 树则判 MERGED (content contained),否则 NEEDS REVIEW。
# 2. Else do a trial 3-way merge of the branch INTO the base (git merge-tree, in memory, no
gc 前保全:把每个 dangling commit 钉在隐藏 ref 命名空间 refs/dangling-backup/<sha> 下——有 ref 指着的对象永不被 gc 回收,且不会污染 `git branch`/`git stash list`。
git update-ref "refs/dangling-backup/$sha" "$sha"
销毁前导出:stash 导出为完整二进制 patch,并额外用 `git archive "$ref^3"` 把 stash 的第三父提交(`git stash -u/-a` 携带的未跟踪文件)打成 tar——因为 `git stash show -p` 不显示这些文件,只导 patch 会静默丢失它们。
# -> stashN-untracked.tar the often-forgotten THIRD parent:
删除前的 compare-and-swap 门禁:`--verify-current BUNDLE` 逐条比对 bundle 里记录的 ref 与仓库当前值,任何 ref 移动/消失即 exit 1。
echo "REFSET_CHANGED checked=$CHECKED changed=$MOVED — rebuild and re-verify the bundle before deletion" >&2
独立 clone 退役准备:脚本先按一长串拒绝清单核查(脏/未跟踪/ignored 字节、stash、reflog-only commit、clone 独有不可达对象、linked worktree、本地 submodule、partial/promisor clone、Git LFS/annex 对象库、tracked content filter、include.path/hooksPath 间接层、浅克隆),全部通过才冻结证据。
# It refuses to certify a clone that still has working-tree bytes, ignored files,
写操作面被硬性限制:备份目录必须是绝对路径、必须不存在(拒绝覆盖)、必须在 clone 与 survivor 两个仓库之外;脚本自身从不移动/删除 checkout、从不改 ref。
"$CLONE/"*|"$SURVIVOR/"*) die "backup directory must be outside both repositories" ;;
工作流纪律以『Outcome contract + 授权边界』写进 prompt:证据范围 ≠ 行动范围,授权是按对象而非按仓库的,保留/退役分相,完成标准用用户世界的可观测事实(远端已有该 commit、分支/worktree 状态正确)而非 bundle 数量或校验和。
- **Evidence scope is not action scope.** A read-only audit may discover another clone, ref,
脚本清单表逐条标注『是否变更』(read-only / 只动 remote-tracking refs / 只加 refs / 只写备份文件),并声明所有 helper 都不授权 checkout、reset、push、stash drop、branch -d、gc。
the helpers authorizes `checkout`, `reset`, `push`, `stash drop`, `branch -d`, or `gc`.
2核心能力
3外部依赖
| 类型 | 依赖 |
|---|---|
| cli | git(含 worktree/fsck/reflog/bundle/merge-tree/update-ref/for-each-ref 等) |
| cli | git ≥ 2.38(merge-tree --write-tree 是内容级判定的前提) |
| cli | uv(仅维护者跑回归测试时用到) |
| cli | sha256sum / shasum(bundle 指纹;缺失即报错) |
| cli | tar(打包 config/hooks/info 元数据) |
| cli | stat(跨 BSD/GNU 的 mtime 与文件模式读取) |
| cli | lsof(macOS 上探测 clone 是否仍被进程占用;仅文档步骤,不在脚本内) |
| network | git fetch(由脚本执行;对远端主机发起出站连接,可能触发宿主 git 凭证助手) |
| network | git fetch --all --prune(合并判定脚本执行,同一出站面) |
| network | git push(仅文本指导:把 backup 分支推到 origin 做三重备份;无脚本执行) |
| package | 无第三方包(Git + 标准 Bash 即可;测试仅用 Python 标准库 unittest/tempfile/subprocess) |
4风险提醒 风险提醒:蓝色 · 知晓即可
- 需执行真实 shell 脚本,且脚本会对远端发起 git fetch(出站网络 + 可能触发宿主凭证助手)。 — loss_audit 与 verify_branch_merged 都会 fetch;在共享/多写者 checkout 中,fetch 会移动 remote-tracking refs——SKILL.md 为此要求先转移独占写权,否则只能保持只读并声明『合并判定不可用』。执行前应确认目标仓库与 remote 身份是你预期的那个。
- 会被审计仓库的内容进入 agent 上下文(提示注入面)。 — 分支名、stash 消息、commit subject、文件路径都会打印并被模型读取;恶意仓库可借此注入指令。脚本有部分加固(禁用 repo 提供的 fsmonitor、GIT_NO_LAZY_FETCH、stash 消息清洗为文件名、`--` 防 pathspec 混淆),但这不是通用注入防御;宿主应对输出保持数据/指令分离的处置。
- 『非破坏性』是脚本层的保证,不是整个 skill 的保证。 — SKILL.md 会引导执行真正的破坏性步骤(rebase --onto 修历史、update-ref -d 撤销 pin、git worktree remove、stash drop、branch -d/-D),这些命令不在脚本里,风险由执行者与授权门禁承担。
- 退役脚本的『无损失』有明确能力边界,容易被误读为全量保证。 — Git LFS/annex 对象库、tracked content filter、include.path/hooksPath 间接层、promisor/partial 克隆、浅克隆、本地 submodule 均被拒绝并要求人工单独审计——即遇到这些状态脚本会直接失败,而不会替你保住那些字节。
- 默认备份位置在 $HOME/.git-backups,且 references 建议改用户全局 git 配置(gc.reflogExpire / gc.reflogExpireUnreachable)。 — 备份目录若被用户清理策略覆盖会失去意义(SKILL.md 要求把备份放在仓库外并写 README 说明来源);`git config --global` 会修改用户 ~/.gitconfig,属文档级建议,需用户自行判断。
- 证据只对 pin 的 commit 有效,且 skill 体量与纪律密度很高。 — 本报告针对 commit d5c4678cb5d4fd6acc9c922690df035dbd33d247(仓库 CHANGELOG 显示 git-safety-net 迭代到 v1.17.0,条款仍在快速增补);SKILL.md 58KB + references 85KB 的规约密度意味着使用者(或 agent)可能只执行到部分条款,导致『按 skill 走却漏了门禁』。
5第二遍独立确认
- [ok] external_deps 逐条反查调用点:git 系列命令 — 在 6 个脚本里逐条确认 git 子命令的真实执行点(fetch/worktree list/fsck --dangling/stash list/bundle create+verify/list-heads/reflog/for-each-ref/update-ref/check-attr/submodule status/cat-file/rev-list)。全部为执行路径上的真实调用,非注释。
- [ok] external_deps:uv 与 lsof 是否真在脚本里 — 两者都不在脚本中执行:`uv run python -m unittest discover -s tests -p 'test_*.py'` 只出现在 SKILL.md § Maintainer verification(维护者本地跑测试);lsof 只出现在 references/merge_verification.md 的退役 READ-DO 序列(macOS 探测进程占用)。已按『文档层工具』而非『运行时依赖』归类。
- [ok] 网络调用面是否存在遗漏(隐藏脚本/混淆内容) — glob 全量(hidden=true, gitignore=false)显示该 skill 目录仅 17 个文件:SKILL.md、3 个 references、6 个 scripts、6 个 tests、evals/evals.json、agents/openai.yaml,无可执行二进制、无压缩/混淆内容。全目录 grep curl/wget/gh/nc/python -c 零命中;唯一 https:// 出现在 find_all_checkouts 的 URL 归一化注释示例里。
- [ok] 反例排查:git_find_all_checkouts.sh 号称不联网,但脚本里有 `git fetch --all --prune` — 该行(第 317 行)位于 `cat <<'FRESHNESS'` heredoc 内,是打印给用户看的建议,不在执行路径上;脚本头部 `# NON-DESTRUCTIVE: runs only \`find\` plus read-only Git queries` 与代码一致(唯一外部命令是 find 与只读 git 查询)。不构成 discrepancy,因此改为标注为『文本指导』。
- [ok] 反查『脚本非破坏性』是否属实(是否有删除/改写仓库对象) — 对 rm -rf / branch -D / reset --hard / stash drop / worktree remove / update-ref -d / gc 全目录扫描:脚本内仅两处相关文本——preserve_danglers 的 `rm -f "$OUT"`(删除自己刚写失败的 patch 文件)与 loss_audit 的提示字符串 `never use 'git worktree remove --force'`。所有真正的破坏性命令(`git update-ref -d`、`git worktree remove`、`--force-with-lease` 等)只出现在 SKILL.md/references 供人执行的步骤中。
- [ok] 『写操作面』核对:备份到底写到哪里、是否会覆盖既有数据 — export 脚本默认写 $HOME/.git-backups/<date>-<repo> 并可 --out 覆盖;preserve_danglers 的 --patch-dir 先绝对化再 mkdir -p;prepare_clone_retirement 强制 `[ ! -e "$OUT" ]`(拒绝已存在目录)、要求绝对路径、并 `case "$OUT/" in "$CLONE/"*|"$SURVIVOR/"*) die ...` 拒绝把备份放进被退役/保留的仓库内。三处均与 SKILL.md『备份放仓库外』一致。
- [ok] 凭证/全局配置读取面反查 — 全目录 grep --global / .gitconfig / credential / HOME 的结果:脚本内只有 export 的 `$HOME/.git-backups/...` 默认输出路径;`git config --global` 只出现在两处 references 中作为给用户的建议命令。prepare_clone_retirement 读取仓库私有配置时显式用 `git config --file <config> --no-includes`(不吞并用户全局配置)。无 token/env 读取。
- [ok] 实现原理是否夸大:『内容级判定是 sound 而非启发式』 — 与脚本逐行比对成立:脚本先 `git merge-base --is-ancestor` 判 ancestor,再 `git merge-tree --write-tree <base> <branch>` 比较结果树与 base 树;MT_RC>=128(git<2.38)时明确退回 NEEDS REVIEW 而非猜 MERGED。声明与实现一致,未夸大。
6结论
b2b944f8a078459b…d5c4678cb5