Skip to content

feat(cli): add the recheck engine and the redocly recheck command - #3080

Merged
adamaltman merged 99 commits into
mainfrom
aa/recheck-relocate-engine
Oct 2, 2026
Merged

adamaltman merged 99 commits into
mainfrom
aa/recheck-relocate-engine

Conversation

@adamaltman

@adamaltman adamaltman commented Sep 3, 2026 •

Copy link
Copy Markdown
Member

What/Why/How?

Recheck Phase 2 moves the prose-linting engine from the Redocly monorepo into this repository and adds the redocly recheck command (design: Redocly/redocly#26970).
This PR carries both; #3082 held the command and folded into this branch.

Engine package.
packages/recheck joins the fixed version group.
What moved: parser, scopes, rules, runner, auto-fix, directives, config validation, and presets.
Also moved: metrics, reporters, examples, the example generator, and two agent skills.
What did not move: the standalone cli.ts, the yargs command files, and recheck.yaml discovery.
The markdownlint parity harness and a drift test that needs the Realm theme source also stayed behind.

The command files became plain actions: runLint, generateBaseline, runReadability, and generateMarkdocSchema.
Each takes a resolved config and returns a typed result; the engine prints nothing.
resolveRecheckConfig takes the ordered recheck/* names and the merged block, and returns normalized rules.
The engine depends on @redocly/openapi-core for the rule merge and composes the presets named in extends itself.
Preset entries carry no message; validate fills a token rule's default at validation.

Core.
Core imports nothing from @redocly/recheck, so it stays lean for browser contexts.
config/recheck.ts owns the recheck/ prefix check, the reserved plugin id, the rule merge, and the block merge.
bundleExtends sets recheck/* entries aside in extends order, across shared files and apis, into Config.recheckExtends.
mergeExtends merges the recheck blocks it sees, and Config.recheck holds the result, typed with RecheckConfig from @redocly/config.

lint reads no rules from either; a custom plugin cannot use the id recheck.
Core pins @redocly/config 0.57, which carries the recheck root key, so check-config accepts the block.

Command.
redocly recheck [paths..] lints by default.
Action flags select one other action: --readability, --generate-baseline, or --generate-markdoc-schema (with --from, --output, --check).
Runs pick up a .redocly.recheck-baseline.yaml next to redocly.yaml by presence; a baseline key in the block is a config error.
--fix applies to linting only.

Output flags: --format (table default), --stats, --max-problems, --summary, --summary-path.
The report goes to stdout and the logs to stderr, so > file gives a clean JSON or SARIF file.
--max-problems applies to every format; the table counts every problem and prints the capped rows.
Selection flags: --tags, --rule, --skip-rule; there is no --severity, the same as lint.
To scope a run to changed files, pass their paths.

With no redocly.yaml, the command uses recheck/markdown and says so on stderr.
With a redocly.yaml that has no recheck/* preset and no recheck block, it checks nothing and says so, the same as lint.
The command reads the root config; it warns when apis.<name> carries recheck settings.
Config errors print the path where the user wrote the key, such as recheck.rules.<key>, and a message without the key.
The handler loads lazily and throws AbortFlowError on a failed run, the same path lint uses; commandWrapper keeps its behavior.

Printing.
The CLI owns every printed line and every report format.
packages/cli/src/commands/recheck/print.ts turns each result into the lines the engine printed before, through the core logger: progress on stderr, payloads on stdout.
The report formats live in packages/cli/src/commands/recheck/formatters/.
The engine's Logger is gone, and the e2e snapshots did not change through the rework.
One accepted edge: when writing a fixed file fails, the run reports the error without the auto-fix header.

CLI build.
packages/cli bundles the engine and the spell-check packages, nspell and dictionary-en; the build copies the dictionary data files next to their chunk.
scripts/local-pack.sh packs the engine.

Review outcome.
Roman and tatomyr settled the config integration on #3082: tatomyr's plan, the nested block, and core imports the presets from the engine.
Commits 21d0f29 to e3ec9e5 implement it.
The round of 2026-09-26 (AlbinaBlazhko17, vadyvas) made the preset data pure.
On 2026-09-27 Roman reversed the dependency direction: core imports nothing from the engine, and the engine depends on core, as respect-core does.

The round of 2026-09-28 (AlbinaBlazhko17) dropped --output-path, renamed --out, aligned --max-problems and the GitHub Actions escaping with lint, and cleaned the error paths.
Two asks live elsewhere: Redocly/redocly#28359 drops baseline from the config schema, and Redocly/redocly#28358 tracks one shared schema.
I answered the config-shape threads on #3082.
On 2026-10-01 Roman audited the tests (one file per module, exact assertions, test-only exports removed) and trimmed core's recheck exports to isRecheckPreset and mergeRecheckRules.

Follow-ups.
apis.<name>.recheck merges in core, but the @redocly/config schema rejects it; claim it there or stop merging it.
A program that embeds the engine without core now loads core too.
Core's main entry loads in about 96 milliseconds, and the engine alone in about 183 milliseconds.
A core subpath export of config/recheck.js, like ./lib/bundle-oas, would remove that cost; it adds public surface, so it waits for a maintainer's go.
A rules value of the wrong type under extends keeps the preset rules; config lint warns about it.

Reference

Testing

Unit tests cover the engine, the recheck config merge and preset ordering in core, and selectAction.
End-to-end tests live in tests/e2e/recheck/ with ten fixtures and snapshots.
They cover lint, JSON and SARIF stdout, readability, and baseline generation.
They also cover the Markdoc schema, the fallback notice, the no-config notice, config errors, and conflicting flags.

Gates: npm run compile, npm run typecheck, npm run lint, npm run format:check, npm run unit, and the recheck and check-config e2e suites.

Screenshots (optional)

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
Adds a large new package and CLI surface plus config-resolution changes (plugins injection, reserved id, extends merging), though API lint paths stay separate and failures are mostly scoped to the recheck command.

Overview
This PR lands the Recheck markdown/prose linting engine in packages/recheck and wires it into Redocly CLI as redocly recheck, with coordinated minor releases for CLI, core, and the new package.

Engine and command. The engine exposes actions (runLint, runReadability, generateBaseline, generateMarkdocSchema) that return structured results; the CLI owns reporting (table/JSON/SARIF/GitHub Actions, summaries, auto-fix output). The command supports lint (default), --readability, --generate-baseline, and --generate-markdoc-schema, plus rule selection, baselines beside redocly.yaml, and skipping API description paths for now. Without config it falls back to recheck/markdown; with redocly.yaml but no recheck preset/block it no-ops with a notice.

Config in openapi-core. loadConfig / createConfig accept optional ready plugins (used to inject @redocly/recheck/presets). Root recheck block merges on resolved Config.recheck; recheck/* in extends resolves like other presets when the recheck plugin is present and is ignored otherwise. lint does not consume recheck rules; plugin id recheck is reserved. commandWrapper can pass plugins so only the recheck command loads presets.

Repo hygiene. Changesets, docs (recheck command and recheck config reference), CONTRIBUTING for the engine, formatter/linter ignores, CLI build copies dictionary-en data for bundled spell-check, and broad unit/e2e coverage.

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

@adamaltman
adamaltman requested review from a team as code owners September 3, 2026 01:35
@changeset-bot

changeset-bot Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e5dbdb5

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

This PR includes changesets to release 6 packages
Name Type
@redocly/cli Minor
@redocly/openapi-core Minor
@redocly/recheck Minor
@redocly/client-generator Patch
@redocly/respect-core Minor
@redocly/reunite-integration 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

Comment thread packages/recheck/src/rules/token/link-fragments.ts Fixed
Comment thread packages/recheck/src/rules/token/link-image-style.ts Fixed
Comment thread packages/recheck/src/rules/token/link-image-style.ts Fixed
Comment thread packages/recheck/src/scopes/sentences.ts Fixed
@github-actions

github-actions Bot commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 83.42% (🎯 79%) 20076 / 24064
🔵 Statements 83.05% (🎯 78%) 21696 / 26122
🔵 Functions 85.21% (🎯 82%) 3820 / 4483
🔵 Branches 75.55% (🎯 71%) 14582 / 19299
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/cli/src/types.ts 100% 100% 100% 100%
packages/cli/src/wrapper.ts 87.71% 62.22% 100% 87.5% 72, 92-94, 129-133
packages/cli/src/commands/lint.ts 94.44% 85% 100% 94.44% 77-79, 146, 180
packages/cli/src/commands/recheck/index.ts 82.19% 73.33% 88.88% 84.61% 44, 48, 64, 98-99, 103-111, 130, 161-163
packages/cli/src/commands/recheck/print.ts 95.09% 86.66% 92.85% 94.89% 31, 51, 57, 62, 85
packages/cli/src/commands/recheck/select-action.ts 94.11% 96.15% 100% 92.3% 9-11
packages/cli/src/commands/recheck/formatters/fixes.ts 100% 100% 100% 100%
packages/cli/src/commands/recheck/formatters/github-actions.ts 100% 100% 100% 100%
packages/cli/src/commands/recheck/formatters/index.ts 100% 100% 100% 100%
packages/cli/src/commands/recheck/formatters/json.ts 100% 50% 100% 100%
packages/cli/src/commands/recheck/formatters/prioritize-problems.ts 81.81% 60% 100% 87.5% 16, 18
packages/cli/src/commands/recheck/formatters/readability.ts 100% 100% 100% 100%
packages/cli/src/commands/recheck/formatters/sarif.ts 100% 75% 100% 100%
packages/cli/src/commands/recheck/formatters/statistics.ts 100% 71.42% 100% 100%
packages/cli/src/commands/recheck/formatters/summary.ts 100% 100% 100% 100%
packages/cli/src/commands/recheck/formatters/table.ts 100% 100% 100% 100%
packages/cli/src/utils/miscellaneous.ts 64.15% 58.33% 77.5% 63.46% 139, 196-246, 272-273, 283-284, 299, 314, 322-355, 366, 391, 400, 408-448, 563
packages/core/src/config/bundle-extends.ts 93.33% 87.5% 100% 92.3% 39
packages/core/src/config/config-resolvers.ts 79.66% 66.83% 94.11% 80.45% 78, 106-109, 201, 210, 266, 286-287, 311, 324, 335, 338-342, 350-359, 383-385, 401, 404, 407, 410, 423-425, 429, 435, 438, 441-444, 447-450, 453-456, 470-472, 479, 482, 485, 488, 491, 494, 519-521
packages/core/src/config/config.ts 62.12% 62.98% 70.45% 62.5% 188-218, 245, 249, 253-259, 262, 266-275, 307, 325-345, 399-407, 440-470
packages/core/src/config/load.ts 90% 68.75% 100% 92.3% 51, 82, 108, 154
packages/core/src/config/recheck.ts 100% 100% 100% 100%
packages/core/src/config/utils.ts 97.67% 81.81% 100% 98.82% 38, 93-95
packages/core/src/types/redocly-yaml.ts 89.21% 77.35% 92.85% 88.88% 493, 525, 531, 567-574, 576, 715-725, 735-751
packages/recheck/src/index.ts 100% 88.88% 100% 100%
packages/recheck/src/presets.ts 100% 100% 100% 100%
packages/recheck/src/actions/baseline.ts 100% 33.33% 100% 100%
packages/recheck/src/actions/lint.ts 96.42% 83.87% 100% 98.14% 63, 100
packages/recheck/src/actions/markdoc-schema.ts 100% 86.36% 100% 100%
packages/recheck/src/actions/readability.ts 100% 66.66% 100% 100%
packages/recheck/src/actions/roots.ts 100% 91.66% 100% 100%
packages/recheck/src/config/resolve.ts 100% 100% 100% 100%
packages/recheck/src/config/schema.ts 100% 100% 100% 100%
packages/recheck/src/config/validate.ts 97.47% 93.33% 100% 98.51% 39, 66-70, 103, 931-937, 1020, 1023, 1042, 1089, 1137-1140
packages/recheck/src/config/presets/api-descriptions.ts 100% 100% 100% 100%
packages/recheck/src/config/presets/google.ts 100% 100% 100% 100%
packages/recheck/src/config/presets/inclusive-language.ts 100% 100% 100% 100%
packages/recheck/src/config/presets/index.ts 100% 100% 100% 100%
packages/recheck/src/config/presets/markdoc.ts 100% 100% 100% 100%
packages/recheck/src/config/presets/markdown-relaxed.ts 90% 50% 100% 100% 32
packages/recheck/src/config/presets/markdown.ts 100% 100% 100% 100%
packages/recheck/src/config/presets/microsoft.ts 100% 100% 100% 100%
packages/recheck/src/config/presets/minimal.ts 100% 100% 100% 100%
packages/recheck/src/config/presets/plain-language.ts 100% 100% 100% 100%
packages/recheck/src/config/presets/prose.ts 100% 100% 100% 100%
packages/recheck/src/config/presets/technical-english.ts 100% 100% 100% 100%
packages/recheck/src/core/auto-fix.ts 100% 96.61% 100% 100%
packages/recheck/src/core/baseline.ts 100% 100% 100% 100%
packages/recheck/src/core/case-preserve.ts 90% 91.66% 100% 100% 12
packages/recheck/src/core/directives.ts 100% 100% 100% 100%
packages/recheck/src/core/files.ts 80.24% 84.61% 76.47% 82.05% 35, 47, 66-79, 163
packages/recheck/src/core/inline-code.ts 100% 66.66% 100% 100%
packages/recheck/src/core/line-endings.ts 100% 100% 100% 100%
packages/recheck/src/core/markdoc-tags.ts 94.91% 82.5% 100% 96.29% 43, 94-95
packages/recheck/src/core/prose-extract.ts 95% 88.88% 100% 97.14% 18, 45
packages/recheck/src/core/readability.ts 100% 100% 100% 100%
packages/recheck/src/core/rule-filters.ts 94.64% 83.33% 100% 100% 49, 79, 90
packages/recheck/src/core/runner.ts 97.14% 94.17% 100% 96.74% 153, 166, 227-228
packages/recheck/src/core/summary.ts 0% 0% 0% 0% 4-47
packages/recheck/src/core/timing.ts 0% 0% 0% 0% 2-21
packages/recheck/src/data/markdoc-realm-schema.ts 100% 100% 100% 100%
packages/recheck/src/data/proper-nouns.ts 100% 100% 100% 100%
packages/recheck/src/data/realm-front-matter-schema.ts 100% 100% 100% 100%
packages/recheck/src/metrics/formulas.ts 90% 90.9% 100% 89.47% 66-67
packages/recheck/src/metrics/statistics.ts 100% 100% 100% 100%
packages/recheck/src/parser/index.ts 100% 96.96% 100% 100%
packages/recheck/src/parser/markdoc/extract-statics.ts 91.89% 70.37% 100% 93.93% 57, 68, 71
packages/recheck/src/parser/markdoc/pairing.ts 97.77% 92.59% 100% 100% 90
packages/recheck/src/parser/markdoc/schema.ts 100% 100% 100% 100%
packages/recheck/src/parser/markdoc/span.ts 96.75% 94.33% 100% 97.53% 166, 260, 281, 446-451, 525, 551, 556, 672, 685
packages/recheck/src/parser/markdoc/structure.ts 100% 100% 100% 100%
packages/recheck/src/parser/markdoc/syntax.ts 92.19% 84.94% 100% 94.44% 114, 133, 146, 165-166, 192, 212, 239-240, 268, 285
packages/recheck/src/rules/registry.ts 77.77% 75% 66.66% 80% 32-34
packages/recheck/src/rules/utils.ts 100% 100% 100% 100%
packages/recheck/src/rules/scope/capitalization.ts 100% 88.63% 100% 100%
packages/recheck/src/rules/scope/conditional.ts 100% 80.76% 100% 100%
packages/recheck/src/rules/scope/consistency.ts 100% 87.5% 100% 100%
packages/recheck/src/rules/scope/length.ts 100% 95.45% 100% 100%
packages/recheck/src/rules/scope/max-image-size.ts 100% 84.61% 100% 100%
packages/recheck/src/rules/scope/metric.ts 100% 100% 100% 100%
packages/recheck/src/rules/scope/occurrence.ts 100% 94.44% 100% 100%
packages/recheck/src/rules/scope/pattern.ts 100% 78.57% 100% 100%
packages/recheck/src/rules/scope/repetition.ts 100% 87.5% 100% 100%
packages/recheck/src/rules/scope/semantic-line-breaks.ts 100% 93.18% 100% 100%
packages/recheck/src/rules/scope/spelling.ts 100% 85.41% 100% 100%
packages/recheck/src/rules/scope/swap.ts 100% 91.66% 100% 100%
packages/recheck/src/rules/scope/title-case.ts 98.03% 89.83% 100% 100% 180, 222
packages/recheck/src/rules/token/blanks-around-fences.ts 100% 83.33% 100% 100%
packages/recheck/src/rules/token/blanks-around-headings.ts 88.37% 84.37% 83.33% 89.47% 15-19
packages/recheck/src/rules/token/blanks-around-lists.ts 100% 83.33% 100% 100%
packages/recheck/src/rules/token/blanks-around-tables.ts 100% 75% 100% 100%
packages/recheck/src/rules/token/code-block-style.ts 100% 87.5% 100% 100%
packages/recheck/src/rules/token/code-fence-style.ts 100% 87.5% 100% 100%
packages/recheck/src/rules/token/commands-show-output.ts 100% 100% 100% 100%
packages/recheck/src/rules/token/descriptive-link-text.ts 100% 71.42% 100% 100%
packages/recheck/src/rules/token/emphasis-style.ts 95.45% 88% 100% 100% 32
packages/recheck/src/rules/token/fenced-code-language.ts 100% 91.66% 100% 100%
packages/recheck/src/rules/token/first-line-h1.ts 93.33% 88.57% 100% 94.59% 25, 29, 78
packages/recheck/src/rules/token/front-matter.ts 95.18% 84% 100% 94.52% 68-70, 74-77, 91-93, 129
packages/recheck/src/rules/token/heading-increment.ts 100% 100% 100% 100%
packages/recheck/src/rules/token/heading-start-left.ts 100% 100% 100% 100%
packages/recheck/src/rules/token/heading-style.ts 88.23% 60.71% 100% 88.23% 33, 35
packages/recheck/src/rules/token/helpers.ts 97.18% 83.52% 100% 97% 169, 172, 175, 229-230, 472-473
packages/recheck/src/rules/token/hr-style.ts 100% 83.33% 100% 100%
packages/recheck/src/rules/token/index.ts 100% 100% 100% 100%
packages/recheck/src/rules/token/line-length.ts 100% 87.5% 100% 100%
packages/recheck/src/rules/token/link-fragments.ts 93.22% 86.25% 100% 94.78% 92-100, 241, 268, 277, 282, 340, 344, 364
packages/recheck/src/rules/token/link-image-reference-definitions.ts 93.75% 64.7% 100% 100% 46
packages/recheck/src/rules/token/link-image-style.ts 98.33% 85.18% 100% 98.33% 30
packages/recheck/src/rules/token/list-indent.ts 100% 83.33% 100% 100%
packages/recheck/src/rules/token/list-length.ts 100% 91.66% 100% 100%
packages/recheck/src/rules/token/list-marker-space.ts 100% 75% 100% 100%
packages/recheck/src/rules/token/markdoc-attributes.ts 97.93% 83.75% 100% 100% 96, 103
packages/recheck/src/rules/token/markdoc-pairing.ts 100% 90% 100% 100%
packages/recheck/src/rules/token/markdoc-syntax.ts 100% 88.88% 100% 100%
packages/recheck/src/rules/token/markdoc-unknown-tag.ts 95.65% 93.75% 100% 100% 38
packages/recheck/src/rules/token/messages.ts 100% 91.66% 100% 100%
packages/recheck/src/rules/token/no-alt-text.ts 100% 84.61% 100% 100%
packages/recheck/src/rules/token/no-bare-urls.ts 96.87% 83.33% 100% 96.55% 27
packages/recheck/src/rules/token/no-blanks-blockquote.ts 100% 87.5% 100% 100%
packages/recheck/src/rules/token/no-duplicate-heading.ts 100% 100% 100% 100%
packages/recheck/src/rules/token/no-duplicate-link-destinations.ts 95.45% 83.33% 100% 100% 34
packages/recheck/src/rules/token/no-emphasis-as-heading.ts 100% 93.33% 100% 100%
packages/recheck/src/rules/token/no-empty-headings.ts 100% 100% 100% 100%
packages/recheck/src/rules/token/no-empty-links.ts 100% 96.29% 100% 100%
packages/recheck/src/rules/token/no-hard-tabs.ts 100% 87.5% 100% 100%
packages/recheck/src/rules/token/no-inline-html.ts 100% 92.85% 100% 100%
packages/recheck/src/rules/token/no-missing-space-atx.ts 100% 90% 100% 100%
packages/recheck/src/rules/token/no-missing-space-closed-atx.ts 90.47% 81.25% 100% 94.44% 19, 23
packages/recheck/src/rules/token/no-multiple-blanks.ts 100% 87.5% 100% 100%
packages/recheck/src/rules/token/no-multiple-space-atx.ts 93.33% 100% 100% 93.33% 16
packages/recheck/src/rules/token/no-multiple-space-blockquote.ts 94.73% 68.42% 100% 100% 26
packages/recheck/src/rules/token/no-multiple-space-closed-atx.ts 100% 100% 100% 100%
packages/recheck/src/rules/token/no-reversed-links.ts 95.45% 87.5% 100% 100% 29
packages/recheck/src/rules/token/no-space-in-code.ts 96.87% 88.88% 100% 100% 17
packages/recheck/src/rules/token/no-space-in-emphasis.ts 100% 69.23% 100% 100%
packages/recheck/src/rules/token/no-space-in-links.ts 100% 77.77% 100% 100%
packages/recheck/src/rules/token/no-trailing-punctuation.ts 100% 87.5% 100% 100%
packages/recheck/src/rules/token/no-trailing-spaces.ts 93.18% 81.48% 100% 92.85% 30, 47, 67
packages/recheck/src/rules/token/ol-prefix.ts 100% 90.9% 100% 100%
packages/recheck/src/rules/token/proper-names.ts 95.74% 80.76% 75% 97.72% 77-82, 87
packages/recheck/src/rules/token/reference-links-images.ts 100% 80% 100% 100%
packages/recheck/src/rules/token/required-headings.ts 94.87% 92.85% 75% 97.22% 33, 66
packages/recheck/src/rules/token/single-h1.ts 100% 93.33% 100% 100%
packages/recheck/src/rules/token/single-trailing-newline.ts 100% 75% 100% 100%
packages/recheck/src/rules/token/strong-style.ts 95.45% 88% 100% 100% 32
packages/recheck/src/rules/token/table-column-count.ts 100% 100% 100% 100%
packages/recheck/src/rules/token/table-column-style.ts 98.38% 92.59% 100% 98.27% 146-155
packages/recheck/src/rules/token/table-pipe-style.ts 100% 95.45% 100% 100%
packages/recheck/src/rules/token/ul-indent.ts 100% 85.71% 100% 100%
packages/recheck/src/rules/token/ul-style.ts 100% 86.66% 100% 100%
packages/recheck/src/scopes/extractor.ts 95.93% 87.68% 96.96% 98.51% 32, 46, 108, 114, 165, 199-200, 361, 383
packages/recheck/src/scopes/selector.ts 97.56% 96.66% 100% 100% 80
packages/recheck/src/scopes/sentences.ts 94.44% 90.22% 100% 98.16% 54, 109, 118, 128, 130, 137, 174, 208
packages/recheck/src/scopes/vocabulary.ts 100% 100% 100% 100%
packages/recheck/src/utils/is-plain-object.ts 100% 100% 100% 100%
Generated in workflow #12189 for commit e5dbdb5 by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

Performance Benchmark (Lower is Faster)

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

Comment thread packages/recheck/src/actions/lint.ts Outdated
@adamaltman

adamaltman commented Sep 3, 2026 •

Copy link
Copy Markdown
Member Author

The latest commit resolves the five review findings:

  • CodeQL, sentences.ts polynomial regex.
    A linear scanner now finds link ranges.
    The scanner visits each bare [ once, so an escape-heavy paragraph stays fast.
    A test checks that a 100 KB paragraph splits in bounded time.
  • CodeQL, link-fragments.ts tag sanitization.
    A character scanner strips summary tags.
    The output is the same as the old one-pass replace.
  • CodeQL, link-image-style.ts escaping.
    Fixes now escape only bare brackets and parens.
    Existing escape pairs pass through unchanged, so the fix does not double-escape a label.
  • CodeQL, core/baseline.ts prototype pollution.
    Baselines build on prototype-free maps, so a file or rule named __proto__ is a plain key.
  • Bugbot, image metadata root.
    Lint and baseline now confine image lookups to the root that contains each file.
    The monorepo commands had the same gap, so this also fixes an inherited bug.
    A test lints a temp directory outside the working directory and expects a report for the oversized image.

@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.

Stale Bugbot comment from a previous run.

Comment thread packages/recheck/src/actions/__tests__/roots.test.ts
Comment thread packages/recheck/README.md Outdated
Comment thread packages/recheck/README.md Outdated
@adamaltman

Copy link
Copy Markdown
Member Author

Applied both README suggestions in 377925a. Thank you.

@adamaltman

Copy link
Copy Markdown
Member Author

Ported Redocly/redocly#26904 in aa8e41c: the idea admonition type in the Realm Markdoc schema.
The engine copy now matches @redocly/recheck@0.13.0, and no other monorepo engine change landed after the fork point.

The engine moves from the Redocly monorepo without its standalone CLI
layer: no bin, no yargs, no recheck.yaml discovery. It joins the fixed
version group.
…erride

The engine tests never type-checked in their old repository. RecheckConfig
now declares the top-level keys next to the rule entries, and the package
joins the existing cognitive-complexity override instead of per-site
disables.
Rule names always carry a slash, so a template-literal index signature
keeps engine keys and rule entries apart at compile time.
The block nests rules under `rules` and takes presets from the root
extends. Engine warnings flow through a warn callback.
Each action takes a resolved config and a Logger. The reporters return
their output through the same Logger, so the engine prints nothing on
its own.
Scan link ranges in sentences.ts with a linear walk instead of a regex.
Strip summary tags with a scanner instead of a one-pass replace.
Escape only bare brackets and parens in link-image-style fixes.
Build baselines on prototype-free maps.
Confine image metadata to the root each file was discovered under.
Split the release sentence in two and state where the docs live.
Removed or merged:
- 21 per-rule "passes a document with no X" tests (identical bare-paragraph
  input), replaced by one table over allTokenRules in token/__tests__/index.test.ts
- 14 helper unit tests duplicated by rule-level tests (filterByPredicate 3,
  getDescendantsByType 3, getParentOfType 3, addRangeToSet 3, getHeadingText 2)
- registry.test.ts (2 tests: a hard-coded rule count and an originals list that
  mirror the source; unregistered rules fail every rule test and presets.test.ts)
- 1 no-duplicate-heading "scales to thousands" test that cannot detect a slowdown
- 1 no-hard-tabs default-codeBlocks test subsumed by the codeBlocks:false test
- utils.test.ts: 3 file-exception variants and the temp-dir setup; kept one
  shouldProcessFile case, the matcher stays covered by fileMatchesAnyPattern
- convergence.test.ts: 4 "removed id" rows to 1 unknown-id test, 2 unknown-key
  tests to 1
- unused tokenRuleUnitHarness test helper
Removed categories:
- 5 detection-only "fix wave" / acceptance files (about 100 sentence-level
  --fix cases) and the per-preset namespace, detection-only, floor and
  fixture tests. They became 4 table-driven contracts over the five style
  presets plus markdoc: namespace, detection-only (data and a real --fix run
  over the violation fixtures), every shipped rule fires, clean prose clean.
- copied preset inventories: the 53-name markdown list, 21 copied default
  messages, 6 markdown-relaxed per-key tests (now one exact contract), the
  minimal list and message copies, the prose data copies, the markdoc shape
  and message tests, and the test-only PROSE_PRESET_ASSERTIONS,
  MARKDOC_PRESET_RULE_NAMES, MARKDOC_VIOLATION_CLASSES and empty
  MARKDOWN/MINIMAL_PRESET_MESSAGES exports plus the unused `messages`
  parameter of registerPresetRules.
- the exact-source-grep test of markdoc report call sites, the registry
  "mutation proof" self-test and the vacuity probe.
- duplicates: composition and stacking tests merged into one table, the
  consistency engine guard replays (owned by consistency.test), two
  README snippet checks subsumed by the resolving test, the all/raw
  lintContent rejections (owned by validate and selector tests), the
  non-vacuity markdoc probe, 5 validate "accepts" cases covered by the
  vocabulary test, 2 runLint path-pattern replays (owned by utils.test), the
  empty-directory twin, and the resolve shared-preset test.
- repaired: the registry-immutability test could not detect a missing
  structuredClone; it now checks every preset.
- the two per-pair coverage harnesses share one helper.
Removed or merged:
- core config recheck.test.ts: 3 tests (orderRecheckPresets x2 duplicated
  by the createConfig preset-order tests; "block is not an object" x2
  duplicated by utils.test mergeExtends, which reaches both guards)
- cli print.test.ts: 1 test (printBaselineRun happy path, covered by the
  generate-baseline e2e snapshot plus the handler baseline test);
  7 printMarkdocSchemaRun tests merged into one table; the applied-fixes
  test no longer re-asserts the reportFixes output by stderr index
- cli formatters: 1 table test (fixable marks and counts, covered by the
  markdown-findings e2e snapshot); a "removed N character(s)" row moved
  into the reportFixes test, its owner
- cli select-action.test.ts: 2 tests (duplicate markdoc-schema assertion;
  two-flags rejection, covered by the conflicting-flags e2e)
- e2e: markdown-clean case and snapshot directory (covered by no-config
  and markdown-findings)

Production: prioritizeProblems drops its unused limit parameter and dead
branch; buildSarif is no longer exported (no caller outside sarif.ts).
Audited the scope-rule, scopes, parser (including Markdoc) and metrics
tests. Every edit was checked with a mutation battery of about 280
production mutations, run before and after the edit. No mutation that
failed a test before the edit passes after it.

Removed or merged in the tests (area test count 1011 -> 962, rows of
tables counted as tests):
- Consolidated into table-driven contracts: capitalization per-style
  fix/clean/code-span cases (about 50 tests), title-case AP/Chicago
  cases (about 37), semantic-line-breaks detect-vs-fix pairs (about 45),
  spelling word/vocab/code-span/digit cases (about 20), swap table-cell
  and case-preserving cases, span.test.ts whitespace, "=" spacing,
  shortcut, "@" variable and bare-function cases, statistics syllables
  (4 -> 1), stripNonProse (8 -> 1), max-image-size reference images (3 -> 1).
- Validation blocks of 10 rule test files replaced by
  expectValidOptions/expectInvalidOptions tables (shared helper in
  scope/__tests__/helpers.ts, also holds the deduplicated
  buildScopedContext that was copied into 7 files).
- Deleted duplicates (about 40): title-case length-preserving loop and 3
  Chicago-only copies; consistency execute-only overlap test, observed
  casing, can't/cannot and extra contraction rows; repetition lone-token
  idempotence; pattern/swap control tests; spelling multi-backtick
  control, config2/42 test and fallback duplicate; sentences reference
  link duplicate; selector "every vocabulary term compiles" block and
  call-shape test; extractor "has a fixture" test; parser flag-omitted
  vs flag-false tests, helper self-test, flag-on duplicates of the
  tokenize tests; tokenize flag-off duplicates, annotation kinds and the
  33-line tag; pairing bare-function duplicate; max-image-size
  within-limit test.
- Repaired 3 tests that could not fail (they passed under the mutation
  they were meant to catch): max-image-size external URL and non-existent
  file fixtures, spelling leading-digit fixture.

Production (net -4 lines): removed the stripNonProse re-export in
rules/scope/metric.ts (only a test used it; the test now imports the
owner), un-exported formatSuggestionSuffix (spelling.ts) and
SUMMARY_BLOCK_SOURCES (extractor.ts), whose only outside callers were
tests.

Kept on purpose: tokenize timing tests (the absolute ceilings guard
inputs the ratio tests never reach), the sentences linear-time test
(about 3 ms against a 2000 ms ceiling), title-case isAllCapsWord and
control-character rows, schema and formula suites.
- The 'baseline file not found' message no longer tells users to remove a
  `baseline` key from the recheck block; that key is now a config error.
- PROVENANCE.md files point to the preset tests that still exist.
@RomanHotsiy

RomanHotsiy commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

@Redocly/dark-side I pushed a few changes to this PR and removed 12K lines of code.
I think this PR is good to merge.
Please, someone approve - it's been a lot time.


Comment s by agent:

I pushed 17 commits to this branch: a test audit, a comment cleanup, a config resolution cleanup, and a few small fixes. Together they remove about 12,600 lines (+6,423 / −19,003), almost all of it tests and comments.

Checks on the final commit: typecheck, lint (0 errors), format, compile, all unit tests (5,881 pass, 2 skipped) and all e2e tests (416 pass).

Test audit

I used the test-audit skill from the Redocly monorepo. Every delete, merge or rewrite had to pass a mutation check, run before and after the edit. I broke the production code on purpose and confirmed the remaining test still failed. Anything I could not prove was left alone.

Commits: e9bb6e47e, 075234f01, 820fc4823, 3333f3171, 6fa2b20bc.

  • Removed:
    • About 100 --fix tests that only checked the text was unchanged. The shipped presets are detection-only, so these tests could not fail for the reason in their names. One table in presets.test.ts now checks this for all five style presets.
    • Tests that copied the data they load: a hard-coded "53 rules" registry test, copied preset messages, and regex counts over source files.
    • Duplicate tests across the unit, CLI and e2e layers, and one redundant e2e case (markdown-clean).
  • Merged: about 200 tests became table rows, and the per-rule option validation tests share one helper.
  • Repaired: a few tests passed even with the bug they were meant to catch put back (the preset immutability test, and some max-image-size and spelling cases). Their inputs now fail when the code is wrong.
  • Added: a few CLI handler tests (--rule, --skip-rule, --fix, the API-description skip, baseline pickup) and a check that recheck block rules never reach lint. I also merged test files so each module has one, as .claude/rules/testing.md asks.
  • Production code:
    • prioritizeProblems loses an unused limit parameter.
    • buildSarif is no longer exported.
    • Several exports used only by tests are gone or private: PROSE_PRESET_ASSERTIONS, MARKDOC_PRESET_RULE_NAMES, MARKDOC_VIOLATION_CLASSES, the empty *_PRESET_MESSAGES constants, the stripNonProse re-export, and the test-only tokenRuleUnitHarness.

Comment cleanup

Commit e7497bf13. I shortened the comments this PR adds and rewrote them in plain English, as AGENTS.md asks. I removed notes about the work process (task, phase and review-round numbers, PR numbers, "used to" history) and comments that only repeated the code. I also removed references to files and workflows that do not exist (task-*.md reports, a nightly recheck-perf workflow, scripts from another repo). The commit changes comments, a few test names and the provenance note in metrics/__tests__/fixtures/expected.json. I checked every changed file with comments stripped, and the code is the same.

Small fixes

Commits 4b078558d and f86c40422. The "baseline file not found" message no longer tells users to remove a baseline key, since that key is now a config error. Two PROVENANCE.md files and the rules guide no longer point to deleted tests.

Follow-up cleanups

Four more commits, one per item from the first review.

  • 37c0e31c3: removed the unused baseline key from the engine config. resolveRecheckConfig already rejects it in the block and reads the baseline from .redocly.recheck-baseline.yaml, and nothing read the value validate returned. It is gone from the schema, validate.ts, the engine config type and the tests that only covered it.
  • 08da759b5: core's entry point now exports only isRecheckPreset (used by the CLI) and mergeRecheckRules (used by the engine). The other recheck helpers stay inside core.
  • bfbb90793: moved the test-only token rule items out of the engine. The list of Recheck-only rules now lives in presets.test.ts. I dropped the tokenRules array and clearTokenRulesForTests from the registry. The two runner tests did not need the clearing, because registering a rule again under the same name replaces it. The rules guide snippet is updated.
  • df6e2bdac: removed the valid-config-with-recheck e2e case. The core lintConfig test already checks the same accept path.

Config resolution cleanup

Commits a32812cbd to f4e301139. recheck/* presets now resolve through core's normal extends path: loadConfig and createConfig take a new plugins option, and the recheck command passes the presets from @redocly/recheck/presets as a plugin. Presets merge with the recheck blocks in extends order, like other presets, and recheck/nope is a config error. Without the plugin (other commands, other users of core), core skips recheck/* entries. The preset data stays in the engine, so core does not grow.

Config.recheckExtends, the special case in bundleExtends, the engine's own extends handling, and core's isRecheckPreset and mergeRecheckRules exports are gone.

Not done

  • The timing tests in parser/markdoc/__tests__/tokenize.test.ts are skipped on CI unless RECHECK_PERF is set, and no workflow sets it. A slowdown would not fail CI. Some of them also fail now and then under heavy load. I could not show that the absolute-time tests catch a slowdown, so I kept them.
  • Nothing tests the argv mapping for --tags, --stats, --summary and --max-problems.

…onfig

The command reads the baseline from `.redocly.recheck-baseline.yaml` next
to `redocly.yaml`, and `resolveRecheckConfig` rejects a `baseline` key in
the block. Nothing read the `baseline` value that `validate` accepted and
returned, so drop it from the schema, `validate`, the engine config type
and the tests that only covered it.
Only `isRecheckPreset` (used by the CLI) and `mergeRecheckRules` (used by
the engine) are used outside core. The other recheck helpers stay inside
core and out of the public API.
- Move the list of Recheck-only token rules into the preset test that uses it.
- Drop the `tokenRules` array and `clearTokenRulesForTests` from the registry.
  Only the name map is used to resolve rules, and registering a rule again
  under the same name replaces it, so the two runner tests no longer need to
  clear the registry.
The core `lintConfig` test already checks that `check-config` accepts the
`recheck` block next to a `recheck/*` preset and rejects `extends` inside
it, so the e2e case checked the same thing a second time.

@tatomyr tatomyr left a comment

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.

The changes mostly look good. My main concern is changing the config resolution behaviour for Recheck compared to the existing approach. Also, the proposed approach won't let to run Recheck in the VS Code extension/Reunite easily.


formatProblems(problems, {
format: argv.format,
format: argv.format === 'table' ? undefined : argv.format,

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.

What is this for? Do you introduce the table format for the lint command? Same for the sarif type above.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No, lint doesn't get a new format. handleLintConfig runs for every command: the wrapper lints redocly.yaml with that command's --format.
recheck defaults to --format=table, which the config problem formatter doesn't have, so nothing would print; undefined falls back to codeframe.
sarif is one JSON document on stdout, so it skips the separate config report, like json, junit and checkstyle.
I added a comment above the line in ae12d0a.

Comment thread tsconfig.json Outdated
Comment thread packages/core/src/config/config.ts Outdated
Core accepts ready plugin objects in loadConfig and createConfig. The
recheck command passes the presets of @redocly/recheck as a plugin, so
recheck/* entries resolve and merge like any other preset, in extends
order. Without the plugin, core skips recheck/* entries.
Config.recheckExtends and the special case in bundleExtends are gone.
Presets resolve in core, so validate, lintContent, lintFiles and
resolveRecheckConfig take the final rules and no extends key. The Markdoc
warning now fires for any enabled markdoc-* rule while markdoc parsing is
off. Core no longer exports mergeRecheckRules.
Core passes preset rule objects through by reference, and validation writes
defaults into the rules it checks, so validate now works on a copy. This
covers lintContent and lintFiles as well as resolveRecheckConfig.

@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.

Fix All in Cursor

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

Reviewed by Cursor Bugbot for commit e5dbdb5. Configure here.

break;
case 'json':
outputJsonFormat(prioritized, fileCount, baseline);
break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

JSON report totalIssues reflects capped count, not actual

Medium Severity

When --max-problems is set, generateReport passes only the capped prioritized slice to outputJsonFormat. Inside that formatter, totalIssues: problems.length then reflects the capped count rather than the true total. The table format correctly receives all problems for counting and only the capped set for display, but JSON and SARIF consumers see a totalIssues that understates the real problem count, with no way to know problems were hidden.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e5dbdb5. Configure here.

@tatomyr

tatomyr commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator

You can ignore linkchecker failures. The 'dead links' it reports all are from this repository and are actually live. Looks like a GitHub availability issue.

@adamaltman
adamaltman merged commit 8f7cbbc into main Oct 2, 2026
52 of 54 checks passed
@adamaltman
adamaltman deleted the aa/recheck-relocate-engine branch October 2, 2026 14:40
@tatomyr tatomyr mentioned this pull request Oct 2, 2026
5 of 7 tasks
adamaltman added a commit that referenced this pull request Oct 3, 2026
…ptions

Brings in the squash-merged engine (#3080), whose preset resolution now
runs through the normal extends path. The tests this branch added move
from the removed `extends` input to the `presetBlock` helper, and the
$ref test loads the presets plugin.

# Conflicts:
#	packages/cli/src/commands/recheck/__tests__/index.test.ts
#	packages/recheck/src/actions/__tests__/lint.test.ts
#	packages/recheck/src/config/__tests__/resolve.test.ts
#	packages/recheck/src/config/resolve.ts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

8 participants