# AGENTS.md 本文件是 Codex 与 Claude Code 共用的项目开发约定。`CLAUDE.md` 用 `@AGENTS.md` 导入本文件;项目规则只在这里维护。双工具入口、技能发现和使用方法见 `docs/AI-COLLABORATION.md`。 ## 协作方式 - 默认用中文沟通,代码标识符沿用项目现有英文命名。 - 按任务读取相关代码与文档;以代码、测试及用户最新要求为准,发现说明过时就同步修正。 - 本地阅读、编辑、类型检查、单测和构建按任务自主完成。部署或生产数据变更须属于用户授权的任务;已有明确授权不重复确认。普通代码修改不隐含上线或修改生产数据。 - 保留用户已有改动。提交、推送与部署分别按用户要求执行。 - 项目技能唯一源文件在 `.claude/skills/`;`.agents/skills/` 通过相对符号链接供 Codex 发现。修改技能时编辑源文件,避免维护两份副本。 ## 这是什么 本仓库统一维护边缘 VPS 节点的部署管理与 Proxy Station 代理节点、分流规则管理平台。Web 界面管理多协议节点(VMess/Trojan/Shadowsocks/Hysteria2)与策略组、规则,向 macOS Surge、iOS ShadowRocket、Android ClashMeta 下发托管订阅。规则模板来自 [ClashConnectRules/Surge](https://github.com/ClashConnectRules/Surge),边缘舰队的工具与私有交付记录已从原 `../edge-nodes/` 迁入本项目。 ### 当前生产约定(2026-10-04) 最新盘点、完整组顺序与成员、节点启用范围和 Session 变更索引见 [当前生产状态](docs/CURRENT-STATE.md)。日期化体检/变更文档保留当时的数量与验证结果;维护前经管理 API 重读,不能用历史快照或初始化模板覆盖最新生产数据。 - 当前登记 30 个节点,12 个启用、18 个停用;12 个启用组、21 个显式成员、130 条规则(128 启用)。停用节点保留凭据,不因文档整理、排序、部署或导入清单核对而自动启用。应用的节点启用状态不等于边缘 VPS 的运行状态。 - 普通代理流量交给 `🚀 Proxy`,只有它引用 `⚡️ Automatic`。Automatic 为 url-test,成员依次为新加坡、美国、日本;规则不直接指向 Automatic。`🤖 AI` 为 select,成员依次为美国、新加坡、日本、`🧠 AI Nodes`,不引用 Automatic。 - `📋 All Nodes` 与 `🧠 AI Nodes` 都是独立 select 组,开启 includeAllNodes、没有筛选正则或显式成员;AI Nodes 紧接 AI 显示。两组收纳相同的订阅范围内可用节点,各自保存客户端选择,不能用引用 All Nodes 代替独立的 AI Nodes。 - 两组按完整节点名称字符串升序输出,保留 Direct/Warp 出口词。现有节点 sortOrder 全为默认 0,IR 按 sortOrder、name 读取;新增/导入节点保持默认值。非零权重会先于名称排序,调整它影响节点声明及其他动态列表,不只是这两个组。Web 节点页仍按站点、协议与出口展示。 - Streaming 当前承接 Disney、Netflix、TikTok、YouTube、GoogleSearch 及欧洲/韩国/香港/台湾流媒体集合;美国/日本流媒体集合直接交给对应地区组,Emby 交给 Proxy。组显示顺序不决定规则匹配顺序。 - 规则优先级保持:STUN 与 googlevideo UDP 防护 → Unbreak DIRECT → 启用的 AI 规则 → 业务例外/广告与其余分流 → 唯一 FINAL。FINAL 仍为 `🎯 NoAuto,dns-failed`,NoAuto → Mainland → DIRECT。保留 Google 更新/翻译直连例外;全局 PROTOCOL DOH 与 Dia 关键词规则继续停用。 - Apple/Global Services/Speedtest/Other 及无节点的香港/台湾/韩国/英国组已删除,不因模板仍存在而恢复。App Store DIRECT、Apple News 美国、Apple Relay AI 例外继续保留。 ### 节点部署管理 - `edge-nodes/README.md` 是节点工作区入口;`edge-nodes/build-delivery-doc.py` 从 Git 忽略的 `edge-nodes/private/fleet.json` 读取清单并离线生成汇总交付页,不在源码里保存真实站点信息。 - 通用部署脚本、参考资料与占位交付模板唯一源码在 `.claude/skills/edge-node-multi-protocol/`,Codex 从 `.agents/skills/edge-node-multi-protocol` 相对链接加载。部署/重建/维护 VPS 节点使用此技能;发布 Proxy Station 应用用 `deploy`,生产订阅数据查询与修改用 `update-db`。 - 真实配置、面板入口、分享链接、二维码、交付文档和各站点脚本工作副本在 `edge-nodes/private/-edge-node-docs/`,全部禁止入 Git 或公开静态托管,也不发送到在线二维码服务。新增配置/产物用 `umask 077`,私有目录用受控备份转交,新克隆不会携带这些文件。 - 当前舰队为 sg(AWS 新加坡)、la(DMIT 洛杉矶,同时承载 Proxy Station)、rn(RackNerd 洛杉矶,当前工作副本为 `rn2-edge-node-docs/`)、jp(VMISS 东京,裸 nginx)。`rn-edge-node-docs/` 与 `private/archive/` 是历史资料,不作为当前配置来源;原兄弟目录仅保留为迁移前备份,后续在本仓库维护。 - 舰队自 2026-10-03 统一使用 IPv4:系统接口禁用 IPv6,Xray/sing-box 仅解析 IPv4 并拒绝 IPv6 目标(含 WARP),节点域名不保留 AAAA。重建或扩展线路须保留这些约束,详见 `docs/EDGE-IPV4-ONLY.md`;仅禁用网卡或删除 AAAA 不能阻止 WARP 隧道内的 IPv6 转发。 - TCP 基础线路由 nginx+3x-ui/Xray 承载 VMess/Trojan/SS × direct/WARP,QUIC(HY2/TUIC)由 sing-box 独立承载。3x-ui 客户端不能只写投影表,必须保留 `settings.clients`,否则升级迁移会丢失认证。改远端前读取节点交付记录与技能参考,备份受影响配置和数据库,并保留已有证书续期动作。 - 迁移工程、修改脚本或复制文件不会授权远端部署;节点与应用的部署各自按任务执行。应用运行时仍通过管理 API/Web 导入节点,不自动读取私有目录。TUIC/SOCKS5 交付线路不属于本系统当前支持的导入类型。 ## 命令 ```bash CI=true pnpm install --frozen-lockfile # 按锁文件安装依赖 pnpm dev # server :3000 + web :5173(都监听 0.0.0.0,局域网可访问) pnpm -r typecheck # 三个包全量类型检查 pnpm test # 服务端单测(vitest) pnpm --filter @proxy-station/web build # macOS Surge:先取实际 HTTPS 订阅到私有临时文件,再用官方 CLI 与规则集快照验证 python3 scripts/check-surge.py /private/tmp/.conf --fetch-rulesets # 单测过滤(在仓库根目录下) pnpm --filter @proxy-station/server exec vitest run parse pnpm --filter @proxy-station/server exec vitest run -t "round-trip" pnpm --filter @proxy-station/server exec vitest # watch 模式 # 部署到 LA 生产机(web|server|all),详见 .claude/skills/deploy/SKILL.md 与 docs/DEPLOY.md bash .claude/skills/deploy/scripts/deploy.sh all # 本机离线生成边缘舰队交付页(清单与产物均在 Git 忽略的私有目录) python3 edge-nodes/build-delivery-doc.py # 校验实际 Clash 配置;生产验收改用实际 HTTPS 入口,真实 URL 仅在本机使用 umask 077 check_dir="$(mktemp -d /private/tmp/proxy-station-check.XXXXXX)" curl -fsSL --max-time 45 "http://localhost:3000/sub//clash" -o "$check_dir/clash.yaml" && \ test -s "$check_dir/clash.yaml" && mihomo -t -d "$check_dir" -f "$check_dir/clash.yaml" ``` 本机开发及当前生产部署使用 Node 22。依赖使用 pnpm 与已提交的 `pnpm-lock.yaml`;不要混用 npm/yarn 或无故更新锁文件。pnpm 在无 TTY 时安装需要 `CI=true`,原生模块(better-sqlite3、esbuild、vue-demi)的构建许可写在 `pnpm-workspace.yaml` 的 `allowBuilds`。 本机会话注入的代理曾造成 git/安装/网络检查挂起;遇到此问题,在单条命令作用域内用 `env -u HTTPS_PROXY -u https_proxy -u HTTP_PROXY -u http_proxy -u ALL_PROXY -u all_proxy <命令>` 直连。现有运维脚本已自行处理代理。不依赖其他工具的全局指令文件,也不修改用户全局代理配置。 ## 架构 应用部分为 pnpm monorepo:`shared/`(zod schema 与 DTO,前后端共用源码,非构建产物)+ `server/`(Hono + better-sqlite3 + Drizzle)+ `web/`(Vue 3 + Naive UI + Pinia)。生产模式由 server 静态托管 web 产物,单端口。`edge-nodes/` 与节点部署技能是 Bash/Python 运维工具,不加入 pnpm 包,也不随应用部署上传;Docker 构建上下文排除它们。 ### 配置生成流水线(核心) ``` SQLite → buildProfileIR({nodeIds}) → ProfileIR → 四个序列化器 ├─ generateSurge(ir, {selfUrl}) ├─ generateShadowrocket(ir) 节点订阅 ├─ generateShadowrocketConf(ir) 策略组+规则 └─ generateClash(ir, {convertBaseUrl}) ``` `server/src/services/ir.ts` 是配置生成流水线的统一读库入口,把节点、策略组、规则、Surge 原文段落收成与客户端无关的 `ProfileIR`;四个生成器只读 IR,互不依赖。**新增客户端就是加一个序列化器**,不要在生成器里再查数据库。 IR 层承担三件防护,改动时不要绕过: - 停用或已删除的策略组会被剔除,引用它的成员与规则一并跳过 - `FINAL` 规则永远保留,其策略组失效时降级为 `DIRECT` - `nodeIds` 为空数组表示「不限制」,非空则按订阅分配的节点范围过滤 ### 客户端能力矩阵 `shared/src/protocols.ts` 的 `clientSupports()` 决定某节点能否下发给某客户端,生成器据此排除并回报 `excluded`。**Surge 不支持 Shadowsocks over WebSocket**;本项目对 Surge 的 VMess 仅下发 `alterId=0`,加密方式只支持 auto/aes-128-gcm/chacha20-poly1305(转换为 IETF 名称)。其他客户端的能力独立判断。不要把无法表达的输入静默改成另一个协议组合。 ### 协议字段差异 同一协议在三端写法不同,映射集中在 `shared/src/protocols.ts` 与各生成器: - SS cipher:分享链接用 Xray 命名 `chacha20-poly1305`,Surge/Clash 要 `chacha20-ietf-poly1305`(`normalizeSsCipher()`) - VMess `alterId=0` 必须输出 Surge 的 `vmess-aead=true`,否则连不上且症状隐晦 - Hysteria2 的 salamander 混淆:Surge 用 `salamander-password=`,Clash 用 `obfs`/`obfs-password` - SS over WS 在 Clash 侧唯一可行方案是 `plugin: v2ray-plugin` 这些写法参考了 `../sub-router` 项目(用户已在生产使用的同类系统),遇到新的客户端适配问题优先查它。 ### 策略组的两种取节点方式 `includeAllNodes` 与 `filterRegex` 可以共存,生成器里 **filterRegex 分支优先**: - 有 `filterRegex` → 动态筛选:Surge 保留显式节点/内置成员,全部成员组作为 `include-other-group` 来源,并可同时启用 `include-all-proxies=true`;正则只筛动态来源,不筛显式成员。Clash 用 `include-all-proxies: true` + `filter` 并保留 `proxies` 显式成员;ShadowRocket 保留显式成员并用 `policy-regex-filter` 动态收节点。新增同地区节点会自动进组 - 仅 `includeAllNodes` → Surge 静态展开全部节点名,Clash/ShadowRocket 仍用动态引用 **地区组靠节点名称正则匹配,不看 `region` 字段**。节点命名规范与新增地区的完整步骤见 `docs/ADDING-REGIONS.md`——命名不对就进不了地区组。 节点与组共用客户端的策略名称空间:禁止名称中的配置分隔符、引号、反斜线、控制字符和首尾空格;不能与内置策略或另一类实体重名。导入预览与实际写入都走同一 schema。组成员必须存在且类型正确,创建/更新(包括改名)校验完整引用图无环,并在事务内写组、替换成员与级联引用。 Surge 不支持组级 `url=`;组的 `testUrl` 仅用于 Clash/ShadowRocket。Surge 使用 General 的 `proxy-test-url` 或节点 `test-url`,fallback 不输出 tolerance。协议、规则和参数规范以 [Surge 官方手册](https://manual.nssurge.com/) 与本机 `surge-cli --check` 为依据,不能凭其他客户端语法推断。 ### 规则集:映射与兜底转换 模板里的 `RULE-SET`/`DOMAIN-SET` 指向外部 URL。Surge/ShadowRocket 直接透传;Clash 需要 rule-providers,`services/rulesets/mapping.ts` 按序尝试映射到 skk.moe 的 Clash 版与 blackmatrix7 的 Clash 目录,映射不到的标记 `needsConvert`,provider URL 指向自托管端点 `/sub/:token/ruleset/:id.yaml`——该端点实时拉取 Surge 版并用 `convert.ts` 转成 classical provider。规则集拉取带 24h 缓存 + etag,失败回落陈旧缓存。 ### ShadowRocket 分两步导入 ShadowRocket 的原生订阅格式只承载节点,因此拆成两个端点:`/shadowrocket`(节点 base64 URI 列表)与 `/shadowrocket-conf`(策略组与规则的 .conf)。节点订阅按完整名称字符串升序排列,保留出口词;该序列化器对副本排序,不修改 IR/数据库,跨客户端名称排序由上述节点权重约定保证。**ShadowRocket 没有 Surge 的 `use=` 订阅引用语法**(真机验证过:`use=true` 会被当成解析不了的成员残留),策略组动态收节点靠 `policy-regex-filter` 从 App 内全部节点里筛(「收纳全部节点」输出 `policy-regex-filter=.*`),显式成员也要保留。因此订阅名随意,但用户在 App 里挂的其他订阅的节点也会被筛进来;这些节点及 App 自定义排序不由本服务控制。本生成器当前保守跳过 `DOMAIN-SET`/`AND`/`OR`/`NOT`/`URL-REGEX`/`PROTOCOL`,并回报 `skippedRules`;这不是客户端不支持这些类型的断言。官方更新记录已确认存在逻辑规则与 QUIC 匹配,具体配置语法仍须以 App 内帮助和真机编译验证,详见 `docs/MOBILE-CLIENT-AUDIT-2026-10-04.md`。**ShadowRocket 的 conf 策略名不可用 Surge 引号机制**:策略名(含空格/emoji)一律裸写,加引号会被当作名字的一部分,导致规则指向不存在的策略组而整条失效、流量落到默认行为走代理——Surge 生成器可以加引号,ShadowRocket 生成器绝不可以。 Clash 节点必须明确下发 UDP 能力:VMess/Trojan/纯 TCP SS/HY2 输出 `udp: true`,SS WS 插件仅转发 TCP,输出 false,不能假设 nginx 的同端口提供 SS UDP。VMess cipher 按 mihomo 命名映射、未知 cipher 明确排除;SS plugin 的 TLS 参数在 `plugin-opts` 内。RULE-SET 保留 no-resolve;逻辑规则逐个转换子类型,仅把 PROTOCOL TCP/UDP 映射 NETWORK,任一子条件无法表达时整条跳过。两端 url-test 保留零 tolerance,fallback 不输出 tolerance。完整体检需分别核对客户端能力与生成器范围,原生解析成功不等于设备连接或隐私验收。 ### 鉴权与订阅 除公开的 `/api/health` 外,`/api/*` 走 `adminAuth`:设置了 `ADMIN_TOKEN` 则要求 Bearer,未设置时仅放行 localhost 与 RFC1918 私有网段(公网 IP 一律拒绝)。`/sub/:token/*` 公开,token 用常量时间比对。**订阅 URL 完全由请求来源决定**——后端只返回路径,前端用 `window.location.origin` 拼接,`#!MANAGED-CONFIG` 与 Clash provider URL 取本次请求的 origin,所以没有也不需要「订阅基址」配置项。 未匹配的 `/api/*` 显式返回 404,不落到 SPA 回退(否则会拿到一页 HTML,调接口时极难排查)。 ## 前端约定 UI 走「站牌导视」设计方向(轨道交通导视系统语言):白底 `#f7f7f4`、深藏青重点色 `#2b3f5c`、**琥珀 `#b26a00` = 直连出口、青 `#007a72` = WARP 净出口**,协议各有线路色圆标。颜色只在承载信息时出现——避免发光、亮边条、装饰性渐变。四个方向的设计对比留在 `docs/design-demos.html`。 两个共用工具: - `utils/nodeName.ts` 的 `nodeDisplayName()`:与出口标签并排显示时剥掉名字末尾的出口词。**下发给客户端的名字必须保留出口词**,客户端里没有标签,用户只能靠名字区分同协议的两条线路 - `utils/clipboard.ts` 的 `copyText()`:`navigator.clipboard` 只在 HTTPS/localhost 可用,局域网 HTTP 访问时是 `undefined`,直接调用会静默失败。必须走这个带 `execCommand` 降级的封装 ## 数据与安全 SQLite 落在 `server/data/`,存有节点真实凭据。`data/`、`*.db`、`.env`、`edge-nodes/private/`、`config.env` 及其备份、`node-links/` 已在 `.gitignore`——**绝不入 git**。可入库的示例、测试、模板一律用占位凭据;填写后的交付副本只放私有目录。节点通过 Web 界面粘贴分享链接导入,应用不直接读 `edge-nodes/private/`(见 `docs/SEEDING.md`)。 schema 变更同时更新 `shared/src/schemas.ts`/DTO(涉及对外字段时)、`server/src/db/schema.ts` 的 Drizzle 定义和 `server/src/db/index.ts` 的启动 SQL。新表在 `BOOTSTRAP_SQL` 中加 `CREATE TABLE IF NOT EXISTS`,旧库补列用同文件下方的轻量 `ALTER TABLE` 循环(吞掉「列已存在」错误)——没有引入 migration 工具。 公网部署必须设 `ADMIN_TOKEN` 并用反代提供 HTTPS:订阅 token 就在 URL 里,明文 HTTP 会泄露。 ## 初始化与已有数据 `server/src/db/seed.ts` 只在策略组为空时从 `server/assets/surge-template.conf` 初始化策略组、规则和 Surge 段落。修改模板不会更新已有数据库;既有订阅仍以数据库为准。生产数据变更使用 `update-db` 技能,经管理 API 修改并先做 SQLite 在线备份,确认备份 root 属主、600 权限,避免绕过校验和级联更新。 组的 members 是整体替换;排序请求始终先读完整列表并提交全量、无重复 ids。规则 reorder API 强制校验完整性与末尾 FINAL;当前组 reorder API 只逐条写顺序,调用方须自行核对完整性。组 PUT 不更新 sortOrder,显示排序使用独立 `/api/groups/reorder`。节点 PUT 使用私有文件提交完整原字段,排序调整后重读确认除 sortOrder/updatedAt 外没有变化。 `server/src/env.ts` 直接读 `process.env`,没有 dotenv 加载;仅复制 `.env.example` 成 `.env` 并不会让 `pnpm dev` 自动读取它。变量须由启动环境提供(Docker Compose 与 systemd 有各自注入方式)。相对 `DATA_DIR` 始终基于 `server/` 解析。 ## Surge AI DNS 与协议规则 改 DNS/隐私行为先读 `docs/SURGE-PRIVACY.md`、`server/src/services/generators/surge-ai-dns.ts` 及对应测试。AI 域名 DNS 映射来自 IR 内启用的 AI 路由规则;不要把全局 DNS 或所有端口 53 请求改送 AI。Surge 的 `PROTOCOL` 应用协议规则不能直接复制到其他客户端:Clash 只把 TCP/UDP 转成 `NETWORK`,其他应用协议跳过;ShadowRocket 跳过 `PROTOCOL`。生成配置校验不能替代设备 DNS/WebRTC 真机验证。 手写 Host 项优先;生成的域名 DNS 映射按连接规则顺序保留 AI 之前的非 AI 域名/规则集例外,不把 Host 当成能表达进程、协议或 IP 条件的路由系统。宽泛 AI 域名规则仍须审阅覆盖范围。 Net.Coffee 的 Claude 页通过随机 `*.d.ip.net.coffee` 探测递归 DNS 出口,不直接查询 Claude 域名;使用 `DOMAIN-SUFFIX,ip.net.coffee,🤖 AI` 同时覆盖页面和探测子域。Surge 派生根域/通配 Host 到 Cloudflare DoH,并随 AI 出口。本机 Chrome+Surge 已复测为美国 Cloudflare「未检测到泄露」,仅代表该探测路径的实际结果,不能推广为所有客户端/域名/远端解析器已验收,见 `docs/NET-COFFEE-DNS-2026-10-04.md`。 规则变更先读 `docs/SURGE-CHANGES.md`。API 必须保住一条启用且末尾的 FINAL;禁止停用、删除、改类型或创建重复 FINAL,排序必须提交完整且无重复的 ids。IR 对遗留坏数据防御性补齐 FINAL,其失效策略降级 DIRECT。重置规则在事务内保留自定义规则,不重复插入原主键,也不能用 reset 代替定向调整生产规则。 规则按首条匹配执行(另留意 pre-matching)。业务例外须在覆盖它的广告/通用规则集之前,特定 UDP 拦截须在对应服务域名规则之前。靠前的 IP 规则集是否加 `no-resolve` 必须按意图决定:它能避免主动解析,但会改变未解析域名的 IP 拦截覆盖。普通规则值与策略引用用 `surge-format.ts` 编码,逻辑表达式保留结构;不要手拼含逗号的正则或含换行的字段。 共享订阅不附公开默认控制口令;默认关闭 Remote Controller/HTTP API,确需使用时按设备配置独立凭据与访问限制。MITM 不关闭上游服务器证书验证;需要 MITM 时另做设备 CA、开关与 HTTPS 行为验证。 ## 验证与完成标准 | 改动 | 建议验证 | |---|---| | 服务端逻辑/解析器/生成器 | `pnpm --filter @proxy-station/server typecheck` + 相关 Vitest;新增行为用占位凭据构造 IR/分享链接用例 | | shared schema/DTO/协议能力 | `pnpm -r typecheck` + `pnpm test`,兼顾前后端消费者与各客户端产物 | | Vue 页面/样式/状态 | `pnpm --filter @proxy-station/web typecheck` + `pnpm --filter @proxy-station/web build`;页面交互与局域网 HTTP 复制按需验证 | | 数据库结构/启动行为 | 临时 `DATA_DIR` 或 `createDb(':memory:')` 验证新库与旧库升级;注意下述导入副作用 | | 文档/协作入口/技能 | 检查路径、导入、符号链接、技能 frontmatter 与脚本语法;不为纯文档修改触发部署 | | 节点部署工具/交付页生成器 | Bash 语法与 Python 编译检查、占位配置离线验证;迁移核对原文件、私有路径 Git 忽略及技能链接,不为迁移执行远端脚本 | 涉及节点、组、规则、DNS 或 Surge 段落时,在上述检查之外对实际订阅运行 `scripts/check-surge.py`;涉及规则集或顺序用 `--fetch-rulesets`,审阅域名遮蔽提示。脚本调用官方 Surge CLI,不更换活动配置;输出 OK 不表示测速、业务连接或设备隐私已验收。敏感配置用权限 600 的临时文件,不进入仓库或对话。生产写入先在线备份,再 API 更新、重读、HTTPS 拉订阅与 CLI 验证;必要时同时用 mihomo 校验受影响的 Clash 产物。 取订阅后先核对下载成功、非空及预期结构,再交给原生解析器。mihomo 在目标配置不存在时可能创建默认配置并报告成功,不能用这个结果验收订阅;所有检查遇到失败即停止,避免前一步失败而最后一条命令成功掩盖错误。 区分数据库、实际 HTTPS 产物、客户端活动配置与设备网络结果。用户授权的同机复测需刷新既有托管配置时,先核对托管 URL、备份并读取最新组选择,用 `surge-cli managed-profile update` 后检查 effective 配置与实际选项。组改名会改变客户端保存选择的身份;必要时仅将原选定节点及父组路径迁移到新名称,保留 All Nodes/AI Nodes 的独立选择,不擅自切换出口或活动配置。客户端模块可覆盖生成的组类型与参数,不能把本机模块效果写回生产数据库。 `db/index.ts` 在模块加载时就执行 `export const db = createDb()`,包括只导入 `createDb` 的情形;临时数据库测试/启动验证应在加载模块前设置绝对路径的临时 `DATA_DIR`,不要拿本地真实 `server/data/` 做试验。现有生成器单测使用内存中的 IR;运行服务还会执行初始化。 改生成器时检查节点过滤、组成员引用、规则顺序和 FINAL 兜底;Clash 可用 mihomo 校验,Surge/ShadowRocket 的客户端语法与实际连接仍需真机验收。`excluded`/`skippedRules` 是生成器返回字段,订阅 HTTP 响应只返回配置正文。 完成后说明改动、已通过的验证与尚未完成的实际验证。新增业务约束或修正旧认识时同步更新这里或相应 `docs/`,让后续 Codex/Claude Code 会话能接续。