Conversation
🦋 Changeset detectedLatest commit: d6175e2 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
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 |
|
- Add "AI" before "agent readiness" in changeset and docs for clarity - Replace <pre> block with fenced code block in score.md - Add security scheme coverage to metrics documentation - Remove resolveIfRef helper, replace with resolveNode that falls back to the original node when resolution fails - Refactor to use walkDocument visitor approach (matching stats command pattern) instead of manually iterating the document tree - Use resolveDocument + normalizeVisitors + walkDocument from openapi-core for proper $ref resolution and spec-format extensibility - Update index.test.ts to mock the new walk infrastructure Made-with: Cursor
79078d6 to
0e328aa
Compare
Co-authored-by: Jacek Łękawa <164185257+JLekawa@users.noreply.github.com>
tatomyr
left a comment
There was a problem hiding this comment.
Left a couple of comments. I haven'd fully reviewed the scoring and collectors though as it takes time.
packages/cli/src/commands/score/__tests__/document-metrics.test.ts
Outdated
Show resolved
Hide resolved
tatomyr
left a comment
There was a problem hiding this comment.
I found some dead code. Please check if it's needed and remove if not. Also, I'm not sure what tests to review as many appear to only test that dead code, so let's handle that first.
- Inline collect-metrics.ts test helper into document-metrics.test.ts - Use parseYaml as yaml directly instead of wrapper function - Remove default parameter from getStylishOutput in formatter tests - Use getMajorSpecVersion + exitWithError for spec version check - Add explicit case 'stylish' before default in format switch - Remove unsupported 'markdown' from score command format choices - Add comment explaining depth=-1 initialization - Clarify anyOf penalty and dependency terminology in docs - Update non-oas3 rejection test for exitWithError (throws) Made-with: Cursor
- Add type cast for parseYaml (returns unknown) in document-metrics tests - Inline collectDocumentMetrics helper into example-coverage tests - Remove collect-metrics.js import from scoring tests, use direct metrics - Add missing debugLogs property to accumulator mock in index tests Made-with: Cursor
packages/cli/src/commands/score/__tests__/document-metrics.test.ts
Outdated
Show resolved
Hide resolved
Extract the metric-collection pipeline from handleScore into a standalone collect-metrics.ts module with two exports: - collectMetrics(): low-level function used by handleScore - collectDocumentMetrics(): high-level convenience used by tests This eliminates ~300 lines of duplicated walker setup across three test files (document-metrics, example-coverage, index) and ensures tests exercise the same code path as the production command. Add $ref-keyed memoization to walkSchema so repeated references to the same component schema return cached stats instead of re-walking. Stripe API: 37.6s → 11.3s (3.3× faster). Made-with: Cursor
Add median alongside averages for parameters, schema depth, polymorphism, and properties in the stylish output. Rename the misleading "Avg max schema depth" to "Schema depth". Made-with: Cursor
Rename workflowClarity to dependencyClarity, workflowDepths to dependencyDepths, and workflow-graph to dependency-graph to align code naming with the "Dependency Clarity" display label. Also adds discoverability subscore, recursive composition keyword stripping for accurate property counting, and updates e2e snapshots. Made-with: Cursor
| } | ||
|
|
||
| describe('printScoreStylish', () => { | ||
| it('outputs scores, subscores, metrics, and hotspots', () => { |
There was a problem hiding this comment.
Instead of mocking and presetting, it's better to simply create e2e tests for that cases.
There was a problem hiding this comment.
Did this, but:
ERROR: Coverage for branches (70.99%) does not meet global threshold (71%)
Error: Process completed with exit code 1.
There was a problem hiding this comment.
That's not a problem. You can lower the threshold in vitest.config.ts. It's expected that we only cover with unit tests only what's reasonable.
- Use isPlainObject from openapi-core for schema cycle detection instead of raw typeof checks that match arrays - Remove formatter unit tests in favor of e2e snapshot coverage - Clarify makeScores() purpose in hotspot tests Made-with: Cursor
| # `score` | ||
|
|
||
| ## Introduction | ||
|
|
There was a problem hiding this comment.
| {% admonition type="warning" name="Important" %} | |
| The `score` command is considered an experimental feature. This means it's still a work in progress and may go through major changes. | |
| The `score` command supports OpenAPI 3.x descriptions only. | |
| {% /admonition %} | |
I suggest adding the 'experimental' admonition similar to the join command.
| {% admonition type="warning" name="OpenAPI 3.x only" %} | ||
| The `score` command supports OpenAPI 3.0, 3.1, and 3.2 descriptions. | ||
| OpenAPI 2.0 (Swagger) and AsyncAPI are not currently supported. | ||
| {% /admonition %} |
There was a problem hiding this comment.
| {% admonition type="warning" name="OpenAPI 3.x only" %} | |
| The `score` command supports OpenAPI 3.0, 3.1, and 3.2 descriptions. | |
| OpenAPI 2.0 (Swagger) and AsyncAPI are not currently supported. | |
| {% /admonition %} |
|
|
||
| ```bash | ||
| redocly score <api> | ||
| redocly score <api> [--format=<option>] [--config=<path>] |
There was a problem hiding this comment.
| redocly score <api> [--format=<option>] [--config=<path>] | |
| redocly score <api> [--format=<option>] |
I don't think we explicitly use any redocly.yaml configuration in this command, so for me it doesn't make too much sense to list it in the examples.
|
|
||
| describe('handleScore', () => { | ||
| beforeEach(() => { | ||
| mockOutput.mockClear(); |
There was a problem hiding this comment.
This is redundant as we configured restoreMocks: true in vitest config.
| mockOutput.mockClear(); |
| metrics: makeDocumentMetrics(new Map([['listItems', makeTestMetrics()]])), | ||
| debugLogs: [], | ||
| }); | ||
| process.exitCode = undefined; |
There was a problem hiding this comment.
Seems redundant. At least, I don't see where we use exitCode in the tests.
| } | ||
| }, | ||
| }, | ||
| Paths: { |
There was a problem hiding this comment.
What is this for? Could PathItems reside outside the Paths?
| Array.from(result.rawMetrics.operations.entries()).map(([key, m]) => [ | ||
| key, | ||
| { | ||
| path: m.path, |
There was a problem hiding this comment.
What is this mapping useful for? Why not simply use m or { ...m }? Are you trying to clean some properties?
| .filter((v) => typeof v === 'string' && v.startsWith('#/')) | ||
| .map((v) => ({ $ref: v })) | ||
| : null; | ||
| const hasDiscriminatorBranches = discriminatorRefs != null && discriminatorRefs.length > 0; |
There was a problem hiding this comment.
| const hasDiscriminatorBranches = discriminatorRefs != null && discriminatorRefs.length > 0; | |
| const hasDiscriminatorBranches = Array.isArray(discriminatorRefs) && discriminatorRefs.length > 0; |
| for (let i = 1; i < discriminatorRefs!.length; i++) { | ||
| const branchStats = walkSchema(discriminatorRefs![i], debug); |
There was a problem hiding this comment.
| for (let i = 1; i < discriminatorRefs!.length; i++) { | |
| const branchStats = walkSchema(discriminatorRefs![i], debug); | |
| for (let i = 1; i < discriminatorRefs.length; i++) { | |
| const branchStats = walkSchema(discriminatorRefs[i], debug); |
I don't think you need those asserts.
| } | ||
|
|
||
| const localPropertyNames: string[] = []; | ||
| if (schema.properties && typeof schema.properties === 'object') { |
There was a problem hiding this comment.
| if (schema.properties && typeof schema.properties === 'object') { | |
| if (isPlainObject(schema.properties)) { |
|
|
||
| function hasExample(mediaType: { example?: unknown; examples?: Record<string, unknown> }): boolean { | ||
| if (mediaType.example !== undefined) return true; | ||
| if (mediaType.examples && typeof mediaType.examples === 'object') { |
There was a problem hiding this comment.
| if (mediaType.examples && typeof mediaType.examples === 'object') { | |
| if (isNotEmptyObject(mediaType.examples)) { |
|
|
||
| const walkSchema = (schemaNode: any, debug = false): SchemaStats => { | ||
| let resolved = schemaNode; | ||
| const ref: string | undefined = |
There was a problem hiding this comment.
It's better to reuse the isRef utility function here.
What/Why/How?
What: Adds a new score command to Redocly CLI that analyzes OpenAPI 3.x descriptions and produces two composite scores: Integration Simplicity (0-100, how easy is this API to integrate) and Agent Readiness (0-100, how usable is this API by AI agents/LLM tooling).
Why: API producers currently lack a quick, deterministic way to assess how developer-friendly or AI-agent-friendly their API descriptions are. The existing stats command counts structural elements but doesn't evaluate quality signals like documentation coverage, example presence, schema complexity, or error response structure. This command fills that gap with actionable, explainable scores.
How: The implementation follows the same pattern as the
statscommand (bundle+analyze), with a clean separation between metric collection and score calculation:collectors/): Walks the bundled document, resolving internal $refs, to gather per-operation raw metrics (parameter counts, schema depth, polymorphism, description/constraint/example coverage, structured error responses, workflow dependency depth via shared schema refs).scoring.ts): Pure functions that normalize raw metrics into subscores and compute weighted composite scores. Thresholds and weights are configurable constants. anyOf is penalized more heavily than oneOf/allOf; discriminator presence improves the agent readiness polymorphism clarity subscore.hotspots.ts): Identifies the operations with the most issues, sorted by number of reasons, with human-readable explanations.--format=stylish(default, with color bar charts) and--format=json(machine-readable for CI/dashboards).Reference
Related to API governance and developer experience tooling. No existing issue -- this is a new feature.
Testing
$refresolution, polymorphism counting (oneOf/anyOf/allOf), constraint detection (including const), example coverage scoring,anyOfpenalty multiplier,discriminatorimpact on agent readiness, deterministic output, and score range validation.tsc --noEmit), all existing tests continue to pass.Screenshots (optional)
Stylish output for Redocly Cafe:
Check yourself
Security