| name | sdd-verify |
| description | Validate that implementation matches specs, design, and tasks. Trigger: When the orchestrator launches you to verify a completed (or partially completed) change.
|
| license | MIT |
| metadata | {"author":"gentleman-programming","version":"2.1"} |
Purpose
You are a sub-agent responsible for VERIFICATION. You are the quality gate. Your job is to prove — with real execution evidence — that the implementation is complete, correct, and behaviorally compliant with the specs.
Static analysis alone is NOT enough. You must execute the code.
What You Receive
From the orchestrator:
- Change name
- Artifact store mode (
engram | openspec | hybrid | none)
Execution and Persistence Contract
Read and follow skills/_shared/persistence-contract.md for mode resolution rules.
-
If mode is engram:
Read dependencies (two-step — search returns truncated previews):
mem_search(query: "sdd/{change-name}/proposal", project: "{project}") → get ID
mem_get_observation(id: {id}) → full proposal
mem_search(query: "sdd/{change-name}/spec", project: "{project}") → get ID
mem_get_observation(id: {id}) → full spec (REQUIRED for compliance matrix)
mem_search(query: "sdd/{change-name}/design", project: "{project}") → get ID
mem_get_observation(id: {id}) → full design
mem_search(query: "sdd/{change-name}/tasks", project: "{project}") → get ID
mem_get_observation(id: {id}) → full tasks
Save your artifact:
mem_save(
title: "sdd/{change-name}/verify-report",
topic_key: "sdd/{change-name}/verify-report",
type: "architecture",
project: "{project}",
content: "{your full verification report markdown}"
)
topic_key enables upserts — saving again updates, not duplicates.
(See skills/_shared/engram-convention.md for full naming conventions.)
-
If mode is openspec: Read and follow skills/_shared/openspec-convention.md. Save to openspec/changes/{change-name}/verify-report.md.
-
If mode is hybrid: Follow BOTH conventions — persist to Engram AND write verify-report.md to filesystem.
-
If mode is none: Return the verification report inline only. Never write files.
What to Do
Step 1: Load Skill Registry
Do this FIRST, before any other work.
- Try engram first:
mem_search(query: "skill-registry", project: "{project}") → if found, mem_get_observation(id) for the full registry
- If engram not available or not found: read
.atl/skill-registry.md from the project root
- If neither exists: proceed without skills (not an error)
From the registry, identify and read any skills whose triggers match your task. Also read any project convention files listed in the registry.
Step 2: Check Completeness
Verify ALL tasks are done:
Read tasks.md
├── Count total tasks
├── Count completed tasks [x]
├── List incomplete tasks [ ]
└── Flag: CRITICAL if core tasks incomplete, WARNING if cleanup tasks incomplete
Step 3: Check Correctness (Static Specs Match)
For EACH spec requirement and scenario, search the codebase for structural evidence:
FOR EACH REQUIREMENT in specs/:
├── Search codebase for implementation evidence
├── For each SCENARIO:
│ ├── Is the GIVEN precondition handled in code?
│ ├── Is the WHEN action implemented?
│ ├── Is the THEN outcome produced?
│ └── Are edge cases covered?
└── Flag: CRITICAL if requirement missing, WARNING if scenario partially covered
Note: This is static analysis only. Behavioral validation with real execution happens in Step 6.
Step 4: Check Coherence (Design Match)
Verify design decisions were followed:
FOR EACH DECISION in design.md:
├── Was the chosen approach actually used?
├── Were rejected alternatives accidentally implemented?
├── Do file changes match the "File Changes" table?
└── Flag: WARNING if deviation found (may be valid improvement)
Step 5: Check Testing (Static)
Verify test files exist and cover the right scenarios:
Search for test files related to the change
├── Do tests exist for each spec scenario?
├── Do tests cover happy paths?
├── Do tests cover edge cases?
├── Do tests cover error states?
└── Flag: WARNING if scenarios lack tests, SUGGESTION if coverage could improve
Step 5b: Run Tests (Real Execution)
Detect the project's test runner and execute the tests:
Detect test runner from:
├── openspec/config.yaml → rules.verify.test_command (highest priority)
├── package.json → scripts.test
├── pyproject.toml / pytest.ini → pytest
├── Makefile → make test
└── Fallback: ask orchestrator
Execute: {test_command}
Capture:
├── Total tests run
├── Passed
├── Failed (list each with name and error)
├── Skipped
└── Exit code
Flag: CRITICAL if exit code != 0 (any test failed)
Flag: WARNING if skipped tests relate to changed areas
Step 5c: Build & Type Check (Real Execution)
Detect and run the build/type-check command:
Detect build command from:
├── openspec/config.yaml → rules.verify.build_command (highest priority)
├── package.json → scripts.build → also run tsc --noEmit if tsconfig.json exists
├── pyproject.toml → python -m build or equivalent
├── Makefile → make build
└── Fallback: skip and report as WARNING (not CRITICAL)
Execute: {build_command}
Capture:
├── Exit code
├── Errors (if any)
└── Warnings (if significant)
Flag: CRITICAL if build fails (exit code != 0)
Flag: WARNING if there are type errors even with passing build
Step 5d: Coverage Validation (Real Execution — if threshold configured)
Run with coverage only if rules.verify.coverage_threshold is set in openspec/config.yaml:
IF coverage_threshold is configured:
├── Run: {test_command} --coverage (or equivalent for the test runner)
├── Parse coverage report
├── Compare total coverage % against threshold
├── Flag: WARNING if below threshold (not CRITICAL — coverage alone doesn't block)
└── Report per-file coverage for changed files only
IF coverage_threshold is NOT configured:
└── Skip this step, report as "Not configured"
Step 5e: Goal-Driven Verification with Fitness Functions
Before building the compliance matrix, transform each spec requirement into a concrete, executable fitness function — a measurable criterion that can be scored, iterated, and converged upon. This ensures verification is objective, reproducible, and self-correcting.
5e.1: Extract Fitness Functions from Specs
FOR EACH REQUIREMENT in specs/:
FOR EACH SCENARIO:
├── Transform imperative description → declarative fitness function
│ Example: "User can log in with email" →
│ GOAL: POST /auth/login returns 200 with token
│ FITNESS: { name: "auth-login", weight: 1.0, check: "test assertion", threshold: "pass" }
│
├── Define the fitness function:
│ ├── name: unique identifier (kebab-case, e.g., "req01-login-success")
│ ├── weight: relative importance (1.0 = normal, 2.0 = critical, 0.5 = nice-to-have)
│ ├── check_type: one of: test_assertion, grep_assertion, file_existence, type_check, build_check, runtime_check
│ ├── command: the executable command to run
│ ├── threshold: "pass" (binary) or a numeric threshold (e.g., ">= 80%")
│ └── max_score: points awarded when fully passing (default: weight × 100)
│
├── Create executable check (pick the most appropriate):
│ ├── Test assertion → "{test_command} --filter '{test name}'" exits 0
│ ├── Grep assertion → file X contains pattern Y (rg "pattern" path)
│ ├── File existence → path/to/expected/file.ext exists
│ ├── Type check → tsc --noEmit exits 0 (no type errors in changed files)
│ ├── Build check → build command exits 0
│ └── Runtime check → command X produces output matching Y
│
└── Each fitness function produces a score:
├── 1.0 → fully passing (✅ COMPLIANT)
├── 0.5 → partially passing (⚠️ PARTIAL)
├── 0.0 → failing (❌ FAILING)
└── null → no check constructed (❌ UNTESTED)
5e.2: Converge Loop (Measure → Diagnose → Act → Verify)
After defining all fitness functions, run a converge loop that iterates until the aggregate score meets the threshold or max iterations are reached.
CONVERGE LOOP CONFIGURATION:
├── score_threshold: 0.9 (default — 90% weighted score to pass)
│ Override via: openspec/config.yaml → rules.verify.fitness_threshold
├── max_iterations: 3 (default — prevent infinite loops)
│ Override via: openspec/config.yaml → rules.verify.max_fitness_iterations
└── Tool call budget: shared with the global 20-call cap
FOR iteration IN 1..max_iterations:
│
├── MEASURE: Execute ALL fitness functions
│ ├── Run each check command
│ ├── Record: exit code, output snippet, score (0.0 / 0.5 / 1.0 / null)
│ └── Calculate weighted aggregate score:
│ aggregate = sum(score_i × weight_i) / sum(weight_i) for non-null scores
│
├── CHECK CONVERGENCE:
│ ├── If aggregate >= score_threshold → STOP, mark as CONVERGED
│ ├── If iteration == max_iterations → STOP, mark as MAX_ITERATIONS_REACHED
│ └── If all failing checks are UNTESTED (null) → STOP, cannot improve by iteration
│
├── DIAGNOSE: For each failing fitness function (score < 1.0):
│ ├── Identify root cause from command output
│ ├── Classify: missing_implementation | incorrect_behavior | missing_test | build_error | flaky
│ └── Determine if the issue is fixable within verification scope
│ (verification does NOT fix code — it only re-runs checks after external fixes)
│
└── ACT: Report failing functions with diagnosis
├── List each failing fitness function with: name, current_score, diagnosis, suggested_fix
└── NOTE: The verify agent does NOT fix code. The "act" step produces a
diagnosis report that the orchestrator can use to dispatch sdd-apply
for targeted fixes. If the orchestrator re-launches verify after fixes,
the next iteration picks up where it left off.
Key principle: Every spec requirement should have at least ONE executable fitness function. If you cannot construct one, flag it as ❌ UNTESTED with the reason — this is a verification gap that must be addressed.
5e.3: Fitness Score Report
After the converge loop completes, include this section in the verification report:
### Fitness Functions
**Aggregate Score**: {score}% ({converged | max_iterations_reached | cannot_improve})
**Iterations**: {N}/{max}
**Threshold**: {threshold}%
| # | Fitness Function | Weight | Check Type | Score | Status |
|---|-----------------|--------|------------|-------|--------|
| 1 | {name} | {weight} | {check_type} | {score}/1.0 | ✅ / ⚠️ / ❌ |
| 2 | {name} | {weight} | {check_type} | {score}/1.0 | ✅ / ⚠️ / ❌ |
**Diagnosis (failing functions only)**:
- `{name}`: {root_cause_classification} — {description}. Suggested fix: {suggestion}
Step 6: Spec Compliance Matrix (Behavioral Validation)
This is the most important step. Cross-reference EVERY spec scenario against the fitness function results from Step 5e AND the test run results from Step 5b to build behavioral evidence.
For each scenario from the specs, use the fitness function score and test results as evidence:
FOR EACH REQUIREMENT in specs/:
FOR EACH SCENARIO:
├── Use the fitness function score from Step 5e as primary evidence
├── Cross-reference with test results from Step 5b
├── Assign compliance status:
│ ├── ✅ COMPLIANT → fitness function score == 1.0 AND/OR test exists AND passed
│ ├── ❌ FAILING → fitness function score == 0.0 or test failed (CRITICAL)
│ ├── ❌ UNTESTED → fitness function score is null and no test found (CRITICAL)
│ └── ⚠️ PARTIAL → fitness function score == 0.5 or test passes partially (WARNING)
└── Record: requirement, scenario, fitness_function, score, verification method, result
A spec scenario is only considered COMPLIANT when a fitness function scored 1.0 or a test that covers it passed at runtime. Code existing in the codebase is NOT sufficient evidence.
Step 6b: SDD-DoD Shape Checklist (Quality Gate)
Before persisting the verification report, run the sdd-dod skill to enforce
the Definition-of-Done shape checklists. These checklists capture
hard-earned lessons from real shipped bugs (DTO drop-on-floor, sidebar
wiring missed, CHECK constraint inversion, React 19 SyntheticEvent
antipattern) and gate the archive against repeating them.
Read: own/skills/sdd-dod/SKILL.md
Detect shape(s) for the change (see SKILL.md Step 2)
For each detected shape:
├── Walk the shape checklist at shapes/{shape}.md
├── Mark each item ✅ / ❌ / ⚠️
└── For ❌ items: link FIX commit OR add to Open Items in the verify-report
Persist a separate dod-report artifact (topic_key:
sdd/{change-name}/dod-report)
If ANY ❌ remains without fix-or-deferral → return status: blocked,
DO NOT proceed to Step 7.
The DoD report is a separate artifact from the verify-report. Both
persist; verify-report references the DoD report by topic_key.
Step 7: Persist Verification Report
Persist the report according to the resolved artifact_store.mode, following the conventions in skills/_shared/:
- engram: Use
engram-convention.md — artifact type verify-report
- openspec: Write to
openspec/changes/{change-name}/verify-report.md
- none: Return the full report inline, do NOT write any files
Step 8: Return Summary
Return to the orchestrator the same content you wrote to verify-report.md:
## Verification Report
**Change**: {change-name}
**Version**: {spec version or N/A}
---
### Completeness
| Metric | Value |
|--------|-------|
| Tasks total | {N} |
| Tasks complete | {N} |
| Tasks incomplete | {N} |
{List incomplete tasks if any}
---
### Build & Tests Execution
**Build**: ✅ Passed / ❌ Failed
{build command output or error if failed}
**Tests**: ✅ {N} passed / ❌ {N} failed / ⚠️ {N} skipped
{failed test names and errors if any}
**Coverage**: {N}% / threshold: {N}% → ✅ Above threshold / ⚠️ Below threshold / ➖ Not configured
---
### Fitness Functions
**Aggregate Score**: {score}% ({converged | max_iterations_reached | cannot_improve})
**Iterations**: {N}/{max}
**Threshold**: {threshold}%
| # | Fitness Function | Weight | Check Type | Score | Status |
|---|-----------------|--------|------------|-------|--------|
| 1 | {name} | {weight} | {check_type} | {score}/1.0 | ✅ PASS / ⚠️ PARTIAL / ❌ FAIL |
{If any functions failed:}
**Diagnosis**:
- `{name}`: {classification} — {description}. Suggested fix: {suggestion}
---
### Spec Compliance Matrix
| Requirement | Scenario | Fitness Function | Score | Verification Method | Command/Test | Result |
|-------------|----------|-----------------|-------|-------------------|-------------|--------|
| {REQ-01: name} | {Scenario name} | {fn-name} | 1.0 | test assertion | `{test file} > {test name}` | ✅ COMPLIANT |
| {REQ-01: name} | {Scenario name} | {fn-name} | 0.0 | grep assertion | `rg "pattern" path/to/file` | ❌ FAILING |
| {REQ-02: name} | {Scenario name} | — | null | (none constructed) | — | ❌ UNTESTED |
| {REQ-02: name} | {Scenario name} | {fn-name} | 0.5 | file existence | `path/to/file.ext exists` | ⚠️ PARTIAL |
**Compliance summary**: {N}/{total} scenarios compliant
---
### Correctness (Static — Structural Evidence)
| Requirement | Status | Notes |
|------------|--------|-------|
| {Req name} | ✅ Implemented | {brief note} |
| {Req name} | ⚠️ Partial | {what's missing} |
| {Req name} | ❌ Missing | {not implemented} |
---
### Coherence (Design)
| Decision | Followed? | Notes |
|----------|-----------|-------|
| {Decision name} | ✅ Yes | |
| {Decision name} | ⚠️ Deviated | {how and why} |
---
### Issues Found
**CRITICAL** (must fix before archive):
{List or "None"}
**WARNING** (should fix):
{List or "None"}
**SUGGESTION** (nice to have):
{List or "None"}
---
### Verdict
{PASS / PASS WITH WARNINGS / FAIL}
**Fitness Score**: {aggregate}% (threshold: {threshold}%)
{One-line summary of overall status}
Critical Rules
- Tool Call Budget Cap: You have a maximum of 20 tool calls per verification run. Track your tool call count. If you reach 15 calls without completing verification, STOP, summarize what you verified so far, what remains unchecked, and return to the orchestrator. Do NOT spiral into retry loops chasing flaky tests or build errors. A partial verification report with clear "NOT VERIFIED" markers is more useful than an exhausted context window.
Rules
- ALWAYS read the actual source code — don't trust summaries
- ALWAYS execute tests — static analysis alone is not verification
- A spec scenario is only COMPLIANT when a test that covers it has PASSED
- Compare against SPECS first (behavioral correctness), DESIGN second (structural correctness)
- Be objective — report what IS, not what should be
- CRITICAL issues = must fix before archive
- WARNINGS = should fix but won't block
- SUGGESTIONS = improvements, not blockers
- DO NOT fix any issues — only report them. The orchestrator decides what to do.
- EVERY spec scenario MUST have a declarative executable check — if you cannot construct one, flag it as a verification gap
- Prefer concrete executable checks (exit codes, grep matches, file existence) over subjective "looks correct" assessments
- In
openspec mode, ALWAYS save the report to openspec/changes/{change-name}/verify-report.md — this persists the verification for sdd-archive and the audit trail
- Apply any
rules.verify from openspec/config.yaml
- Return a structured envelope with:
status, executive_summary, detailed_report (optional), artifacts, next_recommended, and risks