API Spec (producer)
Author the custom-YAML API spec at docs/api/ — the spec-first source of truth for every HTTP endpoint, from which the whole api-doc chain derives (openapi-doc drift-checks Go against it; open-collection renders a runnable Bruno collection; confluence-api-doc publishes Confluence pages). This skill is the producer — it writes docs/api/*.yaml; the three consumers only read it. Every spec rests on evidence (a deterministic script) + an independent fresh-eyes pass + a completeness sweep, never on the running agent's confidence.
ASSET_DIR = <skill base dir>/assets, SKILL_DIR = <skill base dir> (the skill-load message gives the "Base directory for this skill"). Needs PyYAML (same dependency as the rest of the api-doc chain).
Output location
docs/api/
_meta.yaml # global: title · version · base_url · overview · field_info · common_errors · domains
<domain>/<endpoint>.yaml # one endpoint per file (domain = the resource group, e.g. account/, balance/)
index.md # navigation INDEX — GENERATED by apispeccheck.py (never hand-edit)
VERSION.md # changelog (hand-maintained markdown, newest section first)
The full schema (every key, field-object rules, error rules, notes discipline) lives in references/api-spec-template.md — read it before authoring. The api-spec is YAML/markdown, never HTML, and is not OpenAPI.
Mode
Four modes. Draft and Generate both author a spec where none exists yet; the difference is intent and rigor — Draft is a provisional pre-plan design pass, Generate is the authoritative author. Auto-detect (user can override):
- Draft (design-first) — signalled by using-neo's API step routing here, or a "draft / ออกแบบ … ก่อน" request: author a provisional contract from intent only (acceptance criteria / requirements /
docs/knowledge/), before any code exists, so a feature has a contract to design against from the start. It writes the samedocs/api/*.yamlas Generate — single producer, single location (not a separate artifact); its provisional nature is contextual — it is drafted before the code exists — with no marker in the spec. Draft authors the structural contract only — method/path/auth, request/response fields,errors, andcovers_ac— and omitsbusiness_logic: the per-endpoint flow is a design inference that must not be pinned before the code exists, so it is left out and added later (once the real implementation flow is known, e.g. during BUILD or the DOC-step sync-back), then preserved by Update-from-code. Lighter verify (L1 + L3 only — see Step 3). The DOC-step Update-from-code later reconciles this draft against the built code. - Generate — no
docs/api/(or no_meta.yaml) at the target, and not a Draft request → author the spec from intent (full three-layer verify). - Update — a spec already present → apply the delta from intent (a new endpoint, a changed field), or reconcile the spec against built code. The DOC-step Update-from-code (sync-back) reads the Go source and syncs only the structural surface — routes, request/response fields, types — into the spec, preserving all hand-authored M/O,
business_logic,remark, anderrors(a structural sync, never a regenerate-over-top). - Validate — request says "validate/check/เช็ค/ตรงกับ … ไหม" with a spec present → no writes.
Step 1 · Locate the source-of-intent + the spec root
- Spec root — default
docs/api/at the repo root; in a monorepo scope it to the chosen service (e.g.services/<name>/docs/api/). Ask if ambiguous. - Source-of-intent — what the spec must reflect (the input the spec is authored from):
- Draft — intent only: acceptance-criteria (
docs/tasks/<card>/spec.md) / requirements / a JIRA card /docs/knowledge// the user's description. Exclude code — the code does not exist yet (and is not consulted even if some does). No locatable AC/requirements → STOP and ask; never invent an endpoint surface. - Update / Validate — the existing
docs/api/*.yamlis the baseline; the delta is the change request (a new endpoint, a changed field), or — for Update-from-code (sync-back) — the built Go source being reconciled back into the spec. - Generate — gather the intent from whatever exists: a requirements doc / acceptance-criteria (
docs/tasks/<card>/spec.md) / a JIRA card /docs/knowledge// the user's description / existing handler+struct code to document. No locatable intent at all → STOP and ask for the requirements; never invent an endpoint surface.
- Draft — intent only: acceptance-criteria (
- Read
CLAUDE.md/AGENTS.md/READMEfor the service name, base URL, and domain grouping. - When driven from a task spec (using-neo) the intent is the spec's acceptance criteria; record the AC-IDs each endpoint satisfies in
covers_ac. Standalone, omitcovers_ac.
Step 2 · Author / update the endpoint YAML
Write per references/api-spec-template.md:
_meta.yaml— service title/version/base_url/overview, sharedfield_infoenums,common_errors(errors every endpoint may return), per-domainseq/title indomains, andextra_endpointsfor index-only rows (e.g. a health probe).<domain>/<endpoint>.yaml— one file per endpoint: method/path/auth,path_params/query_params/request_bodyfield tables,responses(with nestedobjects:),business_logic(preserve multi-flow as bold-subheaded numbered lists — Generate/Update only; Draft omits it, see Mode), endpoint-specificerrors, andcovers_acwhen driven from a spec. A field's element-specific note → itsremark; only a genuinely cross-cutting note →notes:(omit when empty).- Update — touch the minimum; preserve hand-authored prose; re-run the L1 check after. For Update-from-code (DOC-step sync-back): read the Go source and sync only the structural surface (routes / fields / types); preserve M/O,
business_logic,remark, anderrors— never regenerate over hand-authored semantics. - VERSION.md — append a
## v<N> — <date>section (newest first) per change.
Do not scan Go to author (Draft / Generate / intent-only Update) — those are authored from intent. Only Update-from-code reads Go, and only to reconcile the structural surface against a built implementation (sync-back); the read-only drift report remains openapi-doc.
Step 3 · Three-layer verify
Draft mode runs a lighter verify — L1 + L3 only; skip L1.5/L2. A draft is a provisional design pass, so it must be structurally valid (L1) and cover every AC that needs an endpoint (L3), but the independent fresh-eyes semantic pass (L2) is deferred to the DOC-step Update that reconciles the draft against built code — the point at which the contract is finalized and worth an independent semantic review. Generate / Update run all three layers below.
verify-L1 · Script tripwire (always)
python3 <ASSET_DIR>/apispeccheck.py docs/api # validates every endpoint YAML + regenerates index.md
It checks each endpoint parses + has the required keys; mandatory ∈ {M,O}; every example JSON parses; every object: reference resolves; _meta.yaml is well-formed; and it rewrites index.md so it can never drift. Tripwire, not ground truth.
- exit 0 →
PASS — 0 error(s)→ go to L1.5. - exit 1 → for each ERROR, open the actual YAML, fix the real defect, re-run. Loop until exit 0, OR ~3 rounds with no progress → STOP and escalate. Never fake a green run.
verify-L1.5 · Offer fresh-eyes (default yes)
Ask once via AskUserQuestion: "Run an independent fresh-eyes verify of the authored api-spec? (default: yes)" — no → skip L2 (mark "skipped by user"); yes → L2.
verify-L2 · Fresh-eyes semantic verifier (independent agent)
Dispatch a verifier that did not author the spec — it re-reads the source-of-intent and the YAML independently and judges the semantic fidelity the script cannot (does the contract reflect the intent, M/O vs business rules, errors covering failure paths, business_logic matching the real flow, example consistency):
Agent(subagent_type: "fresh-eyes", description: "verify api-spec", prompt: """
# Role: API-Spec Semantic Verifier
Read first: <SKILL_DIR>/references/api-spec-verifier.md
SKILL_DIR = <skill base dir>
## Task
Independently verify the api-spec just authored against its source-of-intent. Check ONLY
semantic fidelity (not apispeccheck.py's structural checks). Read the intent AND docs/api/*.yaml yourself.
## Under review
docs/api/<domain>/*.yaml (+ docs/api/_meta.yaml)
## Source-of-intent
<paste the intent paths: requirements / docs/tasks/<card>/spec.md / JIRA card / docs/knowledge/ / code>
End with Status: DONE | DONE_WITH_CONCERNS | BLOCKED
""")
SKILL_DIR is mandatory — without it the verifier cannot read its role file and fails silently. The verifier is read-only by tool grant — fresh-eyes holds no write/edit (harness without that type → general-purpose, read-only by instruction only) → you reconcile the YAML → re-run apispeccheck.py. Do not auto-redispatch; offer a second round (default yes), then escalate.
verify-L3 · Completeness sweep (omission critic)
L1/L2 inspect what is present; L3 catches what is missing entirely. Re-enumerate the full intended endpoint inventory from the source-of-intent (every endpoint the requirements/AC call for, or — in sync-back — every route an openapi-doc drift report flagged) and confirm: every intended endpoint has a docs/api/<domain>/*.yaml, every endpoint is reachable from index.md, every covers_ac AC-ID (when driven from a spec) is real, and no endpoint file is an orphan with no intent behind it. Report any whole endpoint the pipeline silently dropped; fix → re-run L1.
Output
## API Spec — <Generate / Update / Validate>
**Spec:** docs/api/ (N endpoints) **Source-of-intent:** <what it was authored from>
**Changes:** Created … / Updated … / Removed …
**Verification (three-layer):**
- L1 apispeccheck.py: ✅ PASS (0 error) / ❌ ESCALATED (N error after ~3 rounds) · loop rounds: 0-3
- L2 fresh-eyes: ✅ Faithful / ⚠️ N gaps fixed / ⏭ Skipped / ⏸ Not run
- L3 completeness sweep: ✅ all intended endpoints covered / ⚠️ N silent omissions fixed
- Verdict: ✅ in sync / ⚠️ gaps to reconcile / ⏸ escalated
**Warnings:** spec-first endpoints not yet built, open questions surfaced for a human
What this skill is NOT
- Not a read-only drift reporter — producing a report of whether the Go code matches the spec without writing is
openapi-doc. This skill reconciles the spec to code (Update-from-code, which writes); the pure drift report stays withopenapi-doc. - Not a Bruno OpenCollection generator (
open-collection) or a Confluence publisher (confluence-api-doc) — both also readdocs/api/*.yaml. This skill is the single producer; those three are consumers. - Not an OpenAPI / Swagger / Postman converter — the custom-YAML api-spec is the single source of truth (it carries M/O + Remark + multi-flow business logic that OpenAPI cannot without
x-hacks). There is no OpenAPI intermediate in this chain. - Authoring never scans Go — Draft / Generate / intent-only Update are authored from intent, before the code. The one exception is Update-from-code (DOC-step sync-back), which reads Go to reconcile the structural surface (routes / fields / types) into an existing spec while preserving hand-authored semantics.