Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

“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 ah on your PATH and run your first check in minutes
  • Command Reference — all ah subcommands 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 lefthook or prek if 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 ah subcommands 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:

FlagDescription
--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:

KindCategoryMeaning
no-tomlstructuralscenario has no matching contract file
orphan-tomlstructuralcontract exists without a matching scenario
slug-collisionstructuraltwo scenarios in one spec have the same id
id-mismatchstructuralscenario slug, filename, and TOML id disagree
no-tests-declaredstructuralcontract has no runnable test entries
missing-runnerstructurala non-shell test type has no configured runner
malformed-contractstructuralTOML cannot be parsed or validated
missing-replacementstructuralsuperseded contract points to an absent replacement
overlay-conflictstructuralselected changes define conflicting staged scenarios
test-failingexecutiona declared test timed out or exited non-zero
quality-mutationqualitymutation score meets threshold (informational)
quality-propertyqualityproperty-based tests passing (informational)
quality-snapshotqualitysnapshot tests passing (informational)

ah doctor

Detect configured frameworks and diagnose config, path, hook, and archetype issues.

ah doctor [--enable <capability>]

Flags:

FlagDescription
--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:

FlagDescription
--jsonEmit 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:

ArgumentDescription
<topic>Finding kind or action slug to explain

Flags:

FlagDescription
--listList all available topics
--jsonEmit 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:

ArgumentDescription
<change>Change id (the change directory must exist under openspec/changes/)
<spec>Spec name (e.g. parser)
--requirementRequirement 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.

ScenarioWhat it verifies
Scenario Discoveryah check finds all scenarios from spec headings
Sidecar Contract Correspondenceevery scenario has a contract; every contract has a scenario
Contract SchemaTOML contracts validate against the schema
Test Runner Executiondeclared tests are executed and results captured
JSON Findingsoutput is a stable JSON envelope with scope/summary/findings
Change Overlay Scope--changes adds staged scenarios to scope
Non-Regression ArchetypeNR contracts are checked without special treatment
Deterministic Scope Boundaryscope is stable across repeated runs
JSON finding schema includes agent-action fieldsfindings carry suggested_action and playbook_command
Quality measurement capabilitiesquality-* findings are emitted and informational
Quality contract schemaquality fields validate in the contract schema
Conformance coverage matrixall finding kinds are covered by at least one contract
apply_command is conditionally presentapply_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.

ScenarioWhat it verifies
CLI Command Namebinary is named ah
Project Initializationah init creates .espectacular/ and hook integration
Correspondence Check Commandah check validates specs and runs tests
Health Check Commandah doctor diagnoses config, paths, hooks, archetypes
Archetype Documentation Commandsah type lists and explains archetypes
Scenario Lifecycle Commandsah scenario new and ah scenario supersede
Archive Companion Commandah archive promotes staged contracts
Upgrade Commandah upgrade detects and reports tool-version drift
Doctor enable flagah doctor --enable <capability> writes config blocks
Explain subcommandah explain prints guidance for finding kinds and actions
Coverage report commandah report displays a conformance coverage matrix
Recommendation findingsah doctor emits recommendations for detected-but-unconfigured adapters
Recommendation findings as JSONah doctor --json emits structured recommendation findings
Report JSON outputah report --json emits a machine-readable conformance matrix
Report exit codesah 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.

ScenarioWhat it verifies
Adapter detection precedenceconfig > manifest > binary on PATH
Python pytest adapterpytest detection, invocation, failure normalization
Rust cargo test adaptercargo detection, invocation, failure normalization
TypeScript vitest adaptervitest detection, invocation, failure normalization
No-adapter-configured pathmissing-runner finding when type has no runner
Custom runner plugin protocolcustom 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.

ScenarioWhat it verifies
Playbook is compile-enforcedevery finding kind has an ah explain topic at compile time
Topic coverageall 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 handlingunknown topics exit 1 with “did you mean” suggestions
Quality finding kind topicsquality-* finding kinds have topics
Adapter topics ship with adaptersadapter-specific topics exist for each adapter

In progress

add-spec-quality-checksah 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.

ScenarioStatus
Spec Lint Commandplanned
Vague Qualifier Detectionplanned
Imperative Step Detectionplanned
Conjunctive Step Bloat Detectionplanned
Missing Negative Scenario Detectionplanned
Missing Non-Goals Detectionplanned
Unresolved Ambiguity Detectionplanned
Lint Finding Schemaplanned

