SuperAPI AI Directory

constraint-driven-development

Coding & development

View repository

Updated 10-16 08:54

About this skill

Constraint-driven development

Overview

The other skills in this skill pack describe what "good" looks like.code-review-and-quality Gives you five axes.test-driven-development Gives you a loop.security-and-hardening Gives you a threat checklist. All of these live in text; the agent may or may not follow them after reading them, and none of them survive past the end of the session.

This skill produces something else:This projectA written record of the quality line, with numbers, that survives the conversation and can be mechanically checked.

The reason behind this matters. When you write code by hand, reading the code tells you whether it is good. An agent writes more code in one afternoon than you can read in a week, so judgment moves from your head into checks that run around the loop. These checks need to exist, need to use numbers you actually chose, and need to be close enough to the work that the agent can fix its own output.

Spec-driven development says clearly what to build. Test-driven development proves it works. Constraint-driven development defines what "good enough to ship" means—before anyone argues about it in a pull request.

When to use

Apply this skill in the following situations:

  • Starting a project or a significant feature with no quality line written down
  • The user asks to "set up constraints", "add quality gates", "define our standards", or "stop the agent shipping junk"
  • The agent is producing at a volume no one reads line by line
  • CI has checks, but no one can say which ones block a merge and which are just for show
  • Coverage, performance, or accessibility numbers are argued about on every PR instead of decided once
  • You are about to run /build auto or any autonomous loop, and the only barrier between it and main is a set of tests the agent wrote itself

When not to use:

  • The project already has CONSTRAINTS.md and the user does not want to change it—go read it and follow it
  • One-off scripts, spikes, throwaway prototypes
  • The user wants a code review right now (code-review-and-quality) or wants to set up a CI pipeline (ci-cd-and-automation)
  • Code before product-market fit, expected lifespan of two weeks—the floor below is still worth it, the rest is not

Load constraints

This interview needs a live user.Do not run it in a non-interactive context(CI、/loop, autonomous run). If you are in one of these and constraints are missing, apply the Floor below, note that you did so, and flag the rest for a human to handle.

Process

Step 1: Detect first, then ask

Never ask what can be read. Before the first question, first collect:

What to look at Where to look
Language and tech stack package.json、pyproject.toml、go.mod、Cargo.toml
Test runner dev dependencies,test script, existing test files
Existing linter eslint.config.*、biome.json、.ruff.toml
Current coverage coverage/ output, or run the suite once
CI .github/workflows/、.gitlab-ci.yml
Agent harness .claude/、.codex/、AGENTS.md

Report what you found in two lines, then ask only about what's left.

Step 2: Four questions, each with a default

Follow interview-me 's one-question-at-a-time discipline, with only one change: here every question has a default, so "I don't know" is also a complete answer and still produces a usable configuration.

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.

Stop at four. A twelve-question intake produces a configuration nobody understands and a user who regrets starting.

Step 3: Write CONSTRAINTS.md

Put one file at the repository root. Any agent on any harness can read it, and a change to it shows up in review—where it belongs.

# 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 |

Then add one line each to AGENTS.md and CLAUDE.md :Read CONSTRAINTS.md before writing code. Do not weaken it to make a change pass.

Step 4: Install what each dimension needs

Choosing a dimension means installing something. Don't leave the user with a number but no mechanism, and don't invent your own checker when a de facto standard tool exists—the tools listed here were chosen because their rule formats and thresholds are what the whole ecosystem targets, so a team's existing configuration keeps working.

Dimension Tool Install Run Blocking condition
Types (TS) tsc Built in tsc --noEmit Any error
Types (Python) mypy pip install mypy mypy . Any error
Lint Your existing configuration Built in eslint . / biome check / ruff check Any error
Coverage (JS) Your test runner Built in vitest run --coverage(or jest --coverage) Coverage of changed lines
Coverage (Python) pytest-cov pip install pytest-cov pytest --cov --cov-report=lcov Same as above
Security: code Semgrep pipx install semgrep semgrep scan --config p/default --config p/owasp-top-ten Any high finding
Security: secrets gitleaks brew install gitleaks gitleaks detect --redact --no-banner Any finding
Security: dependencies osv-scanner brew install osv-scanner osv-scanner scan source -r . High and above
Performance: pages Lighthouse npm i -D lighthouse lighthouse $URL --output=json --quiet LCP、CLS、performance score
Performance: bundle size-limit npm i -D size-limit size-limit --json Byte budget per entry
Accessibility axe-core npm i -D @axe-core/cli axe $URL --tags wcag2a,wcag2aa,wcag21aa Zero critical or serious
Architecture dependency-cruiser npm i -D dependency-cruiser depcruise --validate src Any violation
Assertion quality Stryker npm i -D @stryker-mutator/core stryker run --mutate <changed files> mutation score

