Skip to content

feat(api): restore agent-safe describe/list/search output [DEVEX-702] - #169

Merged
jpage-godaddy merged 3 commits into
mainfrom
cli-DEVEX-702
Jul 31, 2026
Merged

feat(api): restore agent-safe describe/list/search output [DEVEX-702]#169
jpage-godaddy merged 3 commits into
mainfrom
cli-DEVEX-702

Conversation

@jpage-godaddy

@jpage-godaddy jpage-godaddy commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

Restores agent-safe output for api describe/domain list/endpoint list/search (parity gap #9 from the original Rust-port review, DEVEX-702:

  • api describe: schema summarization (schema_type_label/summarize_schema) instead of dumping raw, unresolved JSON schema; GraphQL operation summaries (wires up catalog data that was already embedded but silently dropped); a --method/-m filter; and fuzzy multi-match resolution (0 matches → error, 1 → transparent resolution, >1 → a {message, matches} response with per-match next_actions).
  • api endpoint list / search: rows now include scopes and graphqlOperations; fixed a blind spot where GraphQL operation names weren't searchable at all. (api domain list is unchanged — it lists domains, not endpoints, so scopes/GraphQL counts don't apply there.)

Explicitly deferred

No truncation or overflow-file mechanism is included — schemas and GraphQL operation lists are always returned in full, however large. We prototyped a schema_detail/operationsDetail overflow-file escape hatch during development and reverted it; it's messy (arbitrary temp files, no navigational structure) and not the right long-term shape. Redesigning this as command "hyperlinks" + pagination is tracked separately in DEVEX-967, linked from DEVEX-702.

The same applies to gaps 1, 5, 6, & 7 from the original review (bare-array output on domain list/endpoint list/search instead of a {total, shown, truncated, full_output} wrapper) — cli-engine's --output human table rendering and --limit/--offset/--filter pipeline only work on bare top-level arrays, so wrapping would silently break those for these three commands. Deferred to the same follow-up.

