Skip to content
Back to skills

Acceptance Criteria Check

ASecurity

Acceptance criteria: falsifiable and EARS-shaped (ears-lint), each id named by a test, no stale ids, no undefined Gherkin steps.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 20, 2026
ai-agentspythongojavabashreactnodetestinggit

Works with

  • cli

Security analysis

A93/100
  • highPerforms destructive filesystem operations

Pro shows the line behind each finding and how to fix it

Scanned October 20, 2026

npx -y skills add adnanmokhtar/refract --skill acceptance-criteria-check --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Acceptance Criteria Check?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Acceptance Criteria Check
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/adnanmokhtar-acceptance-criteria-check/badge)](https://www.skillsdirectory.com/skills/adnanmokhtar-acceptance-criteria-check)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: acceptance-criteria-check
description: "Acceptance criteria: falsifiable and EARS-shaped (ears-lint), each id named by a test, no stale ids, no undefined Gherkin steps."
kind: skill
pack: product
allowed-tools: [Read, Grep, Glob, Bash]
---

# Skill: acceptance-criteria-check

## Premise

A criterion is verifiable when someone who was not in the room can observe whether it holds. The test is mechanical and gives every reviewer the same answer: **name the observation that would prove this criterion FAILED**. If no such observation exists, the criterion cannot be verified, and the argument about "done" is scheduled for after delivery instead of now. A verifiable criterion that no test names is the same promise one step later: it is found by the first user, not by the suite.

This skill runs two lanes over every criterion in scope:

- **The shape lane** (§4): the four tests (falsifiable, observable, bounded, singular), the seven failure classes and the numeric-bound check, by hand on any spec, and mechanically by `ears-lint.py` when the spec is in the run-ledger format.
- **The trace lane** (§1–§3): criterion ids joined against the tests that cite them, in both directions, and Gherkin scenarios against their step definitions by the runner's own dry run.

**Every criterion in scope gets a row.** No sampling: an unchecked criterion has been silently accepted. **Traceability is id-anchored only**: an id in a spec, the same id in a test. An inferred link ("this test looks like it covers that criterion") is never a trace; model-inferred trace links over-link and trail plain retrieval baselines ([`precedents/product.md`](../../../../snippets/precedents/product.md) § 31).

## The rule

A criterion is a finding when one of these holds, after the read step:

1. **It cannot fail** (§4): no observation refutes it, or its bound is an adjective, or it joins two independently testable claims.
2. **No test names it** (§1): its id appears in no test title, tag, annotation or name, or appears only in a comment. A test that names it but does not assert it is a link, not proof (§ 31).
3. **A test cites an id no spec holds** (§2): the trace is stale, and the gap it hides is invisible.
4. **Its scenario cannot run** (§3): a Gherkin step with no definition, or an ambiguous one. An unused step definition is dead glue.

## Boundaries

- **Inside a delivery run** (`ai/runs/<run_id>/`), the AC → task → test matrix is repo-baseline `trace-matrix.py`, owned by `@verifier`. This skill calls it and reports its orphans; it never re-derives them.
- **Whether a requirement is the right requirement** (ambiguity with two readings, solution in the problem, the coverage grid, non-functional bounds, metric linkage) is `@requirements-reviewer` (delivery pack, the single owner; product dispatches it). This skill supplies its criteria ledger and its falsifiability column; it does not issue the spec verdict.
- **Whether the requirement should exist** is `evidence-trace`; unsourced requirements become ranked tests in `assumption-ledger`.
- **Whether the test is strong** (assertion strength, skipped or never-collected suites, doubles) is the testing pack (`suite-execution-scan`, when installed). A criterion whose only test is skipped is reported here as untested, with the testing row named as corroboration.
- GitHub Spec Kit's `analyze` coverage table is model-judged and traces requirement → task, not requirement → test; it is read as a list of ids, never as evidence of coverage.

## Lanes

| Spec format | Criterion ids | Shape lane | Trace lane |
|---|---|---|---|
| run-ledger `ai/runs/<run_id>/requirements.md` (`### REQ-###`, `- AC-###: …`) | `REQ-###`, `AC-###` | `python3 -I .claude/scripts/ears-lint.py <requirements.md>` (exit 0 clean · 2 findings) | `python3 -I .claude/scripts/trace-matrix.py <run_id>` |
| a spec or ticket in Markdown (`ai/specs/`, `specs/`, `docs/requirements/`) with ids | `AC-12`, `REQ-7`, a project prefix (pre-flight 0b) | the four manual tests (§4) | the id join (pre-flight 1–2) |
| GitHub Spec Kit (`specs/<feature>/spec.md`, `.specify/`) | `FR-###`, `SC-###` | the four manual tests | the id join |
| Kiro (`.kiro/specs/<feature>/requirements.md`) | numbered requirements in EARS form | the four manual tests (EARS shape read by hand) | the id join, when tests cite the numbers |
| Gherkin (`*.feature`) | scenario tags (`@AC-12`) | the four manual tests per `Then` | cucumber-js / behave dry run (§3) |
| a spec with no ids | none | the four manual tests | `N/A (no criterion ids)`: say so; never infer links |

| Test carrier | How an id is cited |
|---|---|
| Jest, Vitest, Mocha, node:test | the id in the `test` / `it` / `describe` title |
| Playwright | `@AC-12` in the title, or `{ tag: '@AC-12' }`, or an annotation; `npx --no playwright test --list --grep @AC-12` lists them |
| pytest | the id in the test name or a marker (`@pytest.mark.ac("AC-12")`) |
| RSpec, JUnit 5, Kotest, Go | the id in the description, `@DisplayName` / `@Tag`, or `func TestAC12…` |
| Gherkin | a scenario tag `@AC-12` |

## Pre-flight

Run as **one** script from the repo root (bash 3.2). Every `rg` names its path; its stderr goes to `$O/rg.err`, and a non-empty file prints `BLOCKED`: the lane it feeds is `blocked (rg exit 2)`, never zero hits. Ids are normalised (`AC-007` = `AC-7`) before the join. Output goes under `${TMPDIR}`, cleared first.

```bash
X=(-g '!**/node_modules/**' -g '!**/{dist,build,.next,vendor,.venv,coverage,target}/**')
O="${TMPDIR:-/tmp}/ac-trace"; rm -rf "$O"; mkdir -p "$O"      # cleared first: a stale id list poisons the join
PFX="${PFX:-AC|REQ|FR|SC|NFR|US}"     # add the project's own prefixes from 0b, e.g. PFX='AC|REQ|PAY'
SPEC_ROOTS=(); for d in ai/specs ai/runs ai/product specs spec docs/specs docs/requirements requirements .kiro/specs .specify; do [ -d "$d" ] && SPEC_ROOTS+=("$d"); done
[ ${#SPEC_ROOTS[@]} -gt 0 ] || SPEC_ROOTS=(/dev/null)        # never an empty path list: rg would read stdin
TG=(-g '**/*.{test,spec}.{js,jsx,ts,tsx,mjs,cjs}' -g '**/{test,tests,__tests__,spec,e2e,features}/**' -g '**/test_*.py' -g '**/*_test.{py,go}'
    -g '**/*_spec.rb' -g '**/*{Test,Tests,IT}.{java,kt,cs}' -g '**/*.feature')
echo "0a spec roots: ${SPEC_ROOTS[*]}"
# 0b. id prefixes the specs actually define (an id opening a heading, a list item or a table cell)
rg -o --no-filename --pcre2 -g '*.{md,mdx,txt}' '^\s*(?:#{1,6}\s+|[-*+]\s+(?:\*\*)?|\|\s*(?:\*\*)?)\K[A-Z][A-Z0-9]{0,9}-\d{1,5}\b' "${SPEC_ROOTS[@]}" 2>>"$O/rg.err" | sed -E 's/-[0-9]+$//' | sort | uniq -c | sort -rn | head -10
# 1. criterion ids defined, with their spec line (the TR-1 side of the join)
#    a heading id inside a run ledger (`### REQ-010` in ai/runs/*/requirements.md) is a STORY, not a criterion: dropped
rg -n --pcre2 -g '*.{md,mdx,txt}' "^\s*(?:#{1,6}\s+|[-*+]\s+(?:\*\*)?|\|\s*(?:\*\*)?)\K(?:$PFX)-\d{1,5}\b" "${SPEC_ROOTS[@]}" 2>>"$O/rg.err" \
  | grep -vE '^ai/runs/[^:]+/requirements\.md:[0-9]+:[[:space:]]*#{1,6}[[:space:]]' > "$O/defined.lines"
sed -E 's/^[^:]+:[0-9]+://; s/^[^A-Z]*//; s/^([A-Z0-9]+-[0-9]+).*/\1/; s/-0*([0-9])/-\1/' "$O/defined.lines" | sort -u > "$O/defined"; echo "defined: $(wc -l < "$O/defined")"
# 2. ids cited by tests (the TR-2 side); then both joins
rg -o --no-filename --pcre2 "\b(?:$PFX)-\d{1,5}\b" "${TG[@]}" "${X[@]}" . 2>>"$O/rg.err" | sed -E 's/-0*([0-9])/-\1/' | sort -u > "$O/cited"; echo "cited: $(wc -l < "$O/cited")"
echo "TR-1 defined, never cited:"; comm -23 "$O/defined" "$O/cited"
echo "TR-2 cited, never defined:"; comm -13 "$O/defined" "$O/cited"
# 2b. where each cited id sits: a title / tag, or a comment only (a comment is not a trace)
rg -n --pcre2 "^\s*(?://|#|\*|/\*).*\b(?:$PFX)-\d{1,5}\b" "${TG[@]}" "${X[@]}" . 2>>"$O/rg.err"
# 3. Gherkin: feature files, and the runner that can dry-run them (installed only; never npx -y)
rg --files -g '*.feature' "${X[@]}" . 2>>"$O/rg.err" > "$O/features"; echo "features: $(wc -l < "$O/features")"
#    cucumber-js 12.x refuses Node 23 at start-up (measured 2026-10-19 on the launch-gaps fixture): that exit is NOT-RUN, never a §3 result
if [ -s "$O/features" ] && [ -x node_modules/.bin/cucumber-js ]; then
  DO_NOT_TRACK=1 CI=true node_modules/.bin/cucumber-js --dry-run --format usage-json:"$O/usage.json" >/dev/null 2>"$O/cucumber.err"
  rc=$?; if [ ! -s "$O/usage.json" ]; then echo "§3 NOT-RUN (cucumber-js exit $rc wrote no usage.json; node $(node -v 2>/dev/null): $(head -1 "$O/cucumber.err"))"
  else echo "cucumber-js dry run exit $rc — parse $O/usage.json; the exit code is never a PASS"; fi
elif [ -s "$O/features" ] && command -v behave >/dev/null 2>&1; then
  behave --dry-run -f steps.missing > "$O/behave-missing.txt" 2>&1; behave --dry-run -f steps.usage > "$O/behave-usage.txt" 2>&1
else [ -s "$O/features" ] && echo "§3 NOT-RUN (cucumber-js / behave not installed)"; fi
# 4. run-ledger specs: the ears-lint lane
EL=.claude/scripts/ears-lint.py; [ -f "$EL" ] || EL="$HOME/.claude/templates/repo-baseline/.claude/scripts/ears-lint.py"
for f in ai/runs/*/requirements.md; do [ -f "$f" ] || continue
  if [ -f "$EL" ]; then python3 -I "$EL" "$f"; else echo "§4 ears lane NOT-RUN (no ears-lint.py: repo-baseline not installed)"; break; fi; done
[ -s "$O/rg.err" ] && echo "BLOCKED (rg exit 2): $(head -3 "$O/rg.err")"
```

Read 4's result with its counts: `ears-lint: 0 stories, 0 criteria` on a file means the file is not in the run-ledger format and the lane is `N/A (not run-ledger format)`, never a pass (`precedents/product.md` § 34).

### Measured

On 2026-10-19 over the 23 `benchmarks/fixtures` roots: 0 rg exit 2. Only `reward-hack` defines criterion ids (5 `AC-###` in its one spec under `specs/`, prefix histogram `AC 5 · SPEC 1`); its tests cite the same 5, so TR-1 and TR-2 are 0, the correct answer. A scratch copy with one test file deleted and a test citing `AC-009` added returns exactly `AC-3` for TR-1 and `AC-9` for TR-2. No fixture has a `.feature` file. `ears-lint.py` on that spec prints `0 stories, 0 criteria` and exits 0: the spec writes `- **AC-001** WHEN …` without the run-ledger colon, so the lane is N/A there, the case the read rule above exists for. An earlier draft of this script kept the previous run's id list when two runs shared `${TMPDIR}` (every later root reported the first root's 5 ids); the output folder is now cleared first. The product eval measured the real NestJS + React monorepo read-only on the same day: 0 criterion ids in docs or tests, no `.feature` files, 220 ADR references — the trace lane prints `N/A (no criterion ids, no Gherkin)` there. On the `launch-gaps` fixture (integration pass, same day): 9 ids defined, 10 cited; TR-1 `AC-5`, TR-2 `AC-9` `AC-14`, 2b the comment-only `AC-003`; the run ledger's `### REQ-010` story heading, a false TR-1 row in the first version, is dropped by step 1's run-ledger filter; step 4 runs through the `$HOME/.claude/templates/repo-baseline` fallback in a copy with no `.claude/` and prints `AC-011 — vague word in the SHALL clause: quickly`; step 3 prints NOT-RUN (no installed runner).

## Scans for

Severity assumes the criterion is in the release's scope. One step up when the criterion is a primary outcome, the success metric's behaviour, or an authorization rule.

| § | Class | Default |
|---|---|---|
| 1 | PR-TR-1 criterion with no test naming it | medium; high on a primary outcome or an authorization rule |
| 2 | PR-TR-2 test cites an id no spec holds (stale trace) | low |
| 3 | PR-TR-3 undefined or ambiguous Gherkin step; unused step definition | medium (undefined, ambiguous) · low (unused) |
| 4 | PR-TR-4 criterion not falsifiable, not bounded, not singular, not EARS-shaped | medium; BLOCKER on a primary outcome (under `/audit-requirements --spec`) |

### 1. Untested criterion

```
BAD:   specs/refunds.md:14  - **AC-3** WHEN a refund is issued THE ledger SHALL deduct the awarded points
       tests/: no title, tag or name carries AC-3 (only `// see AC-3` in a helper)
GOOD:  test('AC-3 a refund takes back the points the purchase awarded', …) — asserting the deducted balance
```
**Read the code:** Does the test that names the id assert the criterion's observable outcome, or only exercise the path? Is the test collected and run (a `.skip`, an `xit`, a file outside the runner's glob)? Is the id in a title or tag, or in a comment only (2b)? Inside a run, what does `trace-matrix.py` say? **Finding when** no collected test names the id in a title, tag or name, or the only naming test does not assert the outcome. **PROOF:** V427 — the join prints the id under TR-1 before the fix and not after; the new test fails on the pre-fix code when the behaviour is missing.

### 2. Stale trace

```
BAD:   tests/legacy.test.js:1  test('AC-9 legacy discount', …)   — no spec defines AC-9
GOOD:  the id is restored to the spec it came from, or the test is retitled to the criterion it now asserts
```
**Read the code:** Is the prefix an external tracker's (`PROJ-123`), resolved against the tracker rather than the repo (`precedents/product.md` § 35)? Was the criterion renamed, merged or removed (git log of the spec)? **Finding when** the id is the spec's own prefix and no spec in the repo holds it. **PROOF:** V427.

### 3. Gherkin glue

```
BAD:   features/points.feature:9  Then the points balance is reduced     — undefined step
       steps/legacy.steps.ts:22   Given('a gold member', …)               — used by no scenario
GOOD:  every step resolves to one definition; every definition is used by a scenario that runs
```
**Read the code:** Parse the formatter output, never the exit code: `cucumber-js --dry-run` exits 0 with undefined steps (13.3.0 docs, 2026-10-05). Is the step used only under a tagged-out feature (`@wip`, `@manual`) (`precedents/product.md` § 32)? Is the runner installed? Without it the class is `NOT-RUN (cucumber-js / behave not installed)`, and the feature and step files are read by hand for the undefined steps only. **Finding when** the usage output lists an undefined or ambiguous step, or an unused definition outside tagged-out features. **PROOF:** V428.

### 4. Unverifiable criterion — the four tests

**Establish the prior behaviour first** for a criterion that changes an existing surface: locate the surface and record what it does today at `<file:line>` (its states, the errors it already returns, the limits it already enforces). "Loads faster than before" has no bound in prose and gets one the moment "before" is a measured value. Record `NOT APPLICABLE — new surface` where the surface does not exist yet.

**Enumerate** every criterion verbatim with its locator, including the ones embedded in prose ("it should also …" is a criterion wearing a narrative disguise). Then apply the four tests to each:

| Test | Question | Fails when |
|---|---|---|
| **Falsifiable** | what observation would prove this FAILED? | no observation could refute it |
| **Observable** | can it be checked without asking the author? | verification needs the author's intent |
| **Bounded** | are the numbers, volumes and conditions stated? | an adjective stands where a bound belongs |
| **Singular** | is this one criterion? | "and" joins independently testable claims |

Classify each failure:

| Class | Shape | Replacement |
|---|---|---|
| unbounded adjective | fast, responsive, intuitive, easy, robust | a number, a percentile, a measurement point, a load condition |
| deferred standard | properly, correctly, appropriately, as expected | name the standard, or state the behaviour |
| capability not behaviour | "should be able to" | the observable outcome |
| blanket error clause | "handles errors gracefully" | one criterion per named failure, each with its outcome |
| compound | two claims joined by "and" | split |
| intent-dependent | "the user understands that …" | an observable proxy, or move it out of the criteria |
| circular | "works as expected" | state the expectation |

**Check every number that is present.** A latency or throughput criterion needs a unit, a percentile where it is a distribution, a measurement point (server, client, end to end; cold or warm) and a load condition. A number missing the percentile and the measurement point is unbounded, and it is the commonest way an unverifiable criterion passes review.

```
BAD:   - AC-2: the export completes quickly for large accounts
GOOD:  - AC-2: WHEN an account with 50,000 orders requests a CSV export THE service SHALL deliver the file within
         120 s (p95, measured server-side from request to the last byte)
```
**Read the code:** Is a flagged word bounded by the text around it (`precedents/product.md` § 33)? Is the run-ledger file really in that format, or did `ears-lint.py` read 0 criteria (§ 34)? Were the criteria written after the code, restating what was built (check the authoring order)? **Finding when** a criterion fails a test with its class named and its refuting observation written as MISSING; `ears-lint.py` exit 2 rows are findings as printed (mechanical facts: cite them, do not re-judge them). **PROOF:** V429.

## The 3,000-hit path

A backlog can hold thousands of criteria. Triage in this order and record each step in the coverage block:

1. **Scope first.** The release's specs (the ones a run, a ticket or `--spec` names), then the rest.
2. **Run the mechanical lanes on everything** (the id join, `ears-lint.py`, the Gherkin dry run): they cost seconds and need no reading.
3. **Group TR-1 rows by spec**: one decision per spec when the whole spec has no tests yet (one finding, N ids).
4. **Read the four tests by hand** for the release's criteria in full, then the top 30 criteria of the rest by priority (primary outcomes, authorization rules, money).
5. **Ledger the rest** as `Live, unreviewed (k specs / n criteria)`, never `checked`.

## Output

```
acceptance-criteria-check — specs: specs/refunds.md (12 criteria), ai/runs/20261018-refunds/requirements.md (9)
Lanes: ears-lint (run-ledger, 9 criteria) · id join (prefixes AC, REQ) · cucumber-js 13.3.0 dry run (2 features)

Criteria ledger:
| #    | Criterion (verbatim)                               | Falsifiable | Observable | Bounded | Singular | Refuting observation              | Test           | Class |
| AC-2 | "the export completes quickly for large accounts"  | no          | yes        | no      | yes      | MISSING — no bound on size or time | —              | unbounded adjective |
| AC-3 | "WHEN a refund is issued THE ledger SHALL deduct…" | yes         | yes        | yes     | yes      | balance unchanged after a refund  | none (TR-1)    | — |

Findings: 3

1. specs/refunds.md:14  AC-3                                       [report-with-fix]   high
   §1 untested criterion — no collected test names AC-3; tests/helpers/points.ts:40 cites it in a comment only.
   Fix: a test titled `AC-3 …` asserting the deducted balance. Proof: V427 (the join lists AC-3 under TR-1).

2. features/points.feature:9                                        [report-with-fix]   medium
   §3 undefined step "Then the points balance is reduced" (cucumber-js usage-json; dry-run exit 0).
   Fix: a step definition in steps/refund.steps.ts. Proof: V428.

3. specs/refunds.md:9  AC-2                                        [report-flagged]    medium
   §4 unbounded adjective "quickly"; "large accounts" has no size. The prior behaviour is unread (the export
   endpoint was not located). Settle: locate the export route and record today's p95; propose the bound.

Totals: 21 criteria · pass 17 · fail 4 · embedded in prose 1 · numeric criteria missing a bound part 1 (AC-2)
Verification: 5 candidates → 3 findings · 0 unconfirmed · 1 hardening · 0 pre-existing · 1 refuted

Class coverage:
  §1 untested criterion   checked (21 ids defined, 19 cited → 1 finding; 1 comment-only)
  §2 stale trace          checked (1 cited-never-defined → 0 findings: PROJ-88 is a tracker id)
  §3 Gherkin glue         checked (2 features, cucumber-js usage-json → 1 finding)
  §4 shape                checked (ears-lint 9 → 0; manual 12 → 1 finding)
Verdict: PARTIALLY VERIFIABLE (4 failing)
```

`[report-with-fix]`: the lane ran (the join, the lint, the dry run) or both ends were read, and the fix is a test, a step or a rewritten criterion. `[report-flagged]`: one link is unproven (the prior behaviour unread, a runner not installed, an id prefix not resolved); the row names what would settle it. The coverage block lists **all four** classes on every run: `checked (k → m findings)`, `N/A (<reason>)` (no criterion ids, no Gherkin, not run-ledger format), `NOT-RUN (<runner> not installed)`, `blocked (rg exit 2)`, or `Live, unreviewed (k / n)`. Verdict: `VERIFIABLE | PARTIALLY VERIFIABLE (<n> failing) | NOT VERIFIABLE`. The criteria ledger is pasted verbatim into `/audit-requirements --spec`, where it is the only source for the falsifiability column; replacement proposals go in a separate section marked as proposals.

## Precedents

The verifier reads [`precedents/product.md`](../../../../snippets/precedents/product.md) § 31–35 and [`refute-before-report.md`](../../../../snippets/refute-before-report.md) § 4 and § 10. The ones this skill meets most:

- **A bounded adjective is bounded** (§ 33). "Fast (p95 under 300 ms server-side at 500 concurrent requests)" passes. Read the surrounding text before flagging on a keyword.
- **A behaviour needs no number.** "The list shows the ten most recent" needs no percentile.
- **A reachable standard bounds a criterion.** A cross-reference to a documented standard is bounded if the standard is reachable; record `DEFERRED — standard not available` only if it is not.
- **An atomic "and".** "Saves and returns to the list" may be one user-visible outcome; split only when the halves are independently testable and could diverge.
- **An id in a comment or a title is a link** (§ 31); tagged-out features make steps look unused (§ 32); a tracker prefix resolves against the tracker (§ 35).
- **Criteria written after the code** pass every test here and are worthless as a contract: check the authoring order and say so.

## When to run

- Before estimation, so ambiguity is priced out rather than in; before build, as the last gate on the spec.
- Under `/audit-requirements --spec` (the criteria ledger) and `/audit-requirements --repo` (the trace lane across every spec and the scorecard's PRR-TR-1 row).
- After a dispute about whether something was done, to name the criterion that caused it; on an inherited backlog, to size the requirement debt.

## Halt conditions

- Every finding goes through [`refute-before-report.md`](../../../../snippets/refute-before-report.md) (§ 10, correctness lens) with [`precedents/product.md`](../../../../snippets/precedents/product.md) before it is reported; under `/audit-requirements` that is its verification phase, standalone it is a separate second pass and the rows are tagged `self-verified`.
- **No acceptance criteria exist:** the spec has prose and nothing testable. That is the finding; report it and stop. Do not invent criteria: invented criteria become the contract.
- **The actor is unstated** across the criteria set: "the system shall" hides who, and the answer changes what is tested.
- **The surface cannot be located** for a criterion that modifies an existing surface. Where the repository is readable, go and read it (§4, prior behaviour); halt only when neither the criteria nor the code identifies what is being changed.
- **An external standard is unreachable:** record `DEFERRED — standard not available`, never a pass.
- Do not print a trace link the join did not produce, read a dry run's exit code as PASS, or count `ears-lint`'s `0 criteria` as clean. Do not rewrite a criterion in place: proposals go in their own section.

## Related

- `/audit-requirements` dispatches this skill: `--spec` takes the criteria ledger, `--repo` takes the trace lane and the PRR-TR-1 evidence row for `launch-readiness`. `/product-fix` closes TR rows test-first (V427–V429).
- `@requirements-reviewer` (delivery pack, the single owner) issues the spec verdict this ledger feeds; `evidence-trace` and `assumption-ledger` handle the unsourced requirements.
- `ears-lint.py` and `trace-matrix.py` (repo-baseline, `.claude/scripts/`) are the mechanical lanes for run-ledger specs; `@verifier` owns the run matrix.
- The testing pack's `suite-execution-scan` (when installed) owns whether the naming test is collected and run.
- `ai/patterns/acceptance-criteria.md` holds the project's criteria conventions.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.