Spec Kit, Kiro, OpenSpec, BMAD, specdx: what each one actually checks

Two of the five tools check a spec with code. I wrote one password-reset spec with eight seeded defects and ran each tool’s own command on it. OpenSpec 1.14.3 caught three and specdx 0.5.0 caught five. Spec Kit 1.2.3 and BMAD 6.12.4 give spec review to the agent, so they caught none without one. I did not run Kiro.

What each spec-driven development tool checks without an agent

Every one of these tools gives an agent a workflow: write requirements, then a design, then tasks, then code. They differ in what happens to a spec when no model reads it. Some ship a validator that runs in CI. Others ship prompts that ask the agent to review the spec, and those checks run only when an agent runs them, in a different way each time.

Tool Version Check that runs as code Exit code on the seeded spec Seeded defects caught
Spec Kit 1.2.3 check-prerequisites.sh: the files exist and plan.md is not empty 0 0 of 8
Kiro — Requirements analysis: an LLM writes logic, an SMT solver checks it not run not measured
OpenSpec 1.14.3 openspec validate 1 3 of 8
BMAD 6.12.4 None for the PRD; lint_spine.py for the architecture document — 0 of 8
specdx 0.5.0 specdx lint and specdx check 1 5 of 8

I maintain specdx, and I chose the eight defects. Both facts lean the test toward the checks I think matter, so the next section gives the result for each defect and you can weigh them yourself. All four installs and runs are from 2026-10-23.

Eight seeded defects and which tool caught each one

The feature is a password reset by email: six requirements, written once in each tool’s own format. Each copy carries the same eight defects.

Defect Spec Kit OpenSpec BMAD (PRD) specdx
D1. A requirement with no acceptance scenario — error — warning
D2. “The reset page must load fast and be user-friendly” — — — —
D3. A requirement with no MUST or SHALL — warning — —
D4. “The token must expire after TBD minutes” — — — warning
D5. The same requirement written twice — error — —
D6. A reference to a spec that does not exist — — — error
D7. A source file named in the spec that does not exist — — — error, once approved
D8. A section heading with nothing under it — — — warning

OpenSpec and specdx overlap on one defect, D1. Between them they catch seven of the eight. One defect, D2, passed every validator I ran.

Read D6 and D7 with care. specdx has frontmatter fields for a reference to another spec and for the files a spec promises, so in the specdx copy those two defects sit in fields a program reads. The other formats have no such fields, so there the same defects are a sentence of prose. Two of the five specdx catches come from that difference in format, not from a stricter check of the same text.

Spec Kit leaves every content check to the agent

specify init for Claude Code wrote 10 skills, about 2,500 lines of instructions, and 6 shell scripts. The scripts create feature folders and resolve paths. The only check on a spec is in check-prerequisites.sh: it fails when the feature folder or plan.md is missing, when plan.md is empty, and, with flags, when spec.md or tasks.md is missing. On the seeded spec it printed the feature path and exited 0.

The real review is the /speckit-analyze skill. It tells the agent to run six detection passes: duplication, ambiguity, underspecification, alignment with the project constitution, coverage gaps, and inconsistency between the spec, plan and tasks. The ambiguity pass names the exact words in D2: “Flag vague adjectives (fast, scalable, secure, intuitive, robust) lacking measurable criteria”. It also names placeholders such as TODO, and the duplication pass covers D5. But no code runs them. The skill even asks for “Deterministic results: Rerunning without changes should produce consistent IDs and counts”, which is an instruction to a model, not a property of a program.

Kiro checks requirements with an LLM and an SMT solver

Kiro is an IDE and a CLI, not an npm package, so I did not run it; this section comes from its specs documentation and its 12 May 2026 post on requirements analysis. A Kiro spec is three files: requirements.md, design.md and tasks.md.

Requirements analysis is an optional step before design. An LLM first rewrites vague criteria into testable ones. A second LLM step translates each criterion into SMT-LIB logic, samples several translations, and groups them by logical equivalence. Where the samples disagree, Kiro asks a question. An SMT solver then checks the model for conflicts and gaps. Each finding becomes a question with two options: keep the requirement, or change it.

That design aims at D2, a requirement at the wrong level of detail, which no validator in my test caught. The solver step is deterministic, but its input is the output of an LLM, so the full pipeline is not. I have no measurement of how often it is right.

OpenSpec validates requirement structure

openspec validate is a 953-line validator. It requires a Purpose and a Requirements section, at least one #### Scenario: for each requirement, and unique requirement names. It warns when a requirement has no SHALL or MUST, when a Purpose is under 50 characters, and when a requirement is over 500 characters. For change proposals it checks that a “Why” section is 50 to 1,000 characters and that no requirement is both added and removed in the same change.