Test plan

  • cargo test (445/445 passing)
  • cargo clippy -- -D warnings (clean)
  • cargo fmt -- --check (clean)
  • Manual verification: exact/fuzzy/multi-match describe resolution, --method filtering (including the documented quirk where it's ignored during fuzzy fallback), GraphQL summary on a 149-operation endpoint, scopes/graphqlOperations on list/search rows, GraphQL operation names now searchable, fullPath stays hostless across environments (prod and OTE)

🤖 Generated with Claude Code

- api describe: schema summarization (schema_type_label/summarize_schema),
  GraphQL operation summaries, --method/-m flag, and fuzzy multi-match
  resolution when a query has 0/1/many matches.
- api domain list / endpoint list / search: enrich rows with scopes and
  graphql_operations; fix a GraphQL-operation-name blind spot in search.
- No truncation: schemas and GraphQL operation lists are returned in full;
  the truncation + overflow-file escape hatch is deferred to DEVEX-967
  (redesign as command hyperlinks + pagination).

DEVEX-702

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 31, 2026 17:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR restores “agent-safe” discovery output for the Rust api explorer commands by adding schema/GraphQL summarization to api describe, adding a --method/-m narrowing option with exact→fuzzy resolution behavior, and enriching endpoint list/search rows with scopes and GraphQL operation counts.

Changes:

  • Add schema summarization for request/response schemas and include GraphQL operation summaries in api describe, plus exact/multi-match resolution and --method filtering.
  • Extend api endpoint list and api search results with scopes and GraphQL operation counts; make GraphQL operation names searchable.
  • Add tests covering schema/GraphQL summarization and describe/search resolution behaviors.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread rust/src/api_explorer/mod.rs Outdated
Comment thread rust/src/api_explorer/mod.rs Outdated
Comment thread rust/src/api_explorer/mod.rs
Comment thread rust/src/api_explorer/mod.rs
- search_endpoints: only scan GraphQL operations when the core fields
  didn't already match, short-circuiting per-operation instead of
  concatenating every operation into one string up front.
- fullPath: strip base_url's scheme+host generically instead of a
  hard-coded prod hostname, so it stays a hostless path in every
  environment (was silently returning a full URL on non-prod envs).
- Rename graphql_operations -> graphqlOperations for consistency with
  the rest of the camelCase JSON payload.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (3)

rust/src/api_explorer/mod.rs:271

  • find_endpoint_exact no longer uses path_matches_template, so api describe can't resolve concrete request paths with substituted params (e.g. /stores/abc123/orders) even though api call can. This is a behavior regression from the previous find_endpoint-based resolution.
            .endpoints
            .iter()
            .find(|ep| {
                (ep.operation_id.to_lowercase() == q || ep.path.to_lowercase() == q)
                    && method.is_none_or(|m| ep.method.eq_ignore_ascii_case(m))
            })

rust/src/api_explorer/mod.rs:804

  • Exact path equality is not guaranteed to be unique across methods. For example, rust/schemas/api/orders.json has multiple endpoints with the same path /v1/commerce/stores/{storeId}/orders (GET/POST/PUT). With no --method, find_endpoint_exact will pick the first one it encounters, so api describe <path> can silently describe the wrong operation. Consider detecting multiple exact matches and returning the same {message, matches} + next_actions response (ideally with --method populated) instead of selecting an arbitrary endpoint.
            // Exact operationId/path match (optionally narrowed by --method)
            // first; a miss falls back to fuzzy substring search, which
            // ignores --method entirely (matches the original CLI).
            let (domain, ep) = match find_endpoint_exact(catalog, query, method_filter.as_deref())
            {
                Some(hit) => hit,
                None => {

rust/src/api_explorer/mod.rs:56

  • api describe can return a multi-match payload {message, matches} (see the hits.len() > 1 branch), but ApiOperation's output schema doesn't mention these fields. Since output schemas drive --help/--schema, consider adding message/matches as optional fields so consumers aren't surprised by undocumented output.
output_schema!(ApiOperation {
    "domain": "string";
    "baseUrl": "string";
    "operationId": "string";
    "method": "string";
    "path": "string";
    "fullPath": "string";
    "summary": "string", optional;
    "description": "string", optional;
    "parameters": "[]object";
    "requestBody": "object", optional;
    "responses": "object";
    "scopes": "[]string";
    "graphql": "object", optional;
});

Comment thread rust/src/api_explorer/mod.rs Outdated
domain
.endpoints
.iter()
.find(|ep| {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If method isn't provided and multiple endpoints share an exact path match, this finds first hit — should we return the multi-match payload instead?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in c2a221d. find_endpoint_exact now returns every exact match instead of just the first; if there's more than one (common — e.g. GET/POST sharing a collection path) it goes through the same multi_match_result() path as an ambiguous fuzzy match, rather than silently picking whichever endpoint it saw first. Also extended it to match a concrete path against a templated catalog path (path_matches_template), so this is now consistent with what api call already does via find_endpoint.


fn summarize_schema(schema: Option<&Value>) -> Option<Vec<SchemaSummaryProperty>> {
let obj = schema?.as_object()?;
let Some(properties) = obj.get("properties").and_then(Value::as_object) else {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking the schema repo, most catalog request bodies are $ref only, but we only summarize properties/type/enum. Should we resolve $ref against $defs too?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great find. Will do.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented in c2a221d: added a Domain.defs field capturing the embedded $defs, and schema_type_label/summarize_schema now resolve $ref against it (bounded depth to guard against a def-to-def cycle), both at the top level and per-property, preferring a local override over the resolved def per JSON Schema's sibling-keyword rules. Confirmed the scope first — 63 of 82 catalog request bodies (77%) are a bare $ref with no inline properties, so this was returning null for the large majority of write endpoints. createBusiness (whose body is {"$ref": "#/$defs/Business"}) now resolves to the full 27-property list.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

Comment thread rust/src/api_explorer/mod.rs Outdated
"api describe <endpoint>",
format!("{} {} — {}", ep.method, ep.path, ep.summary),
)
.with_param("endpoint", required_value(ep.path.clone()))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.with_param("endpoint", required_value(ep.path.clone()))
.with_param("endpoint", required_value(ep.path.clone()))
.with_param("method", required_value(ep.method.clone()))

Add method so when the same path appears more than once in matches next_actions suggest the method too?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied (via a shared multi_match_result() helper now used by both the fuzzy and exact-collision paths, in c2a221d) — each next_action carries both endpoint and method params, and the command template is "api describe --method ".

- Detect ambiguous exact matches: many catalog paths are shared by
  several endpoints that only differ by method (e.g. GET/POST on the
  same collection). Without --method, this previously picked whichever
  one it saw first; now it's treated as the same ambiguity as a fuzzy
  multi-match, with a shared multi_match_result() helper and each
  next_action carrying both endpoint and method params.
- find_endpoint_exact now also matches a concrete path against a
  templated catalog path (via path_matches_template), matching what
  api call already does via find_endpoint.
- Resolve $ref against a domain's $defs in schema summarization: 63 of
  82 catalog request bodies are a bare $ref with no inline properties,
  so requestBody.schema was null for the large majority of write
  endpoints. schema_type_label/summarize_schema now follow $ref chains
  (bounded depth against cycles), at both the top level and per
  property, preferring a local override over the resolved def.
- ApiOperation's output schema now documents the message/matches
  fields the multi-match response can return.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

Suppressed comments (1)

rust/src/api_explorer/mod.rs:58

  • ApiOperation is registered as the output schema for api describe, but in ambiguous-resolution cases multi_match_result returns only {message, matches} (plus next_actions) and omits fields like domain, baseUrl, operationId, parameters, responses, and scopes. Since with_output_schema feeds --help/--schema, the current schema incorrectly marks many fields as always present, which will mislead consumers and tooling.
output_schema!(ApiOperation {
    "domain": "string";
    "baseUrl": "string";
    "operationId": "string";
    "method": "string";
    "path": "string";
    "fullPath": "string";
    "summary": "string", optional;
    "description": "string", optional;
    "parameters": "[]object";
    "requestBody": "object", optional;
    "responses": "object";
    "scopes": "[]string";
    "graphql": "object", optional;
    "message": "string", optional;
    "matches": "[]object", optional;
});

@jpage-godaddy
jpage-godaddy merged commit 1f33ec6 into main Jul 31, 2026
5 checks passed
@jpage-godaddy
jpage-godaddy deleted the cli-DEVEX-702 branch July 31, 2026 19:42
This was referenced Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants