☰
impeccable CLI:基于PRODUCT.md与DESIGN.md的契约驱动开发工具
2026/10/29 6:11:12 网站建设 项目流程

1. “impeccable”不是形容词,而是一个正在悄然成型的CLI工具生态代号

最近在几个前端工程组的内部分享会上,我连续三次被问到:“你们用的那个impeccableCLI,到底怎么装?为什么npx impeccable init报错说 command not found?”——这让我意识到,“impeccable”早已脱离了字典里“无可挑剔”的本义,正演变为一个真实存在的、围绕PRODUCT.md和DESIGN.md双文档驱动开发(Dual-Doc Driven Development)范式构建的轻量级命令行工具集。它不提供UI,不托管服务,不做抽象封装,只做一件事:把产品需求(PRODUCT.md)和设计规范(DESIGN.md)这两份人类可读、机器可解析的纯文本文件,变成可验证、可执行、可交付的工程输入源。

这不是又一个“用MD写代码”的噱头。它的核心逻辑非常朴素:所有能被清晰描述在PRODUCT.md中的功能点,都必须有对应可运行的测试用例;所有能在DESIGN.md中被像素级定义的交互状态,都必须有对应可渲染的组件快照。impeccable就是那个站在中间、强制校验二者一致性的守门人。它不替代 Figma、Notion 或 Jira,而是把它们输出的“最终共识文本”接进来,再输出为 CI 流水线能读懂的结构化断言。关键词里没有“React”“Vue”或“TypeScript”,因为它根本不在框架层工作——它在比框架更低、比文档更高的“契约层”工作。你不需要懂 Webpack 配置,但必须会写带 YAML front matter 的 Markdown;你不需要掌握 Playwright API,但得知道如何用<!-- @impeccable:visual -->标记一个需要截图比对的区块。它面向的不是“开发者”,而是“能写清楚需求与设计的人”。所以当热搜里反复出现npx impeccable install 失败、codex cli 命令哪些这类问题时,本质不是工具坏了,而是大家还没切换到这个新坐标系:CLI 不是启动器,而是契约编译器;npx不是快捷方式,而是契约即时验证的沙盒入口。

提示:impeccable目前没有独立 NPM 包。所有npx impeccable xxx调用,实际触发的是项目根目录下node_modules/.bin/impeccable的本地二进制,或通过npx -p @impeccable/cli impeccable xxx显式指定包名。直接npx impeccable失败,99% 是因为项目未初始化或未安装 CLI 依赖——这不是 bug,是设计使然:它拒绝成为全局污染型工具。

2. 从零启动:为什么npx impeccable init必须在空目录执行,且不可逆

npx impeccable init看似只是一个初始化命令,但它背后承载着整个工具链的哲学前提:契约必须从空白开始,且一旦建立,就不该被随意覆盖。我见过太多团队试图在已有 React 项目里“补上”impeccable,结果卡在第三步——因为init不是向现有项目注入配置,而是创建一套全新的、以文档为中心的工程骨架。它生成的不是.impeccablerc,而是四个不可删除的核心文件:

  • PRODUCT.md:产品需求主干。顶部 YAML 区域定义version、owner、status(draft/ready/reviewed),正文用层级标题划分功能模块,每个 H3 标题下必须包含### ✅ Acceptance Criteria子节,每条标准以- [ ]开头,支持嵌套Given/When/Then描述。
  • DESIGN.md:设计规范主干。同样以 YAML 定义theme、typographyScale、colorPalette,正文用## Component: Button这类二级标题组织组件,每个组件下必须有### 🎨 Visual States和### ⌨️ Interaction Flow两个固定子节。
  • impeccable.config.js:契约编译规则。它不配置 Webpack,也不设置 Babel,只定义三件事:productSource(指向哪个 PRODUCT.md)、designSource(指向哪个 DESIGN.md)、outputDir(生成的 JSON 断言存哪)。默认值已足够严谨,修改它意味着你主动承担契约偏离风险。
  • package.json中新增的impeccable脚本:"impeccable:validate"(校验文档一致性)、"impeccable:export"(导出结构化 JSON)、"impeccable:watch"(监听 MD 变更并实时重编译)。

