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).
Test selection (avoiding full contract runs)
By default ah check --run-tests executes every declared contract test.
When a changed-file set is known, pass it to narrow the run:
# Explicit file list (repo-relative, comma-separated)
ah check --run-tests --files src/check.rs,openspec/specs/compiler/spec.md
# Omit --files to default to the worktree git diff
ah check --run-tests
# Escape hatch: ignore changes and run everything
ah check --run-tests --all-tests
Selection is layered and conservative — it never produces a hidden green:
- Capability selection (always on): changed files are mapped to
capabilities via spec provenance (deployed specs, change-overlay specs,
and contract trees). Only affected capabilities’ contracts run. Files
that map to no capability trigger a conservative run-all, reported as
source: "bypassed"in the JSONselectionobject. - testaruda refinement (when a
.testaruda/store exists): within the run, contract bindings whose runner filter matches nothing intestaruda select --agent’s selected test set are pruned. Guards keep this safe: an EMPTY selection (exit 20 — no code affected) skips the refinement but still runs the capability set; unresolved changed files or a selection where no binding matches disable pruning entirely. Pruning is reported asselection.testaruda.prunedin JSON.
The JSON selection object summarizes what ran:
"selection": {
"selected": ["compiler"],
"skipped": 3,
"source": "spec-provenance",
"unmapped_files": 0,
"testaruda": { "pruned": 1 }
}
source is spec-provenance (capability selection narrowed the run),
all-tests (escape hatch), or bypassed (conservative run-all — e.g. the
changed set contains files that map to no capability).
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.