Impeccable Doctor 巡检器:AI 设计技能中 PRODUCT/DESIGN 工件漂移的报告与修复指南
本篇技术指南面向 Impeccable——一套让 AI 设计工作流更可靠的设计语言与技能体系。
doctor是其中负责“体检”的维护型命令:它报告并修复项目内 Impeccable 工件(PRODUCT.md、DESIGN.md 及其.impeccable/design.jsonsidecar、.impeccable/config.json、持久化的 surface brief、设计 hook)与当前安装版本所读取的规范之间的漂移。读完本文,你将掌握如何运行一次完整的 doctor 巡检、按 severity 分级处置每一项 finding、在 monorepo 中定位错误的项目上下文,以及如何关闭开机自检但保留按需巡检。
该命令的完整行为契约定义在 doctor.md(源文件位于 skill/reference/doctor.md,并被镜像到 .qoder、.claude、.cursor、.gemini 等各 Agent 运行时目录,如 plugin/skills/impeccable/reference/doctor.md)。
1. doctor 是什么:一处明确的职责边界
doctor 的使命只有一句话:报告并修复项目工件与安装版本之间的漂移。它针对的工件集合是固定的——PRODUCT.md、DESIGN.md 及其 .impeccable/design.json sidecar、.impeccable/config.json、持久化的 surface brief,以及设计 hook。
与之同等重要的是它不做什么,这是该命令设计的红线:
- 这是维护,不是设计。任何 redesign 行为都在范围之外。
- 不打开报告点名的文件以外的任何文件。
- 不以副作用形式运行任何其他命令。
从命令体系角度看,这一点被刻意固化:CLAUDE.md 明确指出 doctor 是一个 utility command(实用命令),不是 design command。它遵循 hooks 与 pin 的模式(在 SKILL.src.md 中加一行路由 + 一份 reference/doctor.md),而不是 Commands 表模式;它刻意不在 IMPECCABLE_SUB_COMMANDS、command-metadata.json、SKILL_CATEGORIES 之中,也不计入 23 个设计命令的配额——维护工具被有意排除在“设计菜单”之外。
2. “过期”之下其实是三类不同的漂移
文档要求把三种漂移严格分开处理,不能混为一谈:
| 漂移类型 | 含义 | 归属 | 处理方式 |
|---|---|---|---|
| Tool version(工具版本) | 已安装的 skill 比已发布版本旧 | context.mjs 在启动时以 UPDATE_AVAILABLE 报告,npx impeccable update 修复 |
不是本命令的职责 |
| Schema drift(模式漂移) | 工件由旧版 Impeccable 写出:含有无人读取的字段、缺少当前期待的字段、文件位于已退役位置 | 机械性、确定性 | 本命令修复其中大部分 |
| Truth drift(事实漂移) | 代码演进后文档不再描述现状 | 没有任何文件对比能裁定 | document 拥有 DESIGN.md、init 拥有 PRODUCT.md,doctor 的职责是递给它们一个具体的缺口,而非模糊的怀疑 |
第三类的关键在“具体”二字:与其说“文档可能过时了”,不如说“## Register 已被废弃、DESIGN.md 第 40 行仍在引用它”——把可执行的具体缺口交到负责重写的命令手里。这也解释了为什么 init 和 document 是对话(conversations),而不是可以被无人值守执行的修复。
3. Step 1:跑一遍完整巡检
3.1 命令形态
node {{scripts_path}}/doctor.mjs --json
其中 {{scripts_path}} 是技能参考文件中的占位符,由运行时解析为已安装 skill 的脚本基目录;在具体 Agent 目录的镜像版本里会被替换为真实路径,例如:
node .qoder/skills/impeccable/scripts/doctor.mjs --json
需要说明的是,doctor.mjs、lib/staleness-deep.mjs 等脚本属于发布到用户项目中的 skill 安装包的一部分,scripts/test-suites.mjs 在其技能脚本清单中即包含了 doctor,这印证了它在分发结构中的既定位置。
3.2 --target:monorepo 中的指路牌
node {{scripts_path}}/doctor.mjs --json --target <path>
当用户在 monorepo 中点名了一个 workspace、文件或路由时,必须加 --target <path>。不带该参数时,报告描述的是仓库根目录;而在 monorepo 中,根目录往往不是那个真正被讨论的项目。
3.3 输出的形状:findings 是数据
输出携带 findings,每个 finding 是结构化记录:id、artifact、path、severity、summary、fix。在 monorepo 中还会携带 workspaces,列出每个 app 的产品与设计解析结果(自有 / 继承 / 无)。
两个需要如实转述的特殊状态:
ruleRegistryAvailable: false:表示被忽略的规则 id 无法被校验——这时要明说列表未经校验,而不是暗示它已通过验证。- 空的
findings数组就是最好的结果:用一句话说明并停止,不要画蛇添足。
从架构上看,这套输出被刻意做成结构化数据:CLAUDE.md 明确“Findings are data”——同一组 finding 数据既渲染为启动指令、也渲染为文本报告、还可输出为 --json,三者同源同构。
4. Step 2:按 severity 行动——severity 说的是“该怎么办”,不是“有多糟”
这是 doctor 最容易被误读的约定。严重程度不度量损坏程度,而规定下一步动作:
auto:不带任何决策。直接运行一次node {{scripts_path}}/doctor.mjs --fix应用它们,然后用一句话报告移动了什么。不需要先征求许可,事后也不需要再问。因为这类修复不涉及判断。CLAUDE.md 的补充约定是:doctor --fix只应用auto,且只应用在不涉及任何判断的地方。mention:用户需要知情,但现在不需要决策。用一句话陈述每条 finding 及其给出的修复选项。route:需要一条具体命令。点出该命令以及它将弥合的缺口,并且只有当用户在本轮明确要求时才执行——init与document是对话式流程,不是可无人值守的修复。
三条纪律:
- 三类 group 在一次巡检中全部报告,不要分批。
- Findings 不是错误,命令不会因为它们而失败退出。
auto类 finding 在启动自检中永不节流、永不展示给用户(见第 7 节)。
5. Step 3:已废弃字段是硬约束
一条报告“已废弃字段”的 finding 不是风格建议。例如当前已废弃的 ## Register 字段——从此刻起,无论它持有任何值,都要把它当作不存在来参与后续每一个决策,并主动提出删除该章节。
文档的告诫非常直白:为了“以防万一”而保留它,正是让一条已退役的轴线继续左右当前输出的方式。schema 漂移的确定性在这里体现:机械地删除旧字段、补齐新字段,正是 doctor 能“修复大部分”的那一类。
6. Step 4:不要在 truth drift 上过度断言
这一节约束的是 agent 的“话术纪律”,两条最具代表性:
design-md-drift:该 finding 统计“自 DESIGN.md 上次被编辑以来,视觉源目录中的提交数”。提交数不是矛盾证据。正确的做法是:报告数字、说明它度量的是什么;如果用户想知道文档是否真的错了,就打开 DESIGN.md 对照当前的 tokens 与 components 亲自读一遍再回答。永远不要因为数字很大就断言 DESIGN.md 已过期。workspace-context-inherited:继承是被设计出来的行为。一条产品记录是否如实地描述了多个 app,这是该问用户的问题,而不是需要修复的缺陷。
7. 启动自检与两层巡检架构:doctor 在其中的位置
要理解 doctor 与日常会话的关系,需要看它背后两级架构(见 CLAUDE.md):
- Tier 1(启动时的廉价子集):
context.mjs在会话开始时报告这些 finding 的子集,即 SKILL.src.md 中描述的内容——启动输出里的一条CONTEXT_STALE指令是同一次报告的廉价子集。注意它是整组共一条指令,而不是逐条刷屏。 - Tier 2(按需的深度巡检):即本文的主角,由
doctor.mjs运行——包括 Git 日志、逐 workspace 扫描、针对实时ANTIPATTERNS注册表的 ignore 列表校验、hook 脚本解析。
节流策略是:mention 与 route 类 finding 借助 lib/staleness-notice.mjs 每个项目每周至多提示一次(缓存在 ~/.impeccable/staleness-check.json,因此无需在项目 gitignore 中额外登记);而 auto finding 从不节流、从不展示。
**Provenance stamps(来源戳)**也直接影响 doctor 的判断逻辑:PRODUCT.md 携带 <!-- impeccable:product-schema N --> 形式的戳(常量定义于 lib/artifact-schema.mjs,模板见 init.md)。戳是 schema 版本号而非发布版本号——v4.0.0 写的 PRODUCT.md 在 v4.0.1 下并不算过期,只有结构形态变化才升级 schema 版本。而 DESIGN.md 刻意不带戳,因为它遵循外部 design.md 规范(由 Stitch 的 linter 校验),DESIGN.md 的每一个信号(sidecar schemaVersion、sidecar 修改时间、章节覆盖、Git 漂移)不靠戳也可度量。
8. Monorepo 专项:四条高价值 finding 的处置
doctor 在 monorepo 场景下最容易出价值也最容易出误判,文档给了四个专项判例:
8.1 workspace-platform-native-evidence:最该重视的一条
一个 workspace 携带原生构建文件,却继承了根记录、而根记录解析为 web——那么该 workspace 整个生命周期都只收到 web 侧的指导,永远不会加载 ios.md 或 android.md。修复方式是在该 workspace 内写一份子级 PRODUCT.md,因为单条继承记录无法同时承载两个平台。
8.2 config-project-roots-match-nothing
说明 projectRoots 的每一个 glob 都未命中,于是仓库根目录被静默当作活动项目顶了上来。目录改名是最常见原因。处理方式:报告这些模式,并询问它们应该指向哪些目录——不要自行猜测。
8.3 config-invalid-build-path / config-build-path-unset:都围绕 buildPath
两条 finding 都指向 .impeccable/config.json 中的同一个键 buildPath(也可能是被 gitignore 的 .impeccable/config.local.json——对单个开发者而言后者胜出)。该键取值 comp 或 code,决定新 surface 是从生成的 comp 构建、还是直接在代码中构建。
三个要点:
- 不可读的值不会回退到相反路径——一个本意是
code的项目可能一直在走 comp-led 流程。因此报告时要给出精确值。 config-build-path-unset只在一种情况下触发:项目已经做过方向性工作、却从未记录偏好。- “提供选项”这一步只在你的工具面存在图像生成能力时才属于该 finding。没有图像生成,就既没有可选的、也没有可说的。其语义背景见 init.md:init 在首次询问时会向用户陈述两种路径的取舍——comp-first(由图像设定方向)与 code-first,并把回答写入
"buildPath": "comp"或"buildPath": "code",且只写入用户真实做出的选择。
8.4 先展示 workspaces 表,再提任何修改
在提出任何改动之前,用 workspaces 表让用户看清:哪些 app 自带上下文、哪些在继承、哪些什么都没有。展示在提议之前,这是 monorepo 场景下的事实纪律。
9. 退出开机自检:想要“只在被要求时报告”
context.mjs 在会话开始时报告这些 finding 的廉价子集,按每个项目每周一次节流。两种关闭方式:
- 项目级:在
.impeccable/config.json中设置"stalenessCheck": false,例如:
{
"stalenessCheck": false
}
- 单会话级:环境变量
IMPECCABLE_NO_STALENESS_CHECK=1。
注意:关闭自检后 doctor 命令本身仍然完全可用。这正是给“只想在主动询问时拿到报告”的用户推荐的组合——CLAUDE.md 还提到,测试套件中断言其他启动指令的用例应设置该环境变量,这也是 tests/context.test.mjs 中更新检查套件的做法,避免自检输出干扰断言。
10. 一次完整的处置流:从自检到巡检到重写
把全文串成一张可执行的时间线:
- 会话启动,
context.mjs的廉价子集给出CONTEXT_STALE(若有)——此时按其自身指令就地处理,不要擅自运行 doctor。 - 用户明确要求体检/报告过期内容时,加载
reference/doctor.md,运行doctor.mjs --json(monorepo 中加--target)。 - 若
findings为空:一句话说明健康,停止。 - 否则按 severity 一次报告全部三组:
auto用--fix静默应用并一句话汇报;mention逐条一句话陈述加修复选项;route点名init/document等对话式命令及其要弥合的缺口,仅当用户本轮要求才执行。 - 对已废弃字段(如
## Register)一律视为不存在并提议删除;对design-md-drift报告数字而非断言过期;在 monorepo 中先给 workspaces 表,再针对workspace-platform-native-evidence等判例提议子级 PRODUCT.md。 - 需要让用户重新掌控节奏时,建议
"stalenessCheck": false或单次IMPECCABLE_NO_STALENESS_CHECK=1,按需巡检的能力不变。
这套流程把“体检查什么、怎么分级、谁有最终处置权、agent 话术的边界”全部标准化,让一次看似主观的“文档过期”判断,变成可复现、可审计、可机械执行的数据驱动巡检。
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