发布于 更新于 约 8 分钟读完 · 2,979 字

本文目录 14 节
  1. 事实记录(不可修改区)
  2. 改动
  3. 验证
  4. 弧线:「文件散」不是要解决的问题
  5. 发现
  6. 1. 挪脚本等于挪状态,对账号工具来说等于换了一台设备 ⚠️首次
  7. 2. 四处「没报错就当成功了」
  8. 方法论
  9. 移动类改动的验证:前后对照,而不是「跑一下没报错」
  10. 不能实际运行的入口,用替身来验证
  11. 升级 CI 依赖:逐个大版本读不兼容项,对照自己的用法
  12. 记忆里的「状态」会过期
  13. 收口状态
  14. 关联文档

2026-09-17 · vpn-guard 目录整理收口 · 挪脚本等于挪状态与四处静默放行 · 迭代复盘

入档:2026-09-17 来源:作者一句「整理一下项目架构,现在文件有点散」,最后做成了 vpn-guard 的一轮目录整理,外加 CI 维护和分支清理,之后项目收口 状态:已落地并推送。main = 7191a14,与 origin 同步;CI 两轮 ubuntu + macOS 全部通过;已合并分支全部删除 环境:Windows 11 Pro,Git Bash + PowerShell 5.1,GitHub Actions(ubuntu-latest / macos-latest)


事实记录(不可修改区)

改动

commit内容
96f80ca7 个 browse-XX.ps1 和 launch-claude-desktop.cmd 移入 shortcuts/;verify-classifier.sh、verify-unix.sh 移入 tests/;CI、中英 README、CONTRIBUTING 同步更新。共 14 个文件,+88 / −28
7191a14actions/checkout@v4 → @v7(v4 跑在已弃用的 Node 20 上)
  • 根目录下受 git 跟踪的文件从 30 个减到 20 个。主脚本、residential-asn.txt、webrtc-leak-test.html、一键体检.* 都没动,用户侧命令一条没变。
  • 删除两个已合并分支:chore/tidy-layout(本轮)、feat/exit-provenance-gate(08-19 那轮的,早已合入 main,远端没有这个分支)。

验证

检查改前(本机)改后(本机)CI
verify-classifier.sh49/4949/49,输出与改前逐字节一致;在仓库外的目录运行,结果也一致两平台 49/49
verify-unix.sh通过 7 / 失败 4通过、失败的清单与改前逐行一致(4 项失败是它在 Windows 上本来就有的环境项)两平台通过 15 / 失败 0
shellcheck本机未安装—ubuntu 实际执行,无 warning 及以上问题;macOS 未预装,跳过
shortcuts\browse-jp.ps1 -DryRun—正常调到 browse-vpn.ps1 并输出决策—
launch-claude-desktop.cmd—没有实际运行(会拉起正在使用的 Claude 桌面版),用替身验证了路径和参数—
  • CI run:35227000953(目录整理后,ubuntu 25s / macOS 1m6s);35227790159(升级 checkout 后,49s / 1m14s,Node 20 弃用告警消失)。
  • 两个归因入档前补做了复现:缩写提交号查 run 返回空、完整 SHA 立刻查到;printf '%s\n' 'a\\b' 在本环境打印出 a\b。
  • 数据来源:git log、本机测试改前改后的输出 diff、gh run view 与 job 日志。

弧线:「文件散」不是要解决的问题

作者要的是整理结构,但真正要先回答的是:哪些文件能动。 读完代码,能动的范围很窄:

  • 主脚本从自身所在目录读取 residential-asn.txt 和 webrtc-leak-test.html;
  • browse-vpn 把 Chrome 配置目录 chrome-XX-profile/ 也建在自身所在目录;
  • CONTRIBUTING 规定分发方式是「拷一个脚本走就能用」,不能引入共享库。