Skip the following five things, and they will come back to bite you:

  1. gitleaks' --redact is not optional. Without it, a matched secret lands in the agent's transcript—which is exactly the path by which a leaked key ends up in logs, summaries, or commit messages. Report the rule and location, never the value.
  2. Lighthouse and axe need a URL. They only work against a running app, so they belong to the runtime stage, aimed at a preview deploy or a local server you start first. If the project has no reachable URL—CLI, library, desktop app—say so clearly, then cut the dimension rather than inventing a check that can't run.
  3. Limit expensive checks to the diff. Running stryker run --mutate against the whole repository takes hours and then gets turned off; running it only on files touched by a change takes less than a minute. The same goes for Semgrep, which accepts a list of paths.
  4. Coverage does not need a second test run. Read the lcov your suite is already writing, and intersect it with git diff . Running the suite twice just to get a number is the fastest way to make people start hating this.
  5. Rules in the Semgrep registry can be run for free; check the licence before redistributing. If this matters to your legal team,opengrep is a drop-in fork with the same rule format and JSON output.

Add each one to the project's own scripts so it is reproducible without an 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 ."
  }
}

This mapping matters more than the tools themselves.check:fast runs after every edit,check:task runs when the agent thinks it is done,check:full runs in CI.

These commands now live in two places—CONSTRAINTS.md 's Checked by column, and these scripts.CONSTRAINTS.md is the authoritative source: it carries the rationale along with each command, and it shows up in review. These scripts are convenience wrappers that must stay consistent with it, not a second source of authority; once they drift, the file wins.

Step 5: Wire it into the lifecycle

The single biggest mistake is running everything everywhere. A check that slows the agent down gets turned off, and a gate that people turn off is worse than no gate, because the quality line still appears to exist.

Stage Command What runs Budget
BUILD /build types, lint, secrets, floor within 5 seconds, only changed files
VERIFY /test relevant tests, coverage of changed lines within 90 seconds
REVIEW /review everything, plus the guards below in minutes
SHIP /ship direction check, no regressions allowed CI

Two rules that keep this within the tolerable range:

  1. Scope to the diff. checks the lines this change touches, not the whole repo. Coverage of changed lines is a number the agent can move; project coverage is a number it inherited.
  2. Cost determines placement. Anything that takes more than a few seconds has to move out of the edit loop. Mutation testing across the whole repo takes hours; on the files the change touches, it takes less than a minute—that is the difference between "checks people will run" and "checks people won't run".

Step 6: Guard the quality line itself

Someone will always point out: if the agent writes both the code and the checks, the checks prove nothing. That claim is half right, and it is worth engineering around it.

Agents don't carefully construct clever vulnerabilities. They hit a red check and then take the cheapest path to green. In review, watch for these five moves in the diff:

  1. The threshold moved. The budget was lowered, a severity was downgraded, a check was moved out of the fast stage. Compare CONSTRAINTS.md with its state at the start of the branch.
  2. The tests got simpler. Added .skipwas added, test files were deleted, assertions were pulled out of the remaining tests.
  3. The checker was silenced. Newly added @ts-ignore or eslint-disable. There are four kinds of suppression to watch for especially, because they turn off the very checks you are relying on:istanbul ignore excluding code from coverage instead of testing it,Stryker disable hiding a surviving mutant,nosemgrep and gitleaks:allow doing the same thing to security findings.
  4. The work isn't done. A stub that throws, an empty catchthat turns failure into silence, a TODO。
  5. There's an exception. A row nobody discussed was added to the Exceptions table.

None of these need any tool beyond git diff . Tightening the quality line should be silent; loosening it should be loud.

Unlike the numbered dimensions, the floor has no de facto standard tool of its own, so an agent asked to enforce it tends to write a checker from scratch, and two agents write two different ones. A reference implementation of these five checks ships with this skill, in references/floor-guard.md(diff-scoped,exit 0/1/2, patterns can be adapted per ecosystem). Use it instead of reinventing it—for the same reason each dimension names a de facto standard tool: to keep the mechanism consistent across every run and every stack.

Not all checks are equally circular. Order them with one question: can the agent make this check pass by writing code that doesn't work at all?

  • External — axe-core encodes WCAG,osv-scanner reads a vulnerability database, Lighthouse measures a real browser. The agent can't argue with these.
  • Project — your lint rules, your layer boundaries. The files are owned by humans.
  • Suite — your own tests. The most useful, and the only truly circular one.

A quality line made entirely of the third kind is worth less than one that contains an external opinion. Have at least one external constraint among the checks.

Step 7: Use a Ratchet when there are no numbers

Set 80% coverage on a 62% codebase and you get a build that's forever red, and then a team that learns to ignore red builds.