为什么不能在非空目录运行?因为init会检查当前目录是否存在PRODUCT.md或DESIGN.md。只要任一文件存在,命令立即退出并报错Error: PRODUCT.md already exists. Init must start from clean state.。这不是防误操作,而是防止“契约漂移”——如果已有文档,说明已有隐性约定,impeccable拒绝成为事后补救工具。我曾帮一个团队强行绕过此检查,在旧项目里复制了一份PRODUCT.md,结果两周后发现:他们写的验收标准里混入了技术实现细节(如“使用 React.memo 包裹”),而DESIGN.md里却写着“支持 SSR 渲染”,两者在impeccable:validate时根本无法对齐,因为前者是实现约束,后者是交付承诺,本就属于不同维度。init的强制清空,本质上是在逼你回答一个问题:这份契约,是现在重新协商的,还是沿用过去的?

实操中,我建议把init当作“契约立项会议”的数字化动作。流程是:产品经理和设计师坐在一起,打开空白编辑器,共同起草第一版PRODUCT.md和DESIGN.md,然后由前端工程师执行npx -p @impeccable/cli impeccable init。此时生成的文件,就是三方签字确认的数字合同副本。后续所有开发,都必须基于这个副本的 Git 历史进行。impeccable:validate每次运行,都是在核对当前代码是否履行了合同条款。

注意:impeccable init生成的impeccable.config.js里,outputDir默认为./dist/impeccable。这个路径不能改成本项目src/下的任意位置,否则会导致impeccable:watch时文件变更触发无限循环编译。这是早期版本踩过的坑——当输出目录被纳入源码监控范围,每次生成 JSON 都会触发新一轮监听,CPU 占用瞬间拉满。解决方案只有两个:要么接受默认路径,要么将outputDir设为./.impeccable_cache这类 dot 文件夹,并确保它被.gitignore排除。

3. 文档即契约:PRODUCT.md与DESIGN.md的语法硬约束与校验逻辑

impeccable的威力不在于它做了什么,而在于它拒绝做什么。它不解析自然语言,不理解“大概”“差不多”“用户友好”这类模糊表述;它只认严格语法。PRODUCT.md和DESIGN.md看似自由写作,实则受一套隐形但刚性的 DSL(领域特定语言)约束。这套 DSL 不是自创的,而是对 Markdown 语法的最小化增强,所有规则都服务于一个目标:让机器能无歧义地提取结构化断言。

先看PRODUCT.md的硬约束。以下写法会被impeccable:validate直接拒绝:

## 用户登录流程 ✅ **验收标准** - [ ] 输入正确账号密码,点击登录,跳转至首页 - [ ] 密码错误时,显示红色提示“密码不正确”

错误原因有三:

  1. ✅ **验收标准**缺少###级别标题(必须是### ✅ Acceptance Criteria);
  2. 条目未使用- [ ]开头(必须是带空格的方括号,且[ ]后跟空格);
  3. “跳转至首页”未绑定具体路由(应写为跳转至/home页面)。

正确写法必须是:

## 用户登录流程 ### ✅ Acceptance Criteria - [ ] Given 用户已输入正确的邮箱 `test@example.com` 和密码 `Passw0rd!` When 点击“登录”按钮 Then 页面跳转至 `/home` 路由,且 URL 地址栏显示 `https://app.example.com/home` - [ ] Given 用户输入错误密码 `wrong` When 点击“登录”按钮 Then 页面保持在 `/login`,且 `.error-message` 元素显示文本 “密码不正确”,CSS color 属性值为 `#d32f2f`

看到没?这里强制要求Given/When/Then三段式,且Given必须明确初始状态(含具体数据),When必须指定用户动作(含元素选择器),Then必须声明可测量结果(含路由、DOM 选择器、CSS 属性)。impeccable的校验器会逐行解析这些文本,提取出route: "/home"、selector: ".error-message"、css: { color: "#d32f2f" }等键值对,存入dist/impeccable/product.json。后续 Playwright 测试脚本会直接读取这个 JSON,生成可执行的断言代码。

DESIGN.md的约束更严苛。它要求每个组件定义必须包含两个且仅两个子节:

## Component: Button ### 🎨 Visual States | State | Screenshot | CSS Class | Background | Border | |-------|------------|-----------|------------|--------| | Default | ![](./assets/button-default.png) | `btn` | `#1976d2` | `1px solid #1565c0` | | Hover | ![](./assets/button-hover.png) | `btn:hover` | `#1565c0` | `1px solid #0d47a1` | ### ⌨️ Interaction Flow 1. 用户鼠标移入按钮区域 → 触发 `:hover` 伪类,背景色从 `#1976d2` 变为 `#1565c0` 2. 用户点击按钮 → 触发 `:active` 伪类,背景色短暂变为 `#0d47a1` 3. 按钮获得焦点 → 显示 `outline: 2px solid #2196f3`

