impeccable doctor:AI 设计工件漂移的检测、分级与修复完全指南
导读: impeccable doctor 是 impeccable 设计技能包中专门负责"体检"维护性命令。它扫描项目里 PRODUCT.md、DESIGN.md 及其 design.json 旁车文件、.impeccable/config.json、持久化的 surface briefs 与 design hook,对照"当前安装版本实际读取的内容"报告并修复两者之间的漂移(drift)。读完本文,你将掌握 doctor 的三种漂移分类、JSON 报告结构、按 severity 分级处置的纪律,以及 Monorepo 下最关键的配置型 finding 的判定与修复方法。
doctor 是什么:职责边界与三种"过期"
官方文档对 doctor 的定位非常克制——Report and repair drift between this project's Impeccable artifacts and what the installed version reads。它只做维护,不做设计:
- 不重新设计任何东西;
- 不打开报告之外的文件;
- 不把任何其他命令作为副作用去执行。
它检查的工件清单是固定的:PRODUCT.md、DESIGN.md 及其 .impeccable/design.json 旁车文件、.impeccable/config.json、持久化的 surface briefs、以及 design hook。
三类漂移要分开处理
"过期(out of date)"在 impeccable 中被拆成三种语义完全不同的漂移,doctor 明确把它们区分开:
| 漂移类型 | 含义 | 谁来负责 |
|---|---|---|
| Tool version(工具版本) | 已安装的 skill 比已发布的旧 | impeccable context 在启动时上报为 UPDATE_AVAILABLE,npx impeccable update 修复 |
| Schema drift(模式漂移) | 工件由旧版 impeccable 写出:存在无人读取的字段、缺失新版本期望的字段、文件位于废弃位置 | doctor 大部分可机械修复 |
| Truth drift(真相漂移) | 代码演进后文档不再如实描述它 | 没有文件比对能裁决;document 拥有 DESIGN.md、init 拥有 PRODUCT.md,doctor 的职责是把"一个具体缺口"递给它们,而不是给一句模糊的怀疑 |
从技能包路由层看,这一职责划分也被刻意设计成边界。skill/SKILL.src.md 中写明:doctor 在用户调用 impeccable doctor、或询问"什么过期了/陈旧了/需要刷新"时加载 reference/doctor.md;并且明确了一条纪律——绝不在设计任务中作为副作用顺手修复漂移,CONTEXT_STALE 这类 finding 只上报不处置,唯一的例外是标记为 auto 的 finding(下次写该文件时本来就会被应用)。
运行一次检测:命令形态与 JSON 输出
基础命令与定位目标
.agent/skills/impeccable/scripts/impeccable doctor --json
当用户点名了某个 workspace、文件或 Monorepo 中的路由时,加 --target <path>:
.agent/skills/impeccable/scripts/impeccable doctor --json --target packages/website
不传 --target 时,报告描述的是仓库根目录——在 Monorepo 中这常常指向错误的项目,因此对 Monorepo 场景,明确目标是第一优先。
实现层面的调用入口:在仓库源码形态中该命令对应
skill/scripts/doctor.mjs(skill 发行版内以node {{scripts_path}}/doctor.mjs --json形式书写);根文档中的.agent/skills/impeccable/scripts/impeccable doctor是已安装 skill 的封装形态。
输出结构:findings 是可消费的数据
--json 输出携带 findings,其中每一条 finding 都包含五个固定字段(见 CLAUDE.md 对数据模型的描述):
{ "id": "finding-名", "artifact": "受影响的工件", "path": "文件路径",
"severity": "auto | mention | route", "summary": "摘要", "fix": "给出的修复" }
- 在 Monorepo 中,输出还包含
workspaces表,列出每个 app 的 product / design 解析结果(自己带上下文、继承、还是什么都没有); ruleRegistryAvailable: false表示被忽略的规则 id 无法通过规则注册表校验——此时要如实说明"该列表未被验证为干净",而不是暗示它已经干净;findings为空数组是最好结果:一行说清即可,然后停手,不要画蛇添足。
doctor 从架构上就不是"找错报警器"。findings 不是错误,命令不会因为存在 finding 而失败;severity 表达的是"接下来该发生什么",而不是"问题有多严重"。
源码纵深:两个检测层
结合 CLAUDE.md 可以看清 doctor 在工程中的层级定位:
- Tier 1:
context启动时输出的是整套发现的廉价子集(CONTEXT_STALE指令); - Tier 2:
lib/staleness-deep.mjs,由skill/scripts/doctor.mjs按需调用——做 git log、逐 workspace 扫描、用实时ANTIPATTERNS规则注册表做忽略列表校验、以及 hook 脚本解析。
也就是说 doctor 是"深检",context 启动检查是"浅检";二者共享同一套 findings 数据结构,让启动指令、文本报告与 --json 渲染的是同一份数据。
按 severity 分级处置:严重度决定动作,而非好坏
doctor 命令约定三种严重度,处置纪律如下:
| severity | 语义 | 处置动作 |
|---|---|---|
auto |
不携带任何决策 | 直接运行一次 impeccable doctor --fix 应用这些修复,然后用一行汇报"移动/修改了什么"。不需要先征得许可,事后也不需要再追问 |
mention |
需要让用户知道,但此刻无需用户做决定 | 用一句话陈述每条,附上它给出的修复建议 |
route |
需要某个具体命令来收口 | 说出命令名以及它能弥合的缺口;只有用户在本轮明确要求时才运行。init 和 document 是"对话",不是可以无人值守替你完成的修复 |
三组 finding 要在一趟中全部报告完毕。注意 auto 修复只对 --fix 生效,且只应用"不涉及判断"的那部分——见 CLAUDE.md 的说明:doctor --fix applies only auto, and only where no judgment is involved。
已弃用字段是绑定的,不是风格建议
某个 finding 如果报告了一个已弃用字段(当前以 ## Register 为实例),那不是一条风格建议。处置规则是:从这一刻起,把该字段当作不存在——无论它现在的值是什么,所有后续决策都不得再参考它;并向用户提议删除整个小节。
保留它的理由即使只是"万一以后还用得上",也是错误的:一个退役的轴,正是靠这种方式继续影响当前输出(steering current output)。这与工件模式注册表的设计一脉相承——CLAUDE.md 要求每当退休 PRODUCT.md 的一个字段时,把它加入 lib/artifact-schema.mjs 的 PRODUCT_DEPRECATED_SECTIONS 并写明原因;原因是装饰性文案之外的关键信息:仅被告知"字段已弃用"的模型会出于保险心态保留它。
不要对真相漂移过度断言
两条 finding 尤其考验克制:
design-md-drift统计的是"DESIGN.md 最后一次编辑之后,视觉源码目录上的提交次数"。提交次数不等于文档矛盾。正确做法是:报告这个数字、说明它衡量的是什么;只有当用户想确认文档是否真的错了时,才去把 DESIGN.md 对着当前 tokens 与组件逐条阅读,然后基于阅读作答。永远不要因为数字大就断言 DESIGN.md 已陈旧。workspace-context-inherited同理。继承是被设计出来的行为;一份 product 记录是否如实地描述了多个 app,这是留给用户判断的问题,不是需要你修复的缺陷。
Monorepo 专项 finding 速查
doctor 在 Monorepo 下有几条高价值 finding,文档逐一给出了判定与修复方向:
workspace-platform-native-evidence:最值得注意的一条
一个 workspace 携带了原生构建文件,却在继承一条解析结果为 web 的根级记录——它的整个生命周期都会收到 web 侧的指导,永远不加载 ios.md 或 android.md。修复方式是在该 workspace 写一份子级 PRODUCT.md:一份被继承的记录无法同时承载两个平台。
config-project-roots-match-nothing:projectRoots 全部落空
每条 projectRoots glob 都没命中,仓库根目录于是静默充当了活动项目。最常见原因是 workspace 目录被重命名。处置:把实际的 patterns 报告出来,问用户这些 glob 应该指向哪些目录。
config-invalid-build-path / config-build-path-unset:都围绕同一个键
两条 finding 都围绕 .impeccable/config.json 中的 buildPath 一个键:
| 取值 | 含义 |
|---|---|
comp |
新 surface 从生成的 comp 开始构建 |
code |
直接在代码中构建 |
注意优先级细节:被 gitignore 的 .impeccable/config.local.json 对该开发者而言优先于 config.json。同时"读取不到值"不会回退到相反路径——所以一个本意是 code 的项目可能在持续走 comp-led 构建;config-invalid-build-path 时要报告精确值。config-build-path-unset 只在"项目已做过方向性工作却从未记录偏好"时触发;并且只有当你所在工具面存在图像生成能力时,其修复提议才成立——没有图像生成就没有可选择的选项,也就无可说。
改动前先展示 workspaces 表
在提出任何改动之前,用 workspaces 表向用户展示:哪些 app 携带自己的上下文、哪些继承、哪些什么都没有。
退出启动检查:报告只在你索要时出现
impeccable context(源码形态即 context.mjs)会在会话启动时报告这些 finding 的廉价子集,每个项目每周最多节流一次。退出方式有两种:
- 在
.impeccable/config.json中设"stalenessCheck": false,彻底静默; - 或者设环境变量
IMPECCABLE_NO_STALENESS_CHECK=1,只对本会话生效。
禁用启动检查不影响 doctor 本身仍然可用。对"只想在自己要求时才看到报告"的用户,这正是建议的组合:关闭 context 的自动浅检,保留按需触发的深检。
节流与发射纪律的源码佐证
CLAUDE.md 给出了工程细节:启动输出已经很重,因此 Tier 1 对整个发现集合只发射一条 CONTEXT_STALE 指令;lib/staleness-notice.mjs 把 mention 与 route finding 节流到每项目每周一次,缓存落在 ~/.impeccable/staleness-check.json(与更新缓存同处,不需要 gitignore 条目)。而 auto finding 永不节流、也从不展示给用户——它本来就是静默自愈的。这正是测试里那些断言"其他启动指令"的用例需要设置该环境变量的原因,也是 doctor 与 context 之间"报告仅当被询问"这一契约的实现基础。
事实溯源:工件版本如何被识别
doctor 所以能把"模式漂移"和"真相漂移"分开,靠的是**来源戳(provenance stamp)**机制。PRODUCT.md 携带 <!-- impeccable:product-schema N --> 注释(常量定义在 lib/artifact-schema.mjs)。两个关键约束(见 CLAUDE.md):
- 戳是模式版本而非发行版本——由 v4.0.0 写出的 PRODUCT.md 在 v4.0.1 下并不算陈旧;只有文件形状(shape)变化时模式版本才递增;
- DESIGN.md 刻意不带戳,因为它遵循 Stitch 的 linter 校验的外部 design.md 规范,且 DESIGN.md 的所有信号(旁车
schemaVersion、旁车 mtime、章节覆盖率、git 漂移)没有戳也能测量。
没有戳时,所有检查都退化为对"文件来自哪个年代"的启发式重建——这正是把 Register 这类弃用字段当作绑定约束、把 auto 静默应用约束在同一数据模型里的根本原因。
结论与最小操作清单
对使用者而言,doctor 的完整流程可以浓缩为四步:--json 跑一趟 → 空结果则一行收尾 → 非空则按 auto(直接 --fix)/ mention(一句话陈述)/ route(点名命令、获准才跑)分级处置 → 对弃用字段与 Monorepo 配置型 finding 单独核实。它始终遵守同一条原则:这是维护,不是设计;报告缺口,不替 init 与 document 做设计决定。
相关仓库文件(可继续深入阅读):
- 命令参考主文档:.agent/skills/impeccable/reference/doctor.md
- 技能包路由与纪律定义:skill/SKILL.src.md
- staleness 架构与 findings 数据模型说明:CLAUDE.md
- 平台分支参考(Monorepo 原生构建场景):skill/reference/ios.md、skill/reference/android.md
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00