impeccable doctor:报告并修复 Impeccable 项目工件漂移的维护指南
doctor 是 impeccable 设计语言体系中的一项维护型命令:它系统性检查项目内 Impeccable 工件(PRODUCT.md、DESIGN.md 及其 .impeccable/design.json 侧车文件、.impeccable/config.json、持久化的 surface brief、以及 design hook)与当前所安装版本所读取的结构之间是否发生漂移,并输出可执行、可分级、可自动修复的报告。本文基于 skill/reference/doctor.md(即本文档的规范源,另在多份 harness 目录如 .grok/skills/impeccable/reference/doctor.md、.claude/skills/impeccable/reference/doctor.md 中同步分发)整理,并结合仓库内的 CLAUDE.md、skill/SKILL.src.md 与真实配置文件展开。读完你将掌握如何运行一次 doctor pass、如何按严重级别采取行动、如何处理单仓与 monorepo 下的典型 finding,以及如何关停会话启动时的廉价检查。
一、doctor 拥有什么,不拥有什么
doctor 的第一条纪律写在文档开头:这是维护,不是设计。不得重新设计任何内容,不得打开报告点名范围之外的文件,也不得作为副作用去运行任何其他命令。
它负责的范围是"项目 Impeccable 工件 vs 安装版本所读取的结构"之间的漂移,包括:
PRODUCT.md(产品记录,归init所有);DESIGN.md及其.impeccable/design.json侧车(设计记录,归document所有);.impeccable/config.json(以及按开发者的本地覆盖文件);- 持久化的 surface brief(方向性契约);
- design hook 配置。
在 .impeccable/config.json 中可以看到本仓库的真实配置形态,它同时管理检测器与 hook 两个区块:detector(含 ignoreRules、ignoreFiles、ignoreValues)与 hook(enabled、limits.maxFindings/maxChars),此外还有 init/document 等命令按需写入的键。
"out of date" 一词之下其实混着三种性质完全不同的漂移,必须分开对待:
| 漂移类型 | 本质 | 责任归属 | 说明 |
|---|---|---|---|
| 工具版本漂移(Tool version) | 安装的 skill 比已发布版本旧 | npx impeccable update |
context.mjs 在启动时以 UPDATE_AVAILABLE 上报,不是本命令的职责 |
| 模式漂移(Schema drift) | 工件由更老的 Impeccable 写成:存在无人读取的字段、缺失现在期望的字段、文件位于已退役的位置 | doctor 本身(机械修复) | 大部分可由 doctor 修复 |
| 事实漂移(Truth drift) | 代码已演进,文档不再描述它 | document 拥有 DESIGN.md,init 拥有 PRODUCT.md |
没有文件比对能裁决它;doctor 的职责是把"具体缺口"而非"模糊怀疑"交给它们 |
换句话说,doctor 只在"结构一致性"上有裁决权,在"内容真伪"上它只负责转交一个定位精确的任务单。
二、第 1 步:运行一次 pass
运行 doctor 的标准命令在本文档源中以模板路径写作:
node {{scripts_path}}/doctor.mjs --json
在某个具体 harness 安装中,例如:
node .grok/skills/impeccable/scripts/doctor.mjs --json
对应的技能路由入口为 /impeccable doctor(见 skill/SKILL.src.md 第 83 行的 Doctor 规则块,插件分发版见 plugin/skills/impeccable/SKILL.md)。需要说明的是:doctor.mjs 本体属于随安装分发的脚本,仓库快照的 skill/scripts/ 目录中并未驻留该脚本(CLAUDE.md 中将其描述为按需运行的 Tier 2 组件),运行前请确认你的安装已就位。
指定目标: 当用户在 monorepo 中点名了一个 workspace、文件或路由时,追加:
node {{scripts_path}}/doctor.mjs --json --target <path>
不带 --target 时,报告描述的是 repo 根目录;而在 monorepo 中这常常是错误的那个项目——这是文档明确提醒的第一处易错点。
输出结构: 报告携带两类载荷:
findings:每个 finding 均含id、artifact、path、severity、summary、fix六个字段;workspaces(monorepo 时):包含每个 app 的 product 与 design 解析结果。
此外还有旗标 ruleRegistryAvailable: false:当它为 false,说明被忽略的 rule id 无法校验(即 ignore 列表里的规则号在现有规则注册表中查不到)——此时要如实说明,而不是暗示这份忽略清单是干净的。
空结果即好消息。 findings 为空数组就是理想结局:用一行话说明并停止,不要画蛇添足。
三、第 2 步:按 severity 行动
severity 字段表达的是"应该发生什么",而不是"问题有多糟"。它对应三档行动:
| severity | 含义 | 你的动作 |
|---|---|---|
auto |
不携带任何决策 | 运行一次 node {{scripts_path}}/doctor.mjs --fix 统一应用这些修复,然后用一行汇报它移动了什么。先征求许可、事后追问都不需要 |
mention |
用户需要知情,但现在无需决策 | 用一句话陈述每一条及其给出的修复建议 |
route |
需要一条具体命令 | 说出那条命令及它将关闭的缺口;仅当用户在本轮明确要求时才运行——init 与 document 是对话式流程,不是你可以无人值守执行的修复 |
三条纪律值得重申:三组 severity 必须在一次汇报中全部覆盖;findings 不是错误,命令不会因为它们而失败;--fix 只应用 auto 类 finding,且只在无需任何判断的地方动手(CLAUDE.md 第 50–71 行的 Artifact staleness 小节对此有完全一致的定义)。
配套的行为红线来自 plugin/skills/impeccable/SKILL.md 第 85 行:绝不在设计任务中把修复漂移当副作用顺手做了。CONTEXT_STALE finding 应被"报告"而非"代为行动",除非用户明确要求;唯一例外是标记为 auto 的 finding,它会在下一次对该文件的写入时被自动应用。
四、第 3 步:被弃用的字段是"绑定"的
当一个 finding 报告某字段已被弃用(当前实例是 ## Register 小节)时,它不是一条风格建议。
从此刻起,无论该字段持有何值,都要在每一项决策中把它当作"不存在"来对待,并向用户提议删除该小节。文档给出的理由一针见血:"just in case 地保留它,正是一个退役的轴继续操纵当前输出的方式。" 残留的旧字段会持续向后续会话的输出施加方向性影响——这正是 schema 漂移中最隐蔽的一种。保留与否没有中间态:要么删除,要么它就一直"活着"。
五、第 4 步:不要在 truth drift 上过度断言
design-md-drift 这条 finding 统计的是:自 DESIGN.md 上次编辑以来,视觉源码目录产生了多少次提交。但请注意:提交次数不等于矛盾。
- 报告这个数字即可;
- 说明它度量的是什么;
- 如果用户想确认文档是否真的错了,那么把 DESIGN.md 与当前的 tokens、组件逐条对照,从对照结果回答;
- 绝不因为数字大就断言 DESIGN.md 已过期。
同样的克制适用于 workspace-context-inherited:继承是设计好的行为,不是缺陷。一条产品记录是否如实地描述了多个 app,这是该问用户的问题,而不是需要你去修的 bug。把继承自动标记为漂移,等于把设计契约误读成脏数据。
六、Monorepo 专项 finding 手册
doctor 在 monorepo 场景下有几条几乎必然出现的 finding,文档按类别给出了明确的处置建议:
workspace-platform-native-evidence——monorepo 中最重要的一条。 当一个 workspace 携带原生构建文件(iOS/Android),却继承了一条解析为 web 的根记录时,它会终生得到 web 方向的引导,永远不会加载对应的 ios.md 或 android.md。修复方式是在该 workspace 放一个子级 PRODUCT.md——因为一条被继承的记录不可能同时容纳两个平台。
config-project-roots-match-nothing。 意味着每个 projectRoots glob 都未命中,于是 repo 根目录被静默当作当前激活项目。最常见原因是 workspace 目录被改名。处置:报告这些 pattern,并询问它们应指向哪些目录。
config-invalid-build-path 与 config-build-path-unset——两条 finding 都只关心一个键: .impeccable/config.json 中的 buildPath(或被 gitignore 的 .impeccable/config.local.json,后者对某位开发者而言优先级更高)。它只取值 comp 或 code,决定新 surface 是从生成的 comp 开始构建,还是直接在代码中构建:
{ "buildPath": "comp" }
关于该键的几条底层事实需要格外小心:
- "读不到值"不会回退到另一条路径。 一个本意是
code的项目,如果键不可读,实际会一直以 comp-led 方式构建——必须报告确切的值,而不是猜。 config-invalid-build-path只在已有值但值非法时触发;config-build-path-unset只在项目做过方向性工作却从未记录偏好时触发。config-build-path-unset中的"询问提供"只有在你的工具面存在图像生成时才成立。没有图像生成能力,就既没有可选择的选项,也没有该说的话——因为 comp 优先的前提是能生成 comp。
buildPath 的记录时机与回退逻辑可以在 README.md 与 skill/reference/init.md、skill/reference/new-work.md 中交叉印证:init 在图像生成可用且未记录偏好时只问一次并写回;new-work 每次执行方向会话时从 buildPath 读取默认、以 { value, toggle } 形式下发页面页脚开关;本机与团队提交值不一致时 config.local.json 胜出;两者皆无且存在图像生成时,默认走 comp 优先。
在任何变更提议之前,先用 workspaces 表向用户展示: 哪些 app 自带上下文、哪些是继承、哪些什么都没有。先给全景,再谈改动。
七、底层实现:两级的 staleness 检查
从 CLAUDE.md 第 50–71 行可以还原 doctor 背后的完整实现设计,它分为两个层级:
Tier 1 —— 会话启动的廉价子集。 context.mjs 在启动时报告这些 findings 中"廉价"的那部分,以每项目每周一次的节流输出(lib/staleness-notice.mjs 负责节流,缓存落在 ~/.impeccable/staleness-check.json)。启动输出已经很重,因此 Tier 1 对整组 finding 只发一条 CONTEXT_STALE 指令。工具版本漂移则由 context.mjs 的 computeUpdateDirective() 计算,输出为 UPDATE_AVAILABLE。
Tier 2 —— 按需的深度 pass。 由 doctor 本体执行,覆盖四个工作维度:Git log(服务于 design-md-drift 这类统计)、按 workspace 的逐个扫描、针对实时 ANTIPATTERNS 注册表的 ignore-list 校验(对应 ruleRegistryAvailable)、以及 hook 脚本解析。
Findings 是数据,不是噪声。 { id, artifact, path, severity, summary, fix } 这一统一形状贯穿三处消费方:启动指令、文本报告、--json 输出——同一个 finding 集在不同通道中以同一语义渲染。节流上有一条巧妙的不对称:auto finding 永不节流、也永不向用户展示(它们只等下一次写入静默生效),而 mention 与 route 则被节流。
八、退出启动时的自动检查
如果你只想在主动询问时才看到报告,有两种退出方式:
- 持久关闭: 在 .impeccable/config.json 中写入
"stalenessCheck": false; - 单会话关闭: 设置环境变量
IMPECCABLE_NO_STALENESS_CHECK=1。
要点:关闭检查后 doctor 命令本身依然可用。这正是向"只想被问时才看报告"的用户推荐组合——启动时安静,随时能手动深查。(顺带一提,CLAUDE.md 提醒:任何断言其他启动指令的测试都应设置该环境变量,这也是其更新检查测试套件的做法。)
九、doctor 在命令体系中的位置:工具而非设计命令
doctor 刻意被排除在"设计菜单"之外。根据 skill/SKILL.src.md 的规则块,doctor 遵循 hooks、pin 一类的模式——SKILL.src.md 中一行 + reference/doctor.md 一份参考文档,而不是 Commands 表模式。它不在 IMPECCABLE_SUB_COMMANDS、command-metadata.json、SKILL_CATEGORIES,也不在 pin.mjs 的 VALID_COMMANDS 中,因此不计入那 23 个设计命令。
理解这条边界对正确使用至关重要:doctor 是维护工具,它的价值在于"让写文档的人拿到具体缺口",而不是取代设计会话。与之配套,skill/SKILL.src.md 与 plugin/skills/impeccable/SKILL.md 都注明:启动输出中的 CONTEXT_STALE 是指向同一份报告的廉价子集,应按它自身的指令就地处理,而不是擅自代跑 doctor。当用户问起"什么过期了、什么过时了、什么需要刷新"时,doctor 才是那个要加载的参考。
十、一份可复制的操作清单
把整份指南压成一次 pass 的操作序列:
- 运行
node {{scripts_path}}/doctor.mjs --json;monorepo 或用户点名目标时追加--target <path>; - 逐项读
findings(id/artifact/path/severity/summary/fix)与workspaces表,留意ruleRegistryAvailable: false; - 对
auto运行一次--fix并一行汇报;对mention一句话陈述;对route点名命令、等待用户确认后再跑(init、document是对话,不无人值守执行); - 将弃用字段(如
## Register)视为不存在,提议删除; - 对
design-md-drift只报数字与其度量含义,绝不凭提交数断言文档过期; - monorepo 中优先处理
workspace-platform-native-evidence,核对projectRootsglob 与buildPath的真实取值,改任何东西之前先亮出workspaces全景; - 空
findings数组即收工;用户只想要按需报告时,关闭stalenessCheck或设置环境变量即可,doctor 随时可再用。
这样的纪律保证了: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 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