Impeccable Doctor 实战指南:用 `impeccable doctor` 报告与修复设计产物的漂移
Impeccable 是让 AI 助手在界面设计上表现更好的设计语言与工具链。在长期迭代的项目中,PRODUCT.md、DESIGN.md 及其 sidecar、.impeccable/config.json、持久化的 surface briefs 与设计检测 hook 等“设计产物”,会与当前安装版本的读取方式逐渐脱节,这种脱节在 Impeccable 中被称为 drift(漂移)。impeccable doctor 正是为此而生的维护命令:它一次性扫描这些产物,报告每一项漂移及其修复建议,并自动应用其中机械性的迁移。读完本文,你将掌握 doctor 的完整使用方式——包括三类漂移的区分、--json/--fix/--target 参数的语义、按严重级别(auto/mention/route)处置发现的行动准则,以及 monorepo 场景与启动检查退出的实战配置。
本文以 skill/reference/doctor.md 为骨架,并结合仓库中 impeccable doctor 的 Rust 实现(crates/context/src/doctor.rs)与其底层漂移检查模块(crates/context/src/staleness.rs、crates/context/src/staleness_deep.rs)展开。
职责边界:doctor 拥有什么,不拥有什么
先明确一个原则:这是维护工作,不是设计工作。doctor 不会重新设计任何界面,不会打开报告中未提及的文件,也不会作为副作用运行任何其他命令。
文档将“过期/过时(out of date)”划分为三种截然不同的漂移,务必区分对待:
- 工具版本漂移(Tool version):已安装的 skill 比已发布的版本旧。
impeccable context在会话启动时会以UPDATE_AVAILABLE指令报告这一点,npx impeccable update即可修复。这不是 doctor 的职责。从实现看,更新检查由 crates/context/src/context_cli.rs 的compute_update_directive完成,它缓存最新版本并生成一段UPDATE_AVAILABLE提示;更新会重写当前会话正在读取的 skill 文件,因此文档明确要求:只提示、不在当前轮次执行更新。 - Schema 漂移(Schema drift):某个产物由旧版 Impeccable 写成:包含没有任何代码读取的字段、缺少现在被期望的字段、文件位于已被废弃的位置。这类漂移是机械性的,doctor 能修复其中的大部分。
- 真相漂移(Truth drift):代码已经前进,而文档不再描述它。没有任何文件比对能解决这个问题——
document拥有DESIGN.md,init拥有PRODUCT.md,doctor 的职责是把某个具体缺口交给它们,而不是交出一个模糊的怀疑。
理解这一分工后,doctor 的命令流程就清晰了。
Step 1:运行一次全面扫描
在会话开始时运行:
{{scripts_path}}/impeccable doctor --json
(在项目环境中即 impeccable doctor --json;Windows 无 sh 的 shell 下使用 impeccable.cmd。)--json 让输出以结构化 JSON 呈现,便于 Agent 解析与后续按严重级别处置。命令行用法与参数解析见 crates/context/src/doctor.rs:
Usage: impeccable doctor [--json] [--fix] [--target <path>]
--json Emit findings as JSON.
--fix Apply the mechanical migrations (severity "auto") only.
--target <path> Select a workspace in a monorepo.
--target:在 monorepo 中选择正确的项目
当用户提到的是 monorepo 中的某个 workspace、文件或路由时,应附加 --target <path>。不加时,报告描述的是仓库根目录——在 monorepo 中这常常是错误的项目。参数解析同时支持 --target <path> 与 --target=<path> 两种写法(见 crates/context/src/target_args.rs)。
理解 JSON 输出结构
输出携带:
findings:每项包含id、artifact、path、severity、summary、fix。其中severity取值为auto、mention、route三者之一(见下文 Step 2)。workspaces(monorepo 下):每个应用的 product 与 design 解析情况,包括productStatus、designStatus、platform等字段,由 crates/context/src/staleness_deep.rs 的check_workspaces生成。ruleRegistryAvailable: false:意味着被忽略的规则 id 无法校验(内置检测器未能解析出规则注册表),应当如实说明,而不是暗示“忽略列表是干净的”。
此外输出还包含 projectRoot、repoRoot、isMonorepo、productPath、designPath、platform 等元信息(见 crates/context/src/doctor.rs)。
空 findings 数组就是最好的结果——用一句话说明“未发现漂移,所有产物与当前版本读取一致”然后停止,不要无谓展开。
Step 2:按严重级别行动
severity 字段表达的是“接下来应该发生什么”,而不是“问题有多糟糕”。三层语义各不相同:
| 严重级别 | 语义 | 行动 |
|---|---|---|
auto |
不携带任何决策,纯机械迁移 | 运行一次 {{scripts_path}}/impeccable doctor --fix 应用全部,然后用一句话报告移动了什么。不需要事先征求许可,事后也不需要再询问 |
mention |
需要用户知道,但现在不需要做任何决定 | 用一句话陈述每一项及其给出的修复建议 |
route |
需要某个特定命令 | 说出命令名及它能弥合的缺口;仅当用户在本轮中明确要求时才运行——init 和 document 是对话式流程,不是你能无人值守代跑的修复 |
三类发现要在同一次通过中全部报告。注意:发现(findings)不是错误,命令不会因为它们而失败——从 crates/context/src/doctor.rs 可以看到,doctor 的进程退出码仅与参数解析是否成功有关。
--fix 的实现(apply_fixes,见 crates/context/src/doctor.rs)会遍历全部 findings,只处理 severity == "auto" 的项,其余记录为“需要用户决策”而跳过。实际实现的自动迁移包括:
design-sidecar-legacy-path:把处于旧位置的DESIGN.jsonsidecar 移动到规范位置.impeccable/design.json(目标位置已存在时跳过,不覆盖)。legacy-live-state:旧版 live 状态文件(.impeccable-live.json、.impeccable-live)需要等无 live 会话运行时手工删除,--fix会跳过并给出理由。- product schema 盖章:当
PRODUCT.md存在内容但没有 schema 版本戳、且没有product-schema-legacy发现时,自动写入<!-- impeccable:product-schema 1 -->戳(版本号来自 crates/context/src/artifact_schema.rs 的PRODUCT_SCHEMA_VERSION)。
Step 3:弃用字段是强制的,不是风格建议
当某个 finding 报告了弃用字段时,这不是一条风格提示。以 ## Register 为例——它是当前版本明确弃用的节:
- 从这一刻起,无论该字段持有何值,对之后的一切决策都视为其不存在;
- 主动提议删除该节。
文档给出的理由很尖锐:“‘以防万一’地保留它,就是让一条已退役的轴继续左右当前的输出。”从 crates/context/src/artifact_schema.rs 可以看到,PRODUCT_DEPRECATED_SECTIONS 明确记录:v4 用四种访客模式(Persuade、Operate、Read、Experience)取代了品牌/产品 register 轴,模式按 surface 选择并持久化在对应 brief 中,“没有任何代码再读取 ## Register”。对应检查在 crates/context/src/staleness.rs 的 check_product 中实现,产出的 finding id 为 product-deprecated-register,严重级别为 mention。
Step 4:不要在真相漂移上过度断言
提交数不是矛盾。design-md-drift 统计的是“自 DESIGN.md 上次编辑以来,视觉源码目录被提交改动的次数”——从 crates/context/src/staleness_deep.rs 的实现看,它借助 git 完成:先取 DESIGN.md 的最后一次提交哈希,再统计 src、app、pages、components、site、styles、public 这七个视觉源码目录中自那以来的提交数(VISUAL_SOURCE_DIRS),超过阈值(当前实现中为 25)才报告。它衡量的是“值得重新阅读这份文档”,而不是“文档是错的”。
因此正确的做法是:
- 报告数字,说明它衡量的是什么;
- 如果用户想知道文档是否真的错了,把
DESIGN.md与当前的 tokens 和组件逐一对照阅读,并据此回答; - 永远不要因为数字大就断言
DESIGN.md已过期。该 finding 的fix文案也写明:先读DESIGN.md对照当前 tokens 与组件再信任它;若确已漂移,document会从代码重新生成。
同样的克制适用于 workspace-context-inherited。继承是设计好的行为——一个 product 记录是否如实地描述了多个应用,这是要问用户的问题,而不是需要修复的缺陷。
Monorepo 专项注意事项
在 monorepo 中,有几类 finding 需要特别留意:
workspace-platform-native-evidence:最重要的一项
某个 workspace 携带原生构建文件,却继承了解析为 web 的根记录——它会终身得到 web 侧指导,永远加载不到 skill/reference/ios.md 或 skill/reference/android.md。修复方式是给该 workspace 一份子级 PRODUCT.md,因为一份继承的记录无法同时承载两个平台。
实现上(crates/context/src/staleness.rs 的 check_native_platform_evidence),它会在 pubspec.yaml(Flutter → adaptive)、ios/Podfile、android/build.gradle(.kts)、ios/Runner.xcodeproj 以及 package.json 中的 react-native、expo、@react-native/metro-config 依赖里寻找原生证据,结合 workspace 的 product_status == "inherited" 给出针对性修复。
config-project-roots-match-nothing:projectRoots 全部落空
每个 projectRoots glob 都没有匹配到任何目录,仓库根目录于是静默地充当了活动项目。重命名 workspace 目录是常见原因。做法:报告这些 pattern,询问它们应当指向哪些目录。
config-invalid-build-path 与 config-build-path-unset:同一个键的两面
两者都涉及 .impeccable/config.json 中的一个键 buildPath(被 gitignore 的 .impeccable/config.local.json 会覆盖它,后者对那个开发者生效)。它取值 comp 或 code,决定新 surface 是从生成的 comp 构建、还是直接在代码中构建。源码中合法值集合为 ["comp", "code"](crates/context/src/staleness.rs 的 BUILD_PATH_VALUES)。
- 不可读的值不会回退到另一条路径:一个本意是
code的项目实际上一直在走 comp 主导的构建。报告确切的值。 config-build-path-unset只在满足两个条件时触发:该项目做过方向性(direction)工作——即存在.impeccable/surfaces或.impeccable/mocks/decision目录——却从未记录偏好;且你的工具面存在图像生成能力时,才属于该 finding 并提供选择。没有图像生成就没有可选的选项,也没有可说的内容。
用 workspaces 表先展示再动手
在提出任何修改前,先用 workspaces 表向用户展示:哪些应用自带上下文、哪些继承、哪些什么都没有。该表由 check_workspaces 构建(crates/context/src/staleness_deep.rs),包含每个 workspace 的 productStatus(own/inherited/none 之一)、designStatus、platform 等,供用户先看清全局。
退出启动检查:按需报告
impeccable context 会在会话启动时报告这些 finding 的“廉价子集”,每个项目每周最多一次(节流实现见 crates/context/src/staleness_notice.rs:RENOTIFY_INTERVAL_MS 为 7 天,状态缓存在 ~/.impeccable/staleness-check.json)。两种方式可以静默它:
{
"stalenessCheck": false
}
写入 .impeccable/config.json;或为单次会话设置环境变量:
IMPECCABLE_NO_STALENESS_CHECK=1
对应实现是 crates/context/src/staleness_notice.rs 的 staleness_check_disabled:环境变量优先,其次读取 config.json / config.local.json 中的 stalenessCheck 布尔值。关闭检查后 doctor 命令本身依然完全可用——对于“只在用户主动询问时想要报告”的场景,这正是推荐组合。
顺带一提,.impeccable/config.json 当前版本可识别的顶层键共有 8 个:hook、detector、updateCheck、stalenessCheck、projectRoots、buildPath、$schema、version(见 crates/context/src/staleness.rs 的 KNOWN_CONFIG_KEYS)。detector 对象内可识别的键为 ignoreRules、ignoreFiles、ignoreValues、designSystem、extensions。出现这些集合之外的键会触发 config-unknown-keys / config-unknown-detector-keys 等 mention 级发现——文档特别提醒:ignoreRule(漏了 s)之于 ignoreRules 是最常见的近拼错误,它从未静默过任何规则。
更多值得了解的发现
doctor 的全量检查还覆盖以下场景(供排查时对照 finding id):
design-sidecar-schema-outdated/design-sidecar-stale:sidecar 的schemaVersion低于当前版本(当前为 2,见 crates/context/src/artifact_schema.rs 的DESIGN_SIDECAR_SCHEMA_VERSION),或DESIGN.md在 sidecar 生成之后被编辑过——修复由document负责,它读取现有DESIGN.md重新生成 sidecar,无需访谈。design-md-coverage:DESIGN.md缺少colors、typography(种子文件时)或components节,Agent 生成新页面时对这些维度没有规范指导,live 设计面板只能渲染通用近似值。detector-ignore-rules-unknown/detector-ignore-files-missing:detector.ignoreRules引用了检测器不存在的规则 id(规则被改名/删除,或 id 拼错从未生效过),或detector.ignoreFiles指向已不存在的文件路径。hook-script-missing/hook-enabled-conflict:hook 清单安装的脚本路径不存在(hook 成为 no-op,UI 编辑一直未被扫描),或 hook 已安装但hook.enabled: false导致“触发后拒绝扫描”。product-schema-legacy/product-schema-outdated:PRODUCT.md没有 schema 戳且缺少当前记录新增的四个节(Positioning、Operating Context、Evidence on Hand、Product Principles),或戳版本低于当前值——均由init通过访谈弥合,保留已确认的答案,绝不从推断重写文件。surface-brief-orphaned:持久化的 surface brief 指向的主目标文件已不存在——询问是 surface 移动了(重定向 brief)还是被删除了(删除 brief)。
与整体 skill 的关系
在 skill/SKILL.src.md 的命令路由中,doctor 的触发条件是用户调用它、或询问“什么过期了 / 陈旧了 / 需要刷新”。Setup 输出中的 CONTEXT_STALE 指令是同一份报告的廉价子集,应按其自身指示就地处理,而不是未经请求就运行完整的 doctor。最后一条铁律值得重申:永远不要在设计任务的副作用中修复漂移——除非用户明确要求,CONTEXT_STALE 发现只被报告、不被执行;唯一例外是标记为 auto 的发现,因为下次写入该文件时它本来就会被执行。这正是 skill/reference/doctor.md 所定义的纪律:doctor 是一次显式、可控、职责单一的维护动作。
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 StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
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