| name | arch:openapi-contract |
| argument-hint | [issue_number] |
| description | Generates an OpenAPI 3.1.0 spec for new or changed HTTP endpoints in a story. Reads scout.yaml and relevant handler files; writes api-{slug}.openapi.yaml to docs/stories/{id}/design/. Skips cleanly if no HTTP endpoints are detected.
|
| context | fork |
| allowed-tools | ["Read","Glob","Grep","Bash","Write"] |
| write-paths | ["docs/"] |
OpenAPI Contract Generator
Produces a minimal but complete OpenAPI 3.1.0 spec covering only the new or changed
HTTP endpoints introduced by a story. Callable standalone or from /arch:design-implementation.
Invocation:
/arch:openapi-contract 460 # for a specific issue
/arch:openapi-contract # uses $AGENT_DOCS_DIR/active-story.yaml
Constraints
- Write:
docs/ only
- Read: everything else — read-only
- One spec file per story; re-running overwrites the existing spec
Workflow
Step 1 — Resolve issue number
if [ -n "$ARGUMENT" ]; then
ISSUE_NUMBER="$ARGUMENT"
else
ISSUE_NUMBER=$(yq e '.issueNumber' "${AGENT_DOCS_DIR:-docs}/active-story.yaml" 2>/dev/null)
fi
if [ -z "$ISSUE_NUMBER" ] || [ "$ISSUE_NUMBER" = "null" ]; then
echo "ERROR No issue number. Pass one or run /github:story-fetch first."
exit 1
fi
REPOSITORY=$(gh repo view --json nameWithOwner --jq '.nameWithOwner')
SCOUT_YAML="docs/stories/${ISSUE_NUMBER}/scout.yaml"
Guard: if $SCOUT_YAML does not exist → exit with error pointing to /scout:prepare-for-dev.
Step 2 — Extract handler/route files from scout
Read docs/stories/{id}/scout.yaml. Collect all change_list entries whose file path
matches any of:
*handler*, *router*, *route*, *controller*, *api*
- common extensions:
.ts, .js, .py, .go, .java
Also collect the story body:
STORY=$(gh issue view $ISSUE_NUMBER --repo $REPOSITORY --json title,body --jq '{title,body}')
Step 3 — Detect HTTP endpoints
Grep each candidate file for HTTP method markers:
grep -n -E "@(Get|Post|Put|Patch|Delete|Head|Options)\(|router\.(get|post|put|patch|delete)\(|app\.(get|post|put|patch|delete)\(|express\.Router" FILE
grep -n -E "@(app|router|blueprint)\.(get|post|put|patch|delete)\(|@api\.(get|post|put|patch|delete)"
grep -n -E "(GET|POST|PUT|PATCH|DELETE)\s+['\"/]"
If no HTTP endpoints detected in any file and story body does not mention "endpoint", "API",
"route", "REST", "HTTP" → print:
arch:openapi-contract: no HTTP endpoints detected for issue #{id} — skipping
Exit 0.
Step 4 — Read NFR registry for security requirements
Read nfr-registry.yaml (locate via Glob for *nfr-registry*). Extract security and
performance NFRs to inform components.securitySchemes and response time SLAs in descriptions.
Step 5 — Generate OpenAPI 3.1.0 spec
Determine {slug}:
- Derive from the primary service/domain name in the detected file paths (2–3 words, kebab-case)
- Example:
lambda/payments/handler.ts → payments-api
Produce the YAML spec:
openapi: "3.1.0"
info:
title: "{Service} API"
version: "0.1.0-design"
description: |
Design-phase spec for story #{issue_number}: {title}.
Generated by /arch:openapi-contract on {YYYY-MM-DD}.
Status: proposed — subject to change before implementation.
servers:
- url: "https://api.example.com/v1"
description: Production
security:
- BearerAuth: []
paths:
/{resource}:
{method}:
operationId: {camelCaseOperationId}
summary: "One-line summary"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/{RequestSchema}"
responses:
"200":
description: Success
content:
application/json:
schema:
$ref: "#/components/schemas/{ResponseSchema}"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
{RequestSchema}:
type: object
required: []
properties: {}
{ResponseSchema}:
type: object
properties: {}
ErrorResponse:
type: object
required: [code, message]
properties:
code:
type: string
message:
type: string
responses:
BadRequest:
description: "Validation error"
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
Unauthorized:
description: "Missing or invalid token"
Forbidden:
description: "Insufficient permissions"
NotFound:
description: "Resource not found"
InternalError:
description: "Unexpected server error"
Step 6 — Write file
mkdir -p "docs/stories/${ISSUE_NUMBER}/design"
OUTPUT="docs/stories/${ISSUE_NUMBER}/design/api-${SLUG}.openapi.yaml"
Write the generated YAML to $OUTPUT.
Print:
arch:openapi-contract: wrote docs/stories/{id}/design/api-{slug}.openapi.yaml
Output
| File | Description |
|---|
docs/stories/{id}/design/api-{slug}.openapi.yaml | OpenAPI 3.1.0 spec (design phase) |
Related Skills
| Skill | Role |
|---|
/arch:design-implementation | Orchestrator — calls this skill conditionally |
/arch:asyncapi-contract | Sibling — generates event-driven contracts |
/arch:design-review | Posts this file to GitHub for review |
/scout:prepare-for-dev | Must run first; writes scout.yaml consumed here |