How to read this page

  • Deployed means the scenario has a passing contract in ah check on main.
  • Planned means the scenario is staged in an OpenSpec change but not yet implemented.
  • Run ah check locally 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 the id field 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

EntryHow 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.

CodeNameDescription
PFPure FunctionalDeterministic behavior; outputs are a function of explicit inputs
SAStateful APIState transitions, persisted data, or ordered operations
BPBoundary ProtocolBehavior at an external boundary or protocol seam
CEContract/EventEmitted events, messages, claims, or cross-tool signals
NRNon-RegressionExisting 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:

  1. Discovers all scenarios from deployed specs (and staged change overlays if --changes is passed)
  2. Checks structural correspondence: every scenario has a contract, every contract has a scenario, no collisions, no orphans
  3. Runs every declared test
  4. 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):

AdapterExplicit configManifest detectionBinary detection
pytestrunners.pytest in configpyproject.toml [tool.pytest], pytest.inipytest on PATH or .py with import pytest
cargorunners.cargo in configCargo.toml present
vitestrunners.vitest in configpackage.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:

SignalWhat it tracks
quality-mutationMutation testing kill rate vs. threshold
quality-propertyProperty-based testing is active and passing
quality-snapshotSnapshot 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 check as a commit gate.
  • It lists only the most common commands — ah explain provides the full playbook for less frequent tasks.
  • The ah:managed delimiter 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 check was 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:

SituationWhether to run
Modified spec filesAlways
Modified contract TOML filesAlways
Modified production code tracked by contractsAlways
Edited tests onlyOptional (contracts pass/fail)
Edited docs onlyUsually not needed
Changed a change overlayah 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

CategoryMeaningExit code impact
structuralSpec/contract mismatch (missing contract, orphan, duplicate ID)Non-zero
executionContract test failed or timed outNon-zero
quality-*Mutation score below threshold, property coverage gapZero (advisory)

Common findings and agent responses

Finding kindWhat it meansAgent action
missing-contractA scenario has no contract fileah explain run_ah_scenario_newah scenario new
orphan-contractA contract file has no matching scenarioRemove or archive the contract
no-tests-declaredContract exists but has no test commandsFill in [[tests.shell]] or [[tests.flags]]
collisionTwo specs declare the same scenario IDRename one scenario heading
test-failingA contract test exited non-zeroInspect test output, fix code
no-tests-ranShell 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-idSame scenario ID appears in two specsDeduplicate
mutation-below-thresholdMutation kill rate too lowAdd 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 │ │ 2. git commit -m “fix: …” │ │ 3. (optional) ah check pre-push │ │ 4. bd close │ └─────────────────────────────────────────────────────────┘


---

## 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 — Heavy ah check usage 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:

  • --strict flag required every invocation (easy to forget)
  • No test execution integration — agents still needed to run cargo test separately
  • 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 validate integration just ran cargo test and 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-tests flag, 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 PointBeforeAfterSeverity
1Two-step validationopenspec validate --strict + cargo testah check does bothHIGH
2No contract verificationopenspec validate only checked spec structureah check validates contracts + runs testsHIGH
3No CI integrationopenspec validate JSON output unclearah check --json has stable schemaMEDIUM
4Agent discoveryNo AGENTS.md block → agents didn’t know to validateah:managed block in AGENTS.mdHIGH
5Manual spec-test mappingAgents read spec + test files by handah check reports missing/orphan contractsMEDIUM
6Forgotten --strictEasy to validate without the right flagsah check defaults to comprehensiveLOW
7No test output in findingsopenspec validate had no test executionah check captures test output in findingsMEDIUM

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)

  1. Discovery is the primary gap. After deployment, agents still only use ah check if the ah:managed block is in their context window. A pi custom tool makes discovery unconditional.

  2. Pain point #1 (two-step validation) is the strongest motivator. Agents ran two commands (openspec validate + cargo test) where one should suffice. ah check already solves this — the pi extension just needs to expose it.

  3. Pain point #4 (agent discovery) is the blocker. The ah:managed block works when an agent reads it, but agents don’t always read AGENTS.md in every session. A pi tool bypasses this entirely.

  4. 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.md contains #### Scenario: Empty input rejected
  • WHEN ah check scans deployed specs
  • THEN it discovers a scenario with id empty-input-rejected
  • AND associates it with the compiler spec

