|
| 1 | +--- |
| 2 | +description: Scan the b3 API server against docs/platform/api and update the docs to match |
| 3 | +argument-hint: "[path to b3 checkout] [--report-only]" |
| 4 | +allowed-tools: Bash, Read, Edit, Write, Grep, Glob |
| 5 | +--- |
| 6 | + |
| 7 | +# Sync `docs/platform/api` with the b3 API server |
| 8 | + |
| 9 | +`$ARGUMENTS` |
| 10 | + |
| 11 | +`b3` is the Go API server behind these docs. This command finds every place the |
| 12 | +two have drifted and fixes the docs. Default b3 checkout: |
| 13 | +`../b3` relative to this repo (i.e. `go.bytebuilders.dev/b3`). If `--report-only` |
| 14 | +is passed, produce the ranked findings list and stop — change nothing. |
| 15 | + |
| 16 | +Docs never drive code: **b3 is always the source of truth.** Never edit b3 to |
| 17 | +match the docs, and never invent an endpoint the router does not register. |
| 18 | + |
| 19 | +## 1. Extract the route table |
| 20 | + |
| 21 | +```bash |
| 22 | +B3=${1:-../b3} |
| 23 | +python3 hack/api-doc-sync/extract_routes.py "$B3/routers/api/v1" > /tmp/routes.txt |
| 24 | +python3 hack/api-doc-sync/compare.py /tmp/routes.txt docs/platform/api \ |
| 25 | + docs/platform/api/openapi.yaml > /tmp/api-diff.txt |
| 26 | +``` |
| 27 | + |
| 28 | +`extract_routes.py` walks the registration call graph from `RegisterRoutes` and |
| 29 | +`RegisterMarketplaceServiceRoutes`, so a helper called inside |
| 30 | +`m.Group("/user", …)` correctly gets the `/user` prefix. Read its docstring |
| 31 | +before trusting an odd-looking path. |
| 32 | + |
| 33 | +**Baseline:** the last full run extracted **547 routes** (545 unique |
| 34 | +method+path) and ended with complete md and openapi.yaml coverage. |
| 35 | + |
| 36 | +**Sanity gate:** if the route count dropped sharply versus the baseline, or many |
| 37 | +paths look truncated, the extractor has stopped understanding a new registration |
| 38 | +style (a new `m.Combo` chain shape, a `m.Group` whose path is a constant, a |
| 39 | +helper invoked as `pkg.Register(m)` rather than `register(m)`). Fix the |
| 40 | +extractor first — a silently short route table produces confidently wrong docs. |
| 41 | + |
| 42 | +## 2. Triage the diff |
| 43 | + |
| 44 | +Work the sections in this order; the first three are actionable, the last is |
| 45 | +advisory. |
| 46 | + |
| 47 | +1. **in openapi.yaml but NOT in code** — a documented path that no longer |
| 48 | + exists, or a *wrong* path. Before deleting anything, grep the code for the |
| 49 | + handler name: a "missing" path is usually a real endpoint under a different |
| 50 | + prefix (a helper moved inside another `m.Group`), and the fix is to rename the |
| 51 | + path, not drop it. |
| 52 | +2. **in CODE but NOT in md** — undocumented endpoints. Write them up (step 3). |
| 53 | +3. **in CODE but NOT in openapi.yaml** — add the path/operation to the spec. |
| 54 | +4. **in md but NOT in code** — advisory. Overview tables put several verbs on one |
| 55 | + line (`| GET/POST/DELETE |`) and curl examples contain concrete values, so |
| 56 | + most entries here are noise. Only chase one if it names a plausible endpoint |
| 57 | + that `grep` cannot find in b3 at all. |
| 58 | + |
| 59 | +Known-good false positive — leave it alone: |
| 60 | + |
| 61 | +- `ANY /*` — the `/api/v1` catch-all 404 handler (`miscellaneous.go`). It is not |
| 62 | + an endpoint and is deliberately absent from the docs and the spec. As of the |
| 63 | + last run it is the **only** entry in both code→md and code→openapi. |
| 64 | + |
| 65 | +`compare.py` already resolves the doc conventions this repo uses: absolute |
| 66 | +headings, headings relative to a page's declared root ("All routes on this page |
| 67 | +are rooted at `/api/v1/user/contracts`"), several verbs sharing one path |
| 68 | +(`PUT · PATCH /x`, `| POST/GET/DELETE |`), `/api/v1`-prefixed table entries, and |
| 69 | +abbreviated `### GET .../kubeDb/views/x` headings (matched as a path suffix). If a |
| 70 | +new page invents another convention, teach the tool rather than rewriting the |
| 71 | +page — but a page whose relative headings state no root cannot be matched, so |
| 72 | +that page does need a root sentence. |
| 73 | + |
| 74 | +For each surviving finding, verify against the source before writing a word: |
| 75 | +open the `file:line` the extractor printed, read the handler, and derive the |
| 76 | +request/response shape from the bound payload type (`binding.Json(X{})` / |
| 77 | +`bind(X{})`) and what the handler actually writes (`ctx.JSON(...)`). Cite what |
| 78 | +you read. If a shape cannot be determined from the code, say so on the page |
| 79 | +instead of inventing fields. |
| 80 | + |
| 81 | +## 3. Update the docs |
| 82 | + |
| 83 | +- Put each endpoint on the page for its API group — the group table in |
| 84 | + `docs/platform/api/README.md` maps base paths to pages. Match the |
| 85 | + surrounding page's existing section style exactly (heading form, auth line, |
| 86 | + path/query parameter tables, JSON examples, `Errors:` line, `curl` block). |
| 87 | +- Respect each page's declared path root: if the page says paths are relative to |
| 88 | + `/api/v1/user/contracts`, write `### GET /{id}`, not the absolute path. |
| 89 | +- Middleware → prose mapping, as the existing pages use it: `reqToken()` → |
| 90 | + "token required"; `reqSiteAdmin()` / `authzCheck(...:site_admin)` → site admin; |
| 91 | + `reqOrgFromQuery()` → resolves the org from `?org=`; `reqClusterAssignment()` |
| 92 | + → owner+cluster resolved and a Kubernetes client built; `authzCheck(X, "perm")` |
| 93 | + → name the permission string. |
| 94 | +- Note availability when the registration is conditional in |
| 95 | + `routers/api/v1/api.go`: `setting.AppsCodeHosted` → "AppsCode-hosted only"; |
| 96 | + `setting.IsBillingEnabled()` → "billing-enabled deployments only". |
| 97 | +- Only add a `> **Verified:** …` note if you actually ran the request against a |
| 98 | + live deployment in this session. Never copy one from another endpoint. |
| 99 | +- Update `docs/platform/api/openapi.yaml` alongside the md: add/rename the path, |
| 100 | + reuse existing `components/schemas` and `parameters` rather than duplicating, |
| 101 | + and tag the operation with the same tag its group's siblings use. |
| 102 | +- If the API-group set itself changed (a whole new `register*APIs` function), |
| 103 | + add a row to the group table and a section page — do not bury a new group |
| 104 | + inside an unrelated page. |
| 105 | + |
| 106 | +## 4. Regenerate and verify |
| 107 | + |
| 108 | +```bash |
| 109 | +python3 hack/api-doc-sync/refresh_reference.py # re-inlines the spec into reference/api.html |
| 110 | +python3 -c "import yaml;yaml.safe_load(open('docs/platform/api/openapi.yaml'))" |
| 111 | +liche -p -h -l -s <each changed md file> # exactly as CI runs it |
| 112 | +python3 hack/api-doc-sync/compare.py /tmp/routes.txt docs/platform/api \ |
| 113 | + docs/platform/api/openapi.yaml | head -40 |
| 114 | +``` |
| 115 | + |
| 116 | +The `liche` invocation must keep `-s`; this repo's links intentionally carry one |
| 117 | +extra `../` (see `CLAUDE.md`). Never "fix" a relative link to match the on-disk |
| 118 | +path: a sibling page in the same directory is `../sibling.md`, and a page in |
| 119 | +another directory is `../../other-dir/page.md`. |
| 120 | + |
| 121 | +`docs/platform/api/README.md` fails this check by design — its links use |
| 122 | +non-standard paths, which is why CI's liche fork passes `-i '^README\.md$'`. Add |
| 123 | +that flag when scanning recursively; the locally installed `liche` may not have |
| 124 | +it, in which case just skip `README.md`. |
| 125 | + |
| 126 | +Re-run `compare.py` at the end and confirm the code→md and code→openapi lists |
| 127 | +contain nothing but the known false positives above. |
| 128 | + |
| 129 | +## 5. Report |
| 130 | + |
| 131 | +Give a ranked summary: wrong paths first, then missing endpoints, then spec-only |
| 132 | +changes. For anything you chose not to change, say why in one line. Note the |
| 133 | +route count so the next run can compare. |
0 commit comments