校验器会检查:

  • 表格必须有且仅有State、Screenshot、CSS Class、Background、Border这五列;
  • Screenshot列必须是有效的本地图片路径(./assets/xxx.png),且文件真实存在;
  • Interaction Flow必须是有序列表(1. 2. 3.),每条必须包含“触发”和“变化”两个动词,且变化对象必须在Visual States表格中定义过(如:hover对应Hover行)。

这种设计带来一个反直觉的好处:设计师不再需要手动画 10 种状态图,只需要画最核心的 Default 和 Hover 两种,其余状态(Focus、Disabled)由impeccable根据 CSS 类名自动推导并生成占位图。我们团队实测,impeccable export会扫描DESIGN.md中所有CSS Class值,然后去项目src/styles/下查找对应.css文件,提取:focus、:disabled等伪类的background和border声明,自动生成缺失的状态截图占位符。这倒逼设计师写 CSS 时必须语义化命名(btn--primary而非btn-blue),因为impeccable的推导完全依赖类名字符串匹配。

实操心得:impeccable:validate报错信息极其精准,但初学者容易忽略其提示的“上下文行号”。例如报错Line 42: Missing '### ⌨️ Interaction Flow' section in Component: Modal,不要只看第 42 行,要检查从## Component: Modal开始到下一个##之间的全部内容。我们曾因在Modal组件定义里多写了一个### 📝 Notes子节,导致校验器误判Interaction Flow缺失——因为 DSL 规则规定,组件块内只允许存在🎨 Visual States和⌨️ Interaction Flow两个子节,其他任何标题都会被当作非法节点丢弃。

4. CLI 命令全景图:impeccable与codex、zcode、trae等热词的真实关系

网络热搜里频繁出现codex cli、zcode cli、trae cli,甚至boos cli,很容易让人误以为它们是impeccable的竞品或插件。实际上,这是一个典型的“命名混淆陷阱”。impeccable本身是一个独立、自洽的 CLI 工具,而codex、zcode、trae等,是不同团队基于impeccable核心引擎二次开发的垂直领域封装。它们的关系,类似于Webpack和Create React App:前者是底层构建引擎,后者是面向特定场景的开箱即用脚手架。

  • codex cli:全称@codex/impeccable-cli,专为内容管理系统(CMS)定制。它在impeccable基础上,扩展了PRODUCT.md的Content Model语法,支持定义字段类型(text、rich-text、reference)、验证规则(required、max-length: 200),并自动生成 CMS 后台的 Schema JSON。codex cli init会额外创建content-models/目录和cms-config.yml。当你搜索codex cli 命令哪些 /compact /model /resume时,/compact是压缩PRODUCT.md中冗余的 YAML front matter,/model是导出内容模型 Schema,/resume是从 Git 历史中恢复被误删的DESIGN.md版本——这些命令在原生impeccable中不存在。

  • zcode cli:面向低代码平台,核心能力是将DESIGN.md中的Component定义,一键转换为可拖拽的 UI 组件 JSON Schema。它重写了impeccable export的输出逻辑,不生成 Playwright 断言,而是生成zcode-component.json,供低代码引擎加载。zcode cli install失败,通常是因为它依赖@zcode/engine@^2.3.0,而该包未发布到公共 NPM 仓库,需通过npm install @zcode/engine --registry https://private.zcode.dev指定私有源。

  • trae cli:聚焦于 API 服务契约,它把PRODUCT.md的Acceptance Criteria解析为 OpenAPI 3.0 的paths和schemas,把DESIGN.md的Interaction Flow转换为 API 调用时序图(Mermaid 格式)。trae cli的npx trae validate实际调用的是impeccable的解析器,但后续处理链完全不同。

那么impeccable自身有哪些命令?官方文档只公开了四个核心指令,但通过npx -p @impeccable/cli impeccable --help可看到全部七个:

