feat(api): add api spec <spec> command#1073
Conversation
|
Claude finished @gu-stav's task in 3m 15s —— View job ReviewThe Cursor Bugbot finding ( A small set of issues worth addressing below; nothing blocking. 1. Changeset includes an internal rename that never reached usersFile:
This rename happened during PR review of an unreleased command (commit Suggested tighter copy: 2.
|
📦 Bundle Stats —
|
| Metric | Value | vs feat/api-command-list (309e558) |
|---|---|---|
| Internal (raw) | 2.1 KB | - |
| Internal (gzip) | 799 B | - |
| Bundled (raw) | 10.97 MB | - |
| Bundled (gzip) | 2.06 MB | - |
| Import time | 869ms | +12ms, +1.4% |
bin:sanity
| Metric | Value | vs feat/api-command-list (309e558) |
|---|---|---|
| Internal (raw) | 975 B | - |
| Internal (gzip) | 460 B | - |
| Bundled (raw) | 9.84 MB | - |
| Bundled (gzip) | 1.77 MB | - |
| Import time | 976ms | +10ms, +1.0% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — @sanity/cli-core
Compared against feat/api-command-list (309e558a)
| Metric | Value | vs feat/api-command-list (309e558) |
|---|---|---|
| Internal (raw) | 95.5 KB | - |
| Internal (gzip) | 22.5 KB | - |
| Bundled (raw) | 21.60 MB | - |
| Bundled (gzip) | 3.42 MB | - |
| Import time | 821ms | +8ms, +1.0% |
🗺️ View treemap · Artifacts
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
📦 Bundle Stats — create-sanity
Compared against feat/api-command-list (309e558a)
| Metric | Value | vs feat/api-command-list (309e558) |
|---|---|---|
| Internal (raw) | 976 B | - |
| Internal (gzip) | 507 B | - |
| Bundled (raw) | 50.7 KB | - |
| Bundled (gzip) | 12.6 KB | - |
| Import time | ❌ ChildProcess denied: node | - |
Details
- Import time regressions over 10% are flagged with
⚠️ - Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e28984b. Configure here.
| if (flags.format === 'openapi') { | ||
| this.log(loaded.yaml) | ||
| return | ||
| } |
There was a problem hiding this comment.
--operation flag silently ignored with --format=openapi
Medium Severity
The --format=openapi code path returns early at line 92–95, before selectOperations is called at line 97. This means --operation=<id> is completely silently ignored — neither applied nor validated — when combined with --format=openapi. A user passing --operation=nonexistentId --format=openapi receives the full spec YAML with no error or warning, contradicting the documented behavior and the command's own example description stating --operation "narrows any output mode to a single operation."
Reviewed by Cursor Bugbot for commit e28984b. Configure here.
Coverage Delta
Comparing 9 changed files against main @ Overall Coverage
|
PR review fixes (#1073): - walkProperty: surface element schema name on `ref` for `array<$ref>` body fields (and the equivalent on path/query params). Without this, the schemas-referenced footer silently omitted the array element type. - Stale `parseOpenApi` docstring updated to match the synthesize-and- debug-log behavior. - Path-item + operation-level params now dedup by `(name, in)` with operation-level wins (per OpenAPI 3.x). - `isStreamingResponse` now matches any 2xx with `text/event-stream` (was 200-only). - Tighten the changeset to a single user-facing sentence. Architecture improvements (from /improve-codebase-architecture): - Unify HTTP fetch: `docsClient.fetchSpec(slug, {format?})` now accepts `'yaml' | 'json'`. `openapi/get.ts` drops its hand-rolled fetch + URL/timeout/headers constants and goes through docsClient, so the docs-endpoint URL + Vercel bypass token + 404 handling live in one module. - Thread component schemas through `ParsedSpec.schemas` instead of re-parsing YAML in `--schema <name>`. Drops `lookupComponentSchema` / `listComponentSchemas` exports and the `yaml` import from parser.ts. One YAML parse per `--schema` invocation, no second parse pass. - Split parser.ts (780 lines) into three files: `parser.ts` keeps types + capability + URL helpers + parseOpenApi orchestration + loaders; `extractors.ts` holds the per-aspect extractors (parameters / requestBody / responses / security); `internal.ts` holds shared shape-coercion helpers (asObject/asString/asArray) and the OpenAPI-shape utilities (schemaRefName / describeType / summarizeInlineObject). Dependency direction: parser → extractors → internal. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
api spec commandapi spec <spec> command
PR review fixes (#1073): - walkProperty: surface element schema name on `ref` for `array<$ref>` body fields (and the equivalent on path/query params). Without this, the schemas-referenced footer silently omitted the array element type. - Stale `parseOpenApi` docstring updated to match the synthesize-and- debug-log behavior. - Path-item + operation-level params now dedup by `(name, in)` with operation-level wins (per OpenAPI 3.x). - `isStreamingResponse` now matches any 2xx with `text/event-stream` (was 200-only). - Tighten the changeset to a single user-facing sentence. Architecture improvements (from /improve-codebase-architecture): - Unify HTTP fetch: `docsClient.fetchSpec(slug, {format?})` now accepts `'yaml' | 'json'`. `openapi/get.ts` drops its hand-rolled fetch + URL/timeout/headers constants and goes through docsClient, so the docs-endpoint URL + Vercel bypass token + 404 handling live in one module. - Thread component schemas through `ParsedSpec.schemas` instead of re-parsing YAML in `--schema <name>`. Drops `lookupComponentSchema` / `listComponentSchemas` exports and the `yaml` import from parser.ts. One YAML parse per `--schema` invocation, no second parse pass. - Split parser.ts (780 lines) into three files: `parser.ts` keeps types + capability + URL helpers + parseOpenApi orchestration + loaders; `extractors.ts` holds the per-aspect extractors (parameters / requestBody / responses / security); `internal.ts` holds shared shape-coercion helpers (asObject/asString/asArray) and the OpenAPI-shape utilities (schemaRefName / describeType / summarizeInlineObject). Dependency direction: parser → extractors → internal. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Reject `--operation` + `--format=openapi` upfront (typo'd op no longer silently produces full raw YAML). - Debug-log when extractRequestBody skips a body-level `$ref`; the operation otherwise renders as bodyless without trace. - Gate "Query params (required)" behind a length check so optional-only operations don't render a dead `(none)` block. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
5026641 to
f006816
Compare
PR review fixes (#1073): - walkProperty: surface element schema name on `ref` for `array<$ref>` body fields (and the equivalent on path/query params). Without this, the schemas-referenced footer silently omitted the array element type. - Stale `parseOpenApi` docstring updated to match the synthesize-and- debug-log behavior. - Path-item + operation-level params now dedup by `(name, in)` with operation-level wins (per OpenAPI 3.x). - `isStreamingResponse` now matches any 2xx with `text/event-stream` (was 200-only). - Tighten the changeset to a single user-facing sentence. Architecture improvements (from /improve-codebase-architecture): - Unify HTTP fetch: `docsClient.fetchSpec(slug, {format?})` now accepts `'yaml' | 'json'`. `openapi/get.ts` drops its hand-rolled fetch + URL/timeout/headers constants and goes through docsClient, so the docs-endpoint URL + Vercel bypass token + 404 handling live in one module. - Thread component schemas through `ParsedSpec.schemas` instead of re-parsing YAML in `--schema <name>`. Drops `lookupComponentSchema` / `listComponentSchemas` exports and the `yaml` import from parser.ts. One YAML parse per `--schema` invocation, no second parse pass. - Split parser.ts (780 lines) into three files: `parser.ts` keeps types + capability + URL helpers + parseOpenApi orchestration + loaders; `extractors.ts` holds the per-aspect extractors (parameters / requestBody / responses / security); `internal.ts` holds shared shape-coercion helpers (asObject/asString/asArray) and the OpenAPI-shape utilities (schemaRefName / describeType / summarizeInlineObject). Dependency direction: parser → extractors → internal. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Reject `--operation` + `--format=openapi` upfront (typo'd op no longer silently produces full raw YAML). - Debug-log when extractRequestBody skips a body-level `$ref`; the operation otherwise renders as bodyless without trace. - Gate "Query params (required)" behind a length check so optional-only operations don't render a dead `(none)` block. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
f006816 to
f13f93f
Compare
PR review fixes (#1073): - walkProperty: surface element schema name on `ref` for `array<$ref>` body fields (and the equivalent on path/query params). Without this, the schemas-referenced footer silently omitted the array element type. - Stale `parseOpenApi` docstring updated to match the synthesize-and- debug-log behavior. - Path-item + operation-level params now dedup by `(name, in)` with operation-level wins (per OpenAPI 3.x). - `isStreamingResponse` now matches any 2xx with `text/event-stream` (was 200-only). - Tighten the changeset to a single user-facing sentence. Architecture improvements (from /improve-codebase-architecture): - Unify HTTP fetch: `docsClient.fetchSpec(slug, {format?})` now accepts `'yaml' | 'json'`. `openapi/get.ts` drops its hand-rolled fetch + URL/timeout/headers constants and goes through docsClient, so the docs-endpoint URL + Vercel bypass token + 404 handling live in one module. - Thread component schemas through `ParsedSpec.schemas` instead of re-parsing YAML in `--schema <name>`. Drops `lookupComponentSchema` / `listComponentSchemas` exports and the `yaml` import from parser.ts. One YAML parse per `--schema` invocation, no second parse pass. - Split parser.ts (780 lines) into three files: `parser.ts` keeps types + capability + URL helpers + parseOpenApi orchestration + loaders; `extractors.ts` holds the per-aspect extractors (parameters / requestBody / responses / security); `internal.ts` holds shared shape-coercion helpers (asObject/asString/asArray) and the OpenAPI-shape utilities (schemaRefName / describeType / summarizeInlineObject). Dependency direction: parser → extractors → internal. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Reject `--operation` + `--format=openapi` upfront (typo'd op no longer silently produces full raw YAML). - Debug-log when extractRequestBody skips a body-level `$ref`; the operation otherwise renders as bodyless without trace. - Gate "Query params (required)" behind a length check so optional-only operations don't render a dead `(none)` block. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
f13f93f to
d10a918
Compare
PR review fixes (#1073): - walkProperty: surface element schema name on `ref` for `array<$ref>` body fields (and the equivalent on path/query params). Without this, the schemas-referenced footer silently omitted the array element type. - Stale `parseOpenApi` docstring updated to match the synthesize-and- debug-log behavior. - Path-item + operation-level params now dedup by `(name, in)` with operation-level wins (per OpenAPI 3.x). - `isStreamingResponse` now matches any 2xx with `text/event-stream` (was 200-only). - Tighten the changeset to a single user-facing sentence. Architecture improvements (from /improve-codebase-architecture): - Unify HTTP fetch: `docsClient.fetchSpec(slug, {format?})` now accepts `'yaml' | 'json'`. `openapi/get.ts` drops its hand-rolled fetch + URL/timeout/headers constants and goes through docsClient, so the docs-endpoint URL + Vercel bypass token + 404 handling live in one module. - Thread component schemas through `ParsedSpec.schemas` instead of re-parsing YAML in `--schema <name>`. Drops `lookupComponentSchema` / `listComponentSchemas` exports and the `yaml` import from parser.ts. One YAML parse per `--schema` invocation, no second parse pass. - Split parser.ts (780 lines) into three files: `parser.ts` keeps types + capability + URL helpers + parseOpenApi orchestration + loaders; `extractors.ts` holds the per-aspect extractors (parameters / requestBody / responses / security); `internal.ts` holds shared shape-coercion helpers (asObject/asString/asArray) and the OpenAPI-shape utilities (schemaRefName / describeType / summarizeInlineObject). Dependency direction: parser → extractors → internal. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Reject `--operation` + `--format=openapi` upfront (typo'd op no longer silently produces full raw YAML). - Debug-log when extractRequestBody skips a body-level `$ref`; the operation otherwise renders as bodyless without trace. - Gate "Query params (required)" behind a length check so optional-only operations don't render a dead `(none)` block. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
d10a918 to
6bf31f5
Compare
Adds `sanity api spec <slug>` for inspecting a single public Sanity
HTTP spec. Three output modes via a single `--format` flag:
- default (no flag) → structured human view (header + per-operation
blocks with typed params, request body, responses, auth, plus a
`Schemas referenced` footer pointing at `--schema`).
- `--format=json` → structured per-operation JSON.
- `--format=openapi`→ raw OpenAPI YAML (byte-for-byte passthrough).
`--operation=<id>` narrows any of the three modes to a single
operation. `--schema=<name>` prints one `components.schemas` entry
(YAML default, JSON with `--format=json`) — the follow-up for `$ref`
pointers surfaced in operation output.
`sanity openapi get` is converted to a deprecation forwarder that
preserves its pre-deprecation passthrough output (raw OpenAPI body,
YAML default, JSON with `--format=json`) so scripts piping stdout
keep working. New structured shape lives on `sanity api spec`.
The parser is enriched to back the spec view: typed parameter objects
(name/in/type/required/description/default/enum/example/ref),
structured request body with composition flattening
(allOf/oneOf/anyOf), responses with schema summaries, security with
case-normalized scheme names. `$ref` policy: link, don't resolve —
schema refs surface as `ref: "<name>"`. Parameter refs DO resolve
inline (path params must survive). `list --json` projects names from
the new param objects so its shape is unchanged.
Operations missing `operationId` get a deterministic synthesized id
(`<method>_<path>`) instead of being skipped. Several Sanity specs
(e.g. `mutation`) omit it; the synthesis keeps every row addressable
without losing visibility.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PR review fixes (#1073): - walkProperty: surface element schema name on `ref` for `array<$ref>` body fields (and the equivalent on path/query params). Without this, the schemas-referenced footer silently omitted the array element type. - Stale `parseOpenApi` docstring updated to match the synthesize-and- debug-log behavior. - Path-item + operation-level params now dedup by `(name, in)` with operation-level wins (per OpenAPI 3.x). - `isStreamingResponse` now matches any 2xx with `text/event-stream` (was 200-only). - Tighten the changeset to a single user-facing sentence. Architecture improvements (from /improve-codebase-architecture): - Unify HTTP fetch: `docsClient.fetchSpec(slug, {format?})` now accepts `'yaml' | 'json'`. `openapi/get.ts` drops its hand-rolled fetch + URL/timeout/headers constants and goes through docsClient, so the docs-endpoint URL + Vercel bypass token + 404 handling live in one module. - Thread component schemas through `ParsedSpec.schemas` instead of re-parsing YAML in `--schema <name>`. Drops `lookupComponentSchema` / `listComponentSchemas` exports and the `yaml` import from parser.ts. One YAML parse per `--schema` invocation, no second parse pass. - Split parser.ts (780 lines) into three files: `parser.ts` keeps types + capability + URL helpers + parseOpenApi orchestration + loaders; `extractors.ts` holds the per-aspect extractors (parameters / requestBody / responses / security); `internal.ts` holds shared shape-coercion helpers (asObject/asString/asArray) and the OpenAPI-shape utilities (schemaRefName / describeType / summarizeInlineObject). Dependency direction: parser → extractors → internal. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Reject `--operation` + `--format=openapi` upfront (typo'd op no longer silently produces full raw YAML). - Debug-log when extractRequestBody skips a body-level `$ref`; the operation otherwise renders as bodyless without trace. - Gate "Query params (required)" behind a length check so optional-only operations don't render a dead `(none)` block. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…load helpers Continues the architecture review's code-reduction pass: - Drop `OperationJsonView` + `toOperationJsonView` from views.ts. Every field on the projection was already on `ParsedOperation`, so `buildSpecJsonView` now passes `operations` straight through. ~30 lines and one extra type gone. - `commands/api/spec.ts`: use `docsUrlFor()` and the shared `loadSingleSpecOrThrow()` helper. The `loadSpec` private method and the local `HTTP_REFERENCE_BASE_URL` constant disappear. - `commands/openapi/get.ts`: same — `docsUrlFor()` + the shared `DOCS_SERVICE_UNAVAILABLE` constant; local URL constant removed. - Add `loadSingleSpecOrThrow()` next to `loadOperationsIndexOrThrow()` in parser.ts so single-spec consumers get the same friendly error. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three additions in response to agent-discoverability review:
- The per-operation JSON exposed both `path` (`/jobs/{jobId}`) and
`endpoint` (`v2021-06-07/jobs/:jobId`) — same shape, different
conventions. Agents would reach for `path` and either hit a routing
mismatch (it isn't callable) or syntax inconsistency. Rename to
`openApiPath` to make the cross-reference role explicit.
- `--schema <name>` defaulted to YAML. The primary consumer following a
`$ref` pointer is an agent — JSON parses without a YAML library.
Switch the default to JSON; humans opt into YAML via `--format=yaml`.
`--format=openapi --schema` was a redundant alias for the same YAML
output and is dropped; `yaml` is the canonical opt-in.
- Clarify in `--web` help text that it's a human-only mode (no
machine-readable output).
Also drops the redundant `optionalQueryParams: string[]` field on
`ParsedOperation` that the upstream parser rewrite made unnecessary —
the per-operation JSON now derives it from `queryParams` directly, and
list-row JSON falls through to `summary || description` when the spec
omits a summary.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drops spec.test.ts's local `JOBS_SPEC_YAML`, `mockIndexAndSpec`, and `afterEach` cleanup in favor of the shared module from the list branch. Enriches the shared Jobs spec with the path-param description, bearer- auth, and 200-response shape that spec.test asserts against — the fixture now covers what every consumer in the topic needs. The two spec-specific YAMLs (REF_SPEC_YAML for `$ref` rendering, OPTIONAL_QUERY_SPEC_YAML for the optional-only-params edge case) stay local — they're not shared and inlining them keeps spec.test self- explanatory for the cases that matter to it alone. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two adjacent cleanups in the parser layer: - Fold `api/internal.ts` (shape-coercion + small OpenAPI helpers) into `api/extractors.ts`. The seam was extracted for testability but its only consumer is `extractors`; the deletion-test concentrates nothing — the guards are 1–2 lines each and the real bug risk lives in how `walkProperty` / `flattenComposition` compose them, not in the guards themselves. One file deleted, one import surface gone. - Enforce operationId uniqueness within a parsed spec. OpenAPI requires it, but validators are lenient; and `synthesizeOperationId` can silently collide with an authored id elsewhere in the same spec. Either case would silently route `--operation=<id>` lookups to whichever record sorted last. `assertUniqueOperationIds` throws with the colliding paths so `fetchAndParseEntry` can skip the spec cleanly (other specs still load). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`(agent-friendly)` and `(human-only; no machine output)` framed the `--format=json` and `--web` flags as for one audience or the other. Drops both: these flags are useful for anyone using the CLI. Also rewords a stale "agents indexing by operationId" comment in the parser to describe the actual invariant (every row stays uniquely addressable by operationId) without singling out any caller, and drops a stale "5-column human table" docblock the previous list branch left behind. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Description: "human view (default)" was a leftover audience tag; reads as "structured view by default". Drops "public" qualifier (the topic implies it). - Examples: "human-readable", "canonical spec source", "human-friendly" audience tags and unnecessary qualifications removed. - `--format` description: dense one-liner mixing definitions with conditional behavior — split into one clause per format, surface the `yaml`-is-paired-with-`--schema` rule explicitly. - `--schema` description: drop OpenAPI internal path syntax (`components.schemas.<name>`) from the user-facing hint; call it "a named component schema". - `--operation` + `--format=openapi` error: "byte-for-byte passthrough" was implementation jargon. Replaced with concrete framing: "Cannot narrow --format=openapi output…". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- `--schema` lookup used `name in schemas`, which walks the prototype chain — `--schema toString` would slip past the not-found guard and render `Object.prototype.toString` as JSON `undefined`. Switched to `Object.hasOwn`. Test added. - `--schema` + `--operation` and `--schema` + `--format=openapi` were silently accepted (the schema branch returns before either flag is consulted). Both combos now reject upfront with a clear error, the same shape as the existing `--operation` + `--format=openapi` guard. Two tests added. - `extractResponses` parsed range status keys (`2XX`/`4XX`/`5XX`) through `parseInt` to single digits (`2`, `4`, `5`). Now requires a fully-numeric key and skips range keys with a debug log. Test added. - Response sort treated `default` as `status: 0`, which sorted it *before* every specific code. Now sorts numeric statuses ascending with `default` last. Test added. - Parse errors propagated through `loadSingleSpecOrThrow` were re-thrown as `DOCS_SERVICE_UNAVAILABLE`, so a spec with bad operationIds told the user the service was down. Split the loader: fetch errors are wrapped (service-unavailable), parse errors propagate with their original message. Test added. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
6bf31f5 to
f1e517f
Compare


Description
Adds
sanity api spec <slug>for inspecting one OpenAPI spec.--format=json— per-operation JSON envelope, agent-friendly.--format=openapi— raw YAML, byte-for-byte passthrough of the upstream spec.--operation=<id>— narrow any output mode to a single operation.--schema <name>— print onecomponents.schemas.<name>entry (follows$refpointers from operation output). Honors--format(default YAML).--web— open the docs page in a browser.Deprecates
sanity openapi get(passthrough output preserved during the back-compat window).This PR is the second in a stack:
api listcommand and deprecateopenapi *#1070 —sanity api listapi spec <spec>command #1073 (this PR) —sanity api spec <slug>sanity api <endpoint>command #1074 —sanity api <endpoint>(default)Architecture refactors (post-review)
$refpolicy: link, never resolve. Body/response schemas carryref: '<schemaName>'; agents follow up with--schema <name>.OperationJsonViewpass-through projection —buildSpecJsonViewnow usesParsedOperationdirectly (~30 lines + one type gone).docsUrlFor()+loadSingleSpecOrThrow()helpers from list-branch refactor; drop localHTTP_REFERENCE_BASE_URLand the bespokeloadSpecprivate method.PR review items addressed
--operation+--format=openapiupfront (typo'd op no longer silently produces full raw YAML).extractRequestBodyskips a body-level$ref(the operation would otherwise render as bodyless without trace).(none)block.What to review
packages/@sanity/cli/src/commands/api/spec.ts— the new command.packages/@sanity/cli/src/api/parser.ts+extractors.ts+internal.ts— the rich OpenAPI parser introduced for this PR.packages/@sanity/cli/src/api/views.ts— list view + spec view, both human and JSON.packages/@sanity/cli/src/commands/openapi/get.ts— deprecation forwarder.Testing
15 unit tests in
commands/api/__tests__/spec.test.tscovering: human view, JSON envelope, OpenAPI YAML passthrough,--operationnarrow + unknown-op error,--schemaYAML default + JSON + unknown-schema error,--operation+--format=openapiconflict, optional-only query gating,--webflow, unknown slug, docs service unreachable. Plus 33 parser tests.