Skip to content

feat: add generate-spec command#2937

Open
sobanieca-redocly wants to merge 22 commits into
mainfrom
feat/generate-spec-command
Open

feat: add generate-spec command#2937
sobanieca-redocly wants to merge 22 commits into
mainfrom
feat/generate-spec-command

Conversation

@sobanieca-redocly

@sobanieca-redocly sobanieca-redocly commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

What/Why/How?

Added new generate-spec command which will infer OAD basing on traffic data. To improve accuracy, there is option to use AI agents to improve results.

Reference

Testing

Screenshots (optional)

image

Check yourself

  • This PR follows the contributing guide
  • All new/updated code is covered by tests
  • Core code changed? - Tested with other Redocly products (internal contributions only)
  • New package installed? - Tested in different environments (browser/node)
  • Documentation update has been considered

Security

  • The security impact of the change has been considered
  • Code follows company security practices and guidelines

Note

Medium Risk
Large new surface area: optional --with-ai sends traffic samples to external CLIs (data-exfiltration concern if misused), plus substantial inference/refinement logic that could produce misleading specs if users treat output as authoritative without their own lint/governance.

Overview
Introduces the experimental generate-spec command: it reads the same traffic formats as drift (HAR, Kong, Nginx/Apache JSON, NDJSON), builds an OpenAPI 3.1 baseline deterministically, then optionally refines it with --with-ai.

The baseline pipeline templatizes identifier-like path segments (using new singularize in @redocly/openapi-core), merges request/response schemas across observations (including oneOf / nullable unions), extracts repeated shapes into components/schemas, and applies conservative format/enum hints from observed strings. --server scopes which exchanges count and sets servers.

With --with-ai, each operation is refined in parallel (--ai-concurrency, default 4) by spawning isolated claude, codex, or cursor CLIs; traffic samples are capped and sent in prompts. Accepted refinements must keep path, method, observed status codes, and pass lint with the built-in spec ruleset—otherwise the baseline is kept and the command can fall back entirely if refinement fails.

Wiring includes yargs registration, docs/sidebars, CODEOWNERS, unit tests, and e2e tests with stub AI binaries.

Reviewed by Cursor Bugbot for commit 2d41e65. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot

changeset-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2d41e65

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@redocly/cli Minor
@redocly/openapi-core Minor
@redocly/respect-core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 71.64% (🎯 69%) 8586 / 11984
🔵 Statements 71.34% (🎯 69%) 8932 / 12519
🔵 Functions 76.34% (🎯 73%) 1701 / 2228
🔵 Branches 63.17% (🎯 61%) 5763 / 9123
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/cli/src/commands/generate-spec/components.ts 92.95% 77.77% 88.88% 94.02% 48, 250, 262, 267-274, 281, 366
packages/cli/src/commands/generate-spec/generator.ts 57.74% 50.95% 63.41% 56.37% 196, 203, 246-256, 259, 279, 345, 358, 360, 424, 431, 447-459, 469, 481, 489, 503-786
packages/cli/src/commands/generate-spec/index.ts 0% 0% 0% 0% 31-113
packages/cli/src/commands/generate-spec/samples.ts 85.13% 72.58% 100% 84.93% 43, 52, 71, 74, 83, 113, 123-127, 171
packages/cli/src/commands/generate-spec/value-inference.ts 93.33% 90% 100% 94.73% 49, 127, 133, 146
packages/cli/src/commands/generate-spec/ai/prompt.ts 42.85% 27.77% 66.66% 42.85% 34-55
packages/cli/src/commands/generate-spec/ai/providers.ts 64.86% 43.33% 69.23% 64.86% 49-50, 57, 60-67, 72-77, 112, 118, 121, 147, 150, 162, 174-185, 191, 194, 211
packages/cli/src/commands/generate-spec/ai/refine.ts 97.01% 83.87% 100% 96.96% 103, 134, 138, 236
packages/core/src/utils/singularize.ts 0% 100% 0% 0% 4
Generated in workflow #10745 for commit 2d41e65 by the Vitest Coverage Report Action

Base automatically changed from feat/traffic-analysis to main July 7, 2026 10:58
@sobanieca-redocly
sobanieca-redocly force-pushed the feat/generate-spec-command branch from 2c94a80 to 6b62b28 Compare July 7, 2026 11:29
@sobanieca-redocly sobanieca-redocly changed the title feat: generate-spec command feat: add generate-spec command Jul 7, 2026
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark (Lower is Faster)

