关于 constraint-driven-development
面向使用 Agent 开发软件的团队,这项技能将项目质量标准转化为可记录、可自动检查的约束。它通过访谈制定门槛,将检查接入开发流程,并提示审查可能削弱门槛的改动。
主要功能
- 01盘点项目现有工具与检查
- 02访谈确定质量门槛
- 03生成 CONSTRAINTS.md
- 04配置各质量维度的检查工具
- 05按开发阶段安排检查
- 06审查门槛弱化与绕过
适用场景
- 01为新项目建立质量门槛
- 02约束自主编码循环的交付标准
- 03明确 CI 检查的阻断与警告规则
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ addyosmani/ agent-skills --skill constraint-driven-development安装后在 Claude Code 中输入/constraint-driven-development确认已加载。
附带文件references/floor-guard.md
详细介绍
约束驱动开发
概述
本套件中的其他技能介绍了什么才算做得好。code-review-and-quality 提供五个衡量维度。test-driven-development 提供一个循环。security-and-hardening 提供一份威胁清单。这些内容都以文字形式存在,由 Agent 阅读并可能遵循,而且在会话结束后都不会留存。
这项技能会产出不同的东西:一份关于这个项目质量标准的书面记录,其中包含具体数字,能够在对话结束后继续留存,并可通过机械方式检查。
原因很重要。编写代码时,阅读代码就能判断它质量如何。而 Agent 一个下午写出的代码,比你一周读得完的还多,因此判断标准必须从你的脑中转移到贯穿开发循环运行的检查中。这些检查必须真实存在,采用你实际选定的数字,并且要足够贴近工作环节触发,让 Agent 能修正自己的输出。
规范驱动开发说明要构建什么。测试驱动开发证明它能正常工作。约束驱动开发则在任何人于拉取请求中争论之前,定义好“达到发布标准”意味着什么。
适用场景
在以下情况下使用此技能:
- 启动项目或重要功能,但尚未写下质量标准
- 用户要求“设置约束”“添加质量门禁”“定义我们的标准”或“阻止 Agent 发布劣质代码”
- Agent 产出的代码量大到没人会逐行阅读
- CI 中有各种检查,但没人说得清哪些会阻止合并,哪些只是摆设
- 覆盖率、性能或无障碍方面的数字每个 PR 都要争论,而不是一次定好
- 即将运行
/build auto或任何自主循环,而阻止它进入 main 的唯一关卡是由 Agent 自己编写的测试套件
不适用场景:
- 项目已有
CONSTRAINTS.md,且用户并不想修改它——阅读并遵循即可 - 一次性脚本、试验性项目、用完即弃的原型
- 用户现在想要代码审查(
code-review-and-quality)或构建 CI 流水线(ci-cd-and-automation) - 尚未达到产品与市场契合点的代码,预计只会存在两周——下文的底线仍值得采用,其余内容则不值得
加载约束
访谈需要用户在线参与。不要在非交互场景中运行访谈(CI、/loop、自主运行)。如果缺少约束且你处于其中一种场景,请采用下文的底线,说明你已这样做,并将其余事项标记给人工处理。
流程
第 1 步:先检测,再提问
能读取的内容,就不要询问。提出第一个问题前,先收集以下信息:
| 内容 | 查看位置 |
|---|---|
| 语言和技术栈 | package.json、pyproject.toml、go.mod、Cargo.toml |
| 测试运行器 | 开发依赖、test 脚本、现有测试文件 |
| 现有代码检查工具 | eslint.config.*、biome.json、.ruff.toml |
| 当前覆盖率 | coverage/ 输出,或运行一次测试套件 |
| CI | .github/workflows/、.gitlab-ci.yml |
| Agent 工具框架 | .claude/、.codex/、AGENTS.md |
用两行报告你的发现,然后只询问尚未明确的内容。
第 2 步:四个问题,每个都有默认值
遵循 interview-me 的一次只问一个问题的原则,但有一点不同:这里的每个问题都有默认值,因此“我不知道”也是完整答案,仍然能生成可用的配置。
Q1: Beyond the floor, which of these do you want enforced?
(a) Test coverage on new code
(b) Security scanning
(c) Performance budgets
(d) Accessibility
(e) Architecture boundaries
GUESS: (a) and (b) — you have a test runner already and you're handling user input.
DEFAULT if unsure: (a) and (b).
Say what each pick costs: (c) and (d) need a running URL, (e) needs a rules file written.
Q2: When a check fails while the agent is mid-task, should it block or warn?
GUESS: Block. You're running agents unattended and a warning nobody reads is a warning.
DEFAULT if unsure: Block on the floor, warn on everything else for the first two weeks.
Q3: Do you have target numbers in mind, or should I measure where you are today and hold that line?
GUESS: Measure. Most teams don't have a number, and an invented one gets ignored.
DEFAULT if unsure: Measure and hold. See "Ratchets" below.
Q4: What's the slowest check you'll tolerate before the agent hands work back?
GUESS: About 90 seconds. Longer and you'll stop running it.
DEFAULT if unsure: 90 seconds at task end, unlimited in CI.
最多问四个问题。问十二个问题会产出一份没人看得懂的配置,也会让用户后悔开始这件事。
第 3 步:编写 CONSTRAINTS.md
在仓库根目录创建一个文件。任何工具框架上的 Agent 都能读取它,对它的修改也会在恰当的位置出现在评审中。
# Constraints
Last reviewed: 2026-08-08 by @addy
## Floor (always enforced, no setup required)
- No new suppression comments: `@ts-ignore`, `eslint-disable`, `# noqa`, `# type: ignore`
- No unimplemented stubs: `throw new Error("Not implemented")`, empty `catch {}`
- No skipped or deleted tests without a reason in the commit message
- No secrets in source
- This file does not get weakened to make a change pass
## Enforced with numbers
| Dimension | Rule | Checked by | Runs at |
|-----------|------|-----------|---------|
| Types | Zero type errors | `tsc --noEmit` | every edit |
| Lint | Zero errors from our config | `biome check` | every edit |
| Secrets | No secrets in source | `gitleaks detect --redact` | every edit |
| Coverage | Changed lines ≥ 80% covered | `vitest run --coverage` + git diff | task end, CI |
| Security: code | No high findings | `semgrep scan --config p/default` | CI |
| Security: deps | Nothing at high or above | `osv-scanner scan source -r .` | CI |
| Accessibility | Zero critical or serious | `axe $PREVIEW_URL --tags wcag2a,wcag2aa,wcag21aa` | preview deploy |
| Performance | LCP ≤ 2500ms, CLS ≤ 0.1 | `lighthouse $PREVIEW_URL --output=json` | preview deploy |
Every row names the command that produces the verdict. A dimension with a
number and no command in this column is an aspiration, not a constraint.
## Measured, not yet enforced
| Metric | Today | Direction |
|--------|-------|-----------|
| Project coverage | 62.4% | must not fall |
| Bundle size (main) | 184 kB | must not grow |
## Exceptions
| ID | Rule | Path | Reason | Owner | Expires |
|----|------|------|--------|-------|---------|
| W1 | `no-explicit-any` | `src/legacy/**` | Rewrite tracked in ENG-441 | @addy | 2026-11-01 |
然后在 AGENTS.md 和 CLAUDE.md 中各添加一行:Read CONSTRAINTS.md before writing code. Do not weaken it to make a change pass.
第 4 步:安装各维度所需的工具
选择一个维度就意味着要安装相应工具。别只给用户一个数字,却不给出实现机制;也别在已有事实标准工具时自己另造检查器——列出这些工具,是因为它们的规则格式和阈值是整个生态系统的目标标准,因此团队现有的配置可以继续使用。
| 维度 | 工具 | 安装 | 运行 | 门槛 |
|---|---|---|---|---|
| 类型(TS) | tsc | 已有 | tsc --noEmit | 任何错误 |
| 类型(Python) | mypy | pip install mypy | mypy . | 任何错误 |
| Lint | 现有配置 | 已有 | eslint . / biome check / ruff check | 任何错误 |
| 覆盖率(JS) | 测试运行器 | 已有 | vitest run --coverage(或 jest --coverage) | 变更行的覆盖率 |
| 覆盖率(Python) | pytest-cov | pip install pytest-cov | pytest --cov --cov-report=lcov | 同上 |
| 安全:代码 | Semgrep | pipx install semgrep | semgrep scan --config p/default --config p/owasp-top-ten | 任何高危发现 |
| 安全:密钥 | gitleaks | brew install gitleaks | gitleaks detect --redact --no-banner | 任何发现 |
| 安全:依赖项 | osv-scanner | brew install osv-scanner | osv-scanner scan source -r . | 高危及以上 |
| 性能:页面 | Lighthouse | npm i -D lighthouse | lighthouse $URL --output=json --quiet | LCP、CLS、性能分数 |
| 性能:包体积 | size-limit | npm i -D size-limit | size-limit --json | 每个入口的字节预算 |
| 无障碍性 | axe-core | npm i -D @axe-core/cli | axe $URL --tags wcag2a,wcag2aa,wcag21aa | 严重或高危问题为零 |
| 架构 | dependency-cruiser | npm i -D dependency-cruiser | depcruise --validate src | 任何违规 |
| 断言质量 | Stryker | npm i -D @stryker-mutator/core | stryker run --mutate <changed files> | 变异分数 |
以下 5 点若忽略,会带来麻烦:
- gitleaks 的
--redact不能省略。 不加它,匹配到的密钥就会出现在 Agent 的对话记录中,进而泄漏到日志、摘要或提交消息里。只报告规则和位置,绝不报告密钥值。 - Lighthouse 和 axe 需要 URL。 它们只能检查正在运行的应用,因此应在运行时阶段对预览部署或预先启动的本地服务器运行。如果项目没有可访问的 URL——例如 CLI、库或桌面应用——就应说明并移除该维度,而不是编造无法运行的检查。
- 将开销较大的检查限定在差异范围内。 对整个仓库运行
stryker run --mutate需要数小时,最终会被关掉;只对变更涉及的文件运行则不到一分钟。Semgrep 也一样,可以传入路径列表。 - 覆盖率不需要再运行一次测试。 读取测试套件已经生成的 lcov,并将其与
git diff求交集。为了获取一个数字而运行两次测试,是让大家对此感到厌烦最快的方式。 - Semgrep 注册表规则可免费运行;重新分发前请检查许可证。 如果法律团队有相关要求,
opengrep是一个即插即用的分支,使用相同的规则格式并生成相同的 JSON 输出。
将每项检查添加到项目自己的脚本中,这样无需 Agent 也能复现:
{
"scripts": {
"check:fast": "tsc --noEmit && eslint . && gitleaks detect --redact --no-banner",
"check:task": "npm run check:fast && vitest run --coverage",
"check:full": "npm run check:task && semgrep scan --config p/default && osv-scanner scan source -r ."
}
}
工具本身不如这种映射重要。编辑后运行 check:fast,Agent 认为任务已完成时运行 check:task,CI 中运行 check:full。
这些命令现在位于两个地方——CONSTRAINTS.md 中的 Checked by 列,以及这些脚本。CONSTRAINTS.md 是规范来源:它会在列出每条命令的同时说明原因,也会出现在代码审查中。脚本只是便捷封装,必须与其保持一致,而不能成为第二个事实来源;如果两者不一致,以该文件为准。
第 5 步:将其融入生命周期
最大的错误,就是到处运行所有检查。一个会让 Agent 卡住的检查会被关闭;而被人关闭的门禁比没有门禁更糟,因为标准看起来仍然存在。
| 阶段 | 命令 | 运行内容 | 耗时预算 |
|---|---|---|---|
| BUILD | /build | 类型检查、lint、密钥检查、基础检查 | 低于 5s,仅检查已更改的文件 |
| VERIFY | /test | 相关测试、已更改行的覆盖率 | 低于 90s |
| REVIEW | /review | 所有检查,以及下面的防护措施 | 数分钟 |
| SHIP | /ship | 方向检查、确保没有回归 | CI |
两条规则能让这一切更容易接受:
- 限定为差异范围。 检查此次更改涉及的行,而不是整个仓库。已更改行的覆盖率是 Agent 可以提升的指标;项目整体覆盖率则是它继承而来的。
- 根据成本决定放置位置。 耗时超过几秒的检查应移出编辑循环。对整个仓库进行变异测试需要数小时;只测试此次更改涉及的文件则不到一分钟,这决定了人们会不会运行这项检查。
第 6 步:守住标准本身
有人会指出,如果 Agent 同时编写代码和检查,那么这些检查就毫无证明力。这个说法有一半正确,值得通过工程手段加以防范。
Agent 不会设计巧妙的漏洞。它们遇到失败的检查,就会选择让检查通过的最省力办法。在审查时,留意差异中是否出现以下五种操作:
- 阈值被调低。 降低了预算、下调了严重性,或从快速阶段移除了检查。将
CONSTRAINTS.md与分支点时的状态进行比较。 - 测试变得更容易。 新增了
.skip、删除了测试文件,或从保留的测试中移除了断言。 - 检查器被屏蔽。 新增了
@ts-ignore或eslint-disable。有四种抑制方式尤其值得关注,因为它们会关闭你所依赖的检查:istanbul ignore会将代码排除在覆盖率之外,而不是对其进行测试;Stryker disable会隐藏存活的变异体;nosemgrep和gitleaks:allow会屏蔽安全问题。 - 工作尚未完成。 会抛出异常的存根、将失败悄然吞掉的空
catch,或本应实现代码的位置上留着TODO。 - 出现了例外。 在无人讨论的情况下,Exceptions 表中新增了一行。
这些检查都不需要 git diff 之外的工具。提高标准应悄无声息;降低标准则应引人注意。
与编号维度不同,基础标准没有事实上的专用工具,因此被要求执行它的 Agent 往往会从头编写检查器,两个 Agent 就会写出两套不同的检查器。本技能附带了这五项检查的参考实现,见 references/floor-guard.md(限定差异范围,退出码为 0/1/2,模式可按生态系统调整)。请在此基础上改造,而不是从头重做,理由与每个维度都指定事实上的工具相同:确保不同运行和技术栈使用相同的机制。
并非所有检查都同样具有循环性。 按照一个问题对它们排序:Agent 能否通过编写无法正常工作的代码来让这项检查通过?
- 外部检查 — axe-core 编码了 WCAG,
osv-scanner会读取漏洞数据库,Lighthouse 会测量真实浏览器。Agent 无法与这些结果争辩。 - 项目检查 — 你的 lint 规则、你的分层边界。文件由人负责。
- 测试套件 — 你自己的测试。最有用,也是唯一真正具有循环性的一类。
完全由第三类检查构成的标准,不如包含外部意见的标准有价值。请确认至少存在一项外部约束。
Step 7:没有目标数值时,使用棘轮机制
在覆盖率只有 62% 的代码库上设定 80% 的覆盖率要求,只会让构建一直报红,最终让团队学会无视红色构建。
另一种做法无需做出任何决定:记录当前水平,然后禁止它变差。将其填入“已测量,尚未强制执行”表格,写明今天的数值和趋势。每次检查都与记录的数值比较,而不是与目标比较。数值提高时,就更新记录;数值下降时,这就是发现的问题。
这也回应了对训练的一个合理质疑。模型会因通过测试而得到奖励,而你几秒钟内就能评估这一点。架构腐化要数月才会显现,而且永远不会进入权重。棘轮机制正是缺失的惩罚,并且会写入构建能够读取的地方。
合理的默认值
用户没有偏好时,就采用以下设置。这些值的选择,是为了让大多数代码库从第一天起就能达到。
| 约束 | 默认值 | 该数值的理由 |
|---|---|---|
| 变更行的覆盖率 | ≥ 80% | 要求编写测试的同时,也允许仅修改配置行 |
| 项目覆盖率 | 当前值,不得下降 | 无需争论即可采用 |
| 变异分数(如使用) | 起始值 ≥ 60% | 适用于此前从未进行变异测试的测试套件;80% 表示已较成熟 |
| 依赖项漏洞 | 不得有高危及以上漏洞 | 低于此级别的大多只是噪声 |
| LCP | ≤ 2500 ms | Core Web Vitals 的“良好”阈值 |
| CLS | ≤ 0.1 | 同上 |
| 无障碍性 | axe 严重或危急违规为零 | 中等和轻微违规往往有争议 |
| 例外有效期 | 90 天 | 足够安排修复,也短到不容易忘记 |
| 棘轮容差 | 0.5% | 当无关文件变动导致数值波动时,用于吸收偏差 |
把数值和理由写在一起。没有依据的阈值,会被下一个碰到它的人删掉。
升级路径
约束有三个强度级别。从第一个开始。
- 仅书面记录。 存在
CONSTRAINTS.md,且 Agent 会阅读。无需成本,能抓住无心之失,但依赖 Agent 遵守。 - 脚本化。 编写一个
npm run check(或make check),运行快速检查,并将其接入 Agent 的编辑后钩子和 CI。结果确定,无需新增依赖。 - 工具支持。 使用专用运行器处理差异范围、预算、棘轮机制和守护检查。配置超出 shell 脚本的承载能力时再采用。references/floor-guard.md 中的底线守护参考,是实现这套守护检查的起点。
大多数项目做到第 2 级就应止步。只有在需要维护的检查执行 shell 代码超过约 30 行时,才升级到第 3 级。
首次运行可以只检查底线。 底线守护只检查差异,无需安装,因此你可以从第一天起就强制执行底线,并在安装每个工具时逐步添加有数值的维度,而不是等所有检查器都就位后才保护第一个提交。如果你想让笔记本电脑保持干净,也可以只在 CI 中运行需要全局安装的安全工具(gitleaks、osv-scanner);在 Runs at 列中声明每个维度的运行位置。
常见借口
| 借口 | 实情 |
|---|---|
| “等代码稳定后再添加约束” | 代码会围绕其演进过程中允许的标准逐渐稳定 |
| “测试就是约束” | 你编写的测试只能证明你认同自己的做法;它们对新代码的覆盖率、依赖风险或包体积增长毫无说明 |
| “我们无法达到 80% 的覆盖率” | 那就别设 80%。设定当前数值并守住它 |
| “这会拖慢 Agent” | 只有把慢速检查放进快速循环时才会。那是放置不当,不是反对约束的理由 |
| “我会记住我们的标准” | Agent 不会,而大部分代码都是它在写 |
| “约束会阻碍我们发布” | 指定负责人和日期的例外可以解除阻碍。删除约束则会让所有人永远不再受其约束 |
危险信号
如果发现以下情况,请停下来重新考虑:
- 访谈超过了四个问题,或者生成的配置用户无法解释
- 设定的预算在当前代码库中不达标,也没有达到目标的计划
- 某个维度在 CONSTRAINTS.md 中写了一个数值,却没有对应的工具
- 明明存在事实标准的检查器,却选择手动编写,导致团队现有的配置被忽略
- 每项约束都由项目自身的测试套件检查,没有外部意见
- 在导致问题的功能所在提交中,CONSTRAINTS.md 也发生了变化
- 某项例外没有负责人,或者到期时间超过一年
- Agent 提议放宽阈值,而不是修复代码
- 耗时较长的检查被加入编辑循环,而且有人已经开始传入
--no-verify - 文件写好后,就再也没人打开过
CONSTRAINTS.md
验证
满足以下条件时,表示该技能已正确应用:
-
CONSTRAINTS.md已存在,其中的每个数值都有明确的理由 - 强制执行底线要求,且在当前代码库上无需改动即可通过
- 用户选择的每个维度都已安装相应工具,并且有一条现在就能运行的命令
- 每项约束都说明了运行位置,且快速阶段耗时不超过几秒
- 至少有一项约束来自外部(不由项目自身的测试判定)
- 仅测量的指标记录了当前数值和变化方向
- 例外项都有负责人和到期日期
-
AGENTS.md或CLAUDE.md指向该文件 - 在当前分支上进行试运行,不会产生用户有异议的失败
另请参阅
interview-me— 此技能的需求收集环节所借鉴的逐次只问一个问题的做法code-review-and-quality— 如何进行审查;此技能决定审查要强制执行哪些要求ci-cd-and-automation— 构建运行这些约束的流水线test-driven-development— 衡量覆盖率和变异测试约束的测试套件security-and-hardening— 安全维度应包含哪些内容performance-optimization— 性能数值的来源