于是我列了三个方案(只挪杂项 / 按平台拆 / 按工具拆),每个都写明代价,交给作者选:

  • 按平台拆:要改 5 个脚本的文件查找逻辑,Chrome 配置目录要重新定位,README 里每份约 60 条命令路径全部失效。
  • 按工具拆:除了上面这些,还多一个结构问题:webrtc-leak-test.html 同时被自查和浏览会话读取,拆开后要么复制两份,要么跨目录查找。

作者选了只挪杂项。

判据:重构目录前,先列出所有「靠位置定位的东西」:同目录读取的文件、写在脚本旁边的状态、被别人按路径调用的入口。能不能动由这份清单决定,不由审美决定。

动手前还核查了一遍现状:桌面、开始菜单、启动项、任务栏固定项和计划任务里,有没有快捷方式指向这个目录(结果是没有)。这些东西不在仓库里,grep 看不见,文件一挪就会悄悄失效。这是 开工前先对基线律_v1 第五种形态(环境基线)的一次正面应用。


发现

1. 挪脚本等于挪状态,对账号工具来说等于换了一台设备 ⚠️首次

$BaseDir = $PSScriptRoot 这一行,让 Chrome 配置目录跟着脚本走。假如把 browse-vpn.ps1 挪进 windows/,下次启动就会在 windows/ 下建一个空配置:

更隐蔽的是,这个过程不报错:脚本照常运行,时区和语言照常对齐,只是身份悄悄换了。一个为了保护账号基线而写的工具,一次「整理目录」就能替用户制造一次基线跳变。

判据:当状态目录的位置跟着代码走时,重构目录就等于迁移数据。 要么不动,要么显式迁移,并在发布说明里写清楚。本轮选择不动,并把这条约束写进了中英 README 和 CONTRIBUTING,免得下一个整理的人再踩。

顺带验证了一个细节:快捷入口改成 "$PSScriptRoot\..\browse-vpn.ps1" 之后,被调脚本拿到的 $PSScriptRoot 是否还是干净路径(不含 ..)?这决定 Chrome 配置目录的路径字符串会不会变。我先在临时目录写了一个两行的探针,确认是干净路径,才采用这种最小改法。

2. 四处「没报错就当成功了」