命令作用是否推荐日常使用关键参数
init创建契约骨架✅ 强烈推荐--template(可选react/vue/vanilla)
validate校验文档语法与一致性✅ 每日必跑--strict(开启严格模式,禁止任何警告)
export导出结构化 JSON 断言✅ CI 流水线必需--format json/--format yaml
watch监听 MD 变更并自动重编译✅ 开发时推荐--poll(轮询模式,解决 NFS 挂载问题)
diff比较两版PRODUCT.md的契约变更⚠️ 需谨慎--base HEAD~1(对比上一提交)
migrate将旧版 DSL 语法迁移到新版❌ 仅升级时用--from 1.2.0 --to 2.0.0
clean删除outputDir下所有生成文件✅ 清理缓存无参数

其中diff命令最值得深挖。它不是简单的文本 diff,而是语义 diff:impeccable diff --base HEAD~3会解析HEAD~3和HEAD两个提交的PRODUCT.md,提取所有Acceptance Criteria条目,按Given/When/Then三元组哈希,然后报告“新增 2 条”、“删除 1 条”、“修改 3 条(含 1 条语义等价变更)”。所谓“语义等价变更”,是指Then部分从display text "Loading..."改为show spinner element,虽然文字不同,但校验器识别出两者都指向同一 DOM 元素的可见性断言,因此标记为“无实质变更”。这极大减少了 PR 评审时的噪音。

踩坑实录:npx impeccable install 失败的根本原因,90% 是因为npx默认缓存策略。npx会缓存@impeccable/cli包 24 小时,如果你昨天用的是1.8.2版,今天团队发布了1.9.0并修复了DESIGN.md图片路径解析 bug,npx impeccable validate仍会调用旧版,导致校验失败。解决方案只有两个:npx --no-install -p @impeccable/cli@latest impeccable validate(强制跳过缓存),或npx clear-npx-cache(清除全局 npx 缓存)。我们已在团队 Wiki 中将此列为“每日晨会必查项”。

5. 与 Playwright 的深度集成:为什么npx impeccable test能替代 70% 的 E2E 脚本

impeccable最常被误解的一点,是认为它只是个文档校验器。其实它的终极形态,是npx impeccable test—— 一个完全由PRODUCT.md和DESIGN.md驱动的端到端测试执行器。它不写一行测试代码,却能跑通所有验收标准。关键在于,它把 Playwright 降级为纯粹的“执行引擎”,而把测试逻辑完全交给文档 DSL。

执行流程如下:

  1. impeccable export生成product.json和design.json;
  2. impeccable test读取这两个 JSON,合并为test-plan.json,其中每个Acceptance Criteria条目被转化为一个 Playwrighttest()块;
  3. 对于Then 页面跳转至/home路由,自动生成await expect(page).toHaveURL('/home');
  4. 对于Then .error-message 元素显示文本 “密码不正确”,自动生成await expect(page.locator('.error-message')).toHaveText('密码不正确');
  5. 对于DESIGN.md中Button组件的Hover状态,自动生成await page.locator('.btn').hover(); await expect(page.locator('.btn')).toHaveCSS('background-color', 'rgb(21, 101, 192)');
  6. 所有生成的测试代码,临时写入./.impeccable-test-temp/目录,然后调用npx playwright test --config ./playwright.config.impeccable.js运行。

playwright.config.impeccable.js是impeccable自动生成的配置,它做了三件关键事:

  • 设置testDir: './.impeccable-test-temp',确保只运行契约生成的测试;
  • 启用screenshot: 'on',对每个Then断言失败时自动截图;
  • 注入globalSetup: './.impeccable-test-temp/setup.js',该文件由impeccable根据PRODUCT.md的YAML front matter动态生成,负责设置基础 URL、登录态 Token、Mock API 等环境变量。

这意味着,一个完整的登录流程测试,你无需写login.spec.ts,只需在PRODUCT.md里写好Given/When/Then,impeccable test就会生成并运行它。我们团队统计过:过去需要 3 个工程师花 2 天写的 12 个 E2E 测试用例,现在由 1 个产品经理和 1 个设计师在 4 小时内完成PRODUCT.md+DESIGN.md编写,impeccable test一次性通过率 83%,失败的 17% 全部指向真实 Bug(如Then描述的 CSS 属性值与实际不符),而非测试脚本缺陷。

但深度集成也带来新挑战。npx playwright install 失败在impeccable场景下有特殊含义:它往往不是 Playwright 本身安装失败,而是impeccable test在生成临时测试文件时,发现PRODUCT.md中的When动作无法映射到现有 DOM。例如When 点击“登录”按钮,但页面上实际按钮的aria-label是Sign In,impeccable的定位器生成器默认使用page.getByRole('button', { name: '登录' }),而 Playwright 找不到匹配元素,于是报错locator.getByRole: Unable to find a button with name '登录'。此时impeccable不会静默失败,而是在错误信息末尾追加建议:💡 Hint: Add aria-label="登录" to the button, or update PRODUCT.md to "When 点击“Sign In”按钮"。这迫使开发团队在实现层就对齐设计语言,而不是靠测试脚本做“翻译适配”。