On the seeded spec it found D1 and D5 as errors and exited 1. D3 is only a warning: alone, it passes with exit 0, and fails only with --strict. OpenSpec has no field for the files a spec promises, and its references point at other OpenSpec stores, not from one spec to another, so D6 and D7 were prose that it does not read. It also passed the inline “TBD” in D4: its placeholder check reads only the Purpose section.

BMAD lints the architecture document, not the PRD

The bmad-method CLI has three commands: install, status, uninstall. There is no validate. The install for Claude Code wrote 29 skills in 235 files. PRD validation is a rubric for a “validator subagent” with seven dimensions, and it says so directly: it wants judgement, “not box-ticking”. The seeded PRD had no code to run against it, so BMAD caught 0 of 8 without an agent. My walkthrough of the method is linked from the vibe coding post.

BMAD does ship one linter, lint_spine.py, for the architecture spine. Its docstring is the clearest sentence any of these tools has on the subject: “LLMs miscount IDs and miss literal placeholders; a grep does not.” It checks four things: placeholder text, duplicate or out-of-order decision IDs, decisions missing their Binds, Prevents or Rule field, and stack rows with no version. On a seeded spine it found the TBD, the reused ID, the missing Rule and the unpinned version, and it passed a Rule that said the page “must load fast and be user-friendly”. It also exits 0 every time, by design: the findings go back to the agent, and the agent decides.

specdx lints references, placeholders and declared files

specdx 0.5.0 has 15 lint rules: 6 for structure and 9 for content. One of them, clarity/ambiguity-score-ai, calls no model; it prints a pointer to an AI mode when an API key is set. The other 14 run as code. specdx check then compares the files and exports a spec declares with the files on disk.

It caught D6 as an error and D4 and D8 as warnings. It caught D1 as an empty Acceptance Criteria section, because in specdx a story’s scenarios are a section. It caught D7 only after I set the design spec to approved. While the spec is a draft, specdx check treats its files as planned, not missing, and exits 3: nothing checkable.

It missed D3 and D5. Its vague-language list has 13 phrases, among them “TBD”, “as needed” and “simply”, and “fast” is not on it. It also printed three warnings about missing edge cases that I had not seeded. This site pins specdx 0.4.0-alpha.24 in CI; the sdx reference page covers the tool, and what dogfooding changed in specdx covers the fixes this site caused.

No validator caught “load fast”

D2 is the easiest of the eight to write by accident, and it is the one every validator passed. “Fast” and “user-friendly” are well-formed, have a MUST, and have an acceptance scenario. Nothing in the text is wrong in a way a parser can see. The defect is that nobody can write a test for it.

That is the line between the two kinds of tool. Code catches what is missing or broken in the structure: a section, a scenario, a reference, a file. Judging whether a requirement means something takes a reader, and every tool here that tries it, whether Spec Kit’s analyze skill, BMAD’s rubric or Kiro’s solver, puts a model in that seat. I wrote about where the structural checks stop in spec drift detection, and about the practice around them in spec-driven development.

So choose by which half you want code to own. If you want CI to fail on a broken spec, the choice is OpenSpec or specdx, and the per-defect table says which defects each one covers. If you want a review of what the requirements mean, all three of the others give that to the agent, and only Kiro adds a solver.

How to rerun the comparison

The versions were Spec Kit 1.2.3 (uvx --from git+https://github.com/github/spec-kit.git@v1.2.3 specify init --integration claude), OpenSpec 1.14.3, BMAD 6.12.4 with the bmm module, and specdx 0.5.0, on Node.js 24.13.0. The commands were:

bash .specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks
openspec validate --specs --strict --no-interactive
specdx lint && specdx check
python3 .claude/skills/bmad-architecture/scripts/lint_spine.py --workspace <dir>

Write the eight defects into each tool’s own format, not a shared one: an OpenSpec requirement needs a #### Scenario: heading, a specdx PRD feature needs a **F1**: prefix and a user story to cover it, and a Spec Kit requirement needs an FR-001 ID. A defect written in the wrong format tests the parser, not the check. A new release can change any row, and this page’s revision log records each rerun.

Revisions

  1. Created; one password-reset spec with eight seeded defects, run that day through Spec Kit 1.2.3, OpenSpec 1.14.3, BMAD 6.12.4 and specdx 0.5.0. Kiro described from its documentation, not run.
  2. Published, on its slot; auto-drafted by the publishing run because no draft existed for the row.