Scenario: Reject duplicate scenario ids

  • GIVEN two scenarios in the same spec slugify to the same id
  • WHEN ah check validates 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-rejected exists under openspec/specs/compiler/spec.md
  • AND .espectacular/compiler/empty-input-rejected.toml does not exist
  • WHEN a user runs ah check
  • THEN the command emits a no-toml structural finding
  • AND exits non-zero

Scenario: Orphan contract fails

  • GIVEN .espectacular/compiler/empty-input-rejected.toml exists
  • AND no matching scenario exists under openspec/specs/compiler/spec.md
  • WHEN a user runs ah check
  • THEN the command emits an orphan-toml structural finding
  • AND exits non-zero

Scenario: Contract id mismatch fails

  • GIVEN .espectacular/compiler/empty-input-rejected.toml contains id = "different-id"
  • AND the matching scenario slug is empty-input-rejected
  • WHEN a user runs ah check
  • THEN the command emits an id-mismatch structural 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-declared structural 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, and authored_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-status structural 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_by value
  • 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.toml maps unit to ["uv", "run", "pytest"]
  • AND a scenario contract declares [[tests.unit]] with flags = "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]] with command = "ah --version | grep -q 'ah '"
  • WHEN a user runs ah check
  • THEN the command executes the shell command through /bin/sh -c from 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-failing execution finding with timed_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 check emits 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.toml does not define runners.integration
  • WHEN a user runs ah check
  • THEN the command emits a missing-runner structural 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-contract structural 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-contract structural 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-failing execution 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 check emits 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 check emits JSON output for that scenario
  • THEN body_markdown contains only the lines after the scenario heading and before the next heading whose level is #### or higher

Scenario: Include checked scope

  • WHEN ah check emits 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 check emits 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.md adds a scenario
  • AND .espectacular/changes/add-parser/compiler/<scenario>.toml exists
  • WHEN a user runs ah check --changes add-parser
  • THEN the command validates the deployed compiler spec plus the add-parser scenario overlay

Scenario: Apply staged metadata update for deployed scenario

  • GIVEN .espectacular/changes/add-parser/compiler/old-behavior.toml has status = "superseded"
  • AND deployed spec compiler contains scenario old-behavior
  • WHEN a user runs ah check --changes add-parser
  • THEN the command validates the staged contract as the active contract for old-behavior in the overlay

Scenario: Reject supersession with missing replacement

  • GIVEN .espectacular/changes/add-parser/compiler/old-behavior.toml has status = "superseded"
  • AND superseded_by = "new-behavior"
  • AND no scenario new-behavior exists 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-conflict structural 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 NR as 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.toml pins a tool version that predates NR support
  • WHEN a user runs ah upgrade
  • THEN the command reports NR as 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 check produces any finding
  • WHEN the JSON output is inspected
  • THEN every finding object contains a suggested_action field with a value from the documented enum

Scenario: Every finding carries playbook_command

  • GIVEN ah check produces any finding
  • WHEN the JSON output is inspected
  • THEN every finding object contains a playbook_command field with a valid ah 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_prose field contains the full markdown body of the scenario heading, verbatim, without truncation

Scenario: Findings are sorted deterministically

  • GIVEN ah check produces multiple findings
  • WHEN the JSON output is inspected
  • THEN the findings array is sorted by (spec_path, scenario_id, kind) in ascending lexicographic order

Scenario: Summary counts by kind

  • GIVEN ah check produces findings of multiple kinds
  • WHEN the JSON output is inspected
  • THEN the envelope summary.counts_by_kind object 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-mutation info 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.property entry
  • WHEN a user runs ah check
  • THEN the gate runs the property test command
  • AND emits a quality-property finding with the run result

Scenario: Snapshot testing runs when declared

  • GIVEN a contract declares a tests.snapshot entry
  • WHEN a user runs ah check
  • THEN the gate runs the snapshot test command
  • AND emits a quality-snapshot finding 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 warning or info
  • 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-failing execution 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-failing execution 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 check is invoked without an explicit --mutation flag
  • 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.mutation as 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, and failing counts

Scenario: Matrix includes archetype totals

  • GIVEN ah report runs 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 report runs
  • THEN the scenario is counted as missing for its spec row
  • AND the archetype column is unassigned

