“Porque mis armas son los pinceles y mis municiones son las pinturas” — Peter de los Polvorines
espectacular
In brief: ah is a CLI that enforces a contract between your specs and your tests. You write behavioral specs in Markdown, attach a TOML contract to each scenario declaring which tests cover it, then run ah check in CI to catch drift.
espectacular is a behavioral verification tool for Rust CLI projects. It lets you write machine-readable specs that describe what your tool does, then continuously check that behavior with ah check. Each spec scenario is paired with a sidecar TOML contract that lists the tests verifying it — when a test is missing, unconfigured, or failing, ah check exits non-zero and blocks the merge.
- Installation & Quick Start — get
ahon your PATH and run your first check in minutes - Command Reference — all
ahsubcommands with flags, exit codes, and example output - Concepts — understand specs, scenarios, contracts, archetypes, and the gate model
Installation & Quick Start
In brief: install ah, run ah init in your repo, then ah check to validate. The worked example below walks through writing a spec scenario, creating its contract, and seeing a passing check. Both ah and espectacular are installed — they are the same binary under two names.
Install
From source (requires Rust toolchain):
cargo install --path .
Or clone and build:
git clone https://github.com/charly-vibes/espectacular
cd espectacular
cargo build --release
# Binary is at target/release/ah
Verify:
ah --version
# ah 0.1.0
espectacular --version
# espectacular 0.1.0
Prerequisites
espectacular requires OpenSpec to manage your spec files. OpenSpec is the directory structure and tooling that stores specs under openspec/specs/ and staged changes under openspec/changes/. Run openspec init once in your repo to set it up before running ah init.
Set up espectacular
Run ah init once in the root of your repo:
ah init
This creates (or refreshes):
.espectacular/config.toml— runner and capability config.espectacular/AGENTS.md— guidance block for AI agents- Hook integration for
lefthookorprekif detected
If ah init reports concerns, use ah doctor to diagnose.
Run your first check
ah check
Output is always a JSON envelope:
{
"scope": { "deployed": true, "changes": [] },
"summary": { "structural": 0, "execution": 0, "passed": 2, "counts_by_kind": {} },
"findings": []
}
Empty findings and structural: 0, execution: 0 means everything is green. If you have specs without contracts yet, you’ll see no-toml findings instead — see When check finds something below.
Worked example
This walks through the full loop: open a change → write a scenario → create a contract → run the check.
Step 1 — Open a change
In OpenSpec, work-in-progress lives under a “change.” Create one:
openspec new my-feature
This creates openspec/changes/my-feature/specs/ where your staged spec lives.
Step 2 — Write a scenario
Edit (or create) the spec for the component you’re working on, e.g. openspec/changes/my-feature/specs/parser/spec.md:
### Requirement: Input validation
#### Scenario: Empty input is rejected
- **GIVEN** an empty string is passed to the parser
- **WHEN** the parser runs
- **THEN** it exits non-zero with a descriptive error message
Step 3 — Create the contract
Generate the contract stub:
ah scenario new my-feature parser \
--requirement "empty-input-is-rejected" \
"Empty input is rejected"
This creates .espectacular/changes/my-feature/parser/empty-input-is-rejected.toml. Open it and add your test:
id = "empty-input-is-rejected"
description = "Empty input is rejected before parsing."
archetype = "PF"
status = "active"
superseded_by = ""
authored_with = "0.1.0"
[[tests.pytest]]
flags = "tests/test_parser.py::test_empty_input_rejected"
timeout_seconds = 60
For archetype choices (PF, SA, BP, CE, NR) see Concepts — Archetypes or run ah type.
Step 4 — Check with the change in scope
ah check --changes my-feature
A passing run exits 0 with passed: 1 in the summary.
Step 5 — Archive when merged
Once the change merges, promote the contract to deployed:
ah archive my-feature
After this, ah check (without --changes) will include the scenario in its scope.
When check finds something
A failing check exits 1 and includes findings in the JSON. For example, if a scenario has no contract yet:
{
"scope": { "deployed": true, "changes": [] },
"summary": { "structural": 1, "execution": 0, "passed": 0, "counts_by_kind": { "no-toml": 1 } },
"findings": [
{
"kind": "no-toml",
"category": "structural",
"spec": "parser",
"spec_path": "openspec/specs/parser/spec.md",
"scenario": { "id": "empty-input-is-rejected", "title": "Empty input is rejected" },
"suggested_action": "run_ah_scenario_new",
"playbook_command": "ah explain run_ah_scenario_new"
}
]
}
Each finding includes a suggested_action and a playbook_command you can run directly:
ah explain run_ah_scenario_new
Use ah explain --list to see all explainable topics.
What’s next
- Command Reference — all
ahsubcommands with flags and examples - Concepts — understand specs, contracts, archetypes, and the gate model
Command Reference
In brief: ah is the espectacular CLI — espectacular is an alias for the same binary. The primary commands are ah check (run in CI to enforce specs) and ah init (run once per repo to set up). Use ah explain <topic> to get guidance on any finding or error.
ah init
Create or refresh .espectacular/ files and hook integration.
ah init
Idempotent: safe to re-run after updating specs or changing hook frameworks. Stubs contract files for any scenarios that have no existing contract. Installs ah check into lefthook or prek if detected.
Exit codes: 0 on success, non-zero if the OpenSpec directory is missing.
ah check
Validate deployed specs and run declared tests. Prints a stable JSON envelope to stdout.
ah check [--changes <id>]
Flags:
| Flag | Description |
|---|---|
--changes <id> | Include one or more staged change overlays (repeat for multiple) |
Exit codes: 0 when findings contains no structural or execution findings; 1 otherwise. Quality findings (quality-*) never cause a non-zero exit.
Note: All JSON examples below show the data field of the genesis envelope — actual output wraps these fields in {ok, envelope_version, cli_version, envelope_kind, data, warnings, hints, meta}.
Example — clean run:
ah check
{
"ok": true,
"envelope_version": "0.1",
"cli_version": "0.4.0",
"envelope_kind": "ok",
"data": {
"scope": { "deployed": true, "changes": [] },
"summary": { "structural": 0, "execution": 0, "passed": 3, "counts_by_kind": {} },
"findings": []
},
"warnings": [],
"hints": [],
"meta": { "duration_ms": 0, "tx": null, "request_id": null, "author": null }
}
Example — with a staged change:
ah check --changes add-parser-validation
{
"scope": { "deployed": true, "changes": ["add-parser-validation"] },
"summary": { "structural": 0, "execution": 0, "passed": 4, "counts_by_kind": {} },
"findings": []
}
Example — with a finding:
{
"scope": { "deployed": true, "changes": [] },
"summary": { "structural": 1, "execution": 0, "passed": 0, "counts_by_kind": { "no-toml": 1 } },
"findings": [
{
"kind": "no-toml",
"category": "structural",
"spec": "parser",
"spec_path": "openspec/specs/parser/spec.md",
"scenario": { "id": "empty-input-is-rejected", "title": "Empty input is rejected" },
"suggested_action": "run_ah_scenario_new",
"playbook_command": "ah explain run_ah_scenario_new"
}
]
}
Run the playbook_command from any finding to get step-by-step guidance:
ah explain run_ah_scenario_new
Finding kinds:
| Kind | Category | Meaning |
|---|---|---|
no-toml | structural | scenario has no matching contract file |
orphan-toml | structural | contract exists without a matching scenario |
slug-collision | structural | two scenarios in one spec have the same id |
id-mismatch | structural | scenario slug, filename, and TOML id disagree |
no-tests-declared | structural | contract has no runnable test entries |
missing-runner | structural | a non-shell test type has no configured runner |
malformed-contract | structural | TOML cannot be parsed or validated |
missing-replacement | structural | superseded contract points to an absent replacement |
overlay-conflict | structural | selected changes define conflicting staged scenarios |
test-failing | execution | a declared test timed out or exited non-zero |
quality-mutation | quality | mutation score meets threshold (informational) |
quality-property | quality | property-based tests passing (informational) |
quality-snapshot | quality | snapshot tests passing (informational) |
ah doctor
Detect configured frameworks and diagnose config, path, hook, and archetype issues.
ah doctor [--enable <capability>]
Flags:
| Flag | Description |
|---|---|
--enable <capability> | Write the config block for a detected-but-unconfigured capability |
Capabilities for --enable: pytest, cargo, vitest, mutation, property, snapshot
Example output:
framework: pytest (configured)
recommendation: vitest detected via manifest — run: ah doctor --enable vitest
Example — enable vitest:
ah doctor --enable vitest
Writes the [runners.vitest] entry to .espectacular/config.toml. If already configured, exits 0 with an “already enabled” message.
Exit codes: 0 when no problems are found (recommendations do not affect exit code); 1 when structural problems exist. --enable exits 0 on success, non-zero for unknown capabilities.
ah doctor --json
Emit diagnostic output as JSON, with each recommendation appearing as a structured finding.
ah doctor --json
{
"findings": [
{
"kind": "recommendation",
"suggested_action": "enable_capability",
"playbook_command": "ah explain enable_capability",
"apply_command": "ah doctor --enable cargo",
"detail": "cargo detected via manifest",
"capability": "cargo"
}
]
}
Each recommendation finding carries playbook_command and apply_command for agent-consumable remediation.
Exit codes: same as ah doctor.
ah report
Display a conformance coverage matrix across all deployed specs and archetypes.
ah report [--json]
Flags:
| Flag | Description |
|---|---|
--json | Emit the matrix as JSON |
Text output (default):
spec archetype covered missing failing total
compiler 0 0 0 3
adapters 0 0 0 1
parser 0 1 0 1
covered: 3 | missing: 1 | failing: 0 | total: 5
JSON output (--json):
{
"matrix": [
{
"spec": "compiler",
"archetype": "",
"covered": 3,
"missing": 0,
"failing": 0,
"total": 3
}
],
"summary": {
"total_scenarios": 5,
"total_contracts": 4,
"covered": 3,
"missing": 1,
"failing": 0
}
}
Exit codes: 0 when all scenarios are covered by contracts; 1 when any scenarios are missing or failing.
ah explain
Print guidance for a finding kind or suggested action.
ah explain [<topic>] [--list] [--json]
Arguments:
| Argument | Description |
|---|---|
<topic> | Finding kind or action slug to explain |
Flags:
| Flag | Description |
|---|---|
--list | List all available topics |
--json | Emit the topic list as JSON (use with --list) |
Example — explain a finding:
ah explain no-toml
## no-toml — Missing contract file
A scenario declared in a spec file has no corresponding contract .toml file
in .espectacular/<component>/.
How to fix: run `ah scenario new` with the spec, scenario id, and heading
to generate the contract stub, then populate the test entries.
ah scenario new <change> <spec> --requirement <scenario-id> <heading>
Example — list all topics:
ah explain --list
Exit codes: 0 on success; 1 for unknown topics (includes “did you mean” suggestions).
ah signals
Read dont rejection events and emit drift signals as JSON.
ah signals
dont is a companion tool that tracks epistemic claims made by AI agents. When an agent makes a claim that is later rejected, dont records it as an event. ah signals reads those events from .dont/events/*.json and re-emits them as structured DriftSignal JSON that CI or the wai project-context tool can consume to surface spec-behavior gaps.
Exit codes: always 0; returns an empty JSON array if no events are found.
ah type
List built-in archetypes, or print full documentation for one.
ah type [<code>]
Example — list all:
ah type
PF — Pure Functional: Deterministic behavior where outputs are a function of explicit inputs.
SA — Stateful API: Behavior involving state transitions, persisted data, or ordered operations.
BP — Boundary Protocol: Behavior at an external boundary or protocol seam.
CE — Contract/Event: Behavior expressed as emitted events, messages, claims, or cross-tool signals.
NR — Non-Regression: Behavior asserting existing guarantees remain true while nearby changes land.
Example — full docs for one archetype:
ah type PF
## PF — Pure Functional
Deterministic behavior where outputs are a function of explicit inputs.
Use for:
- parsers
- formatters
- validators
- pure transformations
- deterministic calculations
Typical test shapes:
- unit examples for representative inputs
- property-based tests for invariants
- boundary input examples
Exit codes: 0 on success; 1 for unknown archetype codes (includes “did you mean” suggestions).
ah scenario new
Append a new scenario to a spec and stage its TOML contract.
ah scenario new <change> <spec> --requirement "<scenario-id>" "<heading>"
Arguments:
| Argument | Description |
|---|---|
<change> | Change id (the change directory must exist under openspec/changes/) |
<spec> | Spec name (e.g. parser) |
--requirement | Requirement grouping name — must match a ### Requirement: heading already in the spec; the new #### Scenario: is appended under it |
<heading> | Human-readable scenario heading to append |
Appends the scenario under the named requirement block and creates the contract stub at .espectacular/changes/<change>/<spec>/<scenario-id>.toml.
Exit codes: 0 on success; 1 if the change or spec is missing, or the requirement block is absent.
ah scenario supersede
Stage a supersession update for an existing contract.
ah scenario supersede <spec> <old-id> --with=<new-id> --in-change=<change>
Marks the old contract status = "superseded" and sets superseded_by to the new scenario id. The replacement scenario must already exist in the named change overlay.
Exit codes: 0 on success; 1 if the deployed contract or replacement is missing.
ah archive
Move staged change contracts into deployed .espectacular/ locations.
ah archive <change>
Run after a change merges. Moves TOML files from .espectacular/changes/<change>/ into .espectacular/<spec>/, failing if any collision would overwrite an active contract without a supersession in place.
Exit codes: 0 on success; 1 on collision or missing staged change.
ah upgrade
Report tool-version drift and update .espectacular/config.toml.
ah upgrade
Compares tool_version in .espectacular/config.toml with the running binary version. Updates the config if they differ, then exits non-zero so CI can detect compatibility changes.
Exit codes: 0 when versions match; 1 when drift is detected (even after updating the config).
Implementation Status
This page maps the deployed behavioral specs to the commands and capabilities that implement them. Each row is a scenario in a spec; if ah check passes, that behavior is verified in CI.
Deployed specs
gate — Core verification engine (13 scenarios)
Covers what ah check does: scenario discovery, contract correspondence, test execution, and JSON output.
| Scenario | What it verifies |
|---|---|
| Scenario Discovery | ah check finds all scenarios from spec headings |
| Sidecar Contract Correspondence | every scenario has a contract; every contract has a scenario |
| Contract Schema | TOML contracts validate against the schema |
| Test Runner Execution | declared tests are executed and results captured |
| JSON Findings | output is a stable JSON envelope with scope/summary/findings |
| Change Overlay Scope | --changes adds staged scenarios to scope |
| Non-Regression Archetype | NR contracts are checked without special treatment |
| Deterministic Scope Boundary | scope is stable across repeated runs |
| JSON finding schema includes agent-action fields | findings carry suggested_action and playbook_command |
| Quality measurement capabilities | quality-* findings are emitted and informational |
| Quality contract schema | quality fields validate in the contract schema |
| Conformance coverage matrix | all finding kinds are covered by at least one contract |
| apply_command is conditionally present | apply_command appears only when applicable |
cli — Command surface (15 scenarios)
Covers the full ah command interface: init, check, doctor, report, explain, type, scenario, archive, upgrade.
| Scenario | What it verifies |
|---|---|
| CLI Command Name | binary is named ah |
| Project Initialization | ah init creates .espectacular/ and hook integration |
| Correspondence Check Command | ah check validates specs and runs tests |
| Health Check Command | ah doctor diagnoses config, paths, hooks, archetypes |
| Archetype Documentation Commands | ah type lists and explains archetypes |
| Scenario Lifecycle Commands | ah scenario new and ah scenario supersede |
| Archive Companion Command | ah archive promotes staged contracts |
| Upgrade Command | ah upgrade detects and reports tool-version drift |
| Doctor enable flag | ah doctor --enable <capability> writes config blocks |
| Explain subcommand | ah explain prints guidance for finding kinds and actions |
| Coverage report command | ah report displays a conformance coverage matrix |
| Recommendation findings | ah doctor emits recommendations for detected-but-unconfigured adapters |
| Recommendation findings as JSON | ah doctor --json emits structured recommendation findings |
| Report JSON output | ah report --json emits a machine-readable conformance matrix |
| Report exit codes | ah report exits 0 when coverage is complete, 1 when gaps exist |
adapters — Language adapter dispatch (6 scenarios)
Covers how ah check maps test types to runners and normalizes output.
| Scenario | What it verifies |
|---|---|
| Adapter detection precedence | config > manifest > binary on PATH |
| Python pytest adapter | pytest detection, invocation, failure normalization |
| Rust cargo test adapter | cargo detection, invocation, failure normalization |
| TypeScript vitest adapter | vitest detection, invocation, failure normalization |
| No-adapter-configured path | missing-runner finding when type has no runner |
| Custom runner plugin protocol | custom runners emit JSON envelopes parsed by ah check |
explain — Playbook system (7 scenarios)
Covers the ah explain topic system and its compile-time completeness guarantee.
| Scenario | What it verifies |
|---|---|
| Playbook is compile-enforced | every finding kind has an ah explain topic at compile time |
| Topic coverage | all finding kinds and suggested actions are covered |
| Structured JSON output | --json emits a machine-readable topic list |
| Topic listing | --list enumerates all topics |
| Unknown topic handling | unknown topics exit 1 with “did you mean” suggestions |
| Quality finding kind topics | quality-* finding kinds have topics |
| Adapter topics ship with adapters | adapter-specific topics exist for each adapter |
In progress
add-spec-quality-checks — ah lint command (0/21 tasks)
Adds spec quality linting: ah lint checks spec files for vague qualifiers, imperative steps, conjunctive bloat, missing negative scenarios, missing non-goals, and unresolved ambiguities.
| Scenario | Status |
|---|---|
| Spec Lint Command | planned |
| Vague Qualifier Detection | planned |
| Imperative Step Detection | planned |
| Conjunctive Step Bloat Detection | planned |
| Missing Negative Scenario Detection | planned |
| Missing Non-Goals Detection | planned |
| Unresolved Ambiguity Detection | planned |
| Lint Finding Schema | planned |
How to read this page
- Deployed means the scenario has a passing contract in
ah checkonmain. - Planned means the scenario is staged in an OpenSpec change but not yet implemented.
- Run
ah checklocally to see current pass/fail state. - Spec source lives in
openspec/specs/.
Concepts
In brief: A spec declares what your tool should do. A contract says which tests verify one scenario in that spec. ah check is the gate — it exits non-zero when a contract is missing or a test fails. See the worked example in Installation to see these pieces together.
The mental model
espectacular enforces a contract between what you said your tool does (specs) and whether it actually does it (tests). Each behavioral claim lives in a spec file; each claim has a sidecar TOML contract that says how to verify it. ah check validates that every claim has a contract and that every contract’s tests pass.
Specs
A spec is a Markdown file under openspec/specs/<name>/spec.md. It describes the intended behavior of one component using ### Requirement: groupings and #### Scenario: headings nested under them.
### Requirement: Input validation
#### Scenario: Empty input is rejected
- **GIVEN** an empty string is passed to the parser
- **WHEN** the parser runs
- **THEN** it exits non-zero with a descriptive error message
Specs are checked into version control. They are append-only: you never rewrite a deployed scenario’s intent — instead you add new scenarios or supersede old ones. The git log becomes a traceable record of when each behavior was declared, extended, or superseded.
Scenarios
A scenario is one #### Scenario: heading nested under a ### Requirement: grouping in a spec file. It has:
- A slug — derived from the scenario heading (by convention, lowercased with hyphens, e.g.
empty-input-is-rejected). This slug must match the contract filename and theidfield inside it. - A body in Given/When/Then form describing the behavior
ah check discovers scenarios by parsing #### Scenario: headings from spec files. Each scenario must have a corresponding contract file, or ah check emits a no-toml finding. Run ah explain no-toml for the fix.
Contracts
A contract is a TOML file at .espectacular/<spec>/<scenario-id>.toml. It is the machine-readable pairing between a scenario and the tests that verify it.
id = "empty-input-is-rejected"
description = "Empty input is rejected before parsing."
archetype = "PF"
status = "active"
superseded_by = ""
authored_with = "0.1.0"
[[tests.pytest]]
flags = "tests/test_parser.py::test_empty_input_rejected"
timeout_seconds = 60
Required fields: id, description, archetype, status, superseded_by, authored_with, tests.
The filename, the id field, and the scenario slug must all agree — ah check emits id-mismatch if they disagree.
Test entry types
| Entry | How it runs |
|---|---|
[[tests.shell]] | Runs command directly via /bin/sh -c |
[[tests.pytest]] | Prepends the configured pytest runner to flags |
[[tests.cargo]] | Prepends the configured cargo runner to flags |
[[tests.vitest]] | Prepends the configured vitest runner to flags |
[[tests.custom]] | Invokes a custom runner and parses its JSON envelope |
[[tests.<type>]] | Any other type, looked up in [runners.<type>] in config |
Staged contracts
During a change in progress, contracts live under .espectacular/changes/<change>/<spec>/. After the change merges, ah archive <change> moves them into .espectacular/<spec>/. See Installation — Step 5.
Archetypes
Every contract declares an archetype, a short code that classifies the kind of behavior being verified. Archetypes guide test design: a PF scenario should have deterministic unit tests; an SA scenario needs to cover state transitions.
| Code | Name | Description |
|---|---|---|
PF | Pure Functional | Deterministic behavior; outputs are a function of explicit inputs |
SA | Stateful API | State transitions, persisted data, or ordered operations |
BP | Boundary Protocol | Behavior at an external boundary or protocol seam |
CE | Contract/Event | Emitted events, messages, claims, or cross-tool signals |
NR | Non-Regression | Existing guarantees remain true while nearby changes land |
Run ah type <code> for full documentation on any archetype, or ah type to list all.
The gate
ah check is the enforcement gate. It:
- Discovers all scenarios from deployed specs (and staged change overlays if
--changesis passed) - Checks structural correspondence: every scenario has a contract, every contract has a scenario, no collisions, no orphans
- Runs every declared test
- Emits a JSON envelope and exits 0 if clean, 1 if any structural or execution finding exists
Local pre-commit (ah init installs this): catches structural findings before git push instead of in CI.
CI (ah check in a workflow step): the enforcement gate, source of truth.
Quality findings (quality-mutation, quality-property, quality-snapshot) are informational — they appear in the output but never cause a non-zero exit. See Command Reference — ah check for the full finding kind table.
Adapters
An adapter maps a test type name to the right runner invocation and normalizes failure output into a common shape. espectacular ships built-in adapters for pytest, cargo, and vitest.
Detection signals (highest-priority source wins):
| Adapter | Explicit config | Manifest detection | Binary detection |
|---|---|---|---|
pytest | runners.pytest in config | pyproject.toml [tool.pytest], pytest.ini | pytest on PATH or .py with import pytest |
cargo | runners.cargo in config | Cargo.toml present | — |
vitest | runners.vitest in config | package.json devDependency | — |
ah doctor shows which frameworks are detected and their source. Use ah doctor --enable <framework> to write the config entry for a detected-but-unconfigured adapter.
Custom runners
A [[tests.custom]] entry invokes a configured runner and expects a JSON envelope on stdout:
{ "exit_code": 0, "passed": true, "findings": [] }
Non-zero exit_code or passed: false maps to a test-failing finding. The findings array lets custom runners emit structured findings directly into ah check output.
Quality signals
Quality signals are optional capabilities that surface test health metadata as findings:
| Signal | What it tracks |
|---|---|
quality-mutation | Mutation testing kill rate vs. threshold |
quality-property | Property-based testing is active and passing |
quality-snapshot | Snapshot testing is active and passing |
Enable them in .espectacular/config.toml or via ah doctor --enable mutation (etc.). Quality findings are informational and never cause ah check to exit non-zero.
File layout
openspec/
└── specs/<spec>/spec.md # deployed spec source
.espectacular/
├── config.toml # runner and capability config
├── AGENTS.md # AI agent guidance
├── <spec>/
│ └── <scenario-id>.toml # deployed contracts
└── changes/
└── <change>/
└── <spec>/
└── <scenario-id>.toml # staged change contracts
schemas/
├── check-output.schema.json # ah check JSON envelope
├── config.schema.json # .espectacular/config.toml
├── scenario-contract.schema.json # contract TOML files
└── custom-runner.schema.json # custom runner JSON envelope
Agent Workflow: ah check as Specification Gate
This document describes how AI agents discover and use ah check — the
espectacular verification gate — in the standard development cycle. It is
extracted from observed patterns across the charly-vibes project ecosystem.
How Agents Discover ah check
Agents learn about ah check through the ah:managed block in a
project’s AGENTS.md (or CLAUDE.md). This block is deployed by ah init
and refreshed by ah init.
<!-- ah:managed:start -->
## espectacular
Run `ah check` to verify spec-test correspondence before committing.
- `ah check` — validate all deployed specs
- `ah check --changes <name>` — validate with a change overlay
- `ah init` — set up or refresh espectacular project files
- `ah doctor` — diagnose setup issues
- `ah explain <topic>` — playbook guidance for finding kinds
- `ah doctor --enable <adapter>` — write adapter config into config.toml
- `ah signals` — emit dont drift signals
<!-- ah:managed:end -->
Key design decisions:
- The block is compact (fits in ~15 lines of agent context) — agents scan it
early in a session and recognize
ah checkas a commit gate. - It lists only the most common commands —
ah explainprovides the full playbook for less frequent tasks. - The
ah:manageddelimiter signals to agents that this section is auto-generated and authoritative.
Typical Usage Patterns
1. Pre-commit gate (most common)
The standard loop when working on OpenSpec projects:
edit → ah check → fix → ah check → commit
Concrete example:
This pattern was observed in testaruda pi sessions, where
ah checkwas the primary feedback loop during contract wiring and spec changes. Source sessions:2026-07-08T15-35-34-510Z,2026-07-16T01-38-10-034Z.
# 1. Make changes to spec or code
# 2. Run the gate
ah check
# 3. If findings appear, fix them
# 4. Re-run to verify
ah check
# 5. Commit when green
git add -p
git commit -m "fix: ..."
When to run ah check:
| Situation | Whether to run |
|---|---|
| Modified spec files | Always |
| Modified contract TOML files | Always |
| Modified production code tracked by contracts | Always |
| Edited tests only | Optional (contracts pass/fail) |
| Edited docs only | Usually not needed |
| Changed a change overlay | ah check --changes <name> |
2. Change-overlay validation
When a staged OpenSpec change is in progress:
ah check --changes add-parser-validation
This validates the base spec plus the overlay’s additional scenarios, without requiring the overlay to be archived first.
Multiple overlays can be validated together by repeating the flag:
ah check --changes add-parser-validation --changes fix-dangling-contracts
Useful when interdependent changes are being developed simultaneously.
3. Fresh project setup
ah init # Scaffold .espectacular/, stub contracts
ah doctor # Verify setup is complete
ah doctor --enable cargo # Configure runner
ah check # Run initial validation
4. Post-ah init verification
After bootstrapping a new project, ah init stubs contracts for every
scenario missing one. The pattern is:
ah init # Stub missing contracts
ah check # Verify — expect structural findings for no-tests-declared
# Fill in test commands for each stub contract, then:
ah check # Green — all contracts wired
git add -A
git commit -m "chore: init espectacular and stub contracts"
How Findings Are Interpreted
ah check emits a JSON envelope with a findings array. Each finding has a
kind, category, and suggested_action that agents can act on.
Finding categories
| Category | Meaning | Exit code impact |
|---|---|---|
structural | Spec/contract mismatch (missing contract, orphan, duplicate ID) | Non-zero |
execution | Contract test failed or timed out | Non-zero |
quality-* | Mutation score below threshold, property coverage gap | Zero (advisory) |
Common findings and agent responses
| Finding kind | What it means | Agent action |
|---|---|---|
missing-contract | A scenario has no contract file | ah explain run_ah_scenario_new → ah scenario new |
orphan-contract | A contract file has no matching scenario | Remove or archive the contract |
no-tests-declared | Contract exists but has no test commands | Fill in [[tests.shell]] or [[tests.flags]] |
collision | Two specs declare the same scenario ID | Rename one scenario heading |
test-failing | A contract test exited non-zero | Inspect test output, fix code |
no-tests-ran | Shell test ran no tests (exit 0, no output) | Run ah check --json to inspect the full captured output. The contract’s [[tests.shell]] command may not match any test files, or the adapter may not be detecting the test framework. Check config and test command. |
duplicate-id | Same scenario ID appears in two specs | Deduplicate |
mutation-below-threshold | Mutation kill rate too low | Add or improve test cases |
Before starting work, ensure ah is available in PATH:
which ah || cargo install --git git@cv:charly-vibes/espectacular.git
Quick reference: interpreting output
{
"summary": { "structural": 0, "execution": 0, "passed": 3, "counts_by_kind": {} },
"findings": []
}
→ Green. All checks pass. Ready to commit.
{
"summary": { "structural": 1, "execution": 0, "passed": 0, "counts_by_kind": { "missing-contract": 1 } },
"findings": [ { "kind": "missing-contract", "category": "structural", ... } ]
}
→ Structural finding. A scenario has no contract file. Run ah scenario new
to create one, then ah check again.
{
"summary": { "structural": 0, "execution": 1, "passed": 2, "counts_by_kind": { "test-failing": 1 } },
"findings": [ { "kind": "test-failing", "category": "execution", ... } ]
}
→ Execution finding. A contract test failed. Fix the code, then ah check
again.
For full guidance on any finding kind:
ah explain <finding-kind>
CI/script usage
In CI pipelines or automated scripts, use --json for machine-readable
output:
ah check --json
# Exit code: 0 if no structural/execution findings, 1 otherwise
The --run-tests flag forces contract test execution even when no spec
changes are detected (useful in CI when dependencies may have changed).
Forcing a commit through findings
On a WIP branch or when findings are acceptable, you can commit despite non-zero exit. This is common during staged change development:
ah check --changes my-change
# If findings are scoped to the change and expected, proceed
git commit -m "wip: my change"
For CI-only enforcement, run ah check in CI and allow local commits to
pass — the managed block in AGENTS.md should still instruct agents to
resolve findings.
Commit Workflow with ah check as Gate
Standard workflow
1. bd claim <ticket> # Claim work
2. git pull --rebase # Sync
3. [edit code / specs] # Make changes
4. ah check # Verify spec-test correspondence
5. [fix findings] # Iterate until green
6. git add <files> # Stage specific files (never git add -A)
7. git commit -m "..." # Commit with descriptive message
8. ah check # Verify pre-push (catches regressions from merge)
9. bd close <ticket> # Close ticket
When findings are expected
Some tickets produce findings that are expected and resolved within the
same ticket. Example: a ticket that adds a new scenario creates a
missing-contract finding until the contract file is written.
In those cases, the workflow is:
1. ah check # Baseline — note existing findings
2. [edit code / specs] # Make changes
3. ah check # Verify — expected findings should match ticket scope
4. [fix unexpected findings]
5. ah check # Green — all expected findings resolved
6. git add && git commit
Pre-push hooks (optional)
Projects with lefthook or prek can integrate ah check as a pre-push
hook. This is configured by ah init and runs automatically:
# .lefthook.yml (auto-generated by ah init)
pre-push:
commands:
ah-check:
run: ah check
skip: false # Block push on failure
If you want ah check to run but not block the push (advisory mode), set
skip: true. This lets you push while still seeing the output.
The actual config generated by ah init uses skip: false by default.
---
## Agent Workflow Diagram
┌─────────────────────────────────────────────────────────┐
│ Session Start │
│ │
│ 1. Agent reads AGENTS.md → discovers ah:managed block │
│ 2. Agent runs ah check to establish baseline │
│ 3. Agent reads any existing findings │
└─────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Development Loop │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Edit │───▶│ah check │───▶│ Fix │ │
│ │ code/ │ │ │ │ findings │──┐ │
│ │ specs │ └──────────┘ └──────────┘ │ │
│ └──────────┘ ▲ │ │
│ │ findings remain │ │
│ └────────────────────────┘ │
│ │ │
│ green │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ ah check passes │ │
│ └────────┬──────────┘ │
└─────────────────────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Commit │
│ │
│ 1. git add
---
## Adding `ah check` to a New Project
For agent operators who want to replicate this pattern:
```bash
# 1. Install ah
cargo install --git git@cv:charly-vibes/espectacular.git
# 2. Ensure the project has an openspec/ directory
# (the root AGENTS.md or CLAUDE.md will be updated
# automatically by ah init)
# 3. Run init
ah init
# 4. Verify
ah doctor # Should show healthy
ah check # Should have findings from stub contracts
# 5. Verify the ah:managed block was written to AGENTS.md
# (ah init does this automatically — no manual edits needed)
Sources
This document is extracted from observed agent workflow patterns in the following pi sessions:
2026-07-08T15-35-34-510Z— Heavyah checkusage during contract wiring for the v0.2.0 release (espectacular project)2026-07-16T01-38-10-034Z— Recent workflow showing the edit → ah check → fix → ah check → commit cycle (testaruda project)
Both sessions demonstrate the ah check feedback loop in practice.
Testaruda was the first OpenSpec project to adopt ah check as a regular
development gate, and its patterns were replicated across the charly-vibes
ecosystem.
Audit: Spec-Validation Patterns Across Charly Projects
Ticket: espectacular-892
Scope: All charly-vibes OpenSpec projects (2026-04 through 2026-07)
Source session: 019f72d1-e2bf-7d6f-a707-403228d4ee3b
Executive Summary
Before ah check was deployed to all 13 charly projects on 2026-07-20,
agents had no standard tool for spec-test correspondence validation.
They relied on a mix of openspec validate --strict (structure-only),
cargo test / just test (test-only), and manual inspection. This audit
documents those patterns as evidence for the pi extension ticket
(espectacular-c9v).
Patterns Observed
Pattern 1: openspec validate --strict (primary pre-espectacular tool)
Used consistently from April through July 2026 across all projects with OpenSpec specs. Validates spec structure (headings, scenarios, IDs) but does not verify contracts or tests.
openspec validate --strict
openspec validate --all --strict
openspec validate --specs --strict
openspec validate add-change-name --strict
Found in: 22+ log entries across 10 projects (atril, fabbro, fotos, khipu, miblioteca, nayra, paranoid, pretender, wai, tRAGar).
Pain points:
--strictflag required every invocation (easy to forget)- No test execution integration — agents still needed to run
cargo testseparately - No contract file validation — only spec structural checks
- No JSON output for CI/script consumption
- Exit code behavior was unclear
Pattern 2: cargo test / just test as spec-validation proxy
Agents frequently used test runners as a proxy for spec verification,
often paired with openspec validate:
just test && openspec validate --all --strict
Found in: 15+ log entries across 7 projects.
Pain points:
- Two separate commands for one validation intent
- No feedback that tests correspond to specs — only that tests pass
- Projects without
openspec validateintegration just rancargo testand assumed spec alignment
Pattern 3: Manual spec-to-test reading (no tool)
Agents read spec files and test files manually to verify correspondence:
"read spec" → "read test file" → "compare scenarios" → "fix" → "commit"
Found in: Occasional entries where agents reviewed scenarios alongside test files (2026-04-20 atril session, 2026-04-30 REPLy.jl).
Pain points:
- Entirely manual — no automation
- High cognitive load for contract wiring
- Easy to miss scenarios or leave orphans
Pattern 4: ah check (current — espectacular)
After espectacular v0.2.0, the intended tool:
ah check # Fast static validation
ah check --changes <name> # With staged change overlay
ah check --json # Machine-readable
Deployment history:
- 2026-06-18: First dogfooded in espectacular itself
- 2026-07-08: v0.2.0 — all espectacular contracts wired, 0 findings
- 2026-07-15: v0.2.2 — human-readable output (silent JSON before)
- 2026-07-20: Deployed to all 13 openspec projects
- 2026-07-21: Performance fix (
--run-testsflag, fast default)
Gap (pre-deployment): Only 2/27 projects had ah check available.
Agents didn’t use it because it wasn’t there to discover.
Pain Points (Evidence for espectacular-c9v)
| # | Pain Point | Before | After | Severity |
|---|---|---|---|---|
| 1 | Two-step validation | openspec validate --strict + cargo test | ah check does both | HIGH |
| 2 | No contract verification | openspec validate only checked spec structure | ah check validates contracts + runs tests | HIGH |
| 3 | No CI integration | openspec validate JSON output unclear | ah check --json has stable schema | MEDIUM |
| 4 | Agent discovery | No AGENTS.md block → agents didn’t know to validate | ah:managed block in AGENTS.md | HIGH |
| 5 | Manual spec-test mapping | Agents read spec + test files by hand | ah check reports missing/orphan contracts | MEDIUM |
| 6 | Forgotten --strict | Easy to validate without the right flags | ah check defaults to comprehensive | LOW |
| 7 | No test output in findings | openspec validate had no test execution | ah check captures test output in findings | MEDIUM |
Timeline
April 2026
└─ openspec validate --strict (sole tool, all projects)
└─ just test / cargo test (as spec proxy)
June 2026
└─ ah check dogfooded in espectacular
└─ openspec validate still used elsewhere
July 10
└─ Last openspec validate usage (tRAGar project)
└─ ah check v0.2.0 in espectacular
July 15
└─ ah check v0.2.2 (human-readable output)
July 18
└─ espectacular-892 ticket created (this audit)
└─ Deployment begins
July 20
└─ ah check deployed to all 13 openspec projects
July 21
└─ Performance fix, --run-tests flag
└─ This audit completed
Recommendations for Pi Extension (espectacular-c9v)
-
Discovery is the primary gap. After deployment, agents still only use
ah checkif theah:managedblock is in their context window. A pi custom tool makes discovery unconditional. -
Pain point #1 (two-step validation) is the strongest motivator. Agents ran two commands (
openspec validate+cargo test) where one should suffice.ah checkalready solves this — the pi extension just needs to expose it. -
Pain point #4 (agent discovery) is the blocker. The
ah:managedblock works when an agent reads it, but agents don’t always readAGENTS.mdin every session. A pi tool bypasses this entirely. -
Manual spec-test mapping (#5) is the rarest but most expensive pattern — it wastes agent context on mechanical comparison.
gate Specification
Purpose
TBD - created by archiving change add-spec-assertions. Update Purpose after archive.
Requirements
Requirement: Scenario Discovery
The system SHALL discover OpenSpec scenarios from #### Scenario: headings in spec.md files.
Scenario: Discover deployed scenario
- GIVEN
openspec/specs/compiler/spec.mdcontains#### Scenario: Empty input rejected - WHEN
ah checkscans deployed specs - THEN it discovers a scenario with id
empty-input-rejected - AND associates it with the
compilerspec
Scenario: Reject duplicate scenario ids
- GIVEN two scenarios in the same spec slugify to the same id
- WHEN
ah checkvalidates the spec - THEN it emits a structural finding for the slug collision
- AND exits non-zero
Requirement: Sidecar Contract Correspondence
The system SHALL require exactly one TOML sidecar contract for each discovered scenario in scope.
Scenario: Missing contract fails
- GIVEN a scenario id
empty-input-rejectedexists underopenspec/specs/compiler/spec.md - AND
.espectacular/compiler/empty-input-rejected.tomldoes not exist - WHEN a user runs
ah check - THEN the command emits a
no-tomlstructural finding - AND exits non-zero
Scenario: Orphan contract fails
- GIVEN
.espectacular/compiler/empty-input-rejected.tomlexists - AND no matching scenario exists under
openspec/specs/compiler/spec.md - WHEN a user runs
ah check - THEN the command emits an
orphan-tomlstructural finding - AND exits non-zero
Scenario: Contract id mismatch fails
- GIVEN
.espectacular/compiler/empty-input-rejected.tomlcontainsid = "different-id" - AND the matching scenario slug is
empty-input-rejected - WHEN a user runs
ah check - THEN the command emits an
id-mismatchstructural finding - AND exits non-zero
Scenario: Empty test set fails
- GIVEN a scenario contract declares no tests
- WHEN a user runs
ah check - THEN the command emits a
no-tests-declaredstructural finding - AND exits non-zero
Requirement: Contract Schema
The system SHALL validate per-scenario TOML contracts before running tests.
Scenario: Validate scenario metadata
- GIVEN a scenario contract contains
id,description,archetype,status, andauthored_with - WHEN a user runs
ah check - THEN the command validates the metadata fields before executing tests
Scenario: Reject unknown status
- GIVEN a scenario contract has
status = "paused" - WHEN a user runs
ah check - THEN the command emits an
invalid-statusstructural finding - AND exits non-zero
Scenario: Validate superseded status
- GIVEN a scenario contract has
status = "superseded" - WHEN a user runs
ah check - THEN the command requires a non-empty
superseded_byvalue - AND still runs the scenario’s declared tests
Requirement: Test Runner Execution
The system SHALL run each declared test command and use its exit code as the execution verdict.
Scenario: Run configured unit test
- GIVEN
.espectacular/config.tomlmapsunitto["uv", "run", "pytest"] - AND a scenario contract declares
[[tests.unit]]withflags = "tests/test_parser.py::test_empty_input" - WHEN a user runs
ah check - THEN the command executes argv
["uv", "run", "pytest", "tests/test_parser.py::test_empty_input"]without a shell from the repository root - AND records the command exit code in JSON output
Scenario: Run shell test
- GIVEN a scenario contract declares
[[tests.shell]]withcommand = "ah --version | grep -q 'ah '" - WHEN a user runs
ah check - THEN the command executes the shell command through
/bin/sh -cfrom the repository root - AND records the command exit code in JSON output
Scenario: Enforce test timeout
- GIVEN a declared test command runs longer than its configured timeout
- WHEN a user runs
ah check - THEN the command stops the test command
- AND emits a
test-failingexecution finding withtimed_out = true - AND exits non-zero
Scenario: Capture bounded output tails
- GIVEN a declared test command writes more than 8 KiB to stdout and stderr
- WHEN
ah checkemits JSON output - THEN the execution finding includes only the final 8 KiB of stdout
- AND includes only the final 8 KiB of stderr
Scenario: Missing runner fails structurally
- GIVEN a scenario contract declares
[[tests.integration]] - AND
.espectacular/config.tomldoes not definerunners.integration - WHEN a user runs
ah check - THEN the command emits a
missing-runnerstructural finding - AND exits non-zero
Scenario: Invalid TOML syntax fails structurally
- GIVEN a scenario contract file contains invalid TOML syntax
- WHEN a user runs
ah check - THEN the command emits a
malformed-contractstructural finding - AND exits non-zero
Scenario: Malformed test entry fails structurally
- GIVEN a non-shell test entry omits
flags - WHEN a user runs
ah check - THEN the command emits a
malformed-contractstructural finding - AND exits non-zero
Scenario: Non-zero declared test fails check
- GIVEN a declared test command exits non-zero
- WHEN a user runs
ah check - THEN the command emits a
test-failingexecution finding - AND exits non-zero
Requirement: JSON Findings
The system SHALL emit stable JSON output for ah check results.
Scenario: Report success with empty findings
- GIVEN every scenario in scope has a valid contract
- AND every declared test command exits zero
- WHEN a user runs
ah check - THEN the command exits zero
- AND emits JSON with
findings = []
Scenario: Report all findings in stable order
- GIVEN multiple scenarios have findings
- WHEN a user runs
ah check - THEN the JSON output includes all findings
- AND orders them by spec path and scenario id
Scenario: Include actionable scenario context
- GIVEN a scenario has a finding
- WHEN
ah checkemits JSON output - THEN the finding includes the scenario id, spec path, scenario title, and scenario body markdown
Scenario: Extract scenario body boundaries
- GIVEN a scenario heading is followed by markdown body lines and then another
####heading - WHEN
ah checkemits JSON output for that scenario - THEN
body_markdowncontains only the lines after the scenario heading and before the next heading whose level is####or higher
Scenario: Include checked scope
- WHEN
ah checkemits JSON output - THEN the top-level JSON includes whether deployed specs were checked
- AND includes any selected OpenSpec changes
Scenario: Include command details for execution findings
- GIVEN a declared test command exits non-zero
- WHEN
ah checkemits JSON output - THEN the finding includes the test type, command, exit code, timeout flag, stdout tail, and stderr tail when available
Requirement: Change Overlay Scope
The system SHALL support checking selected OpenSpec changes as overlays on deployed specs.
Scenario: Check selected change overlay
- GIVEN
openspec/changes/add-parser/specs/compiler/spec.mdadds a scenario - AND
.espectacular/changes/add-parser/compiler/<scenario>.tomlexists - WHEN a user runs
ah check --changes add-parser - THEN the command validates the deployed compiler spec plus the
add-parserscenario overlay
Scenario: Apply staged metadata update for deployed scenario
- GIVEN
.espectacular/changes/add-parser/compiler/old-behavior.tomlhasstatus = "superseded" - AND deployed spec
compilercontains scenarioold-behavior - WHEN a user runs
ah check --changes add-parser - THEN the command validates the staged contract as the active contract for
old-behaviorin the overlay
Scenario: Reject supersession with missing replacement
- GIVEN
.espectacular/changes/add-parser/compiler/old-behavior.tomlhasstatus = "superseded" - AND
superseded_by = "new-behavior" - AND no scenario
new-behaviorexists in deployed specs or the selected change overlay - WHEN a user runs
ah check --changes add-parser - THEN the command emits a structural finding for the missing replacement scenario
- AND exits non-zero
Scenario: Reject conflicting overlays
- GIVEN two selected changes define the same new scenario id for the same spec
- WHEN a user runs
ah check --changes first --changes second - THEN the command emits a structural finding for the conflict
- AND exits non-zero
Scenario: Reject conflicting staged updates for one deployed scenario
- GIVEN two selected changes both stage metadata updates for the same deployed scenario id in the same spec
- WHEN a user runs
ah check --changes first --changes second - THEN the command emits an
overlay-conflictstructural finding - AND exits non-zero
Scenario: Overlay resolution is deterministic
- GIVEN selected changes do not conflict
- WHEN a user runs
ah check --changes zeta --changes alpha - THEN the command resolves selected changes in sorted change-id order
- AND produces the same validation scope as
ah check --changes alpha --changes zeta
Requirement: Non-Regression Archetype
The system SHALL support an NR (Non-Regression) archetype for contracts that assert existing behavior is preserved during change proposals.
Scenario: NR contract is valid
- GIVEN a scenario contract has
archetype = "NR" - WHEN a user runs
ah check - THEN the gate accepts
NRas a valid archetype value - AND validates and runs the contract’s declared tests identically to other archetypes
Scenario: NR contract runs in change overlay scope
- GIVEN a change proposal modifies a capability
- AND an existing scenario is covered by a contract with
archetype = "NR" - WHEN a user runs
ah check --changes <change-id> - THEN the NR contract is validated as part of the overlay scope
- AND a failing NR test exits non-zero
Scenario: ah upgrade reports NR as archetype addition
- GIVEN
.espectacular/config.tomlpins a tool version that predatesNRsupport - WHEN a user runs
ah upgrade - THEN the command reports
NRas a newly available archetype before updating the configured tool version
Requirement: Deterministic Scope Boundary
The system SHALL avoid semantic evaluation of test quality or scenario prose.
Scenario: Do not inspect test internals
- GIVEN a declared test command exists and exits zero
- WHEN a user runs
ah check - THEN the command treats the test as passing
- AND does not inspect assertions, fixtures, mocks, or setup code
Scenario: Do not hash scenario prose
- GIVEN the body text under an existing scenario heading changes
- WHEN a user runs
ah check - THEN the command does not fail solely because the prose changed
Requirement: JSON finding schema includes agent-action fields
The system SHALL include agent-action fields on every finding in the JSON output.
Scenario: Every finding carries suggested_action
- GIVEN
ah checkproduces any finding - WHEN the JSON output is inspected
- THEN every finding object contains a
suggested_actionfield with a value from the documented enum
Scenario: Every finding carries playbook_command
- GIVEN
ah checkproduces any finding - WHEN the JSON output is inspected
- THEN every finding object contains a
playbook_commandfield with a validah explain <topic>invocation
Scenario: scenario_prose is verbatim and untruncated
- GIVEN a finding references a scenario
- WHEN the JSON output is inspected
- THEN the
scenario_prosefield contains the full markdown body of the scenario heading, verbatim, without truncation
Scenario: Findings are sorted deterministically
- GIVEN
ah checkproduces multiple findings - WHEN the JSON output is inspected
- THEN the
findingsarray is sorted by(spec_path, scenario_id, kind)in ascending lexicographic order
Scenario: Summary counts by kind
- GIVEN
ah checkproduces findings of multiple kinds - WHEN the JSON output is inspected
- THEN the envelope
summary.counts_by_kindobject contains the count of each finding kind present
Requirement: Quality measurement capabilities
The system SHALL support opt-in quality measurement capabilities that run during ah check and emit measurement findings without failing the gate.
Scenario: Mutation testing runs when enabled
- GIVEN a contract declares
[quality.mutation] enabled = true - AND a mutation tool is configured in
.espectacular/config.toml - WHEN a user runs
ah check --mutation - THEN the gate runs the mutation tool against the contract’s declared tests
- AND emits a
quality-mutationinfo finding with the measured score - AND exits zero when the score is below any configured threshold
Scenario: Property-based testing runs when declared
- GIVEN a contract declares a
tests.propertyentry - WHEN a user runs
ah check - THEN the gate runs the property test command
- AND emits a
quality-propertyfinding with the run result
Scenario: Snapshot testing runs when declared
- GIVEN a contract declares a
tests.snapshotentry - WHEN a user runs
ah check - THEN the gate runs the snapshot test command
- AND emits a
quality-snapshotfinding with the run result
Scenario: Quality scores below threshold do not fail the gate in v1
- GIVEN a quality measurement capability completes successfully and produces a score below threshold
- WHEN a user runs
ah check - THEN the finding severity is
warningorinfo - AND the overall exit status is zero
Scenario: Property or snapshot command failure fails the gate
- GIVEN a contract declares
[[tests.property]]or[[tests.snapshot]] - AND the declared command exits non-zero or times out
- WHEN a user runs
ah check - THEN the command emits a
test-failingexecution finding - AND the overall exit status is non-zero
Scenario: Mutation tool execution failure fails the gate
- GIVEN mutation measurement is enabled and the mutation tool command exits non-zero before producing a measurement
- WHEN a user runs
ah check --mutation - THEN the command emits a
test-failingexecution finding - AND the overall exit status is non-zero
Scenario: Mutation is off in pre-commit scope by default
- GIVEN mutation testing is configured
- AND
ah checkis invoked without an explicit--mutationflag - WHEN the command runs in pre-commit mode
- THEN mutation testing is skipped
Requirement: Quality contract schema
The system SHALL represent quality measurements without changing the baseline rule that tests.<type> entries are arrays of runnable test declarations.
Scenario: Mutation configuration is not a test entry
- GIVEN mutation measurement is enabled for a scenario contract
- WHEN the contract is validated
- THEN mutation settings are read from a
[quality.mutation]table - AND
tests.mutationas a boolean is rejected as a malformed contract
Scenario: Property and snapshot are runnable test entries
- GIVEN a scenario contract declares
[[tests.property]]or[[tests.snapshot]] - WHEN the contract is validated
- THEN each entry follows the same runnable test-entry shape as other
tests.<type>arrays
Requirement: Conformance coverage matrix
The system SHALL compute a per-spec, per-archetype coverage matrix aggregating scenario contract status across all specs in scope.
Scenario: Matrix counts covered scenarios
- GIVEN
openspec/specs/contains multiple specs, each with scenarios that have contracts - WHEN a user runs
ah report - THEN the command emits a matrix row for each spec with columns for each archetype
- AND each cell contains
covered,missing, andfailingcounts
Scenario: Matrix includes archetype totals
- GIVEN
ah reportruns against deployed specs - WHEN the output is inspected
- THEN the matrix includes a totals row summing counts across all specs
Scenario: Missing contracts appear as uncovered
- GIVEN a deployed scenario has no sidecar contract
- WHEN
ah reportruns - THEN the scenario is counted as
missingfor its spec row - AND the
archetypecolumn isunassigned
Scenario: Machine-readable matrix output
- WHEN a user runs
ah report --json - THEN the command emits a JSON object with a
matrixarray - AND each row contains
spec,archetype,covered,missing, andfailinginteger fields
Requirement: apply_command is conditionally present
The system SHALL set apply_command only when the finding’s suggested_action maps to a concrete, mechanical shell command; it SHALL be null for findings that require non-mechanical human action.
Scenario: apply_command is set for enable_capability findings
- GIVEN
ah checkorah doctorproduces a finding withsuggested_action = enable_capability - WHEN the JSON output is inspected
- THEN
apply_commandcontains theah doctor --enable <capability>invocation
Scenario: apply_command is null for human_review_required findings
- GIVEN
ah checkproduces a finding withsuggested_action = human_review_required - WHEN the JSON output is inspected
- THEN
apply_commandis null or absent
Scenario: apply_command is null for edit_code_not_scenario findings
- GIVEN
ah checkproduces a finding withsuggested_action = edit_code_not_scenario - WHEN the JSON output is inspected
- THEN
apply_commandis null or absent
cli Specification
Purpose
TBD - created by archiving change add-spec-assertions. Update Purpose after archive.
Requirements
Requirement: CLI Command Name
The system SHALL expose the standalone command-line interface as ah.
Scenario: Invoke help
- WHEN a user runs
ah --help - THEN the CLI displays available
ahcommands
Requirement: Project Initialization
The system SHALL provide an idempotent ah init command that prepares a repository for spec-test correspondence checks.
Scenario: Initialize project files
- GIVEN a repository contains an
openspec/directory - WHEN a user runs
ah init - THEN the command creates
.espectacular/config.tomlwhen it is missing - AND writes
.espectacular/AGENTS.md - AND creates top-level
AGENTS.mdandCLAUDE.mdwhen they are absent - AND refreshes managed
ahblocks in top-level instruction files
Scenario: Refuse initialization without OpenSpec
- GIVEN a repository does not contain an
openspec/directory - WHEN a user runs
ah init - THEN the command fails without creating
.espectacular/
Scenario: Stub existing deployed scenarios
- GIVEN deployed OpenSpec scenarios exist under
openspec/specs/ - WHEN a user runs
ah init - THEN the command creates matching
.espectacular/<spec>/<scenario>.tomlstubs for scenarios without contracts - AND the stubs declare no tests until the user or AI fills them in
Scenario: Install supported pre-commit integration
- GIVEN the repository uses
lefthook - WHEN a user runs
ah init - THEN the command installs or refreshes a managed pre-commit integration that runs
ah check
Scenario: Prefer lefthook before prek
- GIVEN the repository has both
lefthookandprekconfigured - WHEN a user runs
ah init - THEN the command installs the managed pre-commit integration through
lefthook
Scenario: Fall back to prek
- GIVEN the repository uses
prek - AND does not use
lefthook - WHEN a user runs
ah init - THEN the command installs or refreshes a managed pre-commit integration through
prek
Scenario: Report missing hook framework
- GIVEN the repository does not use
lefthookorprek - WHEN a user runs
ah init - THEN the command reports a concern that the user or AI must set up pre-commit integration
- AND does not write a raw
.git/hooks/pre-commitfallback
Requirement: Correspondence Check Command
The system SHALL provide ah check as the deterministic gate command.
Scenario: Check deployed specs
- WHEN a user runs
ah check - THEN the command validates deployed specs under
openspec/specs/ - AND validates matching contracts under
.espectacular/<spec>/ - AND emits JSON output
Scenario: Check an OpenSpec change overlay
- WHEN a user runs
ah check --changes add-parser - THEN the command validates deployed specs plus the
add-parserchange overlay - AND validates staged contracts under
.espectacular/changes/add-parser/ - AND includes the selected change in the JSON scope
Requirement: Health Check Command
The system SHALL provide ah doctor for installation health checks.
Scenario: Diagnose project setup
- WHEN a user runs
ah doctor - THEN the command validates
.espectacular/config.toml - AND checks managed instruction blocks
- AND checks supported hook integration
- AND reports tool-version compatibility concerns
Scenario: Diagnose correspondence wiring
- WHEN a user runs
ah doctor - THEN the command reports slug collisions as errors
- AND reports orphan contracts as errors
- AND reports unknown archetype names as warnings
Requirement: Archetype Documentation Commands
The system SHALL expose built-in archetype guidance through ah type commands.
Scenario: List archetypes
- WHEN a user runs
ah type - THEN the command lists all known archetypes with one-line descriptions
- AND includes
PF,SA,BP,CE, andNR
Scenario: Show archetype details
- WHEN a user runs
ah type PF - THEN the command prints the full built-in documentation for the
PFarchetype
Requirement: Scenario Lifecycle Commands
The system SHALL provide commands for append-only scenario authoring.
Scenario: Create scenario in a change
- GIVEN
openspec/changes/add-parser/specs/compiler/spec.mdcontains### Requirement: Parser Input Validation - WHEN a user runs
ah scenario new add-parser compiler --requirement "Parser Input Validation" "Empty input rejected" - THEN the command appends a scenario heading under that requirement
- AND writes placeholder
WHENandTHENlines under the scenario heading - AND creates
.espectacular/changes/add-parser/compiler/empty-input-rejected.tomlwithid, emptydescription, emptyarchetype,status = "active", emptysuperseded_by, andauthored_with
Scenario: Reject scenario creation without target requirement
- GIVEN
openspec/changes/add-parser/specs/compiler/spec.mddoes not contain### Requirement: Parser Input Validation - WHEN a user runs
ah scenario new add-parser compiler --requirement "Parser Input Validation" "Empty input rejected" - THEN the command fails without creating or modifying files
Scenario: Supersede a scenario
- GIVEN scenario
new-behaviorexists in the deployed-plus-add-parseroverlay for speccompiler - WHEN a user runs
ah scenario supersede compiler old-behavior --with=new-behavior --in-change=add-parser - THEN the command stages
.espectacular/changes/add-parser/compiler/old-behavior.toml - AND marks the staged contract as superseded
- AND records
new-behavioras the replacement scenario id
Scenario: Reject supersession with missing replacement
- GIVEN scenario
new-behaviordoes not exist in the deployed-plus-add-parseroverlay for speccompiler - WHEN a user runs
ah scenario supersede compiler old-behavior --with=new-behavior --in-change=add-parser - THEN the command fails without creating or modifying files
Requirement: Archive Companion Command
The system SHALL provide ah archive <change> to move staged scenario contracts after OpenSpec archive.
Scenario: Archive staged contracts
- GIVEN
openspec archive add-parserhas applied the OpenSpec change - AND every staged contract id exists in deployed
openspec/specs/ - WHEN a user runs
ah archive add-parser - THEN the command moves new contracts from
.espectacular/changes/add-parser/<spec>/to.espectacular/<spec>/
Scenario: Refuse archive before OpenSpec archive
- GIVEN
.espectacular/changes/add-parser/compiler/new-behavior.tomlexists - AND deployed
openspec/specs/compiler/spec.mddoes not contain scenarionew-behavior - WHEN a user runs
ah archive add-parser - THEN the command fails without moving staged contracts
Scenario: Refuse archive collision
- GIVEN
.espectacular/compiler/foo.tomlalready exists - AND
.espectacular/changes/add-parser/compiler/foo.tomlis not a superseded metadata update forfoo - WHEN a user runs
ah archive add-parser - THEN the command fails without overwriting the deployed contract
Requirement: Upgrade Command
The system SHALL provide ah upgrade to make tool-version drift explicit.
Scenario: Report compatibility changes
- GIVEN
.espectacular/config.tomlpins an older tool version than the installedah - WHEN a user runs
ah upgrade - THEN the command reports config schema version changes, execution default changes, archetype additions, and archetype deprecations before updating the configured tool version
- AND does not rewrite existing scenario contract
authored_withvalues
Requirement: Doctor enable flag
When a user runs ah doctor --enable <capability> for a detected inactive capability, the system SHALL write exactly one config table for that capability and SHALL print the path and table name written.
Scenario: Enable pytest adapter
- GIVEN pytest is detected by
ah doctor - WHEN a user runs
ah doctor --enable pytest - THEN the command writes
[runners.pytest] command = ["pytest"]to.espectacular/config.toml - AND prints
.espectacular/config.tomland[runners.pytest]
Scenario: Enable cargo adapter
- GIVEN cargo is detected by
ah doctor - WHEN a user runs
ah doctor --enable cargo - THEN the command writes
[runners.cargo] command = ["cargo", "test"]to.espectacular/config.toml - AND prints
.espectacular/config.tomland[runners.cargo]
Scenario: Enable vitest adapter
- GIVEN vitest is detected by
ah doctor - WHEN a user runs
ah doctor --enable vitest - THEN the command writes
[runners.vitest] command = ["vitest", "run"]to.espectacular/config.toml - AND prints
.espectacular/config.tomland[runners.vitest]
Scenario: Enable mutation capability
- GIVEN a mutation testing tool is detected
- WHEN a user runs
ah doctor --enable mutation - THEN the command writes
[capabilities.mutation] enabled = trueto.espectacular/config.toml - AND prints
.espectacular/config.tomland[capabilities.mutation]
Scenario: Enable property capability
- GIVEN a property-based testing framework is detected
- WHEN a user runs
ah doctor --enable property - THEN the command writes
[capabilities.property] enabled = trueto.espectacular/config.toml - AND prints
.espectacular/config.tomland[capabilities.property]
Scenario: Enable snapshot capability
- GIVEN a snapshot testing framework is detected
- WHEN a user runs
ah doctor --enable snapshot - THEN the command writes
[capabilities.snapshot] enabled = trueto.espectacular/config.toml - AND prints
.espectacular/config.tomland[capabilities.snapshot]
Scenario: Enable unknown capability is an error
- WHEN a user runs
ah doctor --enable nonexistent - THEN the command exits non-zero
- AND prints
unrecognized capability: nonexistent
Scenario: Enable already-active capability is a no-op
- GIVEN a capability is already present in
.espectacular/config.toml - WHEN a user runs
ah doctor --enable <capability> - THEN the command reports it is already enabled and makes no changes
Requirement: Explain subcommand
The system SHALL provide an ah explain <topic> subcommand that prints playbook guidance for a finding kind or suggested action.
Scenario: Explain a finding kind
- WHEN a user runs
ah explain no-toml - THEN the command prints markdown guidance for the
no-tomlfinding kind
Scenario: Explain a suggested action
- WHEN a user runs
ah explain run_ah_scenario_new - THEN the command prints markdown guidance for the
run_ah_scenario_newsuggested action
Scenario: Explain a general topic
- WHEN a user runs
ah explain workflow - THEN the command prints markdown guidance for the general
workflowtopic
Scenario: Explain with JSON output
- WHEN a user runs
ah explain no-toml --json - THEN the command emits a JSON object with fields:
topic,summary,when,do,human_approval,related_topics,hints - AND each
hintsitem containskindandmessagestring fields
Scenario: List all topics
- WHEN a user runs
ah explain --list - THEN the command prints all available topic identifiers, one per line
Scenario: Unknown topic is an error
- WHEN a user runs
ah explain no-such-topic - THEN the command exits non-zero
- AND prints either
Run ah explain --listor the sorted list of available topic identifiers
Requirement: Coverage report command
The system SHALL provide ah report to display a conformance coverage matrix across all deployed specs and archetype tiers, modeled on the OpenTelemetry per-language compliance matrix pattern.
Scenario: Report coverage by spec and archetype
- WHEN a user runs
ah report - THEN the command prints a table showing each spec as a row and each archetype as a column
- AND each cell shows covered/missing/failing counts
Scenario: Report exits zero when coverage is complete
- GIVEN every deployed scenario has a valid, passing contract
- WHEN a user runs
ah report - THEN the command exits zero
Scenario: Report exits non-zero when scenarios are missing contracts
- GIVEN at least one deployed scenario has no sidecar contract
- WHEN a user runs
ah report - THEN the command exits non-zero
Scenario: Report JSON output
- WHEN a user runs
ah report --json - THEN the command emits a JSON conformance matrix consumable by CI dashboards and agent harnesses
Requirement: Recommendation findings
The system SHALL emit recommendation findings when ah doctor detects capabilities that are available but not yet configured.
Scenario: Recommendation finding carries enable command
- GIVEN
ah doctordetects an available framework not yet configured - WHEN the output is inspected (JSON or text)
- THEN a
recommendationfinding is present withsuggested_action = enable_capability - AND
apply_commandcontains theah doctor --enable <capability>invocation
Scenario: Recommendation finding is a finding kind, not a log line
- GIVEN
ah doctordetects an available but unconfigured framework - WHEN the output is requested as JSON (
ah doctor --json) - THEN the finding appears in the
findingsarray withkind = recommendation - AND it carries a
playbook_commandfield
adapters Specification
Purpose
TBD - created by archiving change add-quality-measurement-and-adapters. Update Purpose after archive.
Requirements
Requirement: Adapter detection precedence
The system SHALL detect framework availability through a defined precedence chain before invoking any adapter, SHALL dispatch adapters per declared contract test type rather than selecting one global adapter for the whole repository, and SHALL record the selected detection source in doctor and check output.
Scenario: Explicit config takes precedence
- GIVEN
.espectacular/config.tomlselects an adapter for a declared contract test type - WHEN adapter detection runs
- THEN the configured adapter is treated as the strongest signal
- AND the command reports
detection_source = configured
Scenario: Manifest declaration is second
- GIVEN no explicit adapter config exists
- AND a project declares a test framework in its language manifest (e.g.,
pyproject.toml,Cargo.toml,package.json) - WHEN adapter detection runs
- THEN the manifest declaration overrides environment and source signals
Scenario: Environment detection is third
- GIVEN a framework is not declared in a manifest but is installed in the environment
- WHEN adapter detection runs
- THEN the environment presence is used to confirm availability
Scenario: Source import is weakest signal
- GIVEN a framework is not configured, not in the manifest, and not in the environment, but is imported in a test file
- WHEN adapter detection runs
- THEN the source import is recognized as the weakest positive signal
Scenario: Manifest signal wins for the matching adapter
- GIVEN a project manifest declares pytest as the Python test framework
- AND the environment also has vitest installed
- WHEN adapter detection runs for a Python contract test type
- THEN pytest is selected for that Python test invocation because manifest takes precedence over environment
- AND the vitest environment presence remains available for TypeScript test invocations and is not treated as a conflict
Scenario: Detection source is reported
- GIVEN adapter detection selects a framework through manifest, environment, or source-import evidence
- WHEN
ah doctor --jsonorah check --jsonreports the selected adapter - THEN the report includes
adapter,test_type, anddetection_source - AND
detection_sourceis one ofmanifest,environment,source_import, orconfigured
Requirement: Python pytest adapter
The system SHALL provide a bundled pytest adapter that detects pytest, runs the declared test command, and normalizes output into the finding schema.
Scenario: Pytest adapter detects via pyproject.toml
- GIVEN a project contains
pyproject.tomlwith pytest in[tool.pytest.ini_options]or as a dependency - WHEN the pytest adapter runs detection
- THEN it reports pytest as available with the detected version
Scenario: Pytest adapter normalizes zero exit to pass
- GIVEN a contract declares a pytest test command
- WHEN the adapter runs the command and pytest exits zero
- THEN the adapter emits no
test-failingfinding for that contract
Scenario: Pytest adapter normalizes non-zero exit to test-failing
- GIVEN a contract declares a pytest test command
- WHEN the adapter runs the command and pytest exits non-zero
- THEN the adapter emits a
test-failingfinding with bounded stdout/stderr tails
Scenario: Pytest adapter classifies import errors
- GIVEN pytest emits JSON output containing an
ImportError - WHEN the adapter normalizes the failing result
- THEN the finding remains
test-failing - AND the execution context reports
test.type = pytest-import-error
Scenario: Pytest adapter classifies fixture failures
- GIVEN pytest emits JSON output containing a missing fixture failure
- WHEN the adapter normalizes the failing result
- THEN the finding remains
test-failing - AND the execution context reports
test.type = pytest-fixture-error
Scenario: Pytest adapter classifies collection failures
- GIVEN pytest emits JSON output containing a collection error
- WHEN the adapter normalizes the failing result
- THEN the finding remains
test-failing - AND the execution context reports
test.type = pytest-collection-error
Requirement: Rust cargo test adapter
The system SHALL provide a bundled cargo test adapter that detects cargo, runs the declared test command, and normalizes output into the finding schema.
Scenario: Cargo adapter detects via Cargo.toml
- GIVEN a project contains
Cargo.toml - WHEN the cargo adapter runs detection
- THEN it reports cargo test as available
Scenario: Cargo adapter normalizes zero exit to pass
- GIVEN a contract declares a cargo test command
- WHEN the adapter runs the command and cargo exits zero
- THEN the adapter emits no
test-failingfinding for that contract
Scenario: Cargo adapter normalizes non-zero exit to test-failing
- GIVEN a contract declares a cargo test command
- WHEN the adapter runs the command and cargo exits non-zero
- THEN the adapter emits a
test-failingfinding with bounded stdout/stderr tails
Requirement: TypeScript vitest adapter
The system SHALL provide a bundled vitest adapter that detects vitest, runs the declared test command, and normalizes output into the finding schema.
Scenario: Vitest adapter detects via package.json
- GIVEN a project contains
package.jsonwith vitest independenciesordevDependencies - WHEN the vitest adapter runs detection
- THEN it reports vitest as available with the detected version
Scenario: Vitest adapter normalizes zero exit to pass
- GIVEN a contract declares a vitest test command
- WHEN the adapter runs the command and vitest exits zero
- THEN the adapter emits no
test-failingfinding for that contract
Scenario: Vitest adapter normalizes non-zero exit to test-failing
- GIVEN a contract declares a vitest test command
- WHEN the adapter runs the command and vitest exits non-zero
- THEN the adapter emits a
test-failingfinding with bounded stdout/stderr tails
Requirement: No-adapter-configured path
When a contract declares a test command but no adapter is configured or detected for the declared test type, the system SHALL emit a missing-adapter finding with kind, message, suggested_action, and playbook_command fields.
Scenario: Missing adapter emits missing-adapter finding
- GIVEN a contract declares a test command
- AND no adapter is configured in
.espectacular/config.tomlfor the declared test type - AND adapter detection finds no matching framework
- WHEN
ah checkruns - THEN a
missing-adapterfinding is emitted with a message directing the user to runah doctor - AND the finding is distinct from
no-tests-declaredbecause the contract did declare a test
Requirement: Custom runner plugin protocol
The system SHALL support [runners.custom.<name>] config blocks that wire arbitrary shell commands into the adapter layer via a documented JSON envelope defined in schemas/custom-runner.schema.json.
Note: The envelope schema (schemas/custom-runner.schema.json) specifies the top-level structure the shell command must emit. Individual findings within the findings array conform to the full finding schema (schemas/check-output.schema.json), not to the envelope schema.
Scenario: Custom runner emits required envelope fields
- GIVEN a custom runner is configured and invoked
- WHEN the runner’s stdout is parsed
- THEN the envelope contains at minimum:
exit_code(integer),passed(boolean),findings(array) - AND each finding in the array conforms to the full finding schema
Scenario: Empty findings array with zero exit is a pass
- GIVEN a custom runner exits zero
- AND the envelope
findingsarray is empty - WHEN the adapter processes the result
- THEN no
test-failingfinding is emitted for that contract
Scenario: Envelope failure overrides process success
- GIVEN a custom runner exits zero
- AND stdout contains a valid envelope with
passed = falseor non-emptyfindings - WHEN the adapter processes the result
- THEN the adapter emits the envelope findings
- AND treats the contract as not passing
Scenario: Process failure overrides envelope success
- GIVEN a custom runner exits non-zero
- AND stdout contains a valid envelope with
passed = trueand an emptyfindingsarray - WHEN the adapter processes the result
- THEN a
test-failingfinding is emitted with the raw stdout/stderr tails - AND the process exit code is preserved in the finding
Scenario: Custom runner non-zero exit without valid envelope is an error finding
- GIVEN a custom runner exits non-zero
- AND stdout is not a valid envelope
- WHEN the adapter processes the result
- THEN a
test-failingfinding is emitted with the raw stdout/stderr tails
Scenario: Custom runner is not invoked without explicit config
- GIVEN no
[runners.custom.<name>]block exists in config - WHEN
ah checkruns - THEN no custom runner is invoked
explain Specification
Purpose
TBD - created by archiving change add-quality-measurement-and-adapters. Update Purpose after archive.
Requirements
Requirement: Playbook is compile-enforced
The system SHALL fail to build if any FindingKind or SuggestedAction enum variant lacks a corresponding ah explain topic body.
Scenario: Missing topic body is a compile error
- GIVEN a
FindingKindorSuggestedActionvariant has no associated playbook body - WHEN the project is built with
cargo build - THEN the build fails with an error identifying the missing topic
Scenario: All variants have topics at build time
- GIVEN all enum variants have associated playbook bodies
- WHEN the project is built
- THEN the build succeeds and
ah explain --listenumerates them all
Requirement: Topic coverage
The system SHALL provide ah explain topics for every FindingKind value, every SuggestedAction value, and a set of general topics.
Scenario: Finding kind topic exists
- WHEN a user runs
ah explain no-toml - THEN the command prints guidance for the
no-tomlfinding kind and exits zero
Scenario: Suggested action topic exists
- WHEN a user runs
ah explain run_ah_scenario_new - THEN the command prints guidance for the
run_ah_scenario_newaction and exits zero
Scenario: General topic exists
- WHEN a user runs
ah explain workflow - THEN the command prints general workflow guidance and exits zero
Requirement: Structured JSON output
The system SHALL support --json output for ah explain that emits a machine-readable object.
Scenario: JSON output has required fields
- WHEN a user runs
ah explain no-toml --json - THEN the output is a valid JSON object containing:
topic(string),summary(string),when(string),do(array of strings),human_approval(boolean),related_topics(array of strings),hints(array of objects) - AND each
hintsitem containskind(string) andmessage(string)
Scenario: JSON output is valid for every topic
- GIVEN any valid topic identifier
- WHEN
ah explain <topic> --jsonis run - THEN the output passes JSON schema validation
Requirement: Topic listing
The system SHALL enumerate all available topics on demand.
Scenario: List enumerates all topics
- WHEN a user runs
ah explain --list - THEN the command prints all topic identifiers, one per line, and exits zero
Scenario: List is stable across runs
- WHEN
ah explain --listis run twice in succession - THEN the output is identical (topics are sorted alphabetically)
Requirement: Unknown topic handling
When a user requests an unknown ah explain topic, the system SHALL exit non-zero and print either Run ah explain --list or the sorted list of available topic identifiers.
Scenario: Unknown topic exits non-zero
- WHEN a user runs
ah explain no-such-topic - THEN the command exits non-zero
- AND the error message lists available topics or directs the user to
ah explain --list
Requirement: Quality finding kind topics
The system SHALL provide ah explain topics for every quality finding kind introduced by this change: quality-mutation, quality-property, quality-snapshot. These are FindingKind values and are therefore subject to the compile-enforcement requirement.
Scenario: quality-mutation topic exists
- WHEN a user runs
ah explain quality-mutation - THEN the command prints guidance explaining what the mutation score means, how to enable mutation testing, and when the finding appears
Scenario: quality-property topic exists
- WHEN a user runs
ah explain quality-property - THEN the command prints guidance for the
quality-propertyfinding kind and exits zero
Scenario: quality-snapshot topic exists
- WHEN a user runs
ah explain quality-snapshot - THEN the command prints guidance for the
quality-snapshotfinding kind and exits zero
Requirement: Adapter topics ship with adapters
The system SHALL include ah explain topics for progressive-enablement capabilities when their adapter modules are compiled in.
Scenario: Pytest adapter contributes topic
- GIVEN the pytest adapter is compiled into the binary
- WHEN a user runs
ah explain pytest - THEN the command prints guidance for enabling and using the pytest adapter
Scenario: Duplicate topic registration is a compile error
- GIVEN two adapter modules attempt to register the same topic identifier
- WHEN the project is built
- THEN the build fails identifying the conflicting topic name