hidden-contract-audit
Skill0 starsChanged 2 months agoMITView on GitHub
审计「隐形契约」——缓存键 / 派生文件 / 落盘中间产物 / 镜像副本 / 生成式资产 / 对外宣称的数字 / 同一段逻辑的多处副本 / 共享数据的定义位置—— 专抓那类**不报错、只静默走错分支或静默不生效**的缺陷。这类缺陷不崩不响, happy path 全绿,但用户改了东西却没反应、或拿到了错误但看起来正常的产物。 触发词:改了不生效、改了没反应、缓存不失效、总是产出旧版本、静默失败、slogan 数字对不上、 隐形契约、hidden contract、缓存审计、派生资产审计、stale cache、为什么没更新、 批量每次都重跑、缓存形同虚设、落盘没复用、中间产物没被复用、状态标签看不出真假、 同一段逻辑抄了几遍、移动模块/改目录后崩了、在我这能跑、定义位置不对、循环依赖、分层被打破、 样式没生效、写了 class 没效果、统计数字好得不正常、工具报 0 我不信、 硬约束没守住、必须出 N 个却出了垃圾、短料被硬编凑数、跳过还是失败分不清、 退出码不对、测到的数字可信吗、探针把好的说成坏的、 脚本报成功但文件不存在、备份没落盘、打印了成功却什么都没做、安全网没生效、 备份目录里没有刚做的那份、2>/dev/null 吞错、|| true 掩盖失败、封装命令自证。 适用:写/审 缓存层、编译/转换产物、索引清单、多副本同步、目录/模块重构、HTML/CSS 产物、 AI 生成资产(图/文/视频)、对外宣传口径、**以及任何备份/导出/落盘/发布类封装脚本**。
In this repository139 other files it gives its agents.
- AGENTS.mdAGENTS.md
- skills/caveman-learn/CLAUDE.mdCLAUDE.md
- adversarial-reviewSkill
- ai-repo-engineering-layoutSkill
- ai-session-material-minerSkill
- ai-usage-reportSkill
- ardot-effect-fallbackSkill
- audio-friendly-adaptSkill
- automation-resurrectionSkill
- book2vido-videoSkill
- boss-autopilotSkill
- brand-icon-sourcingSkill
- canvas-designSkill
- cavecrewSkill
- caveman-commitSkill
- caveman-compressSkill
- caveman-discoverSkill
- caveman-evidence-reviewSkill
- caveman-exploreSkill
- caveman-helpSkill
- caveman-learnSkill
- caveman-manageSkill
- caveman-optimizeSkill
- caveman-reviewSkill
- caveman-setupSkill
- cavemanSkill
- caveman-statsSkill
- cc-switch-usage-auditSkill
- certification-submission-kitSkill
- comment-reply-playbookSkill
- concept-anchor-figureSkill
- conference-archiveSkill
- constitution-amendmentSkill
- content-format-auditSkill
- content-matrix-channel-auditSkill
- cross-repo-skill-curationSkill
- daily-judgment-briefSkill
- daily-reportSkill
- de-ai-flavorSkill
- desktop-ai-forensicsSkill
- discernment-nudgeSkill
- doc-translateSkill
- doubao-app-layer-archiveSkill
- effort-accountingSkill
- ego-browserSkill
- egress-allowlistSkill
- eval-skillsSkill
- evolve-skillsSkill
- exam-prep-pmpSkill
- fact-check-gateSkill
- fanout-mainline-ledgerSkill
- free-token-channel-auditSkill
- github-repo-gallerySkill
- git-identity-pinSkill
- git-push-in-sandboxSkill
- harness-governanceSkill
- harness-model-upgrade-auditSkill
- investigate-firstSkill
- ip-platform-fit-auditSkill
- lean-buildSkill
- The first 60 shown.
The file
Files
隐形契约审计(Hidden Contract Audit)
一句话
算法很少错;错的是那些"没人当成契约"的东西——键、缓存、落盘文件、派生副本、定义位置。
if 写反会崩,测试会红。但"缓存键少放一个因子"不会崩——它只是安静地给你旧结果。
什么时候用它
- 用户说「我改了,怎么没变」「重跑还是老样子」「这次结果和上次一样」
- 给某物加了缓存 / 中间产物 / 编译步骤 / 快照 / 镜像之后
- 审 PR:看到 diff 里出现
key = hash(...)、if os.path.exists(cache)、if not force: - 发布前:多平台分发、多语言副本、图文双形态(HTML → PNG → 公众号)
- 动目录 / 拆模块 / 提公共函数之前:先查"同一段逻辑抄了几处""共享数据定义在谁家", 否则重构会静默指错层(案例 14/15)
核心动作:对每个"派生/缓存物"问三连
对每一个"由别的东西算出来的东西",逐条回答:
| # | 问题 | 答不上来 = 缺陷 |
| 1 | 这东西由什么决定?(把决定它的输入列全) | 列不全 → 键会漏因子 |
| 2 | 用户改了 X 之后,键变了吗? | 不变 → 改了不生效 |
| 3 | 产物会重算吗?重算的是全部还是只该重算的那部分? | 不重算 = 静默旧值;全重算 = 白烧钱 |
把这三问画成一张表,每一列都是一个潜在的静默分支。
真实案例(都来自行途 XingTu 项目,同一类;条数不写死以免漂移)
| # | 表面现象 | 真因 | 修法 |
| 1 | 改了术语,31 处仍显示旧写法 | 术语是"多副本"——素材源 / 派生资产 / 发布包配件 / CHANGELOG 各自存了一份,只改了一处 | 建 TERMS.md 做 SSoT + 改术语时列全副本清单 |
| 2 | 信息图 HTML 改了,PNG 没变 | PNG 是派生资产,无自动重渲染;HTML 改了 PNG 不会自己知道 | 重渲染 + 实图核对;参数复现不了就宁可回滚别覆盖 |
| 3 | 手改 script.json(分镜),重跑还是老视频 | video_key 挂的是输入文本派生的 script_key,手改输出文件不改输入 → 命中旧分镜渲染的成片 | video_key 改挂 sha256(输出内容) 指纹 |
| 4 | "全命中"却要等 19.9s | 命中路径仍解析全书(extract_pages 5.9s + outline.build 6.0s)。只算了"要不要重做",没算"命中时顺手做了多少无用功" | 大纲读缓存 + 文本落盘 + 抽页延迟执行 → 0.5s |
| 5 | 声称"手改 outline.json 生效",实际无效 | 每次 build() 重建,save() 落的文件白存——"人工可改口子"是文档里的谎言 | 改成优先 load();文档声称的可改点必须实测手改一次 |
| 6 | 修了采样算法,产物还是旧的 | 文本缓存的键含 (书, 章, 字数) 但不含算法本身 | 加 VERSION 常量进键,改算法必须 +1 |
| 7 | slogan / 宣传语里的数字,实测对不上 | 对外宣称的数字也是"派生值"——它由当时某一次产物决定,但产物后来变了、或当初就没对齐 | 把宣称语里的每个数字回挂一条实测(跑 ffprobe / 复现命令),对不上就改口径 |
| 8 | 批量出片每次都重跑模型,缓存形同虚设 | 同一个项目有多个入口,缓存只接在了其中一个上(batch 复用旧的兼容入口,而旧入口显式 enabled=False)。用户以为两条路等价,实际能力分叉 | 每个入口都要能回答"走缓存吗";新入口默认接入,不接入要写理由 |
| 9 | 改一句话,12 张图 + 12 段语音全部重算 | 落盘 ≠ 缓存:中间产物躺在缓存目录里,但代码里没有"已存在即跳过"的判断——它只是工作文件,没有命中条件 | 给每个中间产物挂内容指纹做跳过;没有命中条件的产物,只是垃圾 |
| 10 | 改了提示词,产出一点没变(0.0s / 退出码 0) | 缓存键的因子清单写着"只含影响分镜内容的因子",列了 model/think/句数/章节文本——唯独漏了提示词。语法全对、语义漏项,最像"配置正确"的一类 | 键里挂提示词内容指纹(不是版本号——版本号靠人记得升);把"决定行为但不决定数据"的因子单列一类查 |
| 11 | 注释写着"供 prompt 注入使用",实际零调用点 | 注释记的是写那一刻的意图;代码演进后用途变了,注释没人回头改。读者对代码注释的默认信任度比文档更高,所以更隐蔽 | 读到「供 X 使用」的用途声明 → 搜一遍调用点(30 秒)。零调用的公开名字 = 注释在说谎 |
| 12 | 解析出的正文只有 5 个字符(预期 699) | 子串匹配找结构标记:标记名在文件说明/注释里被引用过 → split(标记) 命中的是被引用的那处。不报错、退出码 0,只是静默取错内容 | 结构性标记一律整行/唯一性精确匹配。判据:这个标记若出现在正文里(被引用/被示例)会怎样? |
| 13 | 日志写「分镜:833 字」,被读成"分镜只有 833 字",于是去查切片是不是漏了 | 一个名词指了两件事:那行打的是 len(text) = 输入切片字数,而分镜大小是 len(scenes)。读者只能猜到错的那个(833 还是单段采样预算,与分镜无关;本章真实链路是 10201 原文 → 2498 输入 → 12 句) | 标签与变量语义一一对应:输入叫「正文切片」,产出才叫「分镜」;并顺手把上限/口径打出来((上限 2500)),让读者不必猜 |
| 14 | 移动任一模块,六处路径推导会分别指到六个不同的层,且其中两处不报错 | 同一件事 N 处各写一遍:Path(__file__).parents[2] 这种"找项目根"的逻辑,6 个模块各自写了一份。改目录时它们不同步——有的指对、有的指空、有的正好还指对(见变体五) | 收敛成唯一函数(project_root()),所有模块调它。判据:同一段逻辑若出现 ≥2 次,且其中任一处改动会让其余处失效 → 它就不是巧合,是缺陷 |
| 15 | 分镜层(scriptwriter)为了拿一份"概念词表",被迫 import 图标资产层(icons) | 共享数据寄居在其中一个消费者家里:CONCEPTS 是"Scene.concept 的合法值域"(领域契约),但它当初是被图标层先用的,于是定义在 icons.py 里。第二个消费者出现时,依赖方向就反了(分层被一次 import 悄悄打破) | 把共享数据上提到中立位置(契约归 models / 常量归 constants),让所有消费者平级依赖它,而不是"一个消费者去另一个消费者家里拿" |
共同点:绝大多数是"某物由 X 决定"这句话没说全——要么漏了因子,要么干脆没定义(案例 9)。 案例 14/15 是另一种形态:没说全的是"这东西该由谁定义" —— 定义位置本身是隐形契约。
案例 9 的隐藏代价(比慢更严重):中间产物往往依赖外部/不可重复的环境。
text2vido 的配音走 edge-tts(联网、云音色),断网会回退本地 say(机器音)——
于是"改一句"这个动作会把整片音色换掉。按项缓存能把不可重复的那部分冻住。
变体一:对外宣称的数字
数字型文案(slogan、副标题、"3 秒完成""不到 1MB""覆盖 100 个模型")是隐形契约里最不设防的一类—— 它不在代码里、没有测试、也没人会在改完代码后想起它。但它一旦被实测打脸,损耗的是整条内容的可信度。
审计动作:
- 把所有对外数字列成一张表,逐个回挂可复现的实测命令(不是"印象里是这样")
- 数字要落成区间而不是点值(点值必然会被某一次运行打破)
- 换了参数/算法/模型之后,回头重核这张表——和第 6 条(算法版本进键)是同一个动作的两种表现
变体二:多入口(缓存只在其中一条路上)
项目一旦长出第二个入口(CLI 子命令、GUI、脚本、兼容旧签名的那条路),缓存就有"覆盖不全"的风险: 旧入口当初就是按"没有缓存"设计的,新能力加在另一个入口上,两条路从此能力分叉。 症状特征:走 A 路径改了没反应(hit),走 B 路径每次都重来(永远 miss)——同一份数据、两种行为。
审计动作:把所有入口列成一张表,逐个回答「它走缓存吗 / 它读目录吗 / 它和主入口差在哪」。 差异要么补齐,要么在文档里写明"此入口刻意不支持 X 及理由"。
变体三:状态标签二义(探针在说谎)
日志/耗时字段本身就是一种"对外宣称",也会漂:
- 同一个值
miss同时表示「查了、没命中」和「根本没启用」→ 用户无法判断是缓存坏了还是我关了它 - 同一列耗时既含"判断"也含"顺手干的活"→ 看不出优化有没有生效
审计动作:状态必须能被三者区分——hit / miss / off(或等价三态)。
判据:一个标签如果可能对应两种截然不同的现实,它就不是个合格的探针。
子类:名词一名两用。 比"状态二义"更隐蔽——它连状态都不涉及, 只是一个词指了两个量。例:日志写「分镜:N 字」, N 其实是输入字数,而"分镜"另有大小(句数)。 读者不会觉得日志错了,只会去查一个不存在的问题("是不是切片算漏了?")。
审计动作:逐个 print / 字段名,问「这个词在本项目里只指这一件事吗?」 同一个词在别处另有含义 → 改名,或把两个含义都打出来。 判据:如果一个数字被误读后,会引导人去排查一个不相干的模块,它就是坏标签。
同类:数字的量纲/口径没写。 同一行里的数字是「花费」还是「节省」?
「整机功耗」还是「增量功耗」?——不写,读者就按自己以为的那个理解。
(实测:≈¥0.0004/条 是花的电费、且是整机摊销(真实边际≈0),
参数 30W / ¥0.6 均为假设值。这些不写清就会变成对外宣称里的软肋。)
变体四:单维指标把两个状态判成同一件事("有"冒充"对")
指标本身也是一种宣称物。若一件事有两层含义,而只用一个维度去量, 两层会被压成同一个数——指标不报错,只是让你分不出好坏。
实测(text2vido 画面层):两版产物的画面填充度几乎相同
(修复前 0.0406 / 修复后 0.0491),但一版是「12 张卡只用了 6 个不同图形、
图标与文案语义相符不到 1/3」,另一版是「12 张卡 12 个图形、语义 12/12」。
只盯填充度,两版是同一档。
修法是加第二个维度:「这批卡片用了几个不同图形」——一个量"有没有",一个量"对不对"。
审计动作:对每个指标问一句——「这个数变好,是否可能只是'有了'而不是'对了'?」 若答案是"可能",就必须配第二个维度。(同源案例见错题库 L-01。)
变体五:本机环境恰好掩盖了缺陷("在我这能跑")
最阴的一类:缺陷真的存在,但当前这台机器恰好补上了它。
实测(案例 14):6 处 parents[2] 路径推导中,两处算出来的根是错的,
却因为本机目录深度、或某个软链/工作目录恰好让它落到对的地方 —— 于是它永远走对路,
测试全绿。换一台机器(打包进 .app、CI、别人的 clone)才炸。
同类形态:
- 绝对路径 / 家目录假设(本机能跑,分发即崩)
- 本机已装的系统工具(
ffmpeg/ 字体 /say)被当成项目自带 - 大小写不敏感的文件系统(macOS 能跑,Linux 炸)
- 缓存目录里恰好留着旧的正确产物,掩盖了"其实不会重建"
审计动作:把"我这能跑"换成"干净环境能跑吗" —— 逐项列出"本机为你兜了哪些底"。 判据:如果这个缺陷换台机器就暴露,那它不是"没问题",是"没被环境踩到"。 (注意:这类缺陷无法靠"再多跑几次"发现——必须换环境,或读代码。)
变体六:硬约束只有"有没有"的守卫,没有"够不够"的守卫
凡是输出有硬约束的环节(必须出 N 个 / 切成 N 条 / 至少 N 字),上游几乎必然只有 一个"非空"守卫 —— 它挡得住"什么都没有",挡不住"只有一点点"。 而硬约束不会因为料不够就放松,于是缺口只能靠同义反复或编造去填。
实测(text2vido):出片硬约束是「12 句」,22 字的一行附录被硬编出 12 句同义反复
("附录D讲作者团队信息" → "附录D提供作者背景"),同时 12 张卡退化成 2 个图标。
日志全绿、退出码 0;只有 --all 跑整本书才踩到 —— 而那时你只会觉得"有几集怪怪的",
不会意识到系统在编。跑测试用的是正经长文,所以本机永远测不出来。
审计动作:对每个硬约束问一句「上游料不够时会发生什么」,并逐条检查:
- 守卫的判定式是
if not x:(只挡空)还是if 量(x) < 需要的量:(挡不够)?(同族 L-08:bool(x)对0/""/[]的误判) - 判据要能被解释给人听(先例:「一句话换一句话」= 原句数 ≥ 目标句数), 不要拍脑袋的阈值 —— 阈值型数字是下一个漂移点。
- 调用点压在"最贵的环节之前":拦晚一步就是白烧一次算力。
- 「跳过」与「失败」必须分开汇报:跳过是主动判断(料不够,不出比硬出好), 失败才是要查的意外。混在一个列表里,用户会以为工具坏了。 且跳过 ≠ 静默 —— 仍然要打原因(否则变成另一种"静默走错")。
- 守护脚本的目标值从配置读、不写死(写死 = 配置改了还在验一个失效口径); 并且必须断言"该放行的零误伤" —— 只验"该拦的拦住"会让判据悄悄收紧过头。
判据:如果一个环节"必须出 N 个",而它的守卫只回答"有没有",那它迟早会开始编。
变体七:工具宣告的成功 ≠ 实际发生("打了一行成功,什么都没做")
前面六个变体说的是产物在骗人;这一类是工具自己在骗人 —— 而且是最不该骗人的那种工具: 安全网(备份 / 导出 / 落盘 / 写库 / 发布)。
实测(2026-09-16,tools/safety/backup-before-op.sh):给三份索引做操作前备份,
命令返回码非零、.backups/ 里一个文件都没有,但终端照样打印
「创建备份: 20260916_095934_….tar.gz」—— 看上去成功了。
发现路径不是脚本报错,而是事后按纪律核对备份清单时,发现"刚做的那份不在列表里"。
四个叠加的成因,每个单看都"不算 bug":
| # | 成因 | 为什么致命 |
| 1 | 描述未消毒就拼进文件名(含 / → 路径被切成不存在的子目录) | 输入合法性没在自己边界上校验 |
| 2 | tar … 2>/dev/null 吞掉失败原因 | 错误被静音,报错变成了沉默 |
| 3 | 「创建备份:」打印在 tar 之前 | 成功叙事先入为主,读者不会再去看后面 |
| 4 | 全程无后置断言(文件存在?非空?条目数 > 0?) | 没人回答"到底发生了没有" |
审计动作 —— 对任何"做了什么"的封装,问三句:
- 成功提示是在行动之后打印的吗?(打印在行动之前 = 伪造凭证)
- 有后置断言吗? ——
[[ -s "$OUT" ]]/assert os.path.getsize(p) > 0/ 条目数 > 0。 - 错误被吞了吗? —— 搜
2>/dev/null·|| true·try: pass·except: continue。 这些不是"容错",是把可诊断的失败改成不可诊断的沉默。
判据:一句"成功"如果不携带可核对的证据(路径 + 大小 + 条数),它就不是报告,是宣告。
(修后形态:✅ 备份已落盘 (344K · 5 条目) /Users/….tar.gz —— 数字可复算,路径可 ls。)
用法上的铁律:拿它的产物当下游依赖前,先看落盘结果,不看调用输出。
("回滚点存在吗"→ ls .backups/,而不是回看你刚才那条命令的 stdout。)
同族:错题库 L-22(断言环境态而非行为变化)· B-045(BSD grep 假阴性骗过校验)· B-049(本案例) —— 一句话共性:验证动作本身没被验证。"绿"和"成功"必须是算出来、看得见的。
高频遗漏的键因子(照着查)
- 指令 / 提示词 / 模板文本 —— ★ 最容易漏的一类:它不改变输入数据,只改变行为。 于是逐个核对"输入"时天然想不起来它。(案例 10)。修法:挂内容指纹,不挂版本号 —— 版本号靠人记得升,迟早会忘,一忘就退回到"改了不生效"。
- 代码/算法版本 —— 改了逻辑但键不变(案例 6)
- 用户手改的输出文件本身 —— 内容指纹 vs 输入指纹(案例 3)
- 配置项的子集 —— 哪些配置真影响这个产物?(改配色不该废 LLM 产物 → 要分层建键)
- 上游产物的版本 —— 上游重算了,下游知道吗?
- 环境 —— 依赖库版本、字体、模型、locale
- 分页/分章等切片参数 ——
max_chars、chunk size
反向检查:有没有"白算"
命中/跳过时,还有多少没必要的活被干了? 逐项问:
- 这个成本能不能推迟到真正需要时?(懒加载,案例 4)
- 这个结果能不能落盘复用?
- 这段代码在"什么都不用做"的路径上是否仍被调用?
判据:全命中的耗时应当接近"读文件 + 判断"的开销。若仍要数秒,就是白算。
交付什么
- 一张三列表:
产物 | 决定它的输入(列全) | 缺失的因子(每个缺口 = 一个待修项) - 一张入口表:
入口 | 走缓存? | 读目录? | 与主入口的差异(差异 = 待补齐或待写明) - 每个"文档 / 注释声称的可改点或用途"实测一次(手改 → 重跑 → 看日志是否真变了; 对「供 X 使用」这类用途声明,直接搜调用点 —— 案例 11)
- 一份"静默分支"清单:所有"不报错但走错路"的判定(尤其
bool(x)/if x:对0/""/[]的误判) - 修复后的复验:同一命令跑两次,第 2 次必须走命中路径且耗时降到 read 级
- 一张"重复逻辑"清单:同一段逻辑出现 ≥2 次的地方(路径推导 / 公式 / 常量)—— 逐个判断"改动时它们会不同步吗"(案例 14)
- 一张"定义位置"清单:每个共享的数据/契约,定义在哪个模块里? 若它寄居在某个消费者模块中(另一个模块为用它而 import 了它)→ 上提到中立位置(案例 15)
- 一张"引用存在性"差集:把所有声明式引用(CSS 类 / 锚点 / 图标 id / 配置键 / 环境变量名)与它们的定义处做集合差 —— 差集就是"写了但没效果"的静默失效点。 这一步必须用脚本,肉眼必漏。
- 测量工具自抽 —— 两个方向都要:凡得出"数字大幅好转"(0 处命中 / 全部通过 / 无异常)
或"数字明显不对",先验工具,再验代码。
- 正向:工具静默返回 0(假阴性)比报错危险 → 挑一个确信命中的样本验它。
- 反向(更阴,因为它让你去改本来是对的代码):
cmd | tail; echo $?拿到的是tail的退出码;zsh 的数组是$pipestatus(小写), 写PIPESTATUS取到空值;而${X:-默认}这类兜底会把"没测到"伪装成"测到了" —— 打出一个格式正常、看起来可信的错数字。 - 判据:我要的这个数是"程序自己说的",还是"我从管道/包装里捞的"? 后者一律重测。
- 最稳的形态:让被测程序自己双通道自报(退出码 + 一行
✅/🔴结论), 人只看程序说的话,不靠 shell 转述。
- 用"最差输入"跑一次:正经样本测不出下限缺陷。对每类输入形态各跑一条边界样本 —— 最短的一节 / 只有标题的条目 / 空文件 / 全是标点 / 只有一行 URL —— 看它是优雅跳过(带原因)还是静默编造。这一步只能靠人挑样本,脚本想不到。
配套铁律
- 落盘 ≠ 缓存。文件在磁盘上只说明"写过",不说明"会被复用"。任何新增的中间产物必须同时定义 命中条件(由什么决定 + 什么时候可跳过)——定义不出来的,它只会占地方。
- 每个入口都要能回答「走缓存吗」。新入口默认接入;不接入必须在文档里写明理由, 否则两条路会静默分叉,而用户以为它们等价。
- 探针本身也要被质疑 —— "失败"和"成功"都要。测"采样是否覆盖到末尾"时,探针取 0.95 位置 可能恰好落在采样间隙里 → 假阳性;反向的坑同样致命:验证脚本里忘了真的制造失败场景 (如忘了移走被依赖的目录),它会一路绿灯 —— 那测的是空气。 探针报失败 → 先证明探针是对的;探针报成功 → 先证明它真的测到了。
- 别用
bool()判"有没有传参"。--chapter 0/--size 0/-n 0都会被bool(0)判成"没传", 于是静默走默认路径、退出码 0、用户拿到完全不对的结果却毫无提示。用is not None。 - 资产加载绝不静默降级。提示词 / 模板 / 配置读不到,就当场报错(并给修复指引), 不要退回一份"内置默认" —— 那会让用户以为"我改的生效了",而真相是他改的那份根本没被读到。
- 改行为前先固化基线(成本最低、回报最高的一招)。想"重构不改行为"?先抓一份真实产物 (如劫持网络层抓实际送出的 payload),重构后与之逐字节比对。这样"没改行为"是证据不是声明 —— 且这类比对第一跑就可能抓出实现缺陷(案例 12 就是这么被抓住的,肉眼绝无可能发现)。
- 单一来源:同一件事只写一处。反例是"6 处各自推导项目根""电费公式抄了 3 遍" —— 这类重复不会立刻报错,代价在未来某次改动时才付。判据不是"看起来整齐",而是 "改动它时,我怎么知道要改几处?" 答不出来就是缺陷(案例 14)。
- 共享的东西住中立位置,不住在某个消费者家里。一旦"B 模块为了用 X 而 import A 模块",
分层就被一次 import 悄悄打破了 —— 而且方向是反的(B 依赖 A,只因为 A 先写了)。
正确形态:所有消费者平级依赖那份共享定义(契约归
models,常量归constants)(案例 15)。 - 区分"没问题"和"没被环境踩到"。本机路径深度 / 已装的系统工具 / 大小写不敏感的文件系统 / 缓存里恰好留着的旧正确产物 —— 都在替缺陷兜底。这类"在我这能跑"不可能靠多跑几次发现, 必须换环境或读代码(变体五)。
- 测量工具本身也要抽查 —— 它会静默给错答案。 实测:用 shell 的多模式 grep 统计 "含 X 的文件数",返回 0,而正确值是 9(换内建检索复核才发现)。 一个报错的工具比一个报 0 的工具安全 —— 0 看起来像"很干净"。 所以:任何"数字大幅好转"的结果,先抽查一条(挑一个你确信命中的样本,看工具报不报它)。 同源:探针纪律(上面那条)的第三次变体 —— 这次的探针是"数数工具"本身。
- 声明式引用要验"被引用的东西存在"。CSS 类名 / 锚点 / 图标名 / 配置键 / 环境变量名 ——
写错了不报错,只是没有效果。实测:写看板时用了
class="closed",而样式表里 从没定义过它 → 那一行静默无样式(同 L-13 家族:声明式配置静默不生效)。 审计动作:用脚本对比"被引用的集合"与"已定义的集合",差集就是静默失效点 (CSS 类 ↔ 样式表;锚点 ↔ 标题;图标 id ↔ 图标表)。手工肉眼查这类必漏。 - 整改有固定顺序:先收敛,再搬家,最后加守护。 顺序错一步就要重做: ①先固化基线(抓一份可逐字节比对的产品)②把"同一件事的多处定义"收敛成一处 (路径 / 公式 / 常量 / 共享数据上提到中立位置)③然后才移动目录或拆模块 —— 此时"移动"从危险操作变成无害操作 ④最后加一条机器断言守住刚建立的方向 (断言要能被反向注入验证其有效)。 反例(行途 XingTu text2vido 的真实教训):先分目录 → 5 处路径推导同时指错层, 其中两处不报错、且被本机环境掩盖(文档里当时只能写成一句禁令"勿随意挪动")。 当一条约束只能写成"别动它"时,说明它还没被抽象好。
与相邻 skill 的分工
adversarial-review—— 换视角审内容/方案/代码的整体质量ponytail—— 砍过度设计- 本 skill —— 只盯**"看不见的契约"这一类**:键 / 缓存 / 派生 / 落盘 / 副本
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

