本文目录 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 | 内容 |
|---|---|
96f80ca | 7 个 browse-XX.ps1 和 launch-claude-desktop.cmd 移入 shortcuts/;verify-classifier.sh、verify-unix.sh 移入 tests/;CI、中英 README、CONTRIBUTING 同步更新。共 14 个文件,+88 / −28 |
7191a14 | actions/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.sh | 49/49 | 49/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/ 下建一个空配置:
- 登录态全部丢失,这是看得见的代价;
- 用一个全新的浏览器配置重新登录各平台,在风控眼里就是一台新设备,正好落在 信任资产保得住造不出_账号基线vs人群先验_v1 权重表第 3 层「一号多设备」。
更隐蔽的是,这个过程不报错:脚本照常运行,时区和语言照常对齐,只是身份悄悄换了。一个为了保护账号基线而写的工具,一次「整理目录」就能替用户制造一次基线跳变。
判据:当状态目录的位置跟着代码走时,重构目录就等于迁移数据。 要么不动,要么显式迁移,并在发布说明里写清楚。本轮选择不动,并把这条约束写进了中英 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 全变成 LF | git 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 里该有几个文件、日志里那条命令是不是真的执行了、查询是不是真的拿到了对象。
方法论
移动类改动的验证:前后对照,而不是「跑一下没报错」
- 改之前先跑一遍,把输出存下来(分类器测试的全文输出、verify-unix 的通过/失败清单);
- 改完再跑,和基线逐字节 diff。要确认的不是「还是全过」,而是「和原来一模一样」;
- 换一个工作目录再跑一次:测试脚本改成了先切到仓库根目录,这个改动只有从别的目录运行才验证得到。
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 的第 ③ 条)。
- 根目录有一个被 git 忽略的
关联文档
- ⚠️ 2026-08-19_vpn-guard出口归属门槛与四处假绿修复_迭代复盘_v1 —— 直接前篇:那一轮留下的分支在本轮清理。那一轮的教训是「测试不投毒,就等于没测试」,本轮是「移动文件不做前后对照,就等于没验证」
- 2026-07-14_vpn-guard从工具到宣传片_全链路复盘_v1 —— 同一工具的第一段弧线
- ⭐ 信任资产保得住造不出_账号基线vs人群先验_v1 —— 发现 1 的风险落点:新配置 = 新设备 = 第 3 层「一号多设备」
- 开工前先对基线律_v1 —— 动手前核查快捷方式、改前先存测试基线,都是它「环境基线」形态的应用
- Windows下编码与DPI的所见非真相_v1 —— 发现 2 的第 ② 条已补为该文第八次验证
- 2026-07-03_云端定时内容生产连环坑复盘_全绿不等于已发_v1 —— 发现 2 第 ③ 条「跳过也显示成功」的前例
- 约束型结论先复核律_文档写的不可能会过期_v1 —— 记忆里的「未合并」是状态过期
- 交付前实测证伪律_v1 ——
$PSScriptRoot探针、替身验证、入档前复现两个归因,都是按它执行的 - 09_平台工程索引 —— 平台工程区入口;本文归入「账号与访问」