PROMPT
Swagger Api Synchronization
Codingπ 0
PROMPT CONTENT
# Set up an OpenAPI spec-sync workflow in this project
I want the same backend-spec workflow I use in another repo: a script that diffs the live
OpenAPI spec against a local snapshot and writes a **frontend-oriented** change report, plus a
`/api-sync` slash command that turns that report into a phased plan.
Fill these in from the repo before you start (ask me only if you can't work it out):
- **Spec source**: find it yourself β see Part 0. Don't ask me for the URL until you've looked.
Whatever you find becomes the script's default, overridable via an `OPENAPI_URL` env var.
- **Snapshot path**: `api-spec/openapi.yaml` Β· **Report path**: `api-spec/CHANGES.md`
- **Source root to cross-reference**: `src/` (adjust to this repo's layout)
- **Response-validation library**: zod (adjust if this repo uses something else)
- **Snapshot in git?** Keep the yaml **gitignored** (too large/noisy for history) but **commit
`CHANGES.md`** β the generated report is the durable record of what changed and when. Also
ignore `api-spec/openapi-*.yaml` and `api-spec/CHANGES-*.md` (dated manual archives).
Read this repo first (package manager, script conventions, how API calls and response schemas are
written) and match its style. Don't invent paths β grep for the real ones.
---
## Part 0 β find the spec before you write anything
Do this first and tell me what you found. Don't guess a URL, and don't ask me until this comes up
empty.
**1. The docs β cheapest place, and usually right.** `README.md`, `CLAUDE.md`,
`AGENTS.md`, `CONTRIBUTING.md`, anything under `docs/`, `.github/`, `.cursor/rules/`, a
`*.http`/`*.rest` scratch file, or a wiki checkout. The URL is often in prose ("API docs:
β¦/swagger"), in a setup step, or next to the backend repo link:
```bash
grep -rniE 'swagger|openapi|api-?docs|redoc|\/v3\/api-docs' --include='*.md' --include='*.mdx' --include='*.txt' --include='*.http' --include='*.rest' . | grep -v node_modules
```
Two gotchas: a **Swagger UI link** (`β¦/swagger-ui/index.html`, `β¦/docs`, `β¦/redoc`) is an HTML
page, not the spec β derive the machine URL from it (`/swagger-ui/index.html` β
`/v3/api-docs`, `/docs` β `/openapi.json`, `/redoc` β the `spec-url` in its HTML) and verify with
curl. And a docs URL may be **stale** β confirm it answers before adopting it, and tell me if the
README points somewhere dead.
**2. A spec file already in or near the repo** β someone usually vendored one:
```bash
find . -path ./node_modules -prune -o -iregex '.*\(swagger\|openapi\|api-docs\).*\.\(ya?ml\|json\)' -print
git ls-files | grep -iE 'swagger|openapi|api-docs'
```
Also check `node_modules/.cache/`, `.next/cache/`, `dist/`, `build/`, `coverage/` and any
gitignored `api/`, `api-spec/`, `docs/`, `schemas/` folder β a previous codegen run often left a
copy there. A stale cached copy is still useful: it's a **baseline to seed the snapshot with**,
so the first real diff is meaningful instead of "everything is new". If you find one, say how
old it is (`git log -1` / file mtime) before deciding to trust it.
**3. A generator config that already names the source** β this is the highest-signal hit, because
it points at whatever URL or path the team actually uses:
```bash
grep -rniE 'openapi|swagger|api-docs' --include='*.json' --include='*.ts' --include='*.js' --include='*.mjs' --include='*.yaml' --include='*.yml' --include='.env*' --include='Makefile' --include='*.sh' -l . | grep -v node_modules
```
Look specifically for: `openapi-typescript` / `orval.config.*` / `kubb.config.*` /
`swagger-typescript-api` / `@hey-api/openapi-ts` config, an `openapi`-ish npm script in
package.json, a `.env*` API base URL, `docker-compose.yml` service URLs, CI workflow steps, or a
committed generated client whose header comment cites its source spec.
**4. Derive it from the API base URL.** If you only find a base URL, probe the conventional paths
for that backend's framework before asking me β FastAPI `/openapi.json`, Spring/springdoc
`/v3/api-docs` (+ `.yaml`), ASP.NET `/swagger/v1/swagger.json`, NestJS `/api-json`, Rails/rswag
`/api-docs/v1/swagger.yaml`, plus plain `/openapi.yaml` and `/swagger.json`:
```bash
curl -sS -o /dev/null -w '%{http_code} %{content_type} %{url_effective}\n' <BASE>/openapi.json
```
Report which ones answered. If they all need auth, say so β don't bake a token into the script.
**5. Nothing works?** Then ask me, and tell me what you ruled out.
### If the spec isn't reachable over HTTP
Don't force the fetch design. Make the source a single `SPEC_SOURCE` that may be **a URL, a local
path, or a shell command** (e.g. the backend repo's own `make openapi`, or a sibling checkout's
generated file), resolved in that order: `--to <file>` flag β `OPENAPI_URL` env β the default you
discovered. Everything downstream β diff, report, snapshot β is unchanged. Say in `CLAUDE.md`
which one this repo uses and how to refresh it.
## Part 1 β `scripts/sync-api.mjs`
A single dependency-light Node ESM script (`js-yaml` is the only new dep; use the repo's package
manager). Flags:
```
node scripts/sync-api.mjs fetch remote β diff vs snapshot β write report + overwrite snapshot
node scripts/sync-api.mjs --check diff only, snapshot untouched, exit 1 if it drifted (CI-friendly)
node scripts/sync-api.mjs --from <file> diff against <file> instead of the snapshot
node scripts/sync-api.mjs --to <file> treat <file> as "remote" instead of fetching (offline)
node scripts/sync-api.mjs --json also print the raw diff as JSON on stdout
```
Add `"sync:api": "node scripts/sync-api.mjs"` to package.json.
**If no snapshot exists yet**: write the fetched spec as the snapshot, print "seeded β re-run after
the backend ships to see a diff", exit 0. Never report the whole API as "new". Exception: if Part 0
turned up an older cached/vendored spec, seed the snapshot from **that** instead and run a real
diff against the live spec on the first run β tell me the cached copy's date so I know what the
baseline means.
### What it must diff
Flatten `paths` into a `"GET /a/b"` β operation map and diff operations *and*
`components.schemas` separately:
**Operations**
- added / removed / changed
- params: new ones (flag `required`), requiredβoptional flips, enum values added/removed β
key a param by `in:name` (or `ref:Name` for `$ref` params), not by array index
- request body and success-response schema: if the `$ref` name changed, report the rename; if the
shape is **inline** (no `$ref`), diff its properties here β an unnamed schema is diffed here or
nowhere. Resolve `allOf: [$ref]` wrappers to the underlying name, and render `oneOf`/`anyOf`
unions as `A | B` (gaining/losing a union member is a real behavioural change).
- new/removed non-2xx status codes
- security requirement changes, newly `deprecated`
**Schemas**
- properties added (mark required) / removed / retyped
- enum values added or removed (on the schema and on each property, including `items.enum`)
- requiredβoptional flips
### The two things that make this report worth having
1. **`error_code` extraction from response prose.** Machine error codes are usually documented
nowhere but each non-2xx response's `description` text ("β¦ already an active member
(already_member)"), so a new branch we need to handle looks like *nothing changed* to a
schema-level diff. Parse them by **context, not vocabulary**: tokens inside `(...)`, tokens
after `error_code`-ish prose, and any snake_case token that appears in some schema's literal
`error_code` enum. Do **not** filter out tokens that collide with field names or enum values β
those collisions are exactly the codes that matter most. Drop tokens introduced by "field X"
phrasing (those are field names, not codes). Report added codes, and for a code that stopped
being documented, grep the source for `"that_code"` and say **which file branches on it** β
that's a dead branch.
2. **Cross-reference every change against the actual code.** Load every source file once via
`git ls-files --cached --others --exclude-standard <src root>` (include untracked so a call
site added this session counts; skip files listed but deleted from the working tree), then:
- **Call sites** for a path: turn path params into single-segment wildcards and require the
match to end at a quote/backtick/`?` so `/orgs/{id}` doesn't match `/orgs/${id}/archive`.
Every removed/changed operation lists its call sites, or "β¬ no call site".
- **Schema mirrors**: find the file holding our response-validation mirror of a spec schema β
match `fooBarSchema` anywhere, plus the bare PascalCase name **only inside a `schemas.ts`**
(elsewhere it collides with unrelated TS identifiers). Adapt the naming convention to
whatever this repo actually uses β grep first.
- A new operation whose path is already referenced in `src/` gets a "β οΈ path already
referenced β check the method" note.
### Report format (`api-spec/CHANGES.md`)
Header with generation date, baseline label, spec `info.version`, and beforeβafter operation and
schema counts; then a small added/removed/changed table; then sections, in this order:
- `## π΄ Removed operations β breaking if we call them` (with call sites)
- `## π Changed operations` (nested bullets per change + call sites)
- `## π’ New operations`, grouped by path area (first segment, with sensible special cases for
this API's prefixes) β summary, response schema, request-body schema
- `## π΄ Removed schemas` (with mirror files)
- `## π Changed schemas` β **mirrored ones sorted first and bolded with the mirror file**, since
those are what can break parsing today; the rest are informational
- `## π’ New schemas` (one comma-separated line)
If nothing changed, the body is exactly "No changes since the last snapshot." Top the file with
"do not edit by hand".
Keep the script commented where a decision is non-obvious (the error_code heuristic, the path
matcher's end anchor, why untracked files are included) β future-me reads those.
## Part 2 β `.claude/commands/api-sync.md`
A slash command (`/api-sync [scope]`, scope optional, also accepts `implement`) that runs the
workflow. Frontmatter: `description` + `argument-hint`. Steps:
1. **Diff** β run `npm run sync:api`, read `api-spec/CHANGES.md`. Call out the two judgement
calls the report can flag but not decide: a changed **request body** on a live call site is
actionable even with no schema mirror (we build bodies by hand), and a new **`error_code`** is
a branch we don't have yet β if it's a field error it must render inline on the field, not
just as a toast. If the report says no changes, say so and stop β don't invent work.
2. **Classify every item** into: Breaking (P0) Β· Silently wrong β a mirrored schema gained a
required field or an enum grew values our validator rejects (P0) Β· Now-incomplete β a
hand-built request body gained a field, or a new `error_code` we don't branch on (P1) Β·
Un-mocks a screen (P1) Β· Extends a screen (P2) Β· Net-new feature (P3) Β· Backend-only (drop).
Never skip an item; if it fits nowhere, list it as an open question. Verify each
classification against the code rather than assuming β open the named mirror file, grep for
the mock fixture, confirm the screen exists.
3. **Write the plan** β a dated section in `ROADMAP.md` (or this repo's equivalent; create one if
there's none), ordered by those priorities and phased so each phase ships independently. Per
item: the endpoints and files that change, what the user can do afterwards that they can't
today (the point of the work β not "wire endpoint X"), and whether it's blocked and on whom.
One line per item. Then update whatever coverage/tracker docs this repo keeps.
4. **Report back in chat** β what the backend shipped in one plain-language paragraph, anything
broken right now with the file to fix, the phases one line each, and genuine questions for the
backend dev only. Stop there; only if the argument contains `implement`, build **Phase 1 only**,
then run this repo's typecheck + lint and report before continuing.
## Part 3 β wire it in
- Add the gitignore entries.
- Add a short **Backend-change workflow** section to `CLAUDE.md` (create it if missing): the
snapshot is local and gitignored, `CHANGES.md` is the committed record, `CHANGES.md` is
generated so never hand-edit it, `/api-sync` when the backend dev says something shipped,
`npm run sync:api -- --check` to detect drift, and the habit of archiving a dated
`api-spec/openapi-YYYY-MM-DD.yaml` before a big backend change as a committed reference point.
- Seed the snapshot by running the script once, and show me the first report β plus a one-line
note on where the spec came from and, if you seeded from a cached copy, how stale it was.RELATED PROMPTS
COMMENTS
No comments yet β be the first.