CLI Version Bundle Lint Check Config
cli-latest ▓ 1.01x ± 0.02 ▓ 1.00x (Fastest) ▓▓▓▓ 1.13x ± 0.04
cli-next ▓ 1.00x (Fastest) ▓ 1.01x ± 0.01 ▓ 1.00x (Fastest)

@sobanieca-redocly

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread packages/cli/src/commands/generate-spec/index.ts
Comment thread packages/cli/src/commands/generate-spec/generator.ts Outdated
@sobanieca-redocly

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread packages/cli/src/commands/generate-spec/samples.ts
Comment thread packages/cli/src/commands/generate-spec/generator.ts Outdated
@sobanieca-redocly

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

2 issues from previous reviews remain unresolved.

Fix All in Cursor

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 956bf00. Configure here.

@sobanieca-redocly

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 3a11b20. Configure here.

@sobanieca-redocly

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit d7ee556. Configure here.

@sobanieca-redocly

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread packages/cli/src/commands/generate-spec/samples.ts
Comment thread packages/cli/src/commands/generate-spec/index.ts
@sobanieca-redocly

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 90a2052. Configure here.

@sobanieca-redocly

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 22674ba. Configure here.

@sobanieca-redocly
sobanieca-redocly marked this pull request as ready for review July 15, 2026 09:34
@sobanieca-redocly
sobanieca-redocly requested review from a team as code owners July 15, 2026 09:34
error instanceof Error ? error.message : String(error)
}\n`
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Final lint drops refinements

Medium Severity

When --with-ai runs, refineSpecWithAi can accept and merge some operations, then fail on the final whole-document lint. The handler catches that error and writes the original baseline YAML, so successful per-operation refinements are discarded instead of being emitted.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 896fd97. Configure here.

Comment thread .changeset/generate-spec-command.md Outdated
Comment thread packages/cli/src/commands/generate-spec/README.md Outdated
Comment thread packages/cli/src/commands/generate-spec/README.md Outdated
Comment thread packages/cli/src/commands/generate-spec/README.md Outdated
Comment thread packages/cli/src/commands/generate-spec/README.md Outdated
Comment thread packages/cli/src/commands/generate-spec/README.md Outdated
Comment thread docs/@v2/commands/generate-spec.md Outdated
Co-authored-by: Jacek Łękawa <164185257+JLekawa@users.noreply.github.com>
Comment thread docs/@v2/commands/generate-spec.md
// cuid ("cjld2cjxh0000qzrmn831i7rn") and cuid2 ("tz4a98xxat96iws9zmbrgj3a") as
// generated by Prisma: 24-25 lowercase alphanumerics with at least one digit.
const CUID_RE = /^[a-z](?=[a-z0-9]*\d)[a-z0-9]{23,24}$/;
export const HTTP_METHODS = new Set([

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.

Can we use already defined constants OPENAPI3_METHOD_NAMES from core instead of redefining them?
Same for drift and proxy commands.
Its already redefined multiple times in

  • packages/cli/src/commands/drift/openapi/loader.ts
  • packages/cli/src/commands/proxy/server.ts

## Usage

```bash
redocly generate-spec <traffic>

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.

should we call it traffic-log or traffic-record to make it more clear?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The description already says "traffic log file or folder", I would keep it short.

| `--ai-provider` | `claude`, `codex`, or `cursor` (default `claude`). |
| `--ai-model` | Model passed to the selected provider. |
| `--ai-concurrency` | Number of operations refined in parallel (default `4`). |
| `-o, --output` | Write the result to a file instead of stdout. |

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.

Should we also mentioned in README the limitation of producing only OAS v3.1.0 description?

return /^[0-9a-f]{16,}$/i.test(segment);
}

export function singularize(value: string): string {

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.

We already have pluralize in our core. Could you please check if you can reuse it instead of implementing singularize ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks. We need singularize, but in this case I've just added an export in core with singularize. Hope it's fine.

return finalizeVariants(foldExcessObjectVariants(variants));
}

function sniffJson(bodyText: string | undefined): unknown {

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.

Can you just reuse isJson here?
We can move it to core from respect if it feet your needs

export function isJSON(str: string): boolean {
  try {
    JSON.parse(str);
    return true;
  } catch {
    return false;
  }
}

}

