claude-md-progressive-disclosurer
GitHub用于优化和重构 CLAUDE.md,通过渐进式披露将低频细节移至 Level 2,保持主文件精简且零信息丢失。解决文件臃肿、规则被淹没及 LLM 不遵守规则的问题。
Trigger Scenarios
Install
npx skills add daymade/claude-code-skills --skill claude-md-progressive-disclosurer -g -y
SKILL.md
Frontmatter
{
"name": "claude-md-progressive-disclosurer",
"description": "Optimize \/ slim down \/ restructure a CLAUDE.md (or AGENTS.md) using progressive disclosure — move low-frequency detail to Level 2 references while keeping Level 1 lean, WITHOUT losing information. Use this whenever the user asks to optimize \/ 精简 \/ 瘦身 \/ 重构 CLAUDE.md, asks \"CLAUDE.md 是不是太大了 \/ 太长了\" (is my CLAUDE.md too big \/ too long \/ bloated), wants to 把内容拆到 reference \/ 外部 \/ Level 2, do 整段外移 \/ 渐进式披露 \/ progressive disclosure, or whenever a CLAUDE.md duplicates info across files or the LLM keeps failing to follow its rules. ALSO trigger the moment an optimization turns into moving \/ cutting \/ compressing sections of a CLAUDE.md — even mid-task while another claude-md skill is already running. Distinct from claude-md quality auditors \/ scorers: this is the restructuring-and-offloading methodology that guarantees zero information loss (grep-verified pointers, verbatim moves, 5b content-integrity audit). Covers hotspot-first profiling (先打热点) and cross-tool truncation (Codex project_doc_max_bytes)."
}
CLAUDE.md 渐进式披露优化器
核心理念
"找到最小的高信号 token 集合,最大化期望结果的可能性。" — Anthropic
目标是最大化信息效率、可读性、可维护性。
本 skill 自身遵守渐进式披露:新方法论以"精炼规则 + 触发条件"留在 SKILL.md,深度战例 / 引文沉到 references/。SKILL.md 行数由信息密度决定、不设为硬目标(约 500 行量级;新增高价值规则可略超,但深度永远沉 references)——skill 自己示范它教别人的事:行数不是 KPI,自洽地不拿"≤N 行"约束自己。
铁律:行数禁作 KPI,可作诊断症状
禁作优化目标 / 成功指标(不可削弱——案例 7/8/9 的防线就是这条):
- 行数少不代表更好,行数多不代表更差
- 评判标准是:单一信息源(同一信息不在多处维护)、认知相关性(当前任务不需要的信息不干扰注意力)、维护一致性(改一处不需要同步另一处)——不是行数
- 禁止在优化方案 / 总结中出现"从 X 行精简到 Y 行"、"减少 Z%"作为成果
- 禁止把"减少行数"作为移动 / 删除某内容的理由
- 一个结构清晰、信息不重复的长文件,胜过砍掉关键信息的短文件
可作诊断症状(官方依据:Claude Code 文档"文件太长 → 规则被淹没 → Claude 不遵守"):
- 允许把"行数异常大 + Claude 反复不遵守某规则"当成触发调查的信号,不是结论
- 调查动作仍是信号分诊(Step 2.1)+ 分层,不是"砍到 N 行"
- 一句话区分:行数可以让你开始怀疑,不可以成为你优化的目标或汇报的成果
触发即 reframe(用户说「太大 / 太长 / 精简 / 瘦身」时——最易在此处跑偏)
这些词触发的本能是「砍行数」。先 reframe,再动手:① 当场声明「行数不是目标,单一信息源 / 认知相关性才是」;② 直接进 Step 2.1 信号分诊,用「这段有没有 canonical source 重复 / 是不是反信号」决定去留,不是用「文件多长」;③ 把「太大吗」当调查的起点,不是砍的许可。用户连续追问「还是太大」时同理——回应是「再做一轮分诊找重复 / 反信号」,分诊空了就诚实说「剩下都是高频核心,再砍会丢信号」,不是继续砍有信息的内容。(实战:把「太大吗」做成减行数任务、一路用「省 39%」当成果汇报、被连续追问拽着越砍越多 → 案例 15、16。)
两层架构
Level 1 (CLAUDE.md) - 每次对话都加载
├── 信息记录原则 ← 防止未来膨胀的自我约束
├── Reference 索引(开头) ← 入口1:遇到问题查这里
├── 核心命令表
├── 铁律/禁令(含代码示例)
├── 常见错误诊断(症状→原因→修复)
├── 代码模式(可直接复制)
├── 目录映射(功能→文件)
├── 修改代码前必读 ← 入口2:改代码前查这里
└── Reference 触发索引(末尾) ← 入口3:长对话后复述
Level 2 (references/) - 按需即时加载
├── 详细 SOP 流程
├── 边缘情况处理
├── 完整配置示例
└── 历史决策记录
但「两层」只是文件层——先选载体,再选层级
渐进式披露不是一个文件内部的事,它在多个层同时发生:MCP 懒加载工具、RAG 按需取知识、 Skills 描述常驻正文按需、以及 Claude Code 的动态工具选择(工具索引层的渐进式披露)。 只在 CLAUDE.md 内部搬 L1↔L2,等于把下表四种载体里的两种(常驻 L1 / reference)当成全部。
先问载体,再问层级。 判据一句话:模型能不能在违规之前就知道自己需要这条规则?
| 违规可恢复 | 违规不可恢复 | |
|---|---|---|
| 触发自报(我知道我要做 X) | → Skill(触发词加载) | → Hook 拦截器(散文拦不住,见下) |
| 触发不自报,但「时刻」工具事件可观测 | → reference(复盘时查) | → Hook 注入器(确定性 100%、常驻成本 0) |
| 触发不自报,且无可观测时刻 | → reference | → 常驻 L1(真正必须常驻的只剩这一格) |
两条轴的定义:
- 触发自报 = 动手前那一刻,命令/文件名/关键词里就写着「我要做这件事」——
aliyun ...、写.tf、跑打包。skill 的描述匹配能接住这类。 - 「时刻」可观测 ≠ 触发自报。这是第三行存在的全部理由:
「我在修测试」不自报「我正要删功能」,但
Write一个.py文件是一个工具事件, hook 能在那一刻开火。规则的语义 hook 判断不了,但时刻它看得见 —— 于是 hook 只负责报时刻、把规则怼到面前,判断仍归模型。 - 只有连一个可挂载的工具事件都找不到的规则(例:「怎么跟这个用户沟通」「他的资源观」), 才真正必须常驻。
Hook 两种形态:拦截器 exit 2 挡住可判定的形态;注入器 exit 0 但在 stdout 输出
hookSpecificOutput,把文本送进模型上下文。注入器让确定性与常驻解耦 ——
一条规则可以 100% 每次出现,却零常驻成本。
⚠️ 别把注入器当成万能替代:它 exit 0、不阻断,模型可以读完照样违反 —— 它换来的是「规则必定在场」,不是「规则必定被遵守」。所以:不可逆 + 形态可判定 → 拦截器; 不可逆 + 只能语义判断 → 注入器 + 常驻规则句两者并存(注入器保证在场,常驻句保证 它出现在最该出现的位置)。同一条规则挂两个载体不算违反 SSOT,前提是 只有一处写规则正文,另一处指向它。
官方载体新增(2026-08 对照 code.claude.com 官方文档核过):~/.claude/rules/(用户级)与项目级 .claude/rules/ 可把规则拆成多主题文件;带 paths: frontmatter 的 rule 只在 Claude 操作匹配文件时加载——官方版的按需载体。⚠️ 边界:paths 按文件读写触发、不按 Bash 命令触发——代理 / git 类"Bash 时刻"规则用不了它,文件类规则(*.py / *.docx / 测试文件)适用。另外三条官方事实随手可用:单文件目标 <200 行(官方原文 "Longer files consume more context and reduce adherence");/doctor 自带 CLAUDE.md trim 检查(v2.1.206+,可当独立第二意见);块级 HTML 注释在注入前被剥掉——维护者备注零 token 成本。auto memory 的 MEMORY.md 只加载前 200 行 / 25KB,塞过期条目挤占的是活配额。
🚫 缩正文前必须实证「真有别的机制在强制吗」,禁止推断。 把 L1 正文缩成「由 X 强制 + 指针」之前,构造一条真实的违规命令喂给 X,看它到底拦不拦:
# ① 名单从「注册表」来,不是从目录来(见下方两个坑)
python3 - <<'PY' > /tmp/registered-hooks.txt
import json, os
s = json.load(open(os.path.expanduser('~/.claude/settings.json')))
for ev, groups in (s.get('hooks') or {}).items():
for g in groups:
for h in g.get('hooks', []):
print(ev, g.get('matcher', '*'), h.get('command', ''))
PY
# ② 从上面的名单里挑一条填进来。用变量而不是 <占位符> —— 在 shell 里 `<` 是重定向符,
# 整块粘贴会得到一个莫名的重定向错误,而不是「这里要你填」
# ⚠️ HOOK_CMD 必须是**数组**:写成字符串 + 不加引号展开 `$HOOK_CMD`,
# bash 会词分割成「解释器 + 路径」,**zsh 不会**(整串当一个命令名 → exit 127)。
# 实测本块第一版就栽在这,与附录 C 的 REFDIRS 是同一个坑。
HOOK_CMD=(bash /绝对路径/那个hook.sh) # ← 换成 registered-hooks.txt 里那条(注意它自己的解释器)
BAD_CMD='一条真实的违规命令' # ← 换成你要试探的那条
# payload 必须完整——缺字段会让判决翻转(见下方第一个坑)
# ⚠️ 必须用 json.dumps 构造,**不能手搓字符串**:BAD_CMD 里只要有 `"` / `\` / 换行,
# 手搓出来就不是合法 JSON,fail-open 的 hook 会答 0 → 你得出「无人强制」并去删规则,
# 而它其实一直拦着。实测同一条含引号的违规命令:手搓 → exit 0,json.dumps → exit 2。
# **而最典型的探针形状 `git commit -m "msg" --no-verify` 恰好就带引号。**
# ⚠️ 下面三道前置检查不能省。**这个探针所有的失败模式都指向「假放行」** ——
# 而假放行正是会让你去删掉一条其实有效的规则的那个方向(实测过的三种):
# ① 没有 python3 → 管道首段崩、hook 收到空 stdin → fail-open 答 0 → 打印「exit=0」
# ② 只粘贴了探针行、漏掉上面两行赋值 → 同样打印「exit=0」(`set -u` 也救不回,
# unbound 发生在管道首段的子 shell 里,脚本不死)
# ③ 贴进 `set -euo pipefail` 脚本时,**hook 拦截(exit 2 = 阳性结果)会先杀死脚本**,
# `echo` 根本执行不到 → strict 宿主里这个探针只可能打印出「放行」一种判决
command -v python3 >/dev/null || { echo "探针不可用:没有 python3(换机器或先装)" >&2; exit 9; }
: "${BAD_CMD:?探针不可用:BAD_CMD 未赋值——你大概只粘贴了下半段}"
[ "${#HOOK_CMD[@]}" -gt 0 ] || { echo "探针不可用:HOOK_CMD 未赋值" >&2; exit 9; }
# 用 if 包住:`if` 的条件位豁免 set -e,所以「拦截」这个阳性结果也打得出来
if BAD_CMD="$BAD_CMD" python3 -c 'import json,os,sys; sys.stdout.write(json.dumps({
"hook_event_name":"PreToolUse","tool_name":"Bash",
"tool_input":{"command":os.environ["BAD_CMD"]},
"cwd":os.getcwd(),"session_id":"probe","transcript_path":"/dev/null"}))' \
| "${HOOK_CMD[@]}" >/dev/null 2>&1
then ec=0; else ec=$?; fi
echo "exit=$ec # 2 = 拦截(这条规则有人强制),0 = 放行"
这个探针有两个会让你得出相反结论的坑,都实测踩过:
- payload 缺字段 → 判决翻转。 真实事件还带
cwd/session_id/transcript_path/hook_event_name, 很多 hook 读它们。实测同一条rm -rf important-data:不带cwd→ exit 0(该 guard 的设计是 「相对路径 + 无 cwd = 判不出,放行」),带cwd→ exit 2。用精简 payload 探,你会给一条 真的有防护的规则判「无人强制」,然后动手拆掉它的正文。 - 枚举目录 ≠ 枚举注册表,而且两个方向都错。 实测某机器:注册 28 条,其中 1 条在 hooks 目录之外
且是
.py(拿bash跑它 → 语法错 → 非 2 → 被记成「没拦」=漏);目录里反而躺着一个.json配置和一个陈旧的*.sh.bak-*(被误当 hook 跑出 exit 2 → 记成「有人强制」=误, 于是授权你删掉真正在起作用的正文)。此外项目级.claude/settings.json、plugin 自带 hook 都不在那个目录里,而matcher决定某个 hook 根本不对这个工具开火。权威源是 settings.json 的hooks块(配合claude --debug),不是ls一个目录。
(hook 事件 schema / 注册方式 / exit 语义的完整说明见 daymade-claude-code:claude-code-hooks skill。)
从「装了 N 个 hook」推断「这条被覆盖了」是最危险的一步 —— 若 X 其实不存在,你就
亲手拆掉了唯一在起作用的防线,还留下一句让后来者以为有保护的谎。实测比推断便宜得多
(详见 references/progressive_disclosure_principles.md 案例 18)。
多入口原则(重要!)
同一 Level 2 资源可以有多个入口,服务于不同查找路径:
| 入口 | 位置 | 触发场景 | 用户心态 |
|---|---|---|---|
| Reference 索引 | 开头 | 遇到错误/问题 | "出 bug 了,查哪个文档?" |
| 修改代码前必读 | 中间 | 准备改代码 | "我要改 X,要注意什么?" |
| Reference 触发索引 | 末尾 | 长对话定位 | "刚才说的那个文档是哪个?" |
这不是重复,是多入口。 就像书有目录(按章节)、索引(按关键词)、快速参考卡(按任务)。
边界(与 SSOT 的张力,必须守住):多入口成立仅当——每个入口 keyed 方式不同(错误索引 / 任务索引 / 末尾复述),且都只指向同一 Level 2 资源、不复制它的正文。如果你把同一段规则正文抄到 3 个地方,那是违反 SSOT 的重复(会各自漂移),不是多入口。一句话判据:入口存的是"路标 + 触发条件",不是"内容副本"。
优化工作流
Step 1: 备份
cp CLAUDE.md CLAUDE.md.bak.$(date +%Y%m%d_%H%M%S)
Step 2: 内容分类
分三阶段。先测量,再分诊,再分层——跳过测量会把力气花在小头上,跳过分诊会把噪音忠实搬进 Level 2,把 reference 变垃圾场。
2.0 热点测量(先于一切提案——性能优化的第一课)
先量化,后动手;按贡献度排序,先打最大的。 优化提案落在 3% 的小头上、而 70% 的热点在旁边没人动,是本 skill 实战里被用户当场打断的真实失败(案例 19:一份 168KB 的全局 CLAUDE.md 占每 session 启动上下文 69%,执行者却先端出一盘扩展清理——用户原话「你没有先去管热点,而是先找了一堆很小很小的东西」)。测量四步(scripts/profile_claude_md.py 产出第 2/3 步;第 1 步是会话内命令、第 4 步手查各消费方配置):
- 全局占比:
/context看这份文件在整个启动上下文里占多大——确认它是不是热点,还是别的类别才是 - 分节字节表:按 heading 统计每节 bytes/lines 并降序——工作顺序 = 这张表的降序(⚠️ 父节字节含全部子节:降序在同层之间比较,容器节跳过看它最大的子节);提案端出去前自问:这是当前最大贡献者吗?不是的话,最大的那个为什么不在最前面?
- 行长分布:>1KB 的巨型行是「规则+战例焊死在一个 bullet」的签名(实战:4.4% 的行承载 35.6% 的字节)
- 消费方上限:这份文件若被其他工具消费(如 Codex 经
~/.codex/AGENTS.mdsymlink 读同一文件),逐个查它们的截断上限——Codexproject_doc_max_bytes默认 32 KiB,超出的尾部静默不可见、无任何报错(实战:上限配过 96 KiB 当时够用,文件长到 164KB 后 41% 的正文对 Codex 隐形数周,整个协作规则 cluster 都在盲区)。修法 = 提上限 + 瘦文件双管齐下;再把「文件 size vs 上限」装进 SessionStart 体检哨兵——没有机械力量看着这个数字,它必然再次静默失效
⚠️ 测量仪器自身的两个坑(都实测踩过,脚本已内建规避;先在已知答案的样本上校准,见案例 17/19):
- heading 正则必须感知 code fence——fence 里的
# 注释会被当成标题,凭空造出不存在的大节(实测造出过一个假的 45.9KB 节,热点排序整个失真) - CJK 文件禁用 chars/4 估 token——中文密集文本实测 ~0.42 token/byte(≈2.4 bytes/token),chars/4 低估一倍以上;有
/context实测值就按实测比率折算,并一律标「est.」
2.1 信号分诊(必要性闸门,先决)
对每个章节先问 Anthropic 官方 litmus:"删掉这一条,Claude 会不会犯错?"
- 会犯错 → 是信号,进入 2.2 分层
- 不会犯错,且属以下任一 → 是反信号,列入"候选删除"清单:
- 能从代码 / 项目结构 / 文件名推断的(如"本项目用 TypeScript")
- 语言 / 框架的标准约定(如"遵循 PEP 8")
- 自明常识(如"写干净的代码""提交前测试")
- 已有独立 canonical source 覆盖的(注明 source 在哪)
- 已过时的一次性修复(不会再复发)
- 确定性必须每次发生的(如"提交前必跑 lint")→ 标记"建议转 hook",不替用户实现(散文保证不了确定性)。先按上方载体表查它落在哪一格,把那一格的结论写进候选清单——deliverable 是带载体判定的建议,不是替用户写 hook。⚠️ 别预设答案一定是 hook:本例「提交前必跑 lint」是触发自报(
git commit自己就报了)且违规可恢复(事后补跑),查表落在 Skill 格,不是任何一种 hook。「确定性必须每次发生」只说明散文不够,没说必须用 hook——哪个载体由表决定
安全栏(与移动同等严格,不可削弱):候选删除 ≠ 立即删除。必须事前逐项列出 + 注明属上面哪类 + 征求用户确认。说不出理由 = 不是反信号,回 2.2 当信号处理。
与案例 8/9 的边界:8/9 是把真信号(debug 提示、代码模式)在移动时压缩掉 = 永远错;这一步是移除已确认反信号(可推断 / 自明)= 正确。区别在"删的是不是信号",不在"删不删"。详见
references/progressive_disclosure_principles.md案例 10。
2.2 分层分类
对通过分诊的信号分类:
| 问题 | 是 | 否 |
|---|---|---|
| 高频使用? | Level 1 | ↓ |
| 违反后果严重? | Level 1 | ↓ |
| 有代码模式需要直接复制? | Level 1 保留模式 | ↓ |
| 有明确触发条件? | Level 2 + 触发条件 | ↓ |
| 历史/参考资料? | Level 2 | 考虑删除 |
Step 3: 创建 Reference 文件
命名:docs/references/{主题}-sop.md
铁律:原样移动,禁止压缩
移动内容到 Level 2 时,必须完整保留原始内容。不要在移动的同时"顺便精简"。
✅ 正确:把 100 行原封不动搬到 Level 2(100 行 → Level 2 100 行)
❌ 错误:把 100 行"精简"到 60 行搬到 Level 2(100 行 → Level 2 60 行,40 行消失)
为什么:压缩 = 变相删除。你认为"不重要"而删掉的内容,可能是某个未来 debug session 的关键线索。优化的目标是改变信息的位置(Level 1 → Level 2),不是改变信息的存在。
怎么做:
- 从原始 CLAUDE.md 中精确复制要移动的段落
- 原样粘贴到 Level 2 文件中
- 可以在 Level 2 中添加结构(标题、分隔线),但不要删减、改写、合并原始内容
- 如果确实有冗余(同一段话在原文中出现了多次),在 Level 2 中保留一份完整的,注释说明去重
整节批量下沉的机械流程(≥3 节时脚本化,禁手搬)
手工复制粘贴 10 个节必出错。用 scripts/sink_sections.py(spec 驱动;实战一次通过 10 节 / 119KB,整串验证 10/10 零丢失):按精确标题行定界提取原文(fence 感知)→ verbatim 追加到目标 reference(带日期 provenance header,新文件配 intro)→ 自底向上替换 L1 压缩版(行号不失效)→ 每节整串子串验证(grep 对多行原文按行 OR、会放过丢半段的搬运,必须 python in 整串判断)→ 任一验证失败自动回滚源文件。两条硬规则:
- 拒写 symlink 目标(含父目录):目标路径任一环节是 symlink(文件本身、或父目录——文件级
islink检查会被目录级 symlink 静默穿透,独立审阅实测打穿过),"本地追加"实际在改 link 指向的那个仓(触发它的版本 bump / commit 义务,且那个仓可能 public)。脚本按realpath ≠ abspath判定并 abort,特意跨 link(如 macOS/tmp)用--allow-symlinked-target显式放行;正确动作是落一个本地兄弟文件 + provenance 注明「与 symlink 源后续合并」 - 先全部提取、后统一替换:提取按原始行号一次做完,替换自底向上——两步交错会让未处理节的行号漂移。压缩版 snippet 必须保留原 start_heading 行(验证器逐个检查标题存活,改名即 FAIL 回滚);用作定界的标题在源文件里必须唯一(重名 abort)
Step 4: 更新 Level 1
- 在开头添加「信息记录原则」(项目概述之后,Reference 索引之前)
- 添加 Reference 索引(紧随信息记录原则之后)
- 用触发条件格式替换详细内容
- 保留代码模式和错误诊断
- 添加「修改代码前必读」表格(按"要改什么"索引)
- 在末尾再放一份触发索引表
⚠️ 写指针前的硬 gate(事中验证,最易跳过、本次最大踩坑):每写一条「→ 某 reference / 详见 X」指针前,当场确认目标文件真有这段内容。
⚠️ 验的方式看你要验什么(Step 5.0 表已实测):只验「这段在不在」→ 抽 3–5 个特异串用 grep -F 查即可;
要验「整段完整搬过去了」→ 不能用 grep —— 原句多行时 grep -F 按行 OR,丢半段照样报命中,
必须用 python3 整串子串判断。三种结果:① 目标已有完整内容 → 写指针;② 目标没有 / 不确定是否完整 → 先把原文 verbatim cut 到目标(回 Step 3),再写指针;③ 绝不写「指向一个其实没有该内容的文件」的假指针。假指针比丢内容更隐蔽——它让 5a「文件存在」通过、却在读者点进去时才发现是空的。Why:5a/5b 是事后验证,假指针那一刻已写进文件;事中 gate 才能在源头拦住。(实战:写「详见 anti-patterns」但那里 0 命中 Stripe 端点 → 案例 15。)
Step 5: 验证(三项全部通过才算完成)
5.0 先标定判据本身 —— 验证器会骗你,而且两个方向都会
下面 5a/5b 全建立在 grep/find 上。一个错的判据会和对的判据一样自信地报告结果,
而这一步的产物(「这段已经下沉了」「这个指针是真的」)会被当成事实写进交付。
所以:先在一个你已知答案的样本上跑一遍判据,确认它真的会命中,再用它去查你不知道答案的。
这一行成本,把「我查过了」变成「我用标定过的判据查过了」。
实测踩过的形态如下。注意它们的坏法不一样——多数是静默假阴性(0 命中被读成「内容丢了」),
但有一条给假阳性(为有损搬运开脱,最危险),还有一条是响亮报错(exit 2,在 2>/dev/null 的
脚本里同样被吞成「没找到」)。所以标定时别只看有没有输出,要看 exit code:
| 陷阱 | 症状 | 修法 |
|---|---|---|
| 递归搜索悄悄跳过 symlink | reference 目录里只要有一个 symlink(skill 安装、SSOT 外置极常见),整片内容对验证器不可见 → 把「已下沉」误报成「未下沉」 | 别去挑递归 flag —— 先把路径解析成真身再读:readlink -f <path> 拿到真实文件,或 find -L <dir> -type f -name '*.md' 枚举后逐个读(两种都不依赖任何实现,实测三种 grep + BSD/GNU find 行为一致)。⚠️ 递归 flag 的 symlink 语义因实现而异,且没有可移植组合——挑哪个都会坑掉一部分读者;三实现实测矩阵见案例 17 ①,此处不复述数值(会漂) |
| 代理判据(拿 A 的存在证明 B 已完成) | 用「日期锚点是否出现在 reference」判是否已下沉 —— 而 reference 的节标题里带个日期就让整段显示为「已下沉」,实际那节里一条子发现都没有 | 判据必须落在被判对象本身上:抽该段的 3–5 个特异串(具体值/命令/专名)逐个查 |
| 行级度量高估工作量 | 「含该锚点的整行」包含大量不需搬的规则正文,量到的是「含有它的行的总长」而非内容本身 | 按段落量,不按含关键词的行的字节数 |
grep -F 对多行原句退化成「按行 OR」(唯一会给假阳性的一条,最危险) |
验 verbatim 搬运时,原句是多行的:grep -F 把它当成多个独立 pattern,命中任意一行就报成功。搬运时丢了半个段落,判据照样报「还在」 —— 它为一次有损搬运出具了无罪证明,而这正是 5b 存在的理由 |
存原句到临时文件用 python3 - <<'PY' 做整串子串判断(需要的原文 in 目标文件内容)—— 它要求连续完整匹配,丢一行就 False。必须 python3:裸 python 在 stock macOS(12.3 起)已被移除。⚠️ 精简 Linux 镜像(如 debian:*-slim)两个都没有,实测 python3 也 ABSENT —— 那种环境下先装再用,别以为换成 python3 就一定跑得起来 |
| 探针串含正则元字符(三种坏法,且同一字符换个位置就换一种) | 实测(BSD 2.6.0 / GNU 3.11):① 中段 *(use * wildcard)→ 两边都 静默 exit 1,长得就像「内容丢了」;② **开头的 **(markdown 粗体 —— CLAUDE.md 里最常见的探针形状)→ BSD 响亮报错 exit 2 repetition-operator operand invalid,而 GNU exit 0 命中 —— 同一个串,一边硬错一边成功;③ 方括号日期 [2026-07-26] → 两边都 exit 2(BSD invalid character range / GNU Invalid range end)。报错在 2>/dev/null 的脚本里和「没找到」长得一模一样。 |
一律 grep -F(固定串),或走上面的 python3 子串判断。脚本里别把 stderr 丢掉 —— exit 2(判据坏了)和 exit 1(真没命中)必须分开处理 |
⚠️ 别把「重新折行」算进上面第 4 行:搬运时重新折行本身就违反反模式 6「原样复制,不改一字」, 判据判它失败是对的,不是误伤。python3 子串判断在这种情况下同样返回 False(实测)—— 它不是用来给折行开脱的,没有任何判据该给折行开脱。
完整战例(判据陷阱如何连环误导同一个执行者)→ references/progressive_disclosure_principles.md 案例 17
(该案例覆盖上表前三行;第 4、5 行来自同期对判据本身的实测,无独立战例)。
判据陷阱有第二层,比第一层更隐蔽:不只判据会骗你,修法也会。
一个依赖具体实现的修法(换个 flag、加个选项)在你机器上验证通过,换台机器静默失效 ——
而你不会收到任何信号。优先选不依赖实现的做法(解析路径而非调递归 flag、
子串判断而非行级匹配);实在要用 flag,就在标准实现(/usr/bin/<tool>)上复验一次。
这一条是在写它的过程中自己撞出来的,所以它不是理论。为修上表第一行的 symlink 问题, 作者写了一段
find -L的替代脚本 —— 在 bash 下完全正确,在 zsh 下把 4 个真实存在的 章节全报「NOT FOUND」。原因是for d in $REFDIRS依赖词分割:bash 有,zsh 没有, 而 zsh 是现代 macOS 的默认登录 shell。改成数组"${ARR[@]}"后两个 shell 输出一致。 教训:「修法也会骗你」不只跨 工具实现(BSD/GNU),还跨 shell、跨 locale、跨版本。 所以定案标准不是「我跑通了」,是「我在读者最可能用的那个环境里跑通了」—— 对 macOS 读者,那至少意味着 bash 和 zsh 各跑一遍。
元规则:本节几条的共同点不是「grep 用法要小心」,而是 判据与被判对象之间只要隔了一层代理,缝隙里就能穿过去东西。 写任何一条验证命令时问一句:它测的到底是不是我想知道的那件事?
5a. 引用文件存在性
# 抓出正文里所有反引号包起来的 .md 路径(不写死 docs/references/——用户级布局是
# ~/.claude/references/,写死会一条都抓不到)
# 尾巴不能省,但**也不能写成 `|| true`**:
# · 为什么需要:一条指针都没有时 grep exit 1。在 **`set -e` 与 `pipefail` 同时开**时
# (单开任一个都不会)整条管道致命,整块在打印任何东西之前就死 —— 而「0 条」
# 正是下面要报的那种情况。实测:`set -o pipefail` 单开跑完、`set -e` 单开跑完、
# `set -eo pipefail` 输出为空 exit 1。
# · 为什么不能用 `|| true`:它把 grep 的 **exit 2(判据坏了,如 cwd 里根本没有 CLAUDE.md)**
# 和 **exit 1(真的 0 命中)** 一起吞掉,于是误报「抓到 0 条 + 🚨 模式没命中」还 exit 0 ——
# 正好违反本文件 5.0 表第 5 行「exit 2 和 exit 1 必须分开处理」。
# · `|| [ $? -eq 1 ]` 只放行 exit 1;exit 2 仍然响亮致死(两 shell 实测)。
grep -oh '`[^`]*\.md`' CLAUDE.md | tr -d '`' | sort -u > /tmp/pointers.txt || [ $? -eq 1 ]
# ⚠️ 先标定:抓到 0 条 ≠ 全部通过,而是「这个模式没匹配上你的写法」
n=$(wc -l < /tmp/pointers.txt | tr -d ' ') # BSD wc 会补空格,去掉
echo "抓到 $n 条指针"
# 用 if 而不是 `[ … ] && echo`:后者在 n>0 时整行 rc=1。
# ⚠️ 别把这说成「会中断 set -e 脚本」——实测 bash/zsh 都**不会**(POSIX 豁免 AND 列表
# 非末位的失败)。真正会出事的是它**作为最后一行**时把 rc=1 泄漏成整个脚本的退出码。
# 用 if 是 rc 中性的卫生做法,不是在修一个「中断」bug。
if [ "$n" -eq 0 ]; then
echo "🚨 0 条 = 模式没命中,不是没问题——先手工确认正文到底怎么写引用的"
fi
while read -r f; do
# ⚠️ 分「可判定 / 不可判定」,别把散文里的东西一律报成断链(见下方真实语料实测)
case "$f" in
*\**|*\?*) echo "– 跳过(glob): $f"; continue ;;
*\<*|*\>*) echo "– 跳过(模板占位): $f"; continue ;;
*" "*) echo "– 跳过(含空格,散文): $f"; continue ;;
*/*) ;;
*) echo "– 跳过(裸文件名,无根): $f"; continue ;;
esac
case "$f" in
/*|\~/*|./*) p="${f/#\~/$HOME}" ;; # 绝对 / 家目录 / 显式相对 → 可判定
*) # 相对路径:首段在 cwd 里存在才可判定。**这条不能省** ——
# Step 3 规定的命名就是 `docs/references/{主题}-sop.md`(无 ./ 前缀),
# 一律当「未知根」跳过 = 5a 对本 skill 自己规定的布局一条都不检
first="${f%%/*}"
if [ -d "$first" ]; then p="$f"
else echo "– 跳过(相对未知根): $f"; continue; fi ;;
esac
[ -e "$p" ] && echo "✓ $f" || echo "✗ MISSING: $f"
done < /tmp/pointers.txt
为什么要那段标定:原版把
docs/references/写死在模式里。在本 skill 自己定义的 用户级布局(~/.claude/references/)上跑,它匹配 0 条 → while 循环一次都不进 → 零输出、exit 0,和「所有引用都存在」的输出完全一样。实测:一份含真断链的 CLAUDE.md 被它判为干净。这正是 5.0 那条「0 命中必须双向读」,而 5a 自己没做。为什么要分「可判定 / 不可判定」:这段的第一版只在合成 fixture 上验过、全绿。 拿真实语料(一份 120KB 的全局 CLAUDE.md)一跑,32 条候选里 10 条「MISSING」是误报 —— 散文里提到的裸文件名(
incident-2026-04-18-*.md)、glob(*/memory/*.md)、 带占位符的模板(<config>/…),以及一对被反引号连在一起、中间有箭头的两个路径。 按本 skill 自己的规矩:误杀健康输入比漏报更糟 —— 31% 的误报率会直接训练读者忽略这个检查。 分类后:可判定 21 条全部正确,不可判定 11 条单独列出待人工确认,误报 0。 而那一轮真的抓到 1 条断链(指向的 memory 文件被挪进了.memory-archive-*dot 目录, 且路径大小写也变了)—— 合成 fixture 永远造不出这种形状。本段命令已在 bash 与 zsh 下各跑一遍、输出字节一致。 为什么要跨 shell 验:见附录 C 里那个「字符串 + 词分割」的坑 —— 同一段脚本 bash 全对、zsh 全错,而 zsh 是现代 macOS 的默认 shell。
5b. 内容完整性(最关键)
对每个从原始 CLAUDE.md 移走的章节,逐一检查:
-
取到「改动前」的原始文件当对照物。首选 Step 1 那份备份——
CLAUDE.md.bak.<时间戳>建于一切编辑之前、之后再没被写过, 天然满足推论②「对照物必须在你的写入范围之外」,且不依赖任何 git:ORIG=$(command ls -1t CLAUDE.md.bak.* 2>/dev/null | tail -1) # 最早那份 = 动手前 echo "对照物:$ORIG"⚠️ 别默认目标在 git 里。 最常见的优化对象是全局
~/.claude/CLAUDE.md, 而~/.claude通常根本不是 git 仓库(fatal: not a git repository)—— 本 skill 两个新战例都出自这个场景。备份法在这里是唯一可行的,git 法直接死在第一步。目标确实在 git 仓里时,git 法可作交叉验证,但有两个坑:
- 别用
HEAD:中途 commit 过的话 HEAD 已在你的写入范围内,等于自己给自己作证。 先git log --oneline -- <文件>找到本次工作之前的那个 ref。 git show <ref>:CLAUDE.md从仓根解析,不是从当前目录。在 monorepo 里优化packages/x/CLAUDE.md,它会静默返回仓根那份(exit 0、无任何警告), 你之后每一条 5b 判定都是噪音。要当前目录那份必须写<ref>:./CLAUDE.md(实测有别)。
- 别用
-
逐节对比:对原始文件的每个
##章节,确认其内容在以下位置之一完整存在:- 新 CLAUDE.md 中(保留在 Level 1)
- 某个 Level 2 reference 文件中(完整移动)
📖 快速暴露整章遗漏的辅助脚本见
references/progressive_disclosure_principles.md附录 C:触发场景——做下面逐节对比前的第一道筛查(脚本不替代人工逐节对比,只查章节标题是否存在)。 -
标记所有差异:
- 如果某段内容在新文件中被缩短 → 必须补回被删减的部分
- 如果某段内容在两个位置都不存在 → 必须补回
- 唯一允许删除的情况:该信息已有独立的 canonical source(如
docs/README.md已是文档索引的 canonical source),且在 Level 1 中有明确的指向
禁止将"故意删除"作为分类来掩盖信息丢失。 每一项"故意删除"都必须说明 canonical source 在哪里。如果说不出来,就不是"故意删除",而是"遗漏"。
压缩重述的保真审计(L1 留了压缩版时必查):压缩最容易丢的不是整段——是限定词。实战(案例 19):原句「public + 0 stars/forks 且用户明确授权」被压成「0 stars 且明确授权」,6 个字符消失,一道闸门的条件字面上放宽了一半;同场审计还抓到「自称只省略战例、实际连 4 条可执行判据也省了」的申报口径不符。两个审计动作:① 对每条压缩重述,把操作性子句(条件 / 数值 / 枚举 / hook 名 / 否定词)与原句逐词 diff——整段丢失 5b 能抓,一个 "/forks" 只有子句级 diff 能抓;② 全文跑 expected-hunks-only 检查——difflib 比对基线,每个非 equal hunk 必须指认到一条已声明的改动,指认不了的就是计划外差异。
独立 agent 做 5b 是默认动作,不是「大量压缩时才用」:执行者自审有「乐观偏差」——倾向相信自己砍掉的内容都有归属。启动一个独立 sub-agent(普通 subagent,禁 fork——fork 继承你的盲区,只会盖个「已审」的章)做完整逐节 5b(读原始文件 + 当前文件 + 所有 reference,逐个信息点验证归属,只返回「真丢失 / 指针失准」清单)。它没有你的 sunk-cost,能抓到你抽查会放过的。prompt 模板 + 批量内容点 grep 脚本见 references/progressive_disclosure_principles.md 附录 D。
为什么从「强烈推荐」升级为默认:本 skill 的真实使用中,执行者抽查 5 点「自我感觉良好」,独立 agent 逐节查 55 点才暴露真问题。更硬的一次(2026-07-26):反模式 6 的规则完整、连「grep 原句逐字节仍命中」的判据都写在文件里,执行者读过、认同、自认在遵守,重写时仍删掉了一条约束——而且它只存在于被删的那处,reference 里没有副本。这不是缺规则,是散文规则由缺陷源本人执行时对该缺陷源天然失效(poka-yoke 的 control vs warning:警告依赖警觉,而警觉正是完成驱动碾过的东西)。唯一逮住它的是换人的第二轮独立审阅。
推论(同源,别只做一半):① 审阅之后的改动同样没被看过 —— 落实审阅结论且逐条复现过,就把「不再派一轮」的理由写进审阅记录:一个 independent-review.md,含审阅者 prompt 原文(让后来者能判断你是不是问了诱导性问题)、每条 finding 及其处置与理由(这是「正当筛选」与「把不利的丢掉」的唯一分界)、以及查不了的东西。
放哪(别让读者自己发明路径):放你自己的、纳入版本控制的私人知识仓,路径 skill-reviews/<被优化对象>/independent-review.md。三条禁区:别放进被优化的那个项目仓(记录里必然带私有路径 / 真名 / 项目细节,而那个仓可能是公开的或将来会公开);别放 /tmp 或任何会被清掉的暂存目录(这是跨 session 的证据,必须活过重启);优化 ~/.claude/CLAUDE.md 时尤其注意 ~/.claude 通常不是 git 仓,记录放进去等于没版本控制。不知道自己的私人知识仓是哪个 → 问用户,别猜。② 对照物必须在你的写入范围之外:拿一份你自己刚改过的 reference 去证明「内容还在」,等于自己给自己作证。优先用早于本次工作的 git ref。
5c. 行数不进验证标准
验证不以行数为通过条件,不计算"原始 X 行 vs 新 Y 行 = 减少 Z%"——这种对账会把你拉回 KPI 思维。
验证标准只有三条:
- 每段信息都有归属(Level 1 或 Level 2 或 canonical source)
- 没有信号丢失(反信号经确认删除不算丢失)
- Level 2 引用都有触发条件
(注:诊断阶段可以看行数当怀疑信号,见开头「铁律」;但验证阶段行数不是任何标准——这两个阶段对行数的态度不同,别混。)
Level 1 内容分类
🔴 绝对不能移走
| 内容类型 | 原因 |
|---|---|
| 核心命令 | 高频使用 |
| 铁律/禁令 | 违反后果严重,必须始终可见 |
| 代码模式 | LLM 需要直接复制,避免重新推导 |
| 错误诊断 | 完整的症状→原因→修复流程 |
| 目录映射 | 帮助 LLM 快速定位文件 |
| 触发索引表 | 帮助 LLM 在长对话中定位 Level 2 |
🟡 保留摘要 + 触发条件
| 内容类型 | Level 1 | Level 2 |
|---|---|---|
| SOP 流程 | 触发条件 + 关键陷阱 | 完整步骤 |
| 配置示例 | 最常用的 1-2 个 | 完整配置 |
| API 文档 | 常用方法签名 | 完整参数说明 |
🟢 可以完全移走
| 内容类型 | 原因 |
|---|---|
| 历史决策记录 | 低频访问 |
| 性能数据 | 参考性质 |
| 技术债务清单 | 按需查看 |
| 边缘情况 | 有明确触发条件时再加载 |
引用格式(四种)
四种引用格式各服务不同场景;规范的"触发条件"写法见下方 原则 2(已含可复制示例)。
| 格式 | 用途 | 触发场景 |
|---|---|---|
| 详细格式 | 正文中的重要引用 | 单条 reference 需展开说明何时读 |
| 问题触发表格 | 开头/末尾 Reference 索引 | 按"错误/问题"查 |
| 任务触发表格 | 「修改代码前必读」 | 按"要改什么"查 |
| 内联格式 | 简短引用 | 正文一句话带过 |
📖 四种格式的完整可复制模板见 references/progressive_disclosure_principles.md 附录 B:触发场景——产出 Reference 索引 / 任务表 / 内联 / 详细引用时。
多样性原则:不要所有引用都用同一格式。
⚠️ @import 不省上下文(技术正确性,最易踩)
@path import 在启动时全量展开载入——拆成 @import 只改善组织,不减少任何上下文(官方 memory 文档原文)。"我把内容拆进 @import 了所以优化了"是假优化。
全局 ~/.claude/CLAUDE.md 真正能省上下文的杠杆只有三条:
- 把非通用内容移到项目级 CLAUDE.md(全局文件会被无关项目加载)
- 留纯文字指针("需要时 Read
references/xxx.md",不是@),让模型按需拉 - 转 skill(描述常驻、正文按需)
本 skill 产出的引用一律用反引号路径,禁止用 @import 做卸载。详见 references/progressive_disclosure_principles.md 案例 11。
核心原则
原则 0:添加「信息记录原则」(防止未来膨胀)
问题:优化完成后,用户会继续要求 Claude "记录这个信息到 CLAUDE.md",如果没有规则指导,CLAUDE.md 会再次膨胀。
解决:在目标 CLAUDE.md 开头(项目概述之后)注入一段「信息记录原则」——规定 Level 1 只记核心命令 / 铁律 / 代码模式 / 触发索引,Level 2 记详细 SOP / 边缘情况 / 历史决策,并定义"用户要求记录信息时"的高频→L1、低频→L2 判断流程(引用 L2 必带触发条件)。
📖 完整可注入模板见 references/progressive_disclosure_principles.md 附录 A:触发场景——执行 Step 4 更新 Level 1 时;附录含可整块复制进目标 CLAUDE.md 的 markdown。
原因:这条规则让 Claude 自己知道什么该记在哪里,实现"自我约束",避免后续对话中 CLAUDE.md 再次膨胀。
原则 1:触发索引表放开头和末尾
原因:LLM 注意力呈 U 型分布——开头和末尾强,中间弱。
| 位置 | 作用 |
|---|---|
| 开头 | 对话开始时建立全局认知:"有哪些 Level 2 可用" |
| 末尾 | 对话变长后复述提醒:"现在应该读哪个 Level 2" |
📖 首/尾索引表完整写法示例见 references/progressive_disclosure_principles.md 案例 4:触发场景——决定触发索引表放哪、按什么格式写时。
原则 2:引用必须有触发条件
错误:详见 native-modules-sop.md
正确:
**📖 何时读 `native-modules-sop.md`**:
- 遇到 `ERR_DLOPEN_FAILED` 错误
- 需要添加新的原生模块
> 包含:ABI 机制、懒加载模式、手动修复命令
原因:没有触发条件,LLM 不知道什么时候该去读。
原则 3:代码模式必须保留在 Level 1
错误:把代码示例移到 Level 2,Level 1 只写"使用懒加载模式"。
正确:Level 1 保留完整的可复制代码:
// ✅ 正确:懒加载,只在需要时加载
let _Database = null;
function getDatabase() {
if (!_Database) {
_Database = require("better-sqlite3");
}
return _Database;
}
原因:LLM 需要直接复制代码,移走后每次都要重新推导或读取 Level 2。
原则 4:用三态优先级,不要"全标铁律"
问题:把每条规则都标"铁律 / HIGHEST / 全局" = 没有优先级。模型无法 triage,注意力被摊薄,最关键的不可逆规则反而被淹没。指令遵循存在约 150–200 条的上限,远超即整体衰减。
第二个机制,比注意力稀释更隐蔽:指令互相干扰。 一个上下文里堆着 20 个工作流的指令时, 它们会彼此冲突("输出一律 JSON" 撞上一个面向客户、JSON 完全不对的流程)。 模型不会报错,它只是悄悄做出更差的决定。 所以「多写一条总没坏处」是错的 —— 每多一条都在提高与既有指令冲突的概率,而这种劣化没有任何报错信号, 你只会看到「它最近好像笨了点」。这也是为什么分层不只是省 token: 把当前任务用不上的指令挪出上下文,本身就在减少干扰源。
诊断信号:同一份文件里出现两条在某个场景下会给出相反建议的规则, 而没有一条说明「哪种情况听哪条」—— 那不是两条规则,是一个待解决的冲突。
出处:这一现象在文献里有名字 —— context pollution(冗余 / 冲突信息扭曲推理) 与 context confusion(模型无法区分指令、数据与结构标记,尤其在系统指令自相矛盾时)。 与 Anthropic「有限注意力预算 → 找最小高信号 token 集」是同一个根因的两面 (见
references/progressive_disclosure_principles.md§研究背书)。
解决(GitHub 2500 仓库实证最有效的结构):输出 Level 1 规则时用三态,而不是一律"铁律":
| 标记 | 含义 | 例 |
|---|---|---|
| ✅ | 总是这样做 | ✅ 提交前跑测试套件 |
| ⚠️ | 先停下问 / 谨慎 | ⚠️ 改 schema 前先确认迁移脚本 |
| 🚫 | 绝不 | 🚫 绝不提交 secret |
位置即优先级(Lost-in-the-Middle,TACL 2024):LLM 注意力 U 型分布,最高危的不可逆规则放文件首或尾,不要埋中间。真正"违反即不可逆伤害"的应是少数(5–7 条),其余降为普通规则——稀缺才有信号。
原则 5:每条保留规则带一行 Why
问题:不带原因的规则,一旦场景变化就被忽略(Builder.io 实证)。带 Why 的规则能跨场景泛化。
解决:Level 1 保留的每条铁律 / 禁令,跟一行 Why:,说明违反会发生什么具体坏事。
错误:🚫 禁止 fallback 默认值
正确:🚫 禁止 fallback 默认值。Why:一个 || 'sk-xxx' 兜底在 .env 缺失时静默回退明文 key,曾在 48h 内被公开仓库扫描器用掉额度。
⚠️ 重述规则时的硬边界:若原句嵌在 case study 混合段落里,原则 4/5 不得直接改写原句——见反模式 6(先整段 verbatim 移 L2,案例 14)。
反模式警告
⚠️ 反模式 1:以行数为目标的过度精简
案例:为了"减少行数",移走了代码模式、诊断流程、目录映射
结果:
- 丢失代码模式,LLM 每次重新推导
- 丢失诊断流程,遇错不知查哪
- 丢失目录映射,找文件效率低
正确:保留所有高频使用的内容。优化的判断标准是信息是否重复维护、是否与当前任务无关,而不是"文件太长"。
⚠️ 反模式 2:无触发条件的引用
案例:详见 xxx.md
问题:LLM 不知道何时加载,要么忽略,要么每次都读。
正确:触发条件 + 内容摘要。
⚠️ 反模式 3:移走代码模式
案例:把常用代码示例移到 Level 2
问题:LLM 每次写代码都要先读 Level 2,增加延迟和 token 消耗。
正确:高频使用的代码模式保留在 Level 1。
⚠️ 反模式 4:删除而非移动
案例:删除"不重要"的章节
问题:信息丢失,未来需要时无处可查。
正确:移到 Level 2,保留触发条件。
⚠️ 反模式 5:用行数当 KPI
案例:优化方案写"从 2000 行精简到 500 行,减少 75%"
问题:把行数当成功指标,会驱动错误决策——为了凑数字而砍掉有用的信息。
正确:用信息质量评估优化效果——信息是否有重复?维护负担是否降低?LLM 是否能更快找到需要的信息?
⚠️ 反模式 6:移动时压缩(变相删除)
规则:移动是移动,精简是精简。这是两个独立操作,不要同时执行。
- 移动内容到 Level 2 时,必须原样复制,不改一字
- 如果发现冗余需要精简:作为单独的后续步骤,逐项列出要删除的内容及理由,征求用户确认
- "既然都在改了,顺便精简一下"是最隐蔽的删除——它披着"优化"的外衣,做着"删除"的事
- 混合段落(规则句 + case study/叙事)的硬边界:原则 4/5、反模式 8 想把规则重述成 ✅/🚫+Why,但混合段落与本反模式冲突——整段必须先 verbatim 移 L2(规则句原句一字不改);L1 的重述是派生副本,与 L2 原句共存、不取代。判据:优化后原规则句的逐字节文本仍完整存在于 L2 verbatim 块。
⚠️ 这条判据别用 grep 验(Step 5.0 表第 4 行实测):原句多行时
grep -F把它拆成多个 pattern 按行 OR, 丢了半段照样报命中 —— 它会为一次有损搬运出具无罪证明。用整串子串判断: 把原句存进临时文件,python3 -c "print(open('orig').read() in open('target').read())",要求连续完整匹配。 原则 4/5 管 L1 如何呈现,不授权销毁信号原句
完整案例分析见
references/progressive_disclosure_principles.md案例 8、案例 14
⚠️ 反模式 7:用"故意删除"掩盖信息丢失
规则:任何"删除"都必须是事前决策(征求用户确认),不是事后分类(发现少了再编理由)。
- 对每项计划删除的内容,必须说明其 canonical source 在哪里
- 如果无法指出 canonical source → 不是"故意删除",是"信息丢失",必须补回
- 对丢失内容分类"严重性"(高/低风险)是在为自己的错误找台阶。正确的态度是:任何丢失都是 bug,fix it
完整案例分析见
references/progressive_disclosure_principles.md案例 9
⚠️ 反模式 8:纯否定规则(不给替代)
案例:🚫 不要用 X —— 没说改用什么。
问题:纯否定会让 agent 瘫痪——它知道不能走这条路,但不知道该走哪条,于是要么卡住要么乱试(Shankar + GitHub 2500 仓库均实证)。
正确:每条 🚫 必配一个 ✅ 改用 Y。
🚫 不要用全局 mutable 单例存请求状态
✅ 改用显式参数传递或 request-scoped context
优化时遇到孤立的禁令,补上正向替代再保留;补不出替代的禁令,说明规则本身没想清楚。
⚠️ 但若禁令原句嵌在 case study 混合段落里,先按反模式 6 整段 verbatim 移 L2,再在 L1 派生重述——不可改写原句(案例 14)。
⚠️ 反模式 9:假指针(指向不存在的内容)
案例:移走一段内容后写「详见 X.md」,但 X.md 里根本没有这段——指针指向空。
问题:比直接丢内容更隐蔽。5a「文件存在」会通过(X.md 确实存在),但内容不在那里;读者点进去才发现,且此时已无从知道原文是什么。本质是反模式 6(移动时压缩)+ 反模式 7(掩盖丢失)的组合:内容被砍 + 用一个看似合规的指针掩盖。
正确:写指针前当场验证目标真有该内容(Step 4 硬 gate;验「在不在」用 grep -F 抽特异串,验「整段完整」必须用 python3 子串判断——grep 会给假阳性,见 Step 5.0 表)。指针指错文件(内容在 A、却写「详见 B」)是同类问题,按内容实际所在地修正、不是删指针。
完整案例分析见
references/progressive_disclosure_principles.md案例 15
信息量检验
✅ 正确的信息量
| 检验项 | 通过标准 |
|---|---|
| 日常命令 | 不需要读 Level 2 |
| 常见错误 | 有完整诊断流程 |
| 代码编写 | 有可复制的模式 |
| 特定问题 | 知道读哪个 Level 2 |
| 触发索引 | 在文档末尾,表格形式 |
❌ 不足的信号
- LLM 反复问同样的问题
- LLM 每次重新推导代码模式
- 用户需要反复提醒规则
❌ 过多的信号
- 大段低频详细流程在 Level 1
- 完全相同的内容在多处(注意:多入口指向同一资源 ≠ 重复)
- 边缘情况和常见情况混在一起
项目级 vs 用户级
| 维度 | 用户级 | 项目级 |
|---|---|---|
| 位置 | ~/.claude/CLAUDE.md |
项目/CLAUDE.md |
| References | ~/.claude/references/ |
docs/references/ |
| 信息范围 | 个人偏好、全局规则 | 项目架构、团队规范 |
硬检查:scope 错放(官方层级文档裁定)
用户级 ~/.claude/CLAUDE.md 会被所有项目加载,只能放普遍适用的东西。优化时对每节做 scope 检查:
| 内容特征 | 归属 | 不这样做的后果 |
|---|---|---|
| 项目名 / 部署目标 / 逐项目路径 / 项目凭据 | 项目级,绝不全局 | 无关项目被污染;没人按项目维护 → 路径/状态腐烂(典型 staleness) |
| 个人偏好、跨项目行为规则 | 用户级 | — |
| 团队规范、项目架构 | 项目级(入 VCS) | — |
工作流加一条:Step 2.1 分诊时,项目特定内容在用户级文件 = 自动判"搬到项目级",不是搬 Level 2、更不是原地修路径。详见 references/progressive_disclosure_principles.md 案例 13。
金丝雀检测法(可选,长期维护)
来源:HN 社区单源("Mr Tinkleberry"),方法论成立、成本极低,作诊断不作保证。
优化后想知道 CLAUDE.md 哪天又膨胀到"规则开始被忽略"——在文件里植入一条无害的命名指令(如"提到临时变量时命名为 tinkle_tmp")。日常对话中观察:Claude 还遵守 = 文件仍在遵守度阈值内;Claude 开始无视这条 = 文件已越过阈值,该重新分诊。比凭感觉判断"是不是太长了"廉价且客观。
快速检查清单
优化完成后,必须逐项检查(不可跳过):
信息完整性(最重要)
- 原始文件的每个章节都有归属——在新 Level 1、Level 2、或有明确 canonical source
- Level 2 文件内容与原始内容完全一致——没有在移动过程中被"精简"
- 没有信号被静默删除——每项删除是反信号且有用户确认/canonical source(反信号删除正当,见 Step 2.1)
- 没有把行数当成果/KPI/移动理由/汇报指标(诊断性观察不在此限,见「铁律」)
- 每条「→ reference」指针都验证过目标真有该内容,且用对了工具:验「在不在」=
grep -F抽特异串;验「整段完整搬到」= python3 整串子串判断(grep 对多行原句会给假阳性)(无假指针 / 指针失准,Step 4 硬 gate;反模式 9;Step 5.0 表) - 跑了独立 agent 5b 审计(默认动作,非「大量压缩时才用」;禁 fork;Step 5b)
- 审阅之后若又改过:要么理由写进审阅记录,要么再派一轮(Step 5b 推论①)
结构质量
- 「信息记录原则」在文档开头(防止未来膨胀)
- Reference 索引在文档开头(入口1:遇到问题查这里)
- 核心命令表完整
- 铁律/禁令有代码示例
- 常见错误有完整诊断流程(症状→原因→修复)
- 代码模式可直接复制
- 目录映射(功能→文件)
- 「修改代码前必读」表格(入口2:按"要改什么"索引)
- Reference 触发索引在文档末尾(入口3:长对话后复述)
- 每个 Level 2 引用都有触发条件
- 引用的文件都存在
- 信号分诊已执行:反信号有候选删除清单 + 用户确认(Step 2.1)
- 每条铁律/禁令带一行
Why:(原则 5) - 优先级用 ✅/⚠️/🚫 三态,不是一律"铁律"(原则 4)
- 每条 🚫 都配了 ✅ 替代(反模式 8)
- 项目特定内容没有留在用户级文件(scope 硬检查)
- 引用未使用
@import做卸载(@import 不省上下文)
Version History
- e00a2ec Current 2026-08-20 11:26


