Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Run development from an executable specification with traceable requirement IDs and merge-time coverage gates. Use when starting a greenfield feature, reviving a stale spec, or gating merges on requirement coverage.
.claude/skills/borghei-spec-driven-workflow/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 165% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 466% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 113% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 96% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 157% | 0% |
Development where the specification is the source of truth and the code is its implementation, rather than a document that was true at kickoff and fiction by week three. The mechanism is unglamorous: every normative statement gets a stable ID, every ID appears in a test, and the merge gate fails when the two drift apart. Without that mechanical link a spec is a memo, and memos do not survive contact with a sprint.
Before writing or auditing a spec, confirm these inputs. If any is unknown or vague, ASK — do not assume:
Stop rule: ask only the 2-3 that most change the output. If the user says "just draft it," proceed and list your assumptions at the top of the artifact.
modality keyword. Two obligations in one sentence become two requirements.
statements no engineer could implement without guessing.
spent discovering ambiguity rather than discussing design.
bashpython3 engineering/spec-driven-workflow/scripts/spec_lint.py \ --spec engineering/spec-driven-workflow/assets/sample_spec.md \ --max-findings 0 --min-precision 0.85 --format text
The shipped sample scores 0.46 on purpose — it contains the exact failures the linter is built to catch, so you can see each rule fire before pointing it at real work.
(REQ-ROTATION-02), so they are stable across unrelated edits elsewhere in the file.
DRIFT line means arequirement's text changed while its ID stayed the same — its tests now verify something the spec no longer says, which is the most dangerous state in the workflow.
bashpython3 engineering/spec-driven-workflow/scripts/spec_parse.py \ --spec engineering/spec-driven-workflow/assets/sample_spec.md \ --baseline engineering/spec-driven-workflow/assets/sample_requirements.json \ --require-acceptance --format text
Exit code is 1 when a mandatory requirement lacks acceptance criteria or when any requirement has drifted, which makes this the first half of the CI gate.
names (def test_rotation_marks_pending_revocation(): # REQ-ROTATION-01).
implementation, and annotations naming IDs the spec no longer contains.
bashpython3 engineering/spec-driven-workflow/scripts/trace_coverage.py \ --requirements engineering/spec-driven-workflow/assets/sample_requirements.json \ --index engineering/spec-driven-workflow/assets/sample_trace_index.json \ --min-coverage 1.0 --format text
Swap --index for --code <path> to scan a real tree. The index form exists so the gate can run against a trace index built by another tool, and so this workflow is runnable straight from a fresh clone.
Precision is a cost, and the right amount depends entirely on who reads the spec next.
| Consumer | Required precision | Test | |----------|-------------------|------| | Engineer on the team who wrote it | Moderate | Shared context fills gaps; acceptance criteria on mandatory requirements only | | Engineer on another team | High | Every requirement has criteria; no undefined domain terms | | A model generating the implementation | Very high | Every requirement quantified; no adjective without a number | | An auditor or regulator | Very high, plus provenance | Every requirement traced to a test result and a decision record |
Writing at "very high" for an internal one-week feature is waste. Writing at "moderate" for a generated implementation produces confidently wrong code, because ambiguity gets resolved silently rather than escalated.
Every requirement sits in one of four states. Only one is acceptable at merge.
| Status | Meaning | Action | |--------|---------|--------| | covered | Implementation and test both annotated | Merge | | untested | Code exists, no test references the ID | Block — this is the state that regresses silently | | test-only | Test exists, no implementation annotated | Usually a missing annotation, occasionally a test asserting nothing | | unimplemented | Neither exists | Block if mandatory; acceptable if explicitly deferred |
| Modality | Keyword | Coverage floor at merge | |----------|---------|------------------------| | Mandatory | must, shall | 1.0 — no exceptions; a mandatory requirement without a test is not implemented | | Recommended | should | 0.8 — deviations recorded in the PR with a reason | | Optional | may, can | No floor — tracked, not gated |
The floors matter less than their being non-negotiable once set. A coverage gate that gets waived twice stops being read.
| Rule | Fires on | Prevents | |------|----------|----------| | unquantified-adjective | fast, scalable, secure, intuitive | Requirements nobody can fail | | passive-no-actor | "notifications should be delivered" | Obligations with no owning component | | no-acceptance-criteria | Normative statement with no given/when/then | Requirements that cannot be verified | | placeholder | TBD, TODO, ??? | Specs that gate merges while still undecided | | compound-requirement | "and/or", two obligations in one sentence | Partial implementations that still pass | | undefined-antecedent | Opens with it/this/they | Requirements that break when reordered | | vague-quantifier | some, several, most | Disagreement discovered at review time |
Mistake: Writing a thorough specification at kickoff, then implementing against reality for six weeks without touching it. Why it happens: Updating the spec has no forcing function. Nothing breaks when it goes stale, so it loses every contest for attention against shipping code. Instead: Put the spec in the same repository, in the same PR, behind the same merge gate as the code. A requirement change and its implementation land together or neither lands. The gate is what converts "we should keep it updated" into a thing that actually happens.
Mistake: "The API must be fast and the interface must be intuitive." Why it happens: These feel like requirements and are easy to agree on precisely because nobody can disagree. Everyone leaves the meeting satisfied and holding different pictures. Instead: Every adjective becomes a number with a unit and a measurement method: "p95 latency under 200ms measured at the load balancer over a 5-minute window." If you cannot produce the number, the requirement is not ready and should be marked as such rather than shipped vague.
Mistake: Editing a requirement's text in place while keeping its ID, so tests that reference the ID now verify something the spec no longer says. Why it happens: Renumbering feels disruptive, and editing text feels smaller than adding a requirement. Both are true; the consequence is still a silent divergence. Instead: Fingerprint requirement text and diff against a committed baseline on every parse. A drift finding forces a decision: either the tests get updated, or the edit was actually a new requirement and needs a new ID.
Mistake: Checking that every requirement has code, but never checking that every annotated code path has a requirement. Why it happens: The forward direction answers "did we build what we promised," which is the question stakeholders ask. Nobody asks the reverse question, so nobody builds the report. Instead: Run coverage in both directions and treat orphan annotations as errors. Orphans mark dead features whose requirement was deleted, typos in IDs, and scope that entered the codebase without ever entering the spec — all three are worth knowing.
Mistake: Setting a 100% coverage gate, waiving it under deadline, and waiving it again the following week. Why it happens: The gate was set at an aspirational number rather than the number the team will actually hold, so the first real deadline breaks it. Instead: Gate only on mandatory requirements, at 1.0, and let should requirements report without blocking. A narrow gate that never gets waived changes behaviour; a broad gate that gets waived teaches everyone that red builds are advisory.
| File | Purpose | |------|---------| | scripts/spec_parse.py | Parse a markdown spec into requirements with stable IDs and content fingerprints; diff against a baseline | | scripts/spec_lint.py | Flag ambiguity — unquantified adjectives, passive requirements, missing criteria, placeholders | | scripts/trace_coverage.py | Bidirectional spec-to-code coverage with orphan-annotation detection and a merge gate exit code | | references/spec-writing-guide.md | Requirement grammar, acceptance-criteria patterns, and worked ambiguous-to-precise rewrites | | references/traceability-model.md | ID schemes, annotation conventions per language, CI wiring, and drift handling | | assets/sample_spec.md | Runnable sample spec containing both precise and deliberately ambiguous requirements | | assets/sample_requirements.json | Parsed baseline for the drift and coverage workflows | | assets/sample_trace_index.json | Prebuilt trace index exercising covered, untested, test-only, and orphan states | | assets/spec-template.md | Skeleton for a new specification with the required structure |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | fail→fail | 16,329 | 4,377 | -73% | 1 | 1 | 0% | 2,547 | 3,256 | +28% | 0 | 0 | — |
case-01 | fail→fail | 18,722 | 16,861 | -10% | 1 | 1 | 0% | 2,884 | 5,563 | +93% | 0 | 0 | — |
case-03 | pass→pass | 9,603 | 7,742 | -19% | 1 | 1 | 0% | 1,806 | 4,039 | +124% | 0 | 0 | — |
case-04 | pass→pass | 9,622 | 7,238 | -25% | 1 | 1 | 0% | 1,567 | 3,835 | +145% | 0 | 0 | — |
case-05 | pass→pass | 6,272 | 7,444 | +19% | 1 | 1 | 0% | 1,075 | 4,095 | +281% | 0 | 0 | — |
case-06 | fail→pass | 22,868 | 3,867 | -83% | 1 | 1 | 0% | 1,261 | 3,338 | +165% | 0 | 0 | — |
case-07 | fail→pass | 11,537 | 2,544 | -78% | 1 | 1 | 0% | 545 | 3,082 | +466% | 0 | 0 | — |
case-08 | pass→pass | 11,294 | 3,775 | -67% | 1 | 1 | 0% | 1,615 | 3,217 | +99% | 0 | 0 | — |
case-09 | pass→pass | 13,730 | 2,501 | -82% | 1 | 1 | 0% | 2,010 | 3,072 | +53% | 0 | 0 | — |
case-10 | fail→pass | 11,095 | 4,578 | -59% | 1 | 1 | 0% | 1,604 | 3,410 | +113% | 0 | 0 | — |
case-11 | fail→pass | 10,311 | 2,681 | -74% | 1 | 1 | 0% | 1,557 | 3,059 | +96% | 0 | 0 | — |
case-12 | fail→pass | 8,684 | 3,623 | -58% | 1 | 1 | 0% | 1,265 | 3,246 | +157% | 0 | 0 | — |
case-13 | pass→pass | 20,582 | 14,919 | -28% | 1 | 1 | 0% | 2,865 | 4,796 | +67% | 0 | 0 | — |
case-14 | pass→pass | 16,465 | 3,197 | -81% | 1 | 1 | 0% | 2,240 | 3,166 | +41% | 0 | 0 | — |
case-15 | fail→pass | 12,785 | 2,747 | -79% | 1 | 1 | 0% | 1,956 | 3,119 | +59% | 0 | 0 | — |
case-16 | pass→pass | 6,862 | 5,060 | -26% | 1 | 1 | 0% | 1,000 | 3,449 | +245% | 0 | 0 | — |
case-17 | pass→pass | 8,675 | 7,267 | -16% | 1 | 1 | 0% | 1,327 | 3,702 | +179% | 0 | 0 | — |
case-18 | pass→pass | 10,289 | 6,236 | -39% | 1 | 1 | 0% | 1,387 | 3,623 | +161% | 0 | 0 | — |
case-19 | pass→pass | 9,701 | 4,109 | -58% | 1 | 1 | 0% | 1,405 | 3,325 | +137% | 0 | 0 | — |
case-20 | pass→pass | 10,674 | 2,249 | -79% | 1 | 1 | 0% | 1,617 | 3,060 | +89% | 0 | 0 | — |
case-21 | pass→pass | 8,635 | 4,115 | -52% | 1 | 1 | 0% | 1,283 | 3,357 | +162% | 0 | 0 | — |
case-22 | fail→pass | 12,265 | 2,636 | -79% | 1 | 1 | 0% | 1,670 | 3,091 | +85% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 20 counted toward the lift figure. The other 2 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +32 percentage points is the difference between those two pass rates over the 20 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.