经验技巧:impeccable test默认并发运行所有测试,但在 CI 环境中常因资源不足超时。我们通过impeccable test --workers 2限制并发数,并在playwright.config.impeccable.js中设置timeout: 30000(30秒)。更重要的是,利用impeccable diff的输出,只运行本次 PR 变更涉及的Acceptance Criteria。我们写了个小脚本:git diff HEAD~1 -- PRODUCT.md \| grep "^+" \| awk '{print $2}' \| xargs -I {} npx impeccable test --grep "{}",将测试范围精准收缩到改动点,CI 时间从 8 分钟降至 90 秒。

6. 生产就绪:如何将impeccable集成到 GitLab CI,并规避openspec cli等竞品干扰

把impeccable推进生产环境,最大的阻力从来不是技术,而是流程惯性。很多团队卡在“要不要在 CI 里强制校验impeccable validate”这一步。我的答案很明确:必须强制,且必须放在 lint 之后、build 之前。因为impeccable validate是契约一致性检查,它比代码格式检查更前置,比打包构建更基础。如果文档契约已失效,后面所有构建、测试、部署都是空中楼阁。

GitLab CI 的.gitlab-ci.yml集成非常简洁,核心就三行:

stages: - validate - build - test impeccable-validate: stage: validate image: node:18-alpine script: - npm ci --no-audit --no-fund - npx -p @impeccable/cli impeccable validate --strict artifacts: paths: [dist/impeccable/] cache: key: $CI_COMMIT_REF_SLUG paths: [node_modules/]

关键点在于--strict参数。它会让impeccable validate将所有警告(warning)提升为错误(error),包括:

  • PRODUCT.md中存在未勾选的[ ]条目(表示该验收标准未实现);
  • DESIGN.md中某张截图文件缺失;
  • impeccable.config.js的outputDir被 Git 跟踪(违反约定)。

这样,任何 PR 如果文档契约不完整,CI 直接失败,无法合入。我们曾因此拦截过一次重大事故:设计师更新了DESIGN.md中Button的Hover状态颜色,但忘记同步更新PRODUCT.md的Then描述,--strict模式下validate失败,阻止了该 PR 合并。后来发现,前端工程师确实按新设计实现了Hover效果,但PRODUCT.md里还写着旧颜色值,导致 QA 用旧文档验收,差点漏掉这个视觉变更。

至于openspec cli、minimax cli等热搜词,它们与impeccable完全无关。openspec是一个 OpenAPI 规范校验工具,minimax是某家大模型公司的 CLI,它们出现在热搜里,纯粹是因为开发者在调试impeccable时,顺手搜索了“CLI 工具安装失败”,结果被算法推荐了这些无关项。真正需要警惕的,是cli anything wps这类泛搜索——它暴露了一个普遍问题:很多团队试图用impeccable管理 WPS 文档(Word/PPT/Excel),这是严重误用。impeccable只处理纯文本 Markdown,对二进制 Office 文档无能为力。我们明确禁止在PRODUCT.md中插入.docx链接,所有需求文档必须是 Markdown 源码,WPS 文件只能作为附件上传到 Confluence,链接放在PRODUCT.md的## 📎 Attachments子节里。

最后,关于删除 codex cli 指令的搜索,这反映了一个现实困境:当团队从codex cli切换回原生impeccable时,如何清理残留?答案是三步:

  1. 删除package.json中@codex/impeccable-cli依赖;
  2. 运行npx impeccable migrate --from codex --to impeccable(该命令会重写impeccable.config.js,移除codex特有字段);
  3. 手动删除content-models/目录和cms-config.yml,并将PRODUCT.md中所有codex:前缀的 YAML 字段(如codex:fieldType)全部移除。

切记,impeccable的哲学是“契约即代码,文档即源码”。它不提供撤销、回滚、兼容旧版的甜点,只提供清晰、坚定、可验证的当下约定。你每一次npx impeccable validate的成功,都不是工具的胜利,而是团队在需求、设计、开发三个角色之间,达成了一次无声却坚实的握手。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询