---
name: acceptance-criteria-gen
description: 'Generates acceptance criteria tied to observable outcomes. Use when a request enters planning or solutioning and the brief has goals but not enough precise behavior to verify.'
metadata:
  model_tier: reasoning
  cacheable: 'false'
  output_format: markdown
  paqad-meta: skill.meta.yaml
---

## What It Does

Turns a scoped request into testable acceptance criteria that describe observable behavior, guardrails, and failure handling without relying on implied implementation details.

## Use This When

When a request is entering planning or solutioning and the current brief has goals but not enough precise behavior to verify. It is especially important before stories, test mapping, or handoff artifacts are written.

## Inputs

- Read the active request, tracker entry, or plan summary first.
- Read the canonical module docs or feature spec that defines the current behavior.
- Read `references/criteria-template.md` before drafting criteria so wording stays consistent.

## Procedure

1. Run `scripts/extract-ac-ids.sh <spec-file>` to get the set of AC ids already taken; never reuse a removed id.
2. For each new criterion, run `scripts/next-ac-id.sh <spec-file>` to allocate the next free flat id.
3. List the user-visible outcomes and constraints; convert each into one observable Given/When/Then line using `assets/output.template.md` as the shape, ending each line with an explicit `(proof: automated|manual|visual)` tag.
4. Surface negative and edge paths with the shared rubric instead of relying on the happy path alone: run `edge-case-detection` (`runtime/base/skills/edge-case-detection/SKILL.md`) over the same request as a sub-step. It is reused as-is (do not clone it) and its category set (empty, stale, loading, retry, permission, concurrency, rollback, integration, overflow, state-skip) already covers the paths acceptance criteria must guard. Follow each returned case's `Apply To:` routing: fold a case that changes an error, empty, or permission path the request actually touches into a flat `AC-n` negative-path criterion here (Given the failing/empty/unauthorized condition, when …, then the observable guard holds); record a case that is a deliberate exclusion (an edge behaviour the change will not handle) under the spec's `## Non-goals` section (parsed into `FeatureSpec.non_goals`) rather than as a criterion. Drop cases that are not credible for this request, and drop criteria that describe implementation choices instead of behavior.
5. Validate the draft with `scripts/lint-ac-output.sh <draft-file>`: exit 0 means the structural contract is met; non-zero means fix and re-lint before returning.

## Output Contract

- Match `assets/output.template.md`: a `## Acceptance criteria` heading, then one flat `- AC-N: Given …, when …, then … (proof: automated|manual|visual).` bullet per criterion, ending with a `## Coverage Notes` block.
- Use FLAT `AC-N` identifiers only. Dotted two-level `AC-N.N` ids are forbidden: the freeze parser reads flat ids and would renumber or drop a dotted one, so a block passing this skill can never fail the freeze on shape.
- Every criterion line carries an explicit `(proof: …)` tag so the frozen criterion has a proof type.
- Identifiers must be stable across spec revisions: removed criteria do not have their identifier reused.
- The output must pass `scripts/lint-ac-output.sh` with exit 0 before returning.

## Escalate / Stop Conditions

- Ask for clarification when the request does not define the actor, trigger, or expected result.
- Warn when canonical docs conflict with the requested behavior and note the drift explicitly.
- Stop short of inventing hidden business rules; record the gap instead.

## Resources

- `references/criteria-template.md`
- `scripts/extract-ac-ids.sh`
- `scripts/next-ac-id.sh`
- `scripts/lint-ac-output.sh`
- `assets/output.template.md`
- `runtime/base/skills/edge-case-detection/SKILL.md`
- `runtime/capabilities/coding/checklists/edge-cases-coding.md`
- `agents/openai.yaml`