The alternative requires no decisions: record where you are now, then refuse to get worse. Put it in the "Measured, not yet enforced" table, with today's numbers and a direction. Every check compares against the recorded value, not against a vision. When the number improves, update it; when it slips, that's a finding.

This also answers a fair objection to training. Models are rewarded for passing tests, and that's something you can evaluate in seconds. Architectural rot only shows up over months, and never reaches the weights. A ratchet is that missing penalty, written where the build can see it.

Reasonable defaults

When the user has no opinion, use these. They were chosen on the basis that most codebases can satisfy them on day one.

Constraint Default Why this number
Coverage of changed lines ≥ 80% High enough to force out a test, low enough to let a line of config through
Project coverage Today's value, no decline allowed Adopting it requires no argument
Mutation score (if used) Start at ≥ 60% This is a typical value for a suite that has never been mutated; 80% is the mature state
Dependency vulnerabilities No high or above allowed Anything below that is mostly noise
LCP ≤ 2500 ms Core Web Vitals' "good" threshold
CLS ≤ 0.1 Same as above
Accessibility Zero axe critical or serious violations Moderate and minor are often arguable
Exception lifetime 90 days Long enough to plan the fix, short enough to still remember
Ratchet tolerance 0.5% Absorb drift when unrelated files move the numbers

Say the number and the reason together. A threshold with no rationale gets deleted by the next person who runs into it.

Escalation path

Constraints work at three levels of "teeth" strength. Start with the first.

  1. Written only. CONSTRAINTS.md It exists and the agent will read it. Zero cost, catches honest mistakes, relies on the agent's self-discipline.
  2. Scripted. A npm run check(or make check) runs a quick check, wired into your agent's post-edit hook and your CI. Deterministic, no new dependencies.
  3. Tooled. A dedicated runner that handles diff scoping, budgets, ratchet, and guard checks. Use it when the configuration outgrows a shell script.references/floor-guard.md The floor-guard reference in is the starting point for the guard-checks half of this level.

Most projects should stop at 2. Move to 3 when you would be maintaining more than about thirty lines of shell that runs checks.

The first run can be floor only. The floor guard is diff-only and needs no installation, so you can enforce the floor on day one, then add the numbered dimensions one by one as each tool is installed—no need to build every checker before the first line of code is protected. Security tools installed in a machine-readable way (gitleaks, osv-scanner) can also run only in CI if you do not want to pollute your laptop; in the Runs at column, declare where each dimension runs.

Common rationalizations

Excuse Reality
"We'll add constraints once the code stabilizes" Code stabilizes around whatever was allowed while it was flowing
"Tests are our constraint" Tests you write yourself only prove you agree with yourself; they say nothing about coverage of changed code, dependency risk, or bundle growth
"We can't reach 80% coverage" Then don't set 80%. Set today's number and hold it
"This will slow the agent down" Only if you put slow checks in the fast loop. That's a placement error, not an argument against constraints
"I'll remember what our standards are" The agent won't, and it is writing most of the code
"Constraints will block our releases" One exception with an owner and a date can let you through. Deleting the constraint lets everyone through forever

Red flags

If you notice these, stop and reconsider:

  • The interview went past four questions, or produced a configuration the user themselves cannot explain
  • A budget was set for a codebase that does not meet the standard today, and there is no plan to meet it
  • A dimension was written into CONSTRAINTS.md with numbers, but there is no tool behind it
  • A de facto standard checker already exists, but you hand-rolled one, causing the team's existing configuration to be ignored
  • Every constraint is checked by the project's own test suite, with no external opinion
  • CONSTRAINTS.md appears in the same commit as the feature that is currently failing
  • An exception has no owner, or an expiration date more than a year out
  • The Agent proposes loosening the threshold instead of fixing the code
  • A slow check fell into the edit loop, and someone has already started passing --no-verify
  • Since CONSTRAINTS.md was written, no one has opened it

Verification

This skill is correctly applied when the following conditions are met:

  • CONSTRAINTS.md exists, and every number in it has a stated reason
  • Floor is enforced and passes on the current codebase without any changes
  • Every dimension the user selected has an installed tool and a command that can run today
  • Every constraint states where it runs, and the fast phase stays within a few seconds
  • At least one constraint is external (not adjudicated by the project's own tests)
  • Measurement-only metrics record today's value and a direction
  • Exceptions have an owner and an expiration date
  • AGENTS.md or CLAUDE.md points to this file
  • Try running it once on the current branch, with no failures the user does not accept

See also

  • interview-me — the one-question-at-a-time discipline this skill's intake borrows
  • code-review-and-quality — how to review; this skill decides what the review executes
  • ci-cd-and-automation — set up the pipeline in which these constraints run
  • test-driven-development — the suite that coverage and mutation constraints measure
  • security-and-hardening — what the security dimension should include
  • performance-optimization — where performance numbers come from