Scenario: Machine-readable matrix output

  • WHEN a user runs ah report --json
  • THEN the command emits a JSON object with a matrix array
  • AND each row contains spec, archetype, covered, missing, and failing integer 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 check or ah doctor produces a finding with suggested_action = enable_capability
  • WHEN the JSON output is inspected
  • THEN apply_command contains the ah doctor --enable <capability> invocation

Scenario: apply_command is null for human_review_required findings

  • GIVEN ah check produces a finding with suggested_action = human_review_required
  • WHEN the JSON output is inspected
  • THEN apply_command is null or absent

Scenario: apply_command is null for edit_code_not_scenario findings

  • GIVEN ah check produces a finding with suggested_action = edit_code_not_scenario
  • WHEN the JSON output is inspected
  • THEN apply_command is 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 ah commands

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.toml when it is missing
  • AND writes .espectacular/AGENTS.md
  • AND creates top-level AGENTS.md and CLAUDE.md when they are absent
  • AND refreshes managed ah blocks 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>.toml stubs 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 lefthook and prek configured
  • 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 lefthook or prek
  • 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-commit fallback

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-parser change 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, and NR

Scenario: Show archetype details

  • WHEN a user runs ah type PF
  • THEN the command prints the full built-in documentation for the PF archetype

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.md contains ### 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 WHEN and THEN lines under the scenario heading
  • AND creates .espectacular/changes/add-parser/compiler/empty-input-rejected.toml with id, empty description, empty archetype, status = "active", empty superseded_by, and authored_with

Scenario: Reject scenario creation without target requirement

  • GIVEN openspec/changes/add-parser/specs/compiler/spec.md does 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-behavior exists in the deployed-plus-add-parser overlay for spec compiler
  • 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-behavior as the replacement scenario id

Scenario: Reject supersession with missing replacement

  • GIVEN scenario new-behavior does not exist in the deployed-plus-add-parser overlay for spec compiler
  • 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-parser has 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.toml exists
  • AND deployed openspec/specs/compiler/spec.md does not contain scenario new-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.toml already exists
  • AND .espectacular/changes/add-parser/compiler/foo.toml is not a superseded metadata update for foo
  • 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.toml pins an older tool version than the installed ah
  • 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_with values

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.toml and [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.toml and [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.toml and [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 = true to .espectacular/config.toml
  • AND prints .espectacular/config.toml and [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 = true to .espectacular/config.toml
  • AND prints .espectacular/config.toml and [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 = true to .espectacular/config.toml
  • AND prints .espectacular/config.toml and [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-toml finding 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_new suggested action

Scenario: Explain a general topic

  • WHEN a user runs ah explain workflow
  • THEN the command prints markdown guidance for the general workflow topic

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 hints item contains kind and message string 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 --list or 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 doctor detects an available framework not yet configured
  • WHEN the output is inspected (JSON or text)
  • THEN a recommendation finding is present with suggested_action = enable_capability
  • AND apply_command contains the ah doctor --enable <capability> invocation

Scenario: Recommendation finding is a finding kind, not a log line

  • GIVEN ah doctor detects an available but unconfigured framework
  • WHEN the output is requested as JSON (ah doctor --json)
  • THEN the finding appears in the findings array with kind = recommendation
  • AND it carries a playbook_command field

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.toml selects 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 --json or ah check --json reports the selected adapter
  • THEN the report includes adapter, test_type, and detection_source
  • AND detection_source is one of manifest, environment, source_import, or configured

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.toml with 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-failing finding 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-failing finding 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-failing finding 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-failing finding 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.json with vitest in dependencies or devDependencies
  • 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-failing finding 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-failing finding 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.toml for the declared test type
  • AND adapter detection finds no matching framework
  • WHEN ah check runs
  • THEN a missing-adapter finding is emitted with a message directing the user to run ah doctor
  • AND the finding is distinct from no-tests-declared because 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 findings array is empty
  • WHEN the adapter processes the result
  • THEN no test-failing finding 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 = false or non-empty findings
  • 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 = true and an empty findings array
  • WHEN the adapter processes the result
  • THEN a test-failing finding 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-failing finding 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 check runs
  • 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 FindingKind or SuggestedAction variant 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 --list enumerates 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-toml finding 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_new action 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 hints item contains kind (string) and message (string)

Scenario: JSON output is valid for every topic

  • GIVEN any valid topic identifier
  • WHEN ah explain <topic> --json is 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 --list is 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-property finding 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-snapshot finding 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