return {
openapi: '3.1.0',

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.

why not 3.2.0 ?

const trafficPath = normalizeFsPath(argv.traffic);
const trafficFormat = argv['traffic-format'];

const baseline = applyValueInference(

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.

should we lint generated spec without ai ?

document.paths[path][method] = fragment.operation;
if (Object.keys(fragment.components).length > 0) {
document.components ??= { schemas: {} };
Object.assign(document.components.schemas, fragment.components);

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.

will it override already existed component ?


import type { TrafficSample } from '../samples.js';

const SYSTEM_INSTRUCTIONS = `You are an expert API designer. You are given one operation from an OpenAPI 3.1 description that was inferred mechanically from recorded HTTP traffic, the component schemas it references, and a sample of the real recorded HTTP exchanges for that operation. The inferred description is only a hypothesis: types are coarse, every observed property is marked required, and descriptions are missing. Alternative body shapes observed in the traffic appear as "oneOf" variants or type unions. Common string formats (uuid, date-time, date, email, uri, ipv4) and small closed enums are detected conservatively from the observed values.

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.

why only 3.1?

- Factor structure shared between variants or schemas with "allOf": extract the common base into components/schemas and compose each variant as "allOf" of the base plus its specific properties.
- Keep the $ref references to the component schemas you were given. You may refine the definition of a referenced component when the samples justify it, and you may add new components, but never rename an existing component and never reuse a reserved component name listed in the input.
- Add concise, useful "summary" and "description" fields to the operation, its parameters and important schema properties.
- Add example values drawn from the real samples where helpful.

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.

email, tokens etc ?

});
});

describe('generate-spec - AI refinement', () => {

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.

can we test the prompt somehow? to avoid regression while editing

Refine the operation into the most accurate OpenAPI 3.1 definition you can justify from the evidence, while staying conservative:
- Keep the path template and the HTTP method exactly as given. Do not rename path parameters, do not move parameters to the path item level, and keep every response status code. The result is programmatically checked and rejected otherwise.
- Keep the operationId unless it is clearly wrong.
- Narrow property types where the samples clearly support it (formats such as date-time, uuid, email, uri; integer vs number; enums when a field only ever takes a small fixed set of values). Verify the formats and enums already detected against the samples; correct or extend them where justified.

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.

Verify the formats and enums already detected against the samples

will it remove what was found in the samples but was not passed to ai?


Refine the operation into the most accurate OpenAPI 3.1 definition you can justify from the evidence, while staying conservative:
- Keep the path template and the HTTP method exactly as given. Do not rename path parameters, do not move parameters to the path item level, and keep every response status code. The result is programmatically checked and rejected otherwise.
- Keep the operationId unless it is clearly wrong.

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.

Keep the operationId unless it is clearly wrong

can it remove operationId ?

import { normalizeServerPrefix, resolvePathForServer } from '../drift/utils/server.js';
import { HTTP_METHODS, templatizePath } from './generator.js';

export interface TrafficSample {

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.

can we add another parameters (e.g. headers) ?

- Relax "required" for properties that are plausibly optional (e.g. absent in some samples, or clearly nullable). Express nullable values as type unions such as ["string", "null"].
- When a request or response body has alternative shapes — the baseline shows "oneOf" variants, or the samples show clearly different payloads — model them explicitly with "oneOf". Name each variant in components/schemas, and add a "discriminator" when a property (such as "type" or "kind") selects the variant.
- Factor structure shared between variants or schemas with "allOf": extract the common base into components/schemas and compose each variant as "allOf" of the base plus its specific properties.
- Keep the $ref references to the component schemas you were given. You may refine the definition of a referenced component when the samples justify it, and you may add new components, but never rename an existing component and never reuse a reserved component name listed in the input.

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.

can concurrency break this instruction?

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2d41e65. Configure here.

};

const workers = Math.max(1, Math.min(options.concurrency ?? 1, operations.length));
await Promise.all(Array.from({ length: workers }, worker));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Parallel workers throw unhandled rejections

Medium Severity

When --ai-concurrency is above 1 and the provider CLI is missing, one worker rethrows CliNotFoundError while others may still be inside refineOperation. Promise.all settles on the first rejection, so additional worker rejections can surface as unhandled promise rejections, and in-flight workers keep calling the provider until their current attempt finishes.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2d41e65. Configure here.

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.

4 participants