#位置表现怎么发现的处理
①CI 的多字节变量检查用 ./*.sh 通配,脚本挪进 tests/ 后自动掉出检查范围,检查照样通过挪文件前逐条读了 CI 步骤事先预防:通配补上 tests/*.sh
②用 Git Bash 的 sed -i 批量改引用7 个 .ps1 的替换没生效(sed 匹配不到也不报错);.cmd 里的 \a 变成了响铃控制字符;被处理的文件 CRLF 全变成 LFgit diff --stat 只列出 1 个文件(应该是 8 个);cat -A 看到 ^G;file 显示换行符变了还原后改用 Edit 工具逐个修改,改完用 file 核对 BOM 和换行符
③CI 的 shellcheck 步骤找不到工具时 echo 一句「跳过」然后正常退出;macOS runner 没预装,所以它在 macOS 上从来没执行过翻 job 日志,确认 ubuntu 实际执行了、macOS 是跳过原有行为,记为遗留项
④在后台等 CI 结果的任务用缩写提交号查 run,gh run list --commit 返回空,后面的 watch 和 view 全失败,但整条后台命令以退出码 0 结束(最后一段 head 成功了),通知显示「已完成」读了任务的实际输出,而不是只看完成通知改用完整 SHA 重查,在前台等到真实结果

关于 ②:我在命令里写的是 \\,到 bash 手里只剩一个 \。事后用 printf '%s\n' 'a\\b' 复现:单引号里写了两个反斜杠,打印出来只有一个。可见这一层反斜杠在命令交给 shell 之前就被折叠了,不是 sed 或 MSYS 的问题。结果是:模式里的 \b 被 sed 当成词边界,静默匹配不到;替换里的 \a 被当成响铃符。结论:在这个环境里,用 shell 命令改带反斜杠的 Windows 路径,不能相信命令里写的转义,改用文件编辑工具。已补进 Windows下编码与DPI的所见非真相_v1,作为第八次验证。

关于 ④:--commit 只认完整 SHA,缩写查不到,但也不报错。入档前复现过:缩写返回空,完整 SHA 立刻查到。

关于 ③:它和 2026-07-03_云端定时内容生产连环坑复盘_全绿不等于已发_v1 里「当日文件已存在 → 跳过 → 显示成功」是同一个问题:在 CI 面板上,「跳过」和「通过」长得一模一样。 这是跨项目的第二次验证。

判据(四条合并):一个动作没报错,只能说明它没崩,不能说明它做了该做的事。验收要看产物: diff 里该有几个文件、日志里那条命令是不是真的执行了、查询是不是真的拿到了对象。


方法论

移动类改动的验证:前后对照,而不是「跑一下没报错」

  1. 改之前先跑一遍,把输出存下来(分类器测试的全文输出、verify-unix 的通过/失败清单);
  2. 改完再跑,和基线逐字节 diff。要确认的不是「还是全过」,而是「和原来一模一样」;
  3. 换一个工作目录再跑一次:测试脚本改成了先切到仓库根目录,这个改动只有从别的目录运行才验证得到。

verify-unix.sh 在 Windows 上本来就有 4 项失败。只看「有没有失败」会得出错误结论;和基线清单对照,才能确认「失败的还是原来那 4 项」。

不能实际运行的入口,用替身来验证

launch-claude-desktop.cmd 会拉起 Claude 桌面版,而执行这次任务的正是这个程序,不能真跑。做法:在临时目录里复刻 shortcuts/ 这一级结构,放一个只打印参数的假 app-vpn.ps1,再把真的 .cmd 拷进去执行。输出是 reached app-vpn.ps1 args=claude-desktop -SystemTz,路径解析和参数传递都得到了证实,被测程序一次都没启动。

替身验证的是调用链,不是被调程序本身。 被调程序这一轮没有改动,不需要重测。

升级 CI 依赖:逐个大版本读不兼容项,对照自己的用法

v4 → v7 跨了三个大版本,逐个读发布说明:

  • v5 改用 Node 24,要求 runner ≥ 2.327.1;
  • v6 把 Git 凭据改存到单独的文件,只影响检出后还要带认证执行 git 命令的工作流;
  • v7 禁止在 pull_request_target / workflow_run 事件里检出 fork 的 PR。

本工作流只由 push / pull_request / workflow_dispatch 触发,检出后也不推送,三条都不沾边,所以直接升到 v7。推送后以「告警消失」作为验收。「有新版」不是升级的理由,「不兼容项和我无关」才是。

记忆里的「状态」会过期

会话开头读到的项目记忆写着「feat/exit-provenance-gate 未合入 main」。用 git merge-base --is-ancestor 一查,早就合进去了。这次没有据此做任何事;但如果信了它,会以为还有一条没收尾的工作线。记忆已更正。这是 约束型结论先复核律_文档写的不可能会过期_v1 里「状态比约束更容易过期」在 AI 记忆上的表现。


收口状态

  • 代码:main = 7191a14,与 origin 同步;本地只剩 main 分支;CI 两个平台全部通过。
  • 结构约定已写进文档:中英 README 的目录结构段,CONTRIBUTING 里的「主脚本留在根目录」。
  • 遗留项(本轮发现,未处理):
    • 根目录有一个被 git 忽略的 chrome-#-profile/(2026-07-31 创建),看起来是某次运行拿到的国家码是 #,原因未查;
    • residential-asn.txt 的头注释说只有 auto-select-node.ps1 读它,实际上两个自查脚本也读;
    • field-report.sh 不在 CI 的语法检查和 shellcheck 名单里;
    • 多字节检查用的 perl -ne 跨文件时 $. 不会归零,报出的行号是累计值;
    • shellcheck 在 macOS 上从来没执行过(见发现 2 的第 ③ 条)。

关联文档

类型/平台工程主题/账号安全主题/回归测试主题/CI