Constraint-Driven Development
Turns the project's quality bar into a written CONSTRAINTS.md contract where every number names the command that checks it, then watches the diff for the five ways an agent quietly lowers that bar.
What it does
Runs a four-question interview, each question carrying a default so that "I do not know" still produces a working configuration, and detects the stack, test runner, linters, current coverage, and CI setup before asking anything. The output is a CONSTRAINTS.md at the repository root holding a floor that needs no setup (no new suppression comments, no unimplemented stubs, no silently skipped tests, no secrets, no weakening of the file itself), a table of enforced dimensions where every row names the command that produces the verdict and the stage it runs at, a second table of metrics that are measured and ratcheted rather than enforced, and an exceptions table where every entry carries an owner and an expiry. Checks are then placed by cost, so types, lint, and secrets run after each edit in under five seconds while mutation testing and dependency scanning move to review and CI, and the expensive ones are scoped to the diff rather than the whole repository. The guard half is the part that matters once an agent is writing both the code and the tests: it reads the diff for a moved threshold, a test made easier, a silenced checker, an unfinished stub, or an undiscussed exception, and it ranks constraints as external, project, or suite so that at least one check exists that the agent cannot satisfy by writing code that does not work. Where no target number exists, it records today value and holds the line instead of setting an aspiration that turns the build permanently red. Distilled from the constraint-driven-development skill in addyosmani/agent-skills, released in 0.6.8 on 2026-08-28 (91,421 stars, 9,763 forks).
When to use
- ✓Starting a project or a significant feature where no quality bar is written down and every pull request re-argues the same coverage and performance numbers
- ✓An agent keeps reaching green by adding suppression comments, skipping tests, or stripping assertions, and you want that behaviour to fail loudly in review
- ✓You need a coverage, performance, or accessibility threshold and have no defensible number to pick, so today value has to become the line
- ✓Before running an autonomous build loop, where the only thing between the agent and the main branch is a test suite the agent also wrote
When not to use
- ✗You want a specific diff reviewed right now: use code-review-checklist, which applies a bar rather than deciding what the bar is
- ✗You need the pipeline itself built or repaired: use ci-cd-pipeline-architect, which constructs the stages these checks run inside
- ✗The question is which tests to write and at what depth: use test-coverage-strategy and test-driven-development
- ✗Throwaway spikes, one-off scripts, and prototypes with a two-week expected lifetime: the floor is still worth it, the numbered dimensions are not
Install
Download the .zip, then unzip into your Claude skills folder.
mkdir -p ~/.claude/skills
unzip ~/Downloads/constraint-driven-development.zip -d ~/.claude/skills/
# Restart Claude Code session.
# Skill is now available — Claude will use it when relevant.SKILL.md
---
name: constraint-driven-development
description: Use when a project has no written quality bar, when an agent keeps silencing checks or skipping tests to reach green, or when coverage and performance numbers get argued about in every pull request instead of decided once. Triggers on "set up constraints", "define our standards", "add quality gates", "stop the agent shipping junk", "what coverage number should we use". Not for reviewing a specific diff, and not for building the CI pipeline itself.
---
# Constraint-Driven Development
Reading the code used to tell you whether it was any good. An agent writes more in an afternoon than anyone will read that week, so the judgment has to move out of your head and into checks that run around the loop. Those checks need to exist, they need numbers someone actually chose, and they need to fire close enough to the work that the agent fixes its own output.
Spec-driven work says what to build. Test-driven work proves it runs. This decides what "good enough to ship" means before anyone argues about it in a pull request, and writes it down where it outlives the session.
## Read the repository before asking anything
Never ask what you can detect. Before the first question, gather the stack from `package.json`, `pyproject.toml`, `go.mod`, or `Cargo.toml`; the test runner from dev dependencies and the `test` script; existing linter config; today's coverage number from the `coverage/` output or one run of the suite; the CI definition; and the agent harness files (`.claude/`, `AGENTS.md`, `CLAUDE.md`). Report what you found in two lines. Then ask only what is left.
## Four questions, each with a default
One at a time, and every one has a default, so "I do not know" is a complete answer that still produces a working config.
1. **Beyond the floor, which dimensions do you want enforced?** Coverage on new code, security scanning, performance budgets, accessibility, architecture boundaries. Say what each pick costs: performance and accessibility need a running URL, architecture needs a rules file written. Default to coverage and security.
2. **When a check fails mid-task, block or warn?** Default to blocking on the floor and warning on everything else for the first two weeks. A warning nobody reads is not a gate.
3. **Do you have target numbers, or should I measure where you are today and hold that line?** Default to measure and hold. An invented number gets ignored.
4. **What is the slowest check you will tolerate before the agent hands work back?** Default to ninety seconds at task end, unlimited in CI.
Stop at four. A twelve-question intake produces a config nobody can explain.
Do not run the interview in a non-interactive context. In CI or an autonomous loop, apply the floor, note that you did, and flag the rest for a human.
## CONSTRAINTS.md
One file at the repository root, so any agent on any harness can read it and a change to it shows up in review where it belongs.
The **floor** is enforced everywhere and needs no setup: no new suppression comments (`@ts-ignore`, `eslint-disable`, `# noqa`, `# type: ignore`), no unimplemented stubs or empty `catch` blocks, no skipped or deleted tests without a reason in the commit message, no secrets in source, and no weakening of this file to make a change pass.
Then a table of enforced dimensions, one row each, with four columns: the rule, the number, **the command that produces the verdict**, and the stage it runs at. A dimension with a number and no command in the third column is an aspiration rather than a constraint, so do not write the row.
A second table holds what is measured but not yet enforced: today's project coverage, today's bundle size, each with a direction of "must not fall" or "must not grow".
A third holds exceptions, and every exception needs an ID, the rule, the path, a reason, an owner, and an expiry date. Ninety days is the default lifetime.
Finish by adding one line to `AGENTS.md` and `CLAUDE.md`: read CONSTRAINTS.md before writing code, and do not weaken it to make a change pass.
## Install what each dimension needs
Picking a dimension means installing something. Do not leave a number with no mechanism behind it, and do not hand-roll a checker when a de facto one exists, because the team's existing config already targets the de facto rule format.
Types run on `tsc --noEmit` or `mypy`. Lint runs on whatever config is already there. Coverage reads the lcov the suite already writes. Code security runs Semgrep, secrets run gitleaks, dependencies run osv-scanner. Page performance runs Lighthouse, bundle size runs size-limit, accessibility runs axe-core, architecture boundaries run dependency-cruiser, and assertion quality runs a mutation tester.
Five things that will bite:
- **Pass `--redact` to gitleaks.** Without it the matched secret lands in the agent transcript, and that is how a live key ends up in a log or a commit message. Report the rule and the location, never the value.
- **Lighthouse and axe need a URL.** They only work against a running application, so they belong against a preview deploy or a local server you start first. If the project is a CLI or a library with no URL to hit, say so and drop the dimension rather than inventing a check that cannot run.
- **Scope the expensive checks to the diff.** Mutation testing across a whole repository takes hours and gets switched off. On the files a change touched it takes under a minute.
- **Coverage needs no second test run.** Read the lcov the suite already produced and intersect it with `git diff`. Running the suite twice to get a number is the fastest way to make people hate this.
- **Check the licence on registry rule packs before redistributing them.** Running them is free; shipping them onward may not be.
Then wire three scripts so the checks are reproducible without an agent: a fast one for after every edit, a task one for when the agent believes it is done, and a full one for CI. CONSTRAINTS.md stays canonical; the scripts mirror it, and if they drift the file wins.
## Placement decides whether anyone keeps it
Running everything everywhere is the single biggest failure. A check that stalls the agent gets switched off, and a gate people switched off is worse than no gate, because the bar still looks like it exists.
| Stage | What runs | Budget |
|---|---|---|
| After each edit | Types, lint, secrets, the floor | under 5 seconds, changed files only |
| Task end | Related tests, coverage on changed lines | under 90 seconds |
| Review | Everything, plus the guard below | minutes |
| CI | Direction checks, no regressions | unbounded |
Two rules keep it tolerable. Check the lines this change touched, not the whole repository, because coverage of changed lines is a number the agent can move and project coverage is one it inherited. And let cost decide placement: anything over a few seconds leaves the edit loop.
## Guard the bar itself
If the agent writes the code and the checks, the checks prove less than they appear to. That is half right and worth engineering around. Agents do not craft clever loopholes; they hit a red check and take the cheapest road to green. Five moves show up in the diff:
1. **The threshold moved.** A budget lowered, a severity dropped, a check pulled out of the fast stage. Compare CONSTRAINTS.md against its state at the branch point.
2. **A test got easier.** A `.skip` added, a test file deleted, assertions removed from tests that stayed.
3. **A checker got silenced.** New suppression comments. Four deserve particular attention because they switch off a check you are relying on: coverage ignores drop code from the measurement instead of testing it, mutation disables hide a surviving mutant, and the Semgrep and gitleaks allow-comments do the same for security findings.
4. **Work is unfinished.** A stub that throws, an empty `catch` turning a failure into silence, a `TODO` standing where the implementation belongs.
5. **An exception appeared.** A new row in the exceptions table nobody discussed.
None of that needs tooling beyond `git diff`. Tightening the bar should be silent; loosening it should be loud.
Rank the constraints themselves by one question: can the agent make this pass by writing code that does not work? **External** checks encode something outside the project, such as WCAG rules, a vulnerability database, or a real browser measurement, and the agent cannot argue with them. **Project** checks are your lint rules and layer boundaries, owned by a human in a file. **Suite** checks are your own tests, the most useful and the only genuinely circular ones. A bar made entirely of the third kind is worth less than one with an outside opinion in it, so confirm at least one external constraint is present.
## When there is no number, ratchet
Set eighty percent coverage on a codebase sitting at sixty-two and you get a red build forever, followed by a team that learns to ignore red builds.
The alternative asks for no decision. Record today's value, then refuse to get worse. Every check compares against the recorded number rather than an aspiration. When it improves, update it. When it drops, that is the finding. Allow about half a percent of tolerance so an unrelated file moving the number does not fail the build.
Sane defaults when the user has no opinion: coverage of changed lines at eighty percent, project coverage held at today's value, mutation score starting at sixty percent, no dependency vulnerabilities at high or above, LCP at or under 2500 ms and CLS at or under 0.1, zero critical or serious accessibility violations, and a ninety-day exception lifetime. State the number and the reason together, because a threshold with no rationale gets deleted by the next person who hits it.
## How much teeth to start with
Three levels, and most projects should stop at the second. Written only means CONSTRAINTS.md exists and agents read it: it costs nothing, catches honest mistakes, and relies on compliance. Scripted means one check command wired into the post-edit hook and CI: deterministic, no new dependency. Tool-backed means a dedicated runner that handles diff scoping, budgets, ratchets, and the guard checks, and it is worth it once you are maintaining more than about thirty lines of check-running shell.
A first run can be floor-only. The floor is diff-scoped and needs no installs, so it can protect the very first commit while the numbered dimensions arrive one tool at a time.
## Stop and reconsider if
The interview ran past four questions, or produced a config the user cannot explain. A budget was set that the codebase fails today with no plan to reach it. A dimension has a number but no tool behind it. A checker was hand-rolled while a de facto one exists. Every constraint is judged by the project's own test suite. CONSTRAINTS.md changed in the same commit as the feature that was failing. An exception has no owner, or an expiry more than a year out. The agent proposed relaxing a threshold instead of fixing the code. Slow checks landed in the edit loop and someone has started bypassing hooks. Or nobody has opened the file since it was written.
## Done when
CONSTRAINTS.md exists and every number in it has a stated reason. The floor passes on the current codebase with no changes. Every chosen dimension has an installed tool and a command that runs today. Each constraint declares where it runs, and the fast stage stays under a few seconds. At least one constraint is external. Measured-only metrics carry today's value and a direction. Exceptions have an owner and an expiry. The harness files point at the file. And a trial run on the current branch produces no failure the user disagrees with.
Example prompts
Once installed, try these prompts in Claude:
- Detect our stack and current coverage, then interview me in four questions and write CONSTRAINTS.md at the repo root. Every enforced row must name the command that checks it and the stage it runs at, and anything I have no number for should be recorded at today value with a direction.
- Read the diff on this branch against the branch point and tell me whether the quality bar was weakened: thresholds edited down, tests skipped or deleted, assertions removed, new suppression comments, unimplemented stubs, or a new exception row. Report each with file and line.
- Sep 1, 2026New skill: a four-question intake with defaults produces a CONSTRAINTS.md where every enforced row names the command that checks it, plus a diff guard for the five moves that weaken a bar and a ratchet for metrics with no agreed target.