From c7f38051118660ad9554030b357fc950e68e5f86 Mon Sep 17 00:00:00 2001 From: Adam Altman Date: Sat, 3 Oct 2026 00:28:59 -0500 Subject: [PATCH 1/2] docs(recheck): document the recheck command fully and update the agent skills Add a Markdown linting section to the docs: overview, presets, every Markdown rule with its options, the prose assertions and scopes, Markdoc parsing, and ways to suppress findings. Expand the command page and the recheck block reference, and add a guide for migrating from markdownlint and Vale. Update the recheck-lint and recheck-config skills for the redocly.yaml recheck block, keep a copy of them under .claude/skills next to the other skills, and add recheck to the redocly-cli skill. --- .changeset/recheck-command.md | 1 + .claude/skills/recheck-config/SKILL.md | 116 +++++ .claude/skills/recheck-lint/SKILL.md | 73 +++ .claude/skills/redocly-cli/SKILL.md | 33 +- README.md | 2 + docs/@v2/commands/recheck.md | 272 +++++++++-- docs/@v2/configuration/index.md | 17 + docs/@v2/configuration/reference/recheck.md | 108 ++++- docs/@v2/guides/index.md | 6 + docs/@v2/guides/migrate-from-markdownlint.md | 195 ++++++++ docs/@v2/index.md | 6 + docs/@v2/recheck/assertions.md | 432 ++++++++++++++++++ docs/@v2/recheck/index.md | 161 +++++++ docs/@v2/recheck/markdoc.md | 128 ++++++ docs/@v2/recheck/presets.md | 161 +++++++ docs/@v2/recheck/rules.md | 415 +++++++++++++++++ docs/@v2/recheck/suppress-findings.md | 157 +++++++ docs/@v2/v2.sidebars.yaml | 18 +- packages/recheck/README.md | 6 +- .../recheck/skills/recheck-config/SKILL.md | 78 +++- packages/recheck/skills/recheck-lint/SKILL.md | 44 +- 21 files changed, 2345 insertions(+), 84 deletions(-) create mode 100644 .claude/skills/recheck-config/SKILL.md create mode 100644 .claude/skills/recheck-lint/SKILL.md create mode 100644 docs/@v2/guides/migrate-from-markdownlint.md create mode 100644 docs/@v2/recheck/assertions.md create mode 100644 docs/@v2/recheck/index.md create mode 100644 docs/@v2/recheck/markdoc.md create mode 100644 docs/@v2/recheck/presets.md create mode 100644 docs/@v2/recheck/rules.md create mode 100644 docs/@v2/recheck/suppress-findings.md diff --git a/.changeset/recheck-command.md b/.changeset/recheck-command.md index bf8f095355..18cb1e4cf4 100644 --- a/.changeset/recheck-command.md +++ b/.changeset/recheck-command.md @@ -9,3 +9,4 @@ It lints Markdown prose and structure from the `recheck` block in `redocly.yaml` Presets merge with the `recheck` blocks in `extends` order, like other presets. The engine's actions return data. The CLI prints it. +Two agent skills ship in the package under `skills/` and with the repository's other skills: `recheck-lint` runs the command on touched Markdown, and `recheck-config` tunes the `recheck` block. diff --git a/.claude/skills/recheck-config/SKILL.md b/.claude/skills/recheck-config/SKILL.md new file mode 100644 index 0000000000..6bc423cfd9 --- /dev/null +++ b/.claude/skills/recheck-config/SKILL.md @@ -0,0 +1,116 @@ +--- +name: recheck-config +description: Use when you create or tune the recheck block and recheck/* presets in redocly.yaml for a project — choose presets, set severities from measured counts, add exceptions, adopt a baseline, and validate the result. +--- + +# Configure recheck in redocly.yaml + +Configure `redocly recheck` for a project the way its maintainers would: measure first, decide per rule, and record intent. +The documentation is at [redocly.com/docs/cli/recheck](https://redocly.com/docs/cli/recheck); favor it over memory for rule names and options. + +## How the configuration is shaped + +Presets go in the root `extends`. +Adjustments go in the `recheck` block, with rules nested under `rules`. +The `lint` command ignores both, so they live next to the API ruleset: + +```yaml +extends: + - recommended + - recheck/markdown + - recheck/prose +recheck: + excludes: + - CHANGELOG.md + rules: + recheck/line-length: off + recheck/no-duplicate-heading: + severity: warn + assertions: + no-duplicate-heading: + siblingsOnly: true +``` + +Rules of the shape: + +- A rule key is `/`: `recheck/line-length`, `google/no-via`, or your own, such as `acme/product-names`. +- A severity string (`off`, `info`, `warn`, `error`) adjusts a rule that a preset defines. + For a key no preset defines, it is a configuration error, because the rule has no assertions. +- A rule object merges over the preset's rule, but its `assertions` merge by assertion id: one option you set on a `swap` or `pattern` assertion replaces the preset's whole word list. + To reject one term from a preset's list, turn the rule off, restate the full list, or use an inline directive. +- A new prose rule needs `severity`, `message`, and `assertions`. + A new Markdown rule, such as `recheck/list-length`, needs only `severity` and `assertions`. +- The block does not accept `extends` or `baseline`; both are configuration errors. +- The command reads the root block only. + A `recheck` block under `apis.` does nothing and prints a warning. + +## Start a new config + +1. Begin with the structural preset, and run recheck to validate it: + + ```yaml + extends: + - recheck/markdown + ``` + + ```bash + npx @redocly/cli recheck docs + npx @redocly/cli check-config + ``` + + The `recheck` command reports configuration errors before it lints anything, and `check-config` validates the block's shape. + +2. Add prose presets deliberately, not wholesale. + `recheck/prose` is a small starter at `warn`. + `recheck/google` and `recheck/microsoft` are large style guides, detection-only, that need tuning before they help. + `recheck/plain-language`, `recheck/inclusive-language`, and `recheck/technical-english` compose onto either. + +3. If the project writes Markdoc tags, add `recheck/markdoc` to `extends` and `markdoc: true` to the block. + Without the flag, the Markdoc rules never report. + +## Tune severities from measurements, not taste + +1. Run the whole document set and count findings per rule: + + ```bash + npx @redocly/cli recheck docs --summary=json --summary-path=recheck-summary.json + ``` + + The summary's `breakdown` has the count per rule and severity. + For examples of each rule's findings, run `--rule=` and read a few. + +2. Decide per rule from the counts: + - `error` — enforce now; the document set is clean or you fix it in the same change. + - `warn` — a worklist; visible, not blocking. + - `off` — decided against; keep a YAML comment that says why, or the decision is lost. + +3. Prefer fixing content over configuring around it. + Run `--fix` first; most structural findings repair themselves. + Review the fix list before you commit: fixes that re-indent lines can move content out of an indented Markdoc tag body. + +## Exceptions, in order of preference + +1. Fix the content. +2. The top-level `excludes` list, or a rule's `excludes` or `appliesTo`, for generated or frozen content such as archives and vendored docs — with a comment naming the reason. +3. `exceptions.lines` for a recurring true positive that must stay as written. +4. An inline `` for a single line. + +## Adopt strictness on a large document set with a baseline + +When the document set has too many errors to fix at once, record them and gate only new ones: + +```bash +npx @redocly/cli recheck docs --generate-baseline +``` + +Commit `.redocly.recheck-baseline.yaml`. +The command writes it next to `redocly.yaml`, and later runs pick it up by presence; there is no configuration key. +It records errors only, as one count per file per rule. + +Counts only step down: when findings get fixed, the run reports stale entries, so regenerate the baseline and commit the diff. + +## Finish + +- `npx @redocly/cli recheck docs` and `npx @redocly/cli check-config` report no configuration errors. +- Every `off` and every exception carries a comment with its reason. +- The project's contributing docs say how contributors run recheck locally, and CI runs it with `--format=github-actions`. diff --git a/.claude/skills/recheck-lint/SKILL.md b/.claude/skills/recheck-lint/SKILL.md new file mode 100644 index 0000000000..c98379314d --- /dev/null +++ b/.claude/skills/recheck-lint/SKILL.md @@ -0,0 +1,73 @@ +--- +name: recheck-lint +description: Use when you write or edit Markdown in a project whose redocly.yaml has a recheck block or a recheck/* preset in extends — run redocly recheck on the touched files before you commit them or hand them over, and fix what it finds. +--- + +# Lint Markdown with recheck before you commit it + +This project lints Markdown structure and prose with `redocly recheck`. +Your Markdown must pass it, the same as code must pass its tests. + +## When this applies + +Look for a `redocly.yaml` in the project root or above the files you touched. +This skill applies when that file has a `recheck` block, or a `recheck/*` preset in its root `extends`, for example: + +```yaml +extends: + - recommended + - recheck/markdown +recheck: + rules: + recheck/line-length: off +``` + +If neither exists, skip this skill. +The command checks nothing in that case and says so. + +## What to do + +1. After you write or edit Markdown files, run recheck on them: + + ```bash + npx @redocly/cli recheck + ``` + + Pass the files you touched, not the whole repository, so the report is yours to act on. + +2. If it reports errors, run the auto-fix first — most structural findings repair themselves: + + ```bash + npx @redocly/cli recheck --fix + ``` + + The command lists every edit it made. + Read the list: a fix inside a Markdoc tag body or a code sample deserves a look. + +3. Fix the remaining errors by hand. + Read each message; the rule name links the finding to its intent. + To see one rule at a time, add `--rule=` with the name the report prints. + +4. Re-run until it reports no errors, and only then commit or output the content. + The command exits with code 1 while errors remain. + +## Rules of conduct + +- Fix content instead of suppressing findings. + Suppress only a true positive that must stay as written, with an inline directive on that one line: + + ```markdown + + ``` + + Name the rule by its full key or by the short name the report prints. + Never disable a rule project-wide to make your change pass, and never edit the `recheck` block to get past a finding. + +- Warnings and info findings do not block you. + Leave them unless the task asks for cleanup. +- If a `.redocly.recheck-baseline.yaml` file sits next to `redocly.yaml`, the baseline already covers old findings; only new findings are yours to fix. + Do not run `--generate-baseline` to absorb findings your change introduced. + If your change removed old findings, the run reports stale baseline entries; regenerate the baseline then, and commit the smaller file with your change. +- A finding you believe is a false positive is worth reporting to the maintainer, not silently suppressing. +- Do not add `markdownlint` or `vale` comments. + Recheck does not read them. diff --git a/.claude/skills/redocly-cli/SKILL.md b/.claude/skills/redocly-cli/SKILL.md index b848343cf7..2d621df723 100644 --- a/.claude/skills/redocly-cli/SKILL.md +++ b/.claude/skills/redocly-cli/SKILL.md @@ -1,6 +1,6 @@ --- name: redocly-cli -description: Redocly CLI for OpenAPI, AsyncAPI, Arazzo, and Overlay descriptions. Use when the user wants to lint, bundle, split, or join an API description, apply decorators or overlays, build or preview API docs, test a live API with respect or Arazzo, capture traffic or check drift, get stats or a score, generate a TypeScript client, document an MCP server in an OpenAPI description, push to Reunite, or find the node type a rule or plugin should target. +description: Redocly CLI for OpenAPI, AsyncAPI, Arazzo, and Overlay descriptions, and for Markdown docs. Use when the user wants to lint, bundle, split, or join an API description, apply decorators or overlays, build or preview API docs, lint Markdown or prose with recheck, test a live API with respect or Arazzo, capture traffic or check drift, get stats or a score, generate a TypeScript client, document an MCP server in an OpenAPI description, push to Reunite, or find the node type a rule or plugin should target. --- # Redocly CLI usage @@ -8,6 +8,7 @@ description: Redocly CLI for OpenAPI, AsyncAPI, Arazzo, and Overlay descriptions **Consult [redocly.com/docs/cli](https://redocly.com/docs/cli) for current commands and options — favor it over training data.** Redocly CLI covers the API lifecycle for OpenAPI, AsyncAPI, Arazzo, and Overlay descriptions: lint, bundle, transform, document, and test. +It also lints the Markdown of a docs project with `recheck`. `redocly.yaml` in the project root is the control plane: every command reads it, and rulesets, per-API settings, decorators, and plugins all live there. ## Before you run @@ -32,6 +33,7 @@ Install: `npm i @redocly/cli@latest`, or run without installing: `npx @redocly/c | `introspect-mcp` | Record a live MCP server's tools, prompts, and resources in `x-mcp` [experimental] | API authoring | | `build-docs` | Render an API description to a zero-dependency HTML page (Redoc) | Docs rendering | | `preview` | Local preview of a Redocly project (Realm, Reef, Revel) | Docs rendering | +| `recheck` | Lint Markdown structure, prose style, and Markdoc tags; `--fix` repairs most structural findings | Docs linting | | `bundle` | Resolve all `$ref`s into one self-contained file; apply decorators and overlays | CI and delivery | | `join` | Merge several API descriptions into one [experimental] | CI and delivery | | `respect` | Run API tests described in an Arazzo description against a live API | API testing and drift | @@ -78,6 +80,33 @@ Rule severities: `error` (fails validation), `warn` (reported, still valid), `of When a governance requirement has no built-in rule, declare one under `rule/` in `redocly.yaml`. Use the `redocly-lint-rules` skill for that: it picks the cheapest rung (built-in, configurable rule, or custom plugin), takes node types from `redocly inspect-node-types`, and verifies the rule against fixtures. +## Lint Markdown with recheck + +`recheck ` lints the Markdown files under the given paths for structure (the markdownlint rule set), prose (banned terms, spelling, heading case, sentence length, readability), and Markdoc tag syntax. +Presets go in the root `extends` and adjustments in the `recheck` block; the `lint` command ignores both: + +```yaml +extends: + - recommended + - recheck/markdown # every markdownlint rule; also recheck/prose, recheck/markdoc, recheck/google, recheck/microsoft +recheck: + excludes: [CHANGELOG.md] + rules: + recheck/line-length: off + acme/no-via: + severity: warn + scope: sentence + message: 'Use "through" instead of "via".' + assertions: + pattern: { ignoreCase: true, tokens: ['\bvia\b'] } +``` + +With no `redocly.yaml`, the command uses `recheck/markdown`. +With a `redocly.yaml` that has neither a preset nor a block, it checks nothing. +Useful flags: `--fix`, `--rule=` to work one rule at a time, `--format=github-actions|json|sarif`, `--max-problems=`, `--generate-baseline` to record existing errors in `.redocly.recheck-baseline.yaml` and fail only on new ones, and `--readability` for per-file scores. +Silence one line with `` rather than turning the rule off. +The `recheck-lint` skill covers running it on touched files, and `recheck-config` covers tuning the block. + ## Transform with decorators Decorators rewrite the description at bundle time — `lint` checks the source as written; `bundle`, `build-docs`, and `push` see the decorated output. @@ -159,6 +188,7 @@ Start from `generate-arazzo ` to scaffold the workflows, then refine th 2. Make the change — spec edit, rule config, or decorator. 3. Verify: - `redocly lint` exits `0` (or reports only warnings you expected). + - After editing Markdown in a project with a `recheck` block or preset: `redocly recheck ` exits `0`. - After editing `redocly.yaml`: `redocly check-config` reports no problems. - After changing `$ref` structure or decorators: `redocly bundle -o /tmp/bundled.yaml` succeeds and the output contains what you intended. @@ -177,3 +207,4 @@ Start from `generate-arazzo ` to scaffold the workflows, then refine th - [Built-in rules](https://redocly.com/docs/cli/rules) - [Decorators](https://redocly.com/docs/cli/decorators) - [Configuration](https://redocly.com/docs/cli/configuration) +- [Markdown and prose linting](https://redocly.com/docs/cli/recheck) diff --git a/README.md b/README.md index c780a01f3d..d0e72de3a3 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,8 @@ npx skills add https://redocly.com - `redocly-cli` — day-to-day usage: lint, bundle, decorate, build docs, test, and generate a client. - `redocly-lint-rules` — turn a check written in plain language into a built-in rule, a configurable rule, or a custom plugin. It also covers migrating rules from other linters, such as Spectral. +- `recheck-lint` — run `redocly recheck` on the Markdown the assistant touched, fix what it finds, and never suppress findings to pass. +- `recheck-config` — write and tune the `recheck` block in `redocly.yaml` from measured counts, and adopt a baseline on a large document set. ## Common tasks diff --git a/docs/@v2/commands/recheck.md b/docs/@v2/commands/recheck.md index cbfcbb54f9..1fae4bc0d3 100644 --- a/docs/@v2/commands/recheck.md +++ b/docs/@v2/commands/recheck.md @@ -2,24 +2,30 @@ ## Introduction -The `recheck` command lints Markdown files for prose and structure problems. -It checks headings, sentences, links, images, and Markdoc tags against a set of rules. +The `recheck` command lints Markdown files for structure and prose problems. +It checks headings, lists, links, tables, code fences, and whitespace, and the words themselves: banned terms, spelling, repeated words, heading case, sentence length, and readability. +With `--fix`, it repairs the findings it can. -Rules come from presets, such as `recheck/markdown`, that you add to the root `extends` in `redocly.yaml`. -The [`recheck` block](../configuration/reference/recheck.md) adjusts those rules. -With no `redocly.yaml`, the command uses `recheck/markdown`. -With a `redocly.yaml` that has neither, the command checks nothing and says so. -The command reads the root `recheck` config; settings under `apis.` are not used. +Rules come from presets such as `recheck/markdown` that you add to the root `extends` of `redocly.yaml`, and from the [`recheck` block](../configuration/reference/recheck.md), which adjusts preset rules and adds your own. +The [Markdown and prose linting](../recheck/index.md) section explains the presets, the rules, and how to write a prose rule. + +How the command picks its rules: + +- With no `redocly.yaml`, it uses `recheck/markdown`. +- With a `redocly.yaml` that has neither a Recheck preset in `extends` nor a `recheck` block, it checks nothing and says so. +- It reads the root configuration only. + Recheck settings under `apis.` are not used, and the command warns about them. ## Usage ```bash redocly recheck redocly recheck ... -redocly recheck [--fix] -redocly recheck [--readability] -redocly recheck [--generate-baseline] -redocly recheck [--generate-markdoc-schema] [--from=...] [--output=] [--check] +redocly recheck ... [--fix] [--rule=] [--skip-rule=] [--tags=] +redocly recheck ... [--format=] [--max-problems=] [--stats] [--summary=] +redocly recheck ... --readability [--format=table|json] +redocly recheck ... --generate-baseline +redocly recheck --generate-markdoc-schema --from=... --output= [--check] redocly recheck --help ``` @@ -31,28 +37,42 @@ Use at most one of them in a run. ## Options -| Option | Type | Description | -| ------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- | -| paths | [string] | Files or directories to lint. Default value is the current directory. | -| --check | boolean | Fail when the generated schema differs from the file in `--output`. Use with `--generate-markdoc-schema`. | -| --config | string | Path to the [configuration file](../configuration/index.md). | -| --fix | boolean | Apply fixes to the Markdown files. Alias: `-f`. | -| --format | string | Format for the report.
**Possible values:** `table`, `json`, `sarif`, `github-actions`. Default value is `table`. | -| --from | [string] | Module paths to read Markdoc tags from. Use with `--generate-markdoc-schema`. | -| --generate-baseline | boolean | Write a baseline file from the current errors. | -| --generate-markdoc-schema | boolean | Generate a Markdoc tag schema from theme modules. Needs `--from` and `--output`. | -| --help | boolean | Show help. | -| --lint-config | string | Specify the severity level for the configuration file.
**Possible values:** `warn`, `error`, `off`. Default value is `warn`. | -| --max-problems | number | Maximum number of problems in the report; applies to every format. | -| --output | string | Output file for the generated schema. | -| --readability | boolean | Report readability scores instead of lint findings. | -| --rule | [string] | Run only these rules. Alias: `-r`. | -| --skip-rule | [string] | Skip these rules. | -| --stats | boolean | Print statistics per rule. Alias: `-s`. | -| --summary | string | Print a summary of the run.
**Possible values:** `json`, `text`. | -| --summary-path | string | Write the summary to this file. | -| --tags | [string] | Run only rules with these tags. | -| --version | boolean | Show version number. | +| Option | Type | Description | +| ------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| paths | [string] | Files or folders to lint. Default value is the current directory. See [Choose files](#choose-files). | +| --check | boolean | Fail when the generated schema differs from the file in `--output`. Use with `--generate-markdoc-schema`. | +| --config | string | Path to the [configuration file](../configuration/index.md). | +| --fix | boolean | Apply fixes to the Markdown files. Alias: `-f`. See [Fix findings](#fix-findings). | +| --format | string | Format for the report.
**Possible values:** `table`, `json`, `sarif`, `github-actions`. Default value is `table`. See [Formats](#formats). | +| --from | [string] | Module paths to read Markdoc tags from. Use with `--generate-markdoc-schema`. | +| --generate-baseline | boolean | Write a baseline file from the current errors. See [Use a baseline](#use-a-baseline). | +| --generate-markdoc-schema | boolean | Generate a Markdoc tag schema from theme modules. Needs `--from` and `--output`. See [Generate a Markdoc tag schema](#generate-a-markdoc-tag-schema). | +| --help | boolean | Show help. | +| --lint-config | string | Specify the severity level for the configuration file.
**Possible values:** `warn`, `error`, `off`. Default value is `warn`. | +| --max-problems | number | Maximum number of problems in the report; applies to every format. See [Limit the report](#limit-the-report). | +| --output | string | Output file for the generated schema. Use with `--generate-markdoc-schema`. | +| --readability | boolean | Report readability scores instead of lint findings. See [Check readability](#check-readability). | +| --rule | [string] | Run only these rules. Alias: `-r`. See [Work rule by rule](#work-rule-by-rule). | +| --skip-rule | [string] | Skip these rules. | +| --stats | boolean | Print the number of findings per rule after the table. Alias: `-s`. | +| --summary | string | Print a summary of the run.
**Possible values:** `json`, `text`. See [Summarize a run](#summarize-a-run). | +| --summary-path | string | Write the summary to this file instead of printing it. | +| --tags | [string] | Run only rules with these tags. | +| --version | boolean | Show version number. | + +Every option can also be set with an environment variable prefixed `REDOCLY_CLI_RECHECK_`, for example `REDOCLY_CLI_RECHECK_FORMAT=json`. + +## Output and exit codes + +The report goes to standard output, and progress messages go to standard error, so a `json` or `sarif` report can be piped to a file or another tool. + +| Exit code | Meaning | +| --------- | ------------------------------------------------------------------------------- | +| 0 | No errors. Warnings and info findings may be present. | +| 1 | At least one error, a configuration error, a missing baseline, or a failed run. | + +Only findings with severity `error` fail the run. +See [severity levels](../recheck/index.md#severity-levels). ## Examples @@ -71,8 +91,48 @@ Then run the command on a folder: redocly recheck docs ``` -The command lints every Markdown file under `docs` with the rules from `recheck/markdown`. -The report goes to stdout and progress messages go to stderr. +The command lints every Markdown file under `docs` with the rules from `recheck/markdown` and prints a table: + +```text +📋 Found 3 issue(s): + +blanks-around-headings docs/index.md:1:1 Headings should be surrounded by blank lines (Expected: 1; Actual: 0; Below) [fixable] +blanks-around-headings docs/index.md:2:1 Headings should be surrounded by blank lines (Expected: 1; Actual: 0; Above) [fixable] +single-h1 docs/index.md:2:1 Multiple top-level headings in the same document + + 2 of 3 fixable with --fix + + 3 error(s) +``` + +Each row shows the rule name without its `recheck/` prefix, the file with line and column, and the message. +`[fixable]` marks the findings that `--fix` repairs. + +### Choose files + +Pass any number of files and folders. +A folder is searched for `.md` and `.markdown` files, and the search skips hidden folders, `node_modules`, `dist`, and `build`: + +```bash +redocly recheck README.md docs guides/intro.md +``` + +A path that is an API description is skipped with a warning. +The command does not lint the Markdown inside API descriptions yet. + +### Fix findings + +```bash +redocly recheck docs --fix +``` + +The command writes the fixes to the files, lints them again until no more fixes apply, and lists every edit it made. +The report then shows only the findings that remain. + +A fix is withheld when it overlaps another fix or would rewrite a [Markdoc tag](../recheck/markdoc.md). +Withheld fixes are counted in the output, and their findings stay in the report. + +Set `fix: false` on a rule in the `recheck` block to keep that rule detection-only. ### Turn off a rule @@ -87,8 +147,79 @@ recheck: ``` The run applies every rule from `recheck/markdown` except `recheck/line-length`. +The [Presets](../recheck/presets.md#tune-a-preset) page shows how to change a severity or an option. + +### Work rule by rule + +A new rule on an existing document set can produce hundreds of findings. +Run one rule at a time to clear them: + +```bash +redocly recheck docs --rule=line-length +redocly recheck docs --rule=line-length --fix +``` + +`--rule` and `--skip-rule` accept the name the report prints or the full key, such as `recheck/line-length` or `google/no-via`. +Repeat the option for several rules. +A name that matches no rule in the configuration is an error, not an empty run, and the message lists the available rules. + +`--tags` runs only the rules that carry one of the given tags. +The Markdown rules carry markdownlint's tags, such as `headings`, `links`, `whitespace`, `code`, and `accessibility`. +Your own rules carry the `tags` you set on them. + +```bash +redocly recheck docs --tags=headings --tags=links +``` + +### Formats + +#### table + +The default, shown in [Lint a folder](#lint-a-folder). +Add `--stats` to print the number of findings per rule after the table. -### Annotate a pull request +#### json + +```bash +redocly recheck docs --format=json > recheck.json +``` + +The report has a `summary` with the file count and a breakdown per rule, and an `issues` list with one entry per finding: + +```json +{ + "summary": { + "filesScanned": 1, + "totalIssues": 1, + "breakdown": { + "recheck/single-h1": { "errors": 1, "warnings": 0, "info": 0, "total": 1 } + } + }, + "issues": [ + { + "file": "docs/index.md", + "line": 2, + "column": 1, + "text": "# Second title", + "match": "Second title", + "ruleName": "recheck/single-h1", + "severity": "error", + "message": "Multiple top-level headings in the same document", + "fixable": false + } + ] +} +``` + +#### sarif + +```bash +redocly recheck docs --format=sarif > recheck.sarif +``` + +A SARIF 2.1.0 report for code scanning tools, such as GitHub code scanning. + +#### github-actions ```bash redocly recheck docs --format=github-actions @@ -102,6 +233,34 @@ Each finding is one line of output: ::error title=recheck/single-h1,file=docs/index.md,line=2,endLine=2,col=1,endColumn=1::Multiple top-level headings in the same document ``` +GitHub shows at most 10 error and 10 warning annotations per step, so pair this format with `--max-problems`: + +```yaml +- name: Lint Markdown + run: npx @redocly/cli@latest recheck docs --format=github-actions --max-problems=20 +``` + +### Limit the report + +```bash +redocly recheck docs --max-problems=20 +``` + +The command sorts the findings by severity, then by file, line, and column, and reports the first 20. +The table's counts still cover every finding, and a note on standard error says how many were hidden. +Without the option, every finding is reported. + +### Summarize a run + +```bash +redocly recheck docs --summary=text +redocly recheck docs --summary=json --summary-path=recheck-summary.json +``` + +The summary counts the files scanned and the findings by severity and by rule. +It prints on standard error, or goes to the file in `--summary-path`. +Use it to decide the severity of each rule from the counts when you [adopt Recheck](../recheck/index.md#adopt-recheck-on-an-existing-project). + ### Use a baseline A baseline records the current errors. @@ -113,14 +272,51 @@ redocly recheck docs --generate-baseline The command writes `.redocly.recheck-baseline.yaml` next to `redocly.yaml`. Commit the file. -Later runs pick it up automatically. +Later runs pick it up automatically and print how many findings matched, how many are new, and how many baseline entries are stale. After you fix errors, generate the baseline again and commit the smaller file. +A stale baseline fails the run, so the file always equals reality. +[Use a baseline](../recheck/suppress-findings.md#use-a-baseline) describes the file format and the rules of comparison. ### Check readability ```bash redocly recheck docs --readability +redocly recheck docs --readability --format=json ``` -The command prints readability scores for each Markdown file instead of lint findings. +The command prints one row per Markdown file with its Flesch reading ease, Flesch-Kincaid grade, Automated Readability Index, word count, and sentence count, followed by the medians. +The score reads the prose only: headings, code, front matter, and Markdoc tags do not count. +A file with no prose shows no score. + +This action never fails the run. +To fail on a readability bound, add a rule with the [`metric` assertion](../recheck/assertions.md#metric), which reads the same prose and the same formulas. + +### Generate a Markdoc tag schema + +```bash +redocly recheck --generate-markdoc-schema --from=@theme/markdoc/schema.js --output=markdoc-tags.yaml +redocly recheck --generate-markdoc-schema --from=@theme/markdoc/schema.js --output=markdoc-tags.yaml --check +``` + +The first command reads the `tags` export of a theme module and writes a tags file that `recheck.markdoc.extend.tagsFile` can load. +The second compares the file with a fresh generation and fails if it differs, for CI. +See [Markdoc tags](../recheck/markdoc.md#generate-a-tags-file-from-a-theme) for the details. + +### Use a custom configuration file + +```bash +redocly recheck docs --config=./config/redocly.yaml +``` + +The baseline file is looked up next to the configuration file that is used. + +## Resources + +- [Markdown and prose linting](../recheck/index.md) +- [`recheck` configuration reference](../configuration/reference/recheck.md) +- [Presets](../recheck/presets.md) +- [Markdown rules](../recheck/rules.md) +- [Prose rules](../recheck/assertions.md) +- [Suppress findings](../recheck/suppress-findings.md) +- [Migrate from markdownlint and Vale](../guides/migrate-from-markdownlint.md) diff --git a/docs/@v2/configuration/index.md b/docs/@v2/configuration/index.md index 2647548da0..5ba7d37a2f 100644 --- a/docs/@v2/configuration/index.md +++ b/docs/@v2/configuration/index.md @@ -95,6 +95,23 @@ You can also define your [configurable rules](../rules/configurable-rules.md) he For more information and examples, visit the [configuring rules documentation](./rules.md). +### Lint Markdown with `recheck` + +The [`recheck` command](../commands/recheck.md) lints Markdown files from the same configuration file. +Add a Recheck preset such as `recheck/markdown` to `extends`, and adjust its rules in the `recheck` block: + +```yaml +extends: + - recommended + - recheck/markdown +recheck: + rules: + recheck/line-length: off +``` + +The `lint` command ignores the Recheck presets and the `recheck` block, and the `recheck` command ignores API rulesets. +For more information, visit the [`recheck` block reference](./reference/recheck.md) and the [Markdown and prose linting](../recheck/index.md) section. + ### Configure OpenAPI features and documentation styles diff --git a/docs/@v2/configuration/reference/recheck.md b/docs/@v2/configuration/reference/recheck.md index f2415d5a36..0eef06038b 100644 --- a/docs/@v2/configuration/reference/recheck.md +++ b/docs/@v2/configuration/reference/recheck.md @@ -3,7 +3,7 @@ ## Introduction The `recheck` block configures the [`recheck`](../../commands/recheck.md) command. -It adjusts the rules that the Recheck presets provide, and it sets file excludes and Markdoc parsing. +It adjusts the rules that the [Recheck presets](../../recheck/presets.md) provide, adds your own rules, and sets file excludes and Markdoc parsing. The block does not accept `extends`. Add Recheck presets, such as `recheck/markdown`, to the root `extends` of `redocly.yaml`. @@ -11,6 +11,11 @@ The block merges on top of the presets that the same file lists in `extends`. Presets and shared config files merge in `extends` order, so a preset listed after a shared file overrides the `recheck` block of that file. An unknown preset name, such as `recheck/nope`, is a configuration error. +Rules merge by key. +A severity string changes only the severity of a preset rule. +A rule object sets its own keys over the preset's, and its `assertions` merge by assertion id: an assertion you set replaces the preset's assertion of the same id as a whole, options included. +A severity string for a key that no preset defines is a configuration error, because the rule has no assertions. + ## Options {% table %} @@ -43,9 +48,10 @@ An unknown preset name, such as `recheck/nope`, is a configuration error. ### Rules object -Each key is a rule name. +Each key is a rule name in the form `/`, such as `recheck/line-length`, `google/no-via`, or `acme/product-names`. Each value is a severity string (`off`, `info`, `warn`, or `error`) or a [rule object](#rule-object). Use a severity string to change the severity of a preset rule or to turn it off. +Use a rule object to change a preset rule's options or to add a rule. ### Rule object @@ -67,29 +73,32 @@ Use a severity string to change the severity of a preset rule or to turn it off. - message - string - The message that the rule reports. - **REQUIRED** for a scope rule, which uses an assertion such as `pattern` or `metric`. - A token rule, which uses an assertion such as `line-length`, takes its default message when the entry has none. + **REQUIRED** for a [prose rule](../../recheck/assertions.md), which uses an assertion such as `pattern` or `metric`. + A [Markdown rule](../../recheck/rules.md), which uses an assertion such as `line-length`, takes its default message when the entry has none. + `%s` placeholders are filled by the assertion; each assertion documents what it fills. --- - assertions - object -- **REQUIRED**. - The checks that the rule runs. - Each assertion type has its own options. +- **REQUIRED** when the rule is not from a preset. + The checks that the rule runs, keyed by assertion id. + A [Markdown rule](../../recheck/rules.md) is keyed by its name, such as `line-length`, and a [prose assertion](../../recheck/assertions.md) by its id, such as `swap`. + Each assertion has its own options. --- - fix - boolean - Let `--fix` apply this rule's fix. - Only some assertion types offer a fix. + Default value is `true` for an assertion that offers a fix. + Set `false` to keep the rule detection-only. --- - tags - [string] -- Labels for the `--tags` option. +- Labels for the `--tags` option of the command. --- @@ -107,7 +116,9 @@ Use a severity string to change the severity of a preset rule or to turn it off. - scope - string or [string] -- Where the rule runs: `all`, `heading`, `sentence`, `paragraph`, `code`, or `raw`. +- Where the rule reads text, such as `summary`, `heading`, `sentence`, or `paragraph`. + Default value is `all`, the whole file. + See [Scopes](../../recheck/assertions.md#scopes) for the full list and the selector syntax. --- @@ -115,6 +126,7 @@ Use a severity string to change the severity of a preset rule or to turn it off. - [string] - File globs that the rule runs on. Default value is every Markdown file. + See [File targeting](../../recheck/suppress-findings.md#file-targeting) for how globs match. --- @@ -127,6 +139,7 @@ Use a severity string to change the severity of a preset rule or to turn it off. - exceptions - [Exceptions object](#exceptions-object) - Files and lines that the rule does not check. + See [Suppress findings](../../recheck/suppress-findings.md). {% /table %} @@ -148,7 +161,9 @@ Use a severity string to change the severity of a preset rule or to turn it off. - lines - [string] -- Line patterns that the rule skips. +- Text fragments. + The rule skips every line that contains one of them. + Matching is case-sensitive. {% /table %} @@ -173,6 +188,7 @@ Use a severity string to change the severity of a preset rule or to turn it off. - extend - [Extend object](#extend-object) - Add your own tags on top of the chosen schema. + See [Markdoc tags](../../recheck/markdoc.md) for the tag schema shape and how parsing changes other rules. {% /table %} @@ -195,32 +211,93 @@ Use a severity string to change the severity of a preset rule or to turn it off. - tagsFile - string -- Path to a YAML file that maps tag names to tag schemas. +- Path to a YAML file that maps tag names to tag schemas, relative to `redocly.yaml`. + Inline `tags` win over the file. + The [`--generate-markdoc-schema`](../../commands/recheck.md#generate-a-markdoc-tag-schema) action writes this file from a theme module. {% /table %} -## Example +## Examples + +### Adjust preset rules ```yaml extends: - recheck/markdown + - recheck/markdoc recheck: excludes: - CHANGELOG.md markdoc: true rules: recheck/line-length: off + recheck/no-duplicate-heading: + severity: warn + assertions: + no-duplicate-heading: + siblingsOnly: true +``` + +This config adds two presets in the root `extends`. +The `recheck` block skips `CHANGELOG.md` for every rule, turns on Markdoc-aware parsing, turns off one rule, and changes the severity and one option of another. + +### Add a prose rule + +```yaml +extends: + - recheck/markdown + - recheck/prose +recheck: + rules: recheck/readability-floor: severity: warn message: 'Readability (%s) is %s; expected between %s and %s.' + appliesTo: + - 'docs/guides/**' assertions: metric: formula: flesch-reading-ease min: 30 + acme/no-via: + severity: warn + scope: sentence + message: 'Use "through" or "by using" instead of "via".' + link: https://example.com/style-guide#via + assertions: + pattern: + ignoreCase: true + tokens: + - '\bvia\b' +``` + +The first rule reads a readability score for the guides only. +The second reports a banned word in every sentence, under the project's own `acme/` namespace. +The [Prose rules](../../recheck/assertions.md) page lists every assertion and its options. + +### Share Recheck configuration between projects + +A shared configuration file can carry both presets and a `recheck` block: + +```yaml +# https://example.com/redocly-shared.yaml +extends: + - recheck/markdown + - recheck/prose +recheck: + rules: + recheck/line-length: off +``` + +```yaml +# redocly.yaml +extends: + - https://example.com/redocly-shared.yaml +recheck: + rules: + recheck/capitalization: error ``` -This config adds the `recheck/markdown` preset in the root `extends`. -The `recheck` block skips `CHANGELOG.md`, turns on Markdoc-aware parsing, turns off one rule, and adds one rule. +The root file's block merges last, so it can tighten or relax what the shared file set. ## Related options @@ -230,3 +307,4 @@ The `recheck` block skips `CHANGELOG.md`, turns on Markdoc-aware parsing, turns ## Resources - Command reference for [`recheck`](../../commands/recheck.md). +- [Markdown and prose linting](../../recheck/index.md), [Presets](../../recheck/presets.md), [Markdown rules](../../recheck/rules.md), [Prose rules](../../recheck/assertions.md), [Markdoc tags](../../recheck/markdoc.md), and [Suppress findings](../../recheck/suppress-findings.md). diff --git a/docs/@v2/guides/index.md b/docs/@v2/guides/index.md index 8945d5394b..2cda8dbaec 100644 --- a/docs/@v2/guides/index.md +++ b/docs/@v2/guides/index.md @@ -145,4 +145,10 @@ Switch from Spectral to Redocly CLI's linting and tooling. Replace the deprecated swagger-cli package with Redocly CLI. {% /card %} +{% card title="Migrate from markdownlint and Vale" + to="./migrate-from-markdownlint" + %} +Move Markdown and prose linting to the `recheck` command. +{% /card %} + {% /cards %} diff --git a/docs/@v2/guides/migrate-from-markdownlint.md b/docs/@v2/guides/migrate-from-markdownlint.md new file mode 100644 index 0000000000..f13846822f --- /dev/null +++ b/docs/@v2/guides/migrate-from-markdownlint.md @@ -0,0 +1,195 @@ +# Migrate from markdownlint and Vale + +The `recheck` command covers the ground of [markdownlint](https://github.com/DavidAnson/markdownlint) and [Vale](https://vale.sh) in one tool and one configuration file. +This guide maps the configuration of each tool onto Recheck. + +The first step is to [install Redocly CLI](../installation.md). + +## Replace markdownlint + +### Update the command + +Replace `markdownlint docs` or `markdownlint-cli2 "docs/**/*.md"` with: + +```bash +redocly recheck docs +``` + +Replace `--fix` with the same flag: + +```bash +redocly recheck docs --fix +``` + +### Translate the configuration + +Every markdownlint rule has a Recheck rule with the same name under the `recheck/` prefix. +Start from the preset that matches your markdownlint style, then move each override from `.markdownlint.yaml` into the `recheck` block: + +| markdownlint | Recheck | +| ----------------------------- | ---------------------------------------------------------------------- | +| `default: true` | `extends: [recheck/markdown]` | +| `extends: style/relaxed.json` | `extends: [recheck/markdown-relaxed]` | +| `MD013: false` | `recheck/line-length: off` | +| `MD013: { line_length: 120 }` | `recheck/line-length` with `assertions.line-length.lineLength: 120` | +| `blank_lines: false` | The five `blanks-around-*` and `no-multiple-blanks` rules set to `off` | + +Option names use camel case instead of snake case: `line_length` becomes `lineLength`, and `siblings_only` becomes `siblingsOnly`. + +For example, this `.markdownlint.yaml`: + +```yaml +default: true +MD004: false +MD013: + line_length: 120 + tables: false +MD024: + siblings_only: true +MD033: false +``` + +becomes this `redocly.yaml`: + +```yaml +extends: + - recheck/markdown +recheck: + rules: + recheck/ul-style: off + recheck/line-length: + severity: error + assertions: + line-length: + lineLength: 120 + tables: false + recheck/no-duplicate-heading: + severity: error + assertions: + no-duplicate-heading: + siblingsOnly: true + recheck/no-inline-html: off +``` + +The [Markdown rules](../recheck/rules.md) page lists every rule with its markdownlint id and options. + +### Replace inline comments + +Recheck does not read markdownlint's HTML comment directives. +Replace each one with the equivalent [Recheck directive](../recheck/suppress-findings.md#inline-directives), written as an HTML comment in the same place: + +| markdownlint comment | Recheck comment | +| -------------------------------------- | --------------------------------------- | +| `markdownlint-disable MD013` | `recheck-disable line-length` | +| `markdownlint-enable MD013` | `recheck-enable line-length` | +| `markdownlint-disable-next-line MD013` | `recheck-disable-next-line line-length` | +| `markdownlint-disable-file` | `recheck-disable-file` | + +Recheck names rules by name, not by `MD` id. + +### Two differences in defaults + +- `no-trailing-spaces` allows exactly two trailing spaces by default, because that is a Markdown hard line break. + Set `strict: true` to report every trailing space, as markdownlint does. +- `no-duplicate-heading` adds the `respectSections`, `caseSensitive`, and `ignoreCommonHeadings` options. + Their defaults match markdownlint. + +## Replace Vale + +### Update the command + +Replace `vale docs` with the same `recheck` command. +Structural and prose rules run in one pass: + +```bash +redocly recheck docs +``` + +### Translate styles + +A Vale style is a folder of YAML rule files. +Each file becomes one entry in `recheck.rules`, and the Vale `extends` type becomes the Recheck assertion: + +| Vale `extends` | Recheck assertion | +| ---------------- | ------------------------------------------------------- | +| `substitution` | `swap` | +| `existence` | `pattern` | +| `occurrence` | `occurrence` | +| `repetition` | `repetition` | +| `consistency` | `consistency` | +| `conditional` | `conditional` | +| `capitalization` | `capitalization` | +| `metric` | `metric` | +| `spelling` | `spelling` | +| `sequence` | No equivalent. These rules need part-of-speech tagging. | + +The other Vale keys map one to one: `message` stays `message`, `level` becomes `severity` (`suggestion` becomes `info`), `scope` stays `scope`, `link` stays `link`, `ignorecase` becomes `ignoreCase`, and `nonword` stays `nonword`. +Vale's `swap`, `tokens`, and `either` keys move under the assertion. + +For example, this Vale rule in `styles/Acme/BritishEnglish.yml`: + +```yaml +extends: substitution +message: 'Use the US spelling "%s" instead of British "%s".' +level: error +ignorecase: true +swap: + colour: color + behaviour: behavior +``` + +becomes: + +```yaml +recheck: + rules: + acme/british-english: + severity: error + scope: summary + message: 'Use the US spelling "%s" instead of British "%s".' + assertions: + swap: + ignoreCase: true + wordBoundary: true + pairs: + colour: color + behaviour: behavior +``` + +Vale skips code by default, and Recheck's prose scopes do the same. +Set `scope: summary` to read the document's prose, or a narrower scope such as `sentence` or `heading`. +The default scope `all` reads the whole file, code included. +The [Prose rules](../recheck/assertions.md) page lists every assertion and option. + +### Replace packages + +Vale packages for the Google and Microsoft style guides have Recheck presets: + +| Vale package | Recheck preset | +| ---------------------- | -------------------------------------------- | +| `Google` | `recheck/google` | +| `Microsoft` | `recheck/microsoft` | +| `alex` or `Inclusive` | `recheck/inclusive-language` | +| `proselint` (in part) | `recheck/plain-language` | +| `write-good` (in part) | `recheck/prose` and `recheck/plain-language` | + +The presets are adapted from the live style guides, not from the Vale packages, so their rule sets differ in detail. +See [Presets](../recheck/presets.md) for what each one contains. + +### Replace the vocabulary + +Vale's `accept.txt` becomes the `exceptions` list of a `capitalization` rule or the `vocab` list of a `spelling` rule. +Vale's `reject.txt` becomes a `pattern` rule. +Recheck adds a built-in list of technical proper nouns to both, so most entries for product names are no longer needed. + +### Replace in-text comments + +Vale's `vale off` and `vale on` comments become `recheck-disable` and `recheck-enable`. +A comment that turns one Vale rule off, such as `vale Acme.Rule = NO`, becomes `recheck-disable acme/rule`. +Each one stays an HTML comment in the same place. + +## Run both tools during the transition + +Recheck reports in the formats that CI expects: `github-actions` for pull request annotations, `sarif` for code scanning, and `json` for scripts. +Run Recheck next to the old tools for a few pull requests, compare the findings, then remove the old configuration. +A [baseline](../recheck/suppress-findings.md#use-a-baseline) keeps the switch from failing the build on findings the old tools never reported. diff --git a/docs/@v2/index.md b/docs/@v2/index.md index ce30a765ae..cde9dee5f6 100644 --- a/docs/@v2/index.md +++ b/docs/@v2/index.md @@ -38,6 +38,12 @@ Publish a subset of endpoints, or use decorators to enhance your existing OpenAP Check that your API is up to standard on every revision. Our ready-made rulesets, built-in and configurable rules let you compose the API standards that fit each of your APIs. {% /card %} +{% card title="Markdown and prose linting" + to="./recheck" + %} +Lint the Markdown of your docs for structure, prose style, and Markdoc tags with one command, with presets from the Google and Microsoft style guides. +{% /card %} + {% card title="Generate SDK client" to="./commands/generate-client" diff --git a/docs/@v2/recheck/assertions.md b/docs/@v2/recheck/assertions.md new file mode 100644 index 0000000000..21d3076efd --- /dev/null +++ b/docs/@v2/recheck/assertions.md @@ -0,0 +1,432 @@ +--- +seo: + title: Recheck prose rules + description: Write your own prose and style rules for the recheck command with scopes and assertions. +--- + +# Prose rules + +A prose rule checks the words of a document rather than its Markdown syntax. +You write one in the `recheck` block from three parts: a **scope** that selects the text, an **assertion** that checks it, and a **message** that explains the finding. + +```yaml +recheck: + rules: + recheck/no-gerund-headings: + severity: warn + scope: heading + message: 'Do not start a heading with a gerund.' + link: https://example.com/style-guide#headings + assertions: + pattern: + ignoreCase: true + tokens: + - '^\w+ing\b' +``` + +This page lists every scope and every assertion with its options. +The [`recheck` configuration reference](../configuration/reference/recheck.md) lists the other keys of a rule, such as `appliesTo`, `excludes`, and `exceptions`. + +## Rule keys and messages + +A rule key is `/`, in lowercase, for example `recheck/us-spelling` or `acme/product-names`. +The presets use their own namespaces, such as `google/`, so your keys never collide with theirs. +The report prints the key without the `recheck/` prefix. + +A prose rule needs a `message`. +The message can hold `%s` placeholders, which the assertion fills in order. +Each assertion lists what it fills. +Most assertions fill two values; `length` fills three and `metric` fills four. +A message with more placeholders than the assertion fills is a configuration error. + +## Scopes + +The `scope` key selects which parts of a file the rule reads. +The default is `all`. + +| Scope | Selects | +| -------------- | ------------------------------------------------------------------------------------------------------- | +| `all` | The whole file as one segment. | +| `raw` | The raw file content, without scope segmentation. | +| `summary` | The document's prose: paragraph, heading, list item, blockquote, and table cell text. Alias: `default`. | +| `sentence` | Each sentence of the prose. | +| `paragraph` | Each paragraph. | +| `heading` | Each heading. `heading.h1` to `heading.h6` select one level. | +| `list-item` | The text of each list item. | +| `blockquote` | The text of each blockquote. | +| `table.header` | Each table header cell. | +| `table.cell` | Each table body cell. | +| `code` | Each code block. | +| `frontmatter` | The YAML front matter. | +| `html` | Each raw HTML block. | +| `comment` | Each HTML comment. | +| `alt` | The alt text of each image. | +| `link` | The text of each link. | +| `markdoc.tag` | Each Markdoc tag span. Needs [`markdoc: true`](./markdoc.md). | + +A list of scopes selects all of them: + +```yaml +scope: + - heading.h1 + - heading.h2 +``` + +A selector narrows a scope: `~` excludes a scope, and `&` joins conditions. +For example, `'~blockquote & ~heading'` selects everything except blockquotes and headings. +`all` and `raw` cover the whole file, so they cannot be combined with another scope. + +Prose scopes never include inline code, and when [Markdoc parsing](./markdoc.md) is on, they do not include the tag syntax either. +Set `includeCode: true` on `swap` or `pattern` to match inside inline code. + +## Assertions + +Each rule has one or more assertions under `assertions`, keyed by assertion id. +The [Markdown rules](./rules.md) are also assertions, keyed by rule name, and a rule can combine both kinds. + +| Assertion | Checks | Fixable | +| ---------------------- | --------------------------------------------------------- | ------- | +| `swap` | A word or phrase that should be replaced. | Yes | +| `pattern` | A regex that should not match. | No | +| `occurrence` | How many times a regex matches in a segment. | No | +| `repetition` | An adjacent repeated word. | Yes | +| `consistency` | One spelling of a pair across the file. | Yes | +| `conditional` | A pattern that requires another pattern in the same file. | No | +| `capitalization` | Title case, sentence case, or a custom pattern. | Yes | +| `length` | The size of a segment in characters, words, or sentences. | No | +| `metric` | A readability score for the whole document. | No | +| `spelling` | Words the dictionary does not know. | No | +| `semantic-line-breaks` | One sentence or phrase per line. | Yes | +| `max-image-size` | The file size of linked images. | No | + +### `swap` + +Reports a word or phrase and suggests a replacement. +With `--fix`, each match is replaced, and the match's own casing is applied to the replacement: a capitalized match gets a capitalized replacement, and an all-caps match gets an all-caps replacement. +When two pairs match overlapping text, the longest match wins. + +```yaml +assertions: + swap: + ignoreCase: true + wordBoundary: true + pairs: + utilize: use + in order to: to +``` + +| Option | Type | Required | Description | +| -------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------- | +| `pairs` | object | Yes | Text to find mapped to its replacement. | +| `ignoreCase` | boolean | No | Match without regard to case. Default `false`. | +| `wordBoundary` | boolean | No | Match whole words only. Default `false`. | +| `keysAreRegex` | boolean | No | Treat each key as a regex, for example `favou?rite`. An invalid regex matches nothing. Default `false`. | +| `includeCode` | boolean | No | Also match inside inline code spans. Default `false`. | + +Message placeholders: the replacement, then the matched text. +With the pair `utilize: use`, the message `'Use "%s" instead of "%s".'` prints `Use "use" instead of "utilize".` + +### `pattern` + +Reports each match of a regex. + +```yaml +assertions: + pattern: + ignoreCase: true + tokens: + - '\bvery\b' + - '\breally\b' +``` + +| Option | Type | Required | Description | +| ------------- | -------- | -------- | -------------------------------------------------------------- | +| `tokens` | [string] | Yes | Regex patterns. An invalid pattern matches nothing. | +| `ignoreCase` | boolean | No | Match without regard to case. Default `false`. | +| `nonword` | boolean | No | Do not add word boundaries around each token. Default `false`. | +| `includeCode` | boolean | No | Also match inside inline code spans. Default `false`. | + +Message placeholders: the matched text. + +### `occurrence` + +Counts the matches of a regex in each segment and reports the segment when the count is outside `min` and `max`. +`min: 1` without `max` requires the pattern to be present. + +```yaml +scope: paragraph +assertions: + occurrence: + pattern: '[.!?]' + max: 3 +``` + +| Option | Type | Required | Description | +| ------------ | ------- | --------------------- | ---------------------------------------------- | +| `pattern` | string | Yes | The regex to count. | +| `min` | number | One of `min` or `max` | Fewer matches is a finding. | +| `max` | number | One of `min` or `max` | More matches is a finding. | +| `ignoreCase` | boolean | No | Match without regard to case. Default `false`. | + +Message placeholders: the match count, then the bound that was crossed. + +### `repetition` + +Reports an adjacent repeated word, such as "the the", including across a line break. +With `--fix`, the pair collapses to the first word. + +```yaml +assertions: + repetition: {} +``` + +| Option | Type | Required | Description | +| ------------ | ------- | -------- | ------------------------------------------------------------------------------------- | +| `pattern` | string | No | The regex that defines a word. Default `\w+`. | +| `ignoreCase` | boolean | No | Compare without regard to case. Default `true`, because "The the" is the common typo. | + +Message placeholders: the repeated word. + +### `consistency` + +Each entry of `either` names two variants. +The variant that appears first in a file wins, and later uses of the other variant are reported. +With `--fix`, they are replaced with the winning variant as written in `either`. + +```yaml +assertions: + consistency: + ignoreCase: true + either: + behavior: behaviour + color: colour +``` + +| Option | Type | Required | Description | +| ------------ | ------- | -------- | ------------------------------------------------ | +| `either` | object | Yes | Pairs of variants. Each pair has its own winner. | +| `ignoreCase` | boolean | No | Match without regard to case. Default `false`. | + +Message placeholders: the later variant, then the variant that appeared first. + +### `conditional` + +If `first` matches in the rule's scope, `second` must match somewhere in the whole file, code blocks included. +Otherwise each `first` match is reported. + +```yaml +assertions: + conditional: + first: '\bTBD\b' + second: 'https://github\.com/\S+/issues/\d+' +``` + +| Option | Type | Required | Description | +| ------------ | ------- | -------- | ---------------------------------------------- | +| `first` | string | Yes | The regex that requires `second`. | +| `second` | string | Yes | The regex that must appear in the file. | +| `ignoreCase` | boolean | No | Match without regard to case. Default `false`. | + +Message placeholders: the `first` match, then the `second` pattern. + +### `capitalization` + +Reports a segment whose casing does not match the required style. + +```yaml +scope: heading +assertions: + capitalization: + match: $sentence + exceptions: [Redocly, Reunite] +``` + +| Option | Type | Required | Description | +| ------------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------- | +| `match` | string | Yes | `$title`, `$sentence`, `$lower`, `$upper`, or a regex the whole segment must match. | +| `style` | string | No | The stopword list for `$title`: `ap` or `chicago`. Default `ap`. | +| `exceptions` | [string] | No | Words and phrases that keep their casing as written, such as `GitHub` or `VS Code`. | +| `builtinVocabulary` | boolean | No | Add the built-in list of technical proper nouns, such as `OpenAPI`, `npm`, and `Node.js`, to `exceptions`. Default `true`. | + +- `$title` capitalizes every word except articles, conjunctions, and prepositions. + `ap` lowercases prepositions of three letters or fewer; `chicago` lowercases every preposition. + The first and last words are always capitalized. +- `$sentence` capitalizes the first word and lowercases the rest, except exceptions and words in all caps. +- `$lower` and `$upper` require the whole segment in that case. +- A regex must match the whole segment. + This mode is detection-only. + +Words in all caps, such as `API`, and inline code are never changed. +`--fix` rewrites a single-line segment for the four `$` styles. +A segment that spans several lines is skipped. + +Message placeholders: the segment text, then the `match` value. + +### `length` + +Measures each segment in the rule's scope and reports it when the size is outside `min` and `max`. + +```yaml +scope: sentence +assertions: + length: + unit: words + max: 25 +``` + +| Option | Type | Required | Description | +| ------ | ------ | --------------------- | -------------------------------------- | +| `unit` | string | Yes | `characters`, `words`, or `sentences`. | +| `min` | number | One of `min` or `max` | A smaller segment is a finding. | +| `max` | number | One of `min` or `max` | A larger segment is a finding. | + +Message placeholders: the measured size, the unit, then the bound that was crossed. + +### `metric` + +Scores the prose of the whole document with a readability formula and reports the file once, at line 1, when the score is outside `min` and `max`. +The score reads the same prose as `redocly recheck --readability`: headings, code, front matter, and Markdoc tags do not count. +A file with no prose is never reported. + +```yaml +assertions: + metric: + formula: flesch-reading-ease + min: 30 +``` + +| Option | Type | Required | Description | +| --------- | ------ | --------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `formula` | string | Yes | `flesch-reading-ease`, `flesch-kincaid-grade`, `gunning-fog`, `smog`, `coleman-liau`, or `automated-readability`. | +| `min` | number | One of `min` or `max` | A lower score is a finding. | +| `max` | number | One of `min` or `max` | A higher score is a finding. | + +The assertion always reads the `summary` scope. +Setting another scope prints a warning and has no effect. + +Message placeholders: the formula, the score, `min`, then `max`. + +### `spelling` + +Reports words that a Hunspell dictionary does not recognize, with up to three suggestions. +The English dictionary ships with Redocly CLI. + +```yaml +scope: summary +assertions: + spelling: + vocab: [Redocly, Reunite] + ignore: ['\bAcme\w*'] +``` + +| Option | Type | Required | Description | +| ------------------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | +| `dictionary` | string | No | Base path of a custom Hunspell dictionary, without the `.aff` and `.dic` extensions, relative to the working directory. | +| `vocab` | [string] | No | Words that are never reported, matched without regard to case. | +| `ignore` | [string] | No | Regex patterns. A word that matches any of them is never reported. | +| `builtinVocabulary` | boolean | No | Also accept the built-in list of technical proper nouns. Default `true`. | + +Words in all caps and words next to a digit, such as `utf8`, are not checked. +Inline code is never checked. + +Message placeholders: the unknown word, then a suggestion suffix in the form `— did you mean: world?`, or an empty string when there is no suggestion. + +### `semantic-line-breaks` + +Requires one sentence, or one phrase, per line. +With `--fix`, long lines are split after each sentence, and list item continuation lines are indented under the item text. + +```yaml +assertions: + semantic-line-breaks: + mode: sentence +``` + +| Option | Type | Required | Description | +| ------------------ | ------- | -------- | ----------------------------------------------------------------------- | +| `mode` | string | Yes | `sentence` breaks after each sentence; `phrase` also breaks at clauses. | +| `maxPhrase` | number | No | In `phrase` mode, the longest phrase that may stay on one line. | +| `ignoreCodeBlocks` | boolean | No | Skip code blocks. | +| `ignoreTables` | boolean | No | Skip tables. | + +### `max-image-size` + +Reports an image whose file is larger than the limit. +The image path is resolved from the Markdown file and must stay inside the folder passed to the command. + +```yaml +assertions: + max-image-size: + maxSizeKB: 200 +``` + +| Option | Type | Required | Description | +| ------------ | -------- | -------- | --------------------------------------------------------------------------------- | +| `maxSizeKB` | number | No | The limit in kilobytes. Default `100`. | +| `extensions` | [string] | No | File extensions to check. Default `jpg`, `jpeg`, `png`, `gif`, `webp`, and `svg`. | + +## Built-in proper nouns + +The `capitalization` and `spelling` assertions share a list of technical proper nouns, such as `OpenAPI`, `GraphQL`, `npm`, `Node.js`, `VS Code`, and `Kubernetes`. +It saves every project from listing the same names. +`capitalization` keeps them cased as written, and `spelling` accepts them as words. +Set `builtinVocabulary: false` on a rule to use only its own `exceptions` or `vocab`. + +The list leaves out words that are also ordinary English, such as `Chrome` or `Windows`, because an exception for them would weaken the check. +Add names like that in the rule's own `exceptions` or `vocab`. +The list is exported as `TECHNICAL_PROPER_NOUNS` from the `@redocly/recheck` package. + +## Examples + +### US spelling + +```yaml +recheck: + rules: + recheck/us-spelling: + severity: error + scope: summary + message: 'Use the US spelling "%s" instead of "%s".' + assertions: + swap: + ignoreCase: true + wordBoundary: true + pairs: + colour: color + behaviour: behavior + organise: organize +``` + +### Sentence length with an exception for a frozen page + +```yaml +recheck: + rules: + acme/sentence-length: + severity: warn + scope: sentence + message: 'Sentence is %s %s long; keep it under %s.' + assertions: + length: + unit: words + max: 25 + exceptions: + files: + - docs/legal/** +``` + +### Readability floor for guides only + +```yaml +recheck: + rules: + acme/readability-floor: + severity: warn + message: 'Readability (%s) is %s; expected between %s and %s.' + appliesTo: + - docs/guides/** + assertions: + metric: + formula: flesch-reading-ease + min: 40 +``` diff --git a/docs/@v2/recheck/index.md b/docs/@v2/recheck/index.md new file mode 100644 index 0000000000..dfce6094f0 --- /dev/null +++ b/docs/@v2/recheck/index.md @@ -0,0 +1,161 @@ +--- +seo: + title: Lint Markdown and prose with Recheck + description: Check Markdown structure, prose style, and Markdoc tags with the redocly recheck command. +--- + +# Markdown and prose linting + +Recheck is the Markdown linter built into Redocly CLI. +The [`recheck` command](../commands/recheck.md) reads the Markdown files in a folder and reports problems with their structure, their prose, and their Markdoc tags. +It replaces a separate markdownlint and Vale setup with one tool and one configuration file. + +Recheck has two kinds of rules: + +- **Markdown rules** check structure and formatting: heading levels, list indentation, blank lines, link syntax, tables, and code fences. + They port every built-in markdownlint rule, and most of them fix the file for you with `--fix`. + See [Markdown rules](./rules.md). +- **Prose rules** check the words: banned terms, spelling, repeated words, heading capitalization, sentence length, and readability. + Each prose rule is an assertion such as `swap` or `pattern` applied to a scope such as `heading` or `sentence`. + See [Prose rules](./assertions.md). + +Ready-made [presets](./presets.md) bundle these rules: a markdownlint-compatible structural set, a prose starter, Markdoc tag checks, and rule sets adapted from the Google and Microsoft style guides. + +## Quickstart + +Run the command with no configuration to lint a folder with the `recheck/markdown` preset: + +```bash +npx @redocly/cli@latest recheck docs +``` + +The report lists each finding with the rule name, the file and line, and a message. +Findings marked `[fixable]` can be repaired automatically: + +```bash +npx @redocly/cli@latest recheck docs --fix +``` + +To keep the configuration, create a `redocly.yaml` file and add a preset to `extends`: + +```yaml +extends: + - recheck/markdown +``` + +If `redocly.yaml` already exists, add the preset next to your API ruleset: + +```yaml +extends: + - recommended + - recheck/markdown +``` + +The `lint` command ignores Recheck presets, and the `recheck` command ignores API rulesets, so the two live side by side in one file. + +## Configure rules + +Presets go in the root `extends`. +Adjustments go in the [`recheck` block](../configuration/reference/recheck.md). +Each preset rule has a key such as `recheck/line-length` or `google/no-via`, and the block sets a severity or options for that key: + +```yaml +extends: + - recheck/markdown + - recheck/prose +recheck: + excludes: + - CHANGELOG.md + rules: + recheck/line-length: off + recheck/no-duplicate-heading: + severity: warn + assertions: + no-duplicate-heading: + siblingsOnly: true +``` + +The block can also declare new rules. +A new rule names an assertion, and for a prose assertion, a message: + +```yaml +recheck: + rules: + recheck/no-via: + severity: warn + scope: sentence + message: 'Use "through" or "by using" instead of "via".' + assertions: + pattern: + ignoreCase: true + tokens: + - '\bvia\b' +``` + +Read [Prose rules](./assertions.md) for every assertion and its options. + +### Severity levels + +| Severity | Effect | +| -------- | ------------------------------------------------------------ | +| `error` | Reported, and the command exits with code 1. | +| `warn` | Reported. The command still exits with code 0. | +| `info` | Reported as an informational message. The exit code stays 0. | +| `off` | The rule does not run. | + +### Fix findings automatically + +A rule is fixable when its assertion knows how to repair the text. +Most Markdown rules are fixable, and so are the `swap`, `repetition`, `consistency`, `capitalization`, and `semantic-line-breaks` assertions. +Set `fix: false` on a rule to keep it detection-only. +The style guide presets set `fix: false` on every rule, because automatic word swaps changed the meaning of correct text in testing. + +## Adopt Recheck on an existing project + +A large documentation set rarely passes a new linter on the first run. +Three tools make adoption gradual: + +- **Work rule by rule** with `--rule ` to see one rule's findings, then fix or configure that rule before moving to the next. +- **Measure before deciding** with `--stats` or `--summary=json`, which count findings per rule. + Set a noisy rule to `warn` or `off` from the counts, not from taste. +- **Record a baseline** with `--generate-baseline`. + The baseline records today's errors, and later runs fail only on new ones. + See [Suppress findings](./suppress-findings.md#use-a-baseline). + +For single lines or files that must stay as written, use [inline directives or exceptions](./suppress-findings.md). + +## Run in CI + +The `github-actions` format turns each finding into an annotation on the pull request: + +```yaml +- name: Lint Markdown + run: npx @redocly/cli@latest recheck docs --format=github-actions +``` + +The `sarif` format uploads to code scanning tools, and `json` feeds your own scripts. +See the [`recheck` command](../commands/recheck.md#formats) for the details of each format. + +## Use with AI assistants + +Two agent skills teach an AI coding assistant to work with Recheck: + +- `recheck-lint` runs the command on the Markdown the assistant touched, applies `--fix`, and fixes the rest by hand before the change is committed. +- `recheck-config` writes and tunes the `recheck` block from measured counts instead of guesses. + +Install them with the other Redocly CLI skills: + +```bash +npx skills add https://redocly.com +``` + +## Related pages + +- [`recheck` command](../commands/recheck.md) +- [`recheck` configuration reference](../configuration/reference/recheck.md) +- [Presets](./presets.md) +- [Markdown rules](./rules.md) +- [Prose rules](./assertions.md) +- [Markdoc tags](./markdoc.md) +- [Suppress findings](./suppress-findings.md) +- [Migrate from markdownlint and Vale](../guides/migrate-from-markdownlint.md) diff --git a/docs/@v2/recheck/markdoc.md b/docs/@v2/recheck/markdoc.md new file mode 100644 index 0000000000..c953b6f56b --- /dev/null +++ b/docs/@v2/recheck/markdoc.md @@ -0,0 +1,128 @@ +--- +seo: + title: Lint Markdoc tags with Recheck + description: Turn on Markdoc-aware parsing in Recheck, validate tags against a schema, and generate a tag schema from a theme. +--- + +# Markdoc tags + +Redocly projects write Markdown with [Markdoc](https://markdoc.dev) tags such as `{% admonition type="info" %}`. +Recheck parses those tags when you turn Markdoc parsing on, and the [`recheck/markdoc` preset](./presets.md#recheckmarkdoc) validates them. + +Markdoc parsing is off by default, because Liquid and Jinja templates use the same `{% %}` delimiters for unrelated syntax. + +## Turn on Markdoc parsing + +Set `markdoc: true` in the `recheck` block and add the preset: + +```yaml +extends: + - recheck/markdown + - recheck/markdoc +recheck: + markdoc: true +``` + +`markdoc: true` is the short form of `markdoc: { schema: realm }`, which validates tags against the built-in schema of the Realm theme. + +The preset's four rules only report when parsing is on. +With the preset but without `markdoc`, the command prints a warning that the rules can never report. + +To write _about_ Markdoc syntax, wrap the literal tag in a code span, such as `` `{% partial /%}` ``. +A code span never parses as a tag. + +## Choose or extend the schema + +The object form picks the schema and adds your own tags: + +```yaml +recheck: + markdoc: + schema: realm + extend: + tagsFile: ./markdoc-tags.yaml + tags: + raw-partial: + selfClosing: true + attributes: + file: + type: string + required: true +``` + +| Key | Description | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `schema` | **Required.** `realm` validates tags against the built-in Realm schema. `false` parses and pairs tags without a schema check. | +| `extend.tags` | Your own tags, merged over the schema. | +| `extend.tagsFile` | A YAML file of tags, relative to `redocly.yaml`. Inline `tags` win over it. Set `tags`, `tagsFile`, or both. | + +With `schema: false`, tag pairing and the `markdoc.tag` scope still work, and `markdoc-syntax` and `markdoc-pairing` still report. +Only `markdoc-unknown-tag` and `markdoc-attributes` go quiet, because there is no schema to check against. + +Tags merge in the order built-in schema, then `tagsFile`, then inline `tags`. +A tag defined twice is replaced whole, not merged attribute by attribute. + +A `tagsFile` that does not exist, is not valid YAML, or holds an invalid tag entry is a configuration error. +The run fails rather than silently skipping the Markdoc checks. + +### Tag schema shape + +Each tag has these keys: + +```yaml +tag-name: + selfClosing: true + attributes: + level: + type: string + required: true + default: info + enum: [info, warning, danger] +``` + +| Key | Type | Description | +| ---------------------------- | -------- | -------------------------------------------------------- | +| `selfClosing` | boolean | The tag is written as `{% tag /%}` and has no close tag. | +| `attributes..type` | string | **Required.** `string`, `number`, or `boolean`. | +| `attributes..required` | boolean | The attribute must be present. | +| `attributes..default` | any | The default value. | +| `attributes..enum` | [string] | The allowed values. | +| `attributes..dynamic` | boolean | The value is computed, so only its presence is checked. | + +## Generate a tags file from a theme + +A project that defines tags in a theme module, such as `@theme/markdoc/schema.ts`, can generate the tags file instead of writing it by hand: + +```bash +redocly recheck --generate-markdoc-schema --from=@theme/markdoc/schema.js --output=markdoc-tags.yaml +``` + +| Option | Description | +| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--from` | A module that exports `tags`, as a named export or on the default export. Repeat it to merge several modules. Paths are relative to the working directory. | +| `--output` | Where to write the YAML file. | +| `--check` | Compare the file in `--output` with a fresh generation and fail if it differs, without writing. Use it in CI. | + +The command extracts the parts of each tag it can check statically: `selfClosing`, and each attribute's `type`, `required`, `default`, and `enum`. +An attribute with a custom class or a `validate` function is written as `dynamic: true`. +Two modules that define the same tag with different shapes fail the command, so the merge never picks one by flag order. + +The command imports each module with Node.js, so the module must be JavaScript. +Compile a TypeScript module first, or run the CLI under a loader such as `tsx`. + +The generated file opens with a header that names its source modules and the command to regenerate it. +Commit the file and point `extend.tagsFile` at it. + +## How Markdoc parsing changes other rules + +Turning `markdoc` on changes how every rule sees a tag, not only the Markdoc rules: + +- **Prose scopes leave the tag out.** + `paragraph`, `heading`, `list-item`, `blockquote`, and table cell scopes blank out the `{% ... %}` span before a rule runs, with the same width, so a `swap` or `pattern` match cannot fire on tag syntax, and a `length` count does not include it. + A heading or cell whose whole text is a tag produces no segment. +- **`--fix` never rewrites a tag.** + A fix that would change the bytes of a tag is withheld and reported as a skipped fix. +- **Indented code blocks and setext headings are off.** + Markdoc's own parser does not recognize them, and Realm renders them as prose, so Recheck matches that while the flag is on. + A `{% table %}` line followed by `---` is a table row, not a heading. + Expect `heading-style`, `blanks-around-headings`, `capitalization`, and `code-block-style` findings to move the first time you turn the flag on. diff --git a/docs/@v2/recheck/presets.md b/docs/@v2/recheck/presets.md new file mode 100644 index 0000000000..6ef59a2e5e --- /dev/null +++ b/docs/@v2/recheck/presets.md @@ -0,0 +1,161 @@ +--- +seo: + title: Recheck presets + description: The rule sets that the recheck command can extend, and how to combine and tune them. +--- + +# Presets + +A preset is a named set of Recheck rules. +Add presets to the root `extends` of `redocly.yaml`, and adjust their rules in the [`recheck` block](../configuration/reference/recheck.md). + +```yaml +extends: + - recheck/markdown + - recheck/prose +``` + +Presets merge in the order listed, so a later preset overrides an earlier one for the same rule key. +The `recheck` block of the same file merges last. +An unknown preset name such as `recheck/nope` is a configuration error. + +## Structural presets + +These presets contain the [Markdown rules](./rules.md). +Their rule keys start with `recheck/`. + +### recheck/markdown + +All 53 markdownlint rules at `error`, with the upstream default options. +This is the preset the command uses when there is no `redocly.yaml`. +It is the equivalent of markdownlint's `default: true`. + +### recheck/markdown-relaxed + +The same 53 rules as `recheck/markdown`, with eleven rules turned off, the same as markdownlint's `relaxed` style: +`no-trailing-spaces`, `no-hard-tabs`, `no-multiple-blanks`, `no-multiple-space-blockquote`, `no-blanks-blockquote`, `line-length`, `ul-indent`, `no-inline-html`, `no-bare-urls`, `fenced-code-language`, and `first-line-h1`. + +### recheck/minimal + +Five rules that catch mistakes rather than enforce a style: `no-trailing-spaces`, `no-hard-tabs`, `single-trailing-newline`, `no-reversed-links`, and `no-empty-links`. + +### recheck/markdoc + +Four rules that check Markdoc tag syntax such as `{% tag attr="value" %}`. +They run only when the `recheck` block also turns on [Markdoc parsing](./markdoc.md) with `markdoc: true`. +Without it, the command prints a warning that the rules can never report. + +| Rule | Severity | Checks | +| ----------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `recheck/markdoc-syntax` | `error` | Malformed tag spans, unquoted attribute values, and close tags that carry attributes. | +| `recheck/markdoc-pairing` | `error` | Unclosed, orphaned, or crossed tag pairs, and a self-closing tag written with a close tag. | +| `recheck/markdoc-unknown-tag` | `warn` | A tag name the schema does not declare. | +| `recheck/markdoc-attributes` | `error` | A missing required attribute, a value outside the attribute's `enum`, a wrong type, or a duplicate attribute. An unknown attribute name is always reported at `warn`, whatever the rule's severity. | + +### recheck/api-descriptions + +The `recheck/markdown` rules without the seven that do not fit Markdown embedded in another document, such as an OpenAPI `description` field: `single-h1`, `first-line-h1`, `front-matter`, `single-trailing-newline`, `link-fragments`, `line-length`, and `ul-indent`. +The `recheck` command does not lint API descriptions yet, so this preset is for a later release. + +## Prose presets + +These presets contain [prose rules](./assertions.md). +Add them next to a structural preset, because they check words and not Markdown syntax. + +### recheck/prose + +A small starter set, all at `warn`, scoped to prose so that code samples and front matter are never touched: + +| Rule | Checks | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | +| `recheck/repetition` | An adjacent repeated word, such as "the the". Fixable. | +| `recheck/consistency` | One spelling per file for `behavior`/`behaviour`, `color`/`colour`, `license`/`licence`, and `organize`/`organise`. Fixable. | +| `recheck/capitalization` | Headings use sentence case. Detection-only, because an automatic fix lowercases proper nouns that are not in the exceptions list. | + +`extends: [recheck/markdown, recheck/prose]` replaces a markdownlint plus Vale setup in one line. + +### recheck/google + +Rules adapted from the [Google developer documentation style guide](https://developers.google.com/style) (CC BY 4.0). +The 99 rules cover heading, list, table, and link structure, sentence-case headings, sentence length, voice and contractions, plain language, product naming, and inclusive terminology. +Rule keys start with `google/`. +Structural rules are `error`; word choice and punctuation rules are `warn`. +Every rule is detection-only. + +Each rule links to its source page in the guide. +The provenance of every rule, and every guide entry that was considered and left out, is recorded in [`presets/google/PROVENANCE.md`](https://github.com/Redocly/redocly-cli/blob/main/packages/recheck/presets/google/PROVENANCE.md). + +### recheck/microsoft + +Rules adapted from the [Microsoft Writing Style Guide](https://learn.microsoft.com/en-us/style-guide/welcome/) (CC BY 4.0). +The 93 rules cover structure, the guide's numeric limits (paragraph length, list length, comma density, alt text length), the "use contractions" rule, US spelling, bias-free terminology, and a large A-Z word list. +Rule keys start with `microsoft/`. +Structural rules and the unconditional word list entries are `error`; voice and punctuation rules are `warn`. +Every rule is detection-only. + +Unlike `recheck/google`, this preset flags the input-specific verbs `click`, `press`, and `hit` in favor of `select`. +The provenance is in [`presets/microsoft/PROVENANCE.md`](https://github.com/Redocly/redocly-cli/blob/main/packages/recheck/presets/microsoft/PROVENANCE.md). + +### recheck/inclusive-language + +Eleven rules for terms that both the Google and the Microsoft guides say to avoid, such as `master`/`slave`, `blacklist`/`whitelist`, `he/she`, and ableist phrases. +Rule keys start with `inclusive-language/`. +All rules are `warn` and detection-only. + +Because the preset is the intersection of the two style guides, most of its rules duplicate a rule in `recheck/google` or `recheck/microsoft`. +Use it on its own or with `recheck/prose`. + +### recheck/plain-language + +Ten rules from the [US federal plain language guidance](https://digital.gov/guides/plain-language) (public domain): paragraph length, filler phrases, complex words, redundant pairs, double negatives, jargon, and excess intensifiers. +Rule keys start with `plain-language/`. +The paragraph length rules are `error`; the rest are `warn`. +All rules are detection-only. + +The RFC 2119 keyword `shall` is never flagged, because API documentation uses it with a defined meaning. + +### recheck/technical-english + +Three rules that follow the principles of ASD-STE100 Simplified Technical English: at most 25 words per sentence (`warn`), at most 6 sentences per paragraph (`warn`), and a passive voice heuristic (`info`). +Rule keys start with `technical-english/`. +The preset reproduces no part of the standard and not its dictionary. +For word choice, add `recheck/plain-language`. + +ASD-STE100 Simplified Technical English is a copyright and a trademark of ASD, Brussels, Belgium. +This preset is an independent work that ASD does not review or endorse. + +## Tune a preset + +Your `recheck` block wins over every preset. +Set a rule to a severity string to change how it reports: + +```yaml +extends: + - recheck/markdown + - recheck/microsoft +recheck: + rules: + microsoft/az-navigation: off # turn a rule off + microsoft/heading-sentence-case: warn # downgrade an error + recheck/line-length: + severity: warn + assertions: + line-length: + lineLength: 120 +``` + +To silence one occurrence instead of the whole rule, use an [inline directive](./suppress-findings.md#inline-directives). + +{% admonition type="warning" name="Assertion options replace, they do not merge" %} +A rule's `assertions` merge by assertion id, not by option. +If you set one option on a preset's `swap` or `pattern` assertion, your object replaces the preset's whole assertion, including its `pairs` or `tokens`. +The config then fails validation with a message such as `swap requires a "pairs" object`. + +To reject one term from a bundled word list, turn the rule off, restate the whole list, or use an inline directive on each occurrence. +The `exceptions` option of `capitalization` and the `ignore` option of `spelling` compose with the built-in lists, so they do not have this edge. +{% /admonition %} + +## Example configs + +The repository ships a generated example config for each style guide preset under [`packages/recheck/examples/`](https://github.com/Redocly/redocly-cli/tree/main/packages/recheck/examples). +Each file shows the two-line `extends` to paste, the tuning patterns that work, and the preset's full resolved rule set for reference. diff --git a/docs/@v2/recheck/rules.md b/docs/@v2/recheck/rules.md new file mode 100644 index 0000000000..0ea61b9814 --- /dev/null +++ b/docs/@v2/recheck/rules.md @@ -0,0 +1,415 @@ +--- +seo: + title: Recheck Markdown rules + description: Every structural Markdown rule that the recheck command ships, with its options. +--- + +# Markdown rules + +Markdown rules check the structure and formatting of a file: headings, lists, links, images, tables, code fences, and whitespace. +Recheck ports every built-in [markdownlint](https://github.com/DavidAnson/markdownlint) rule and adds a few of its own. +The [`recheck/markdown` preset](./presets.md#recheckmarkdown) turns the 53 ported rules on at `error`. + +A Markdown rule is configured under its key in the `recheck` block. +The key of a preset rule is `recheck/`, and the rule's options go under the assertion of the same name: + +```yaml +extends: + - recheck/markdown +recheck: + rules: + recheck/line-length: + severity: warn + assertions: + line-length: + lineLength: 120 + codeBlocks: false + recheck/ul-style: off +``` + +A rule that is not in any preset is added the same way. +The `message` is optional for a Markdown rule, because each rule has its own: + +```yaml +recheck: + rules: + recheck/list-length: + severity: warn + assertions: + list-length: + min: 2 + max: 10 +``` + +## Rules in the markdown preset + +The table lists each rule with its markdownlint id and whether `--fix` can repair it. +Options are listed after the table. + +### Document headings + +| Rule | markdownlint | Fixable | Checks | +| ------------------------------ | ------------ | ------- | ------------------------------------------------------------------------------- | +| `heading-increment` | MD001 | No | Heading levels increase by one level at a time. | +| `heading-style` | MD003 | No | One heading style (ATX, closed ATX, or setext) across the file. | +| `no-missing-space-atx` | MD018 | Yes | A space after the `#` of a heading. | +| `no-multiple-space-atx` | MD019 | Yes | One space after the `#` of a heading. | +| `no-missing-space-closed-atx` | MD020 | Yes | Spaces inside the hashes of a closed ATX heading. | +| `no-multiple-space-closed-atx` | MD021 | Yes | One space inside the hashes of a closed ATX heading. | +| `blanks-around-headings` | MD022 | Yes | Blank lines around headings. | +| `heading-start-left` | MD023 | Yes | Headings start at the beginning of the line. | +| `no-duplicate-heading` | MD024 | No | No two headings with the same text. | +| `single-h1` | MD025 | No | One top-level heading per file. Alias: `single-title`. | +| `no-trailing-punctuation` | MD026 | Yes | No trailing punctuation in a heading. | +| `no-emphasis-as-heading` | MD036 | No | Emphasis is not used in place of a heading. | +| `first-line-h1` | MD041 | No | The first line of the file is a top-level heading. Alias: `first-line-heading`. | +| `required-headings` | MD043 | No | The file follows a required heading structure. | + +### Whitespace and lines + +| Rule | markdownlint | Fixable | Checks | +| ------------------------- | ------------ | ------- | ------------------------------------------ | +| `no-trailing-spaces` | MD009 | Yes | No trailing spaces at the end of a line. | +| `no-hard-tabs` | MD010 | Yes | No hard tabs. | +| `no-multiple-blanks` | MD012 | Yes | No consecutive blank lines. | +| `line-length` | MD013 | No | Lines stay within a length. | +| `single-trailing-newline` | MD047 | Yes | The file ends with one newline. | +| `hr-style` | MD035 | No | One horizontal rule style across the file. | + +### Lists + +| Rule | markdownlint | Fixable | Checks | +| --------------------- | ------------ | ------- | -------------------------------------------------------- | +| `ul-style` | MD004 | Yes | One bullet style (`-`, `*`, or `+`) for unordered lists. | +| `list-indent` | MD005 | Yes | Items at the same level share the same indentation. | +| `ul-indent` | MD007 | Yes | Nested unordered lists indent by a fixed width. | +| `ol-prefix` | MD029 | Yes | Ordered list numbers follow one style. | +| `list-marker-space` | MD030 | Yes | A fixed number of spaces after a list marker. | +| `blanks-around-lists` | MD032 | Yes | Blank lines around lists. | + +### Code + +| Rule | markdownlint | Fixable | Checks | +| ---------------------- | ------------ | ------- | ------------------------------------------------------------- | +| `commands-show-output` | MD014 | Yes | No `$` before a shell command unless the output is shown too. | +| `blanks-around-fences` | MD031 | Yes | Blank lines around fenced code blocks. | +| `no-space-in-code` | MD038 | Yes | No spaces inside inline code spans. | +| `fenced-code-language` | MD040 | No | Every fenced code block names a language. | +| `code-block-style` | MD046 | No | One code block style (fenced or indented) across the file. | +| `code-fence-style` | MD048 | No | One fence style (backticks or tildes) across the file. | + +### Links and images + +| Rule | markdownlint | Fixable | Checks | +| ---------------------------------- | ------------ | ------- | ----------------------------------------------------------- | +| `no-reversed-links` | MD011 | Yes | Link syntax is not reversed, as in `(text)[url]`. | +| `no-space-in-links` | MD039 | Yes | No spaces inside link text. | +| `no-empty-links` | MD042 | No | Every link has a destination. | +| `no-bare-urls` | MD034 | Yes | URLs are wrapped in angle brackets or a link. | +| `no-alt-text` | MD045 | No | Every image has alt text. | +| `link-fragments` | MD051 | Yes | `#fragment` links point at a heading or anchor that exists. | +| `reference-links-images` | MD052 | No | Reference links and images use a label that is defined. | +| `link-image-reference-definitions` | MD053 | Yes | Every reference definition is used. | +| `link-image-style` | MD054 | Yes | Only the allowed link and image styles are used. | +| `descriptive-link-text` | MD059 | No | Link text is descriptive, not "click here" or "more". | + +### Emphasis and inline + +| Rule | markdownlint | Fixable | Checks | +| ---------------------- | ------------ | ------- | ------------------------------------------------ | +| `no-space-in-emphasis` | MD037 | Yes | No spaces inside emphasis markers. | +| `no-inline-html` | MD033 | No | No inline HTML. | +| `proper-names` | MD044 | Yes | Listed names use the configured capitalization. | +| `emphasis-style` | MD049 | Yes | One emphasis style (`*` or `_`) across the file. | +| `strong-style` | MD050 | Yes | One strong style (`**` or `__`) across the file. | + +### Blockquotes and tables + +| Rule | markdownlint | Fixable | Checks | +| ------------------------------ | ------------ | ------- | ----------------------------------------------- | +| `no-multiple-space-blockquote` | MD027 | Yes | One space after the `>` of a blockquote. | +| `no-blanks-blockquote` | MD028 | No | No blank line inside a blockquote. | +| `table-pipe-style` | MD055 | No | One table pipe style across the file. | +| `table-column-count` | MD056 | No | Every table row has the same number of columns. | +| `blanks-around-tables` | MD058 | Yes | Blank lines around tables. | +| `table-column-style` | MD060 | Yes | Table columns follow one alignment style. | + +## Rules outside the presets + +These rules have no markdownlint counterpart and no preset turns them on. +Add them to the `recheck` block by name. + +| Rule | Fixable | Checks | +| -------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `front-matter` | No | Front matter matches a JSON Schema. See [Front matter](#front-matter). | +| `list-length` | No | A list has at least `min` items (default 2) and at most `max` items (no default). Nested lists count on their own. | +| `no-empty-headings` | No | A heading has text content. Inline code counts as content. | +| `no-duplicate-link-destinations` | No | The same destination is not linked with different text in one file. Repeating the same text for the same destination is fine. | + +The four Markdoc rules are documented with the [`recheck/markdoc` preset](./presets.md#recheckmarkdoc). + +## Options + +Options go under the assertion with the rule's name. +A rule not listed here has no options. +An option name a rule does not recognize is ignored without a warning. + +### `blanks-around-fences` + +| Option | Type | Default | Description | +| ----------- | ------- | ------- | --------------------------------------------------------- | +| `listItems` | boolean | `true` | Also require blank lines around fences inside list items. | + +### `blanks-around-headings` + +| Option | Type | Default | Description | +| -------------------- | ------- | ------- | -------------------------------------------------------- | +| `linesAbove` | number | `1` | Blank lines required above a heading. | +| `linesBelow` | number | `1` | Blank lines required below a heading. | +| `includeFrontMatter` | boolean | `false` | Require a blank line between front matter and a heading. | + +### `code-block-style`, `code-fence-style`, `emphasis-style`, `heading-style`, `hr-style`, `strong-style`, `table-pipe-style`, `ul-style` + +| Option | Type | Default | Description | +| ------- | ------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `style` | string | `consistent` | `consistent` uses the first style found in the file. The other values name one style, for example `fenced`, `backtick`, `asterisk`, `underscore`, `atx`, `dash`, `leading_and_trailing`, or `sublist`. | + +The accepted names match markdownlint's options for the rule with the same id. + +### `descriptive-link-text` + +| Option | Type | Default | Description | +| ----------------- | -------- | ---------------------------------------- | ----------------------------- | +| `prohibitedTexts` | [string] | `['click here', 'here', 'link', 'more']` | Link texts that are reported. | + +### `fenced-code-language` + +| Option | Type | Default | Description | +| ------------------ | -------- | ------- | ---------------------------------------------------------- | +| `allowedLanguages` | [string] | `[]` | When set, only these languages are allowed. | +| `languageOnly` | boolean | `false` | Report an info string that carries more than the language. | + +### `first-line-h1`, `single-h1`, `heading-increment` + +| Option | Type | Default | Description | +| ------------------ | ------- | ---------------------- | ------------------------------------------------------------------------------------------------------- | +| `frontMatterTitle` | string | `^\s*"?title"?\s*[:=]` | A regex. Front matter that matches it counts as the top-level heading. Set `''` to ignore front matter. | +| `level` | number | `1` | `first-line-h1` and `single-h1` only: the heading level that counts as the title. | +| `allowPreamble` | boolean | `false` | `first-line-h1` only: allow content before the first heading. | + +### `line-length` + +| Option | Type | Default | Description | +| --------------------- | ------- | ------- | ------------------------------------------------------------------- | +| `lineLength` | number | `80` | Maximum line length. | +| `headingLineLength` | number | | Maximum length of heading lines. Falls back to `lineLength`. | +| `codeBlockLineLength` | number | | Maximum length of lines in code blocks. Falls back to `lineLength`. | +| `codeBlocks` | boolean | `true` | Check code blocks. | +| `tables` | boolean | `true` | Check tables. | +| `headings` | boolean | `true` | Check headings. | +| `strict` | boolean | `false` | Report long lines even when they hold no whitespace to break at. | +| `stern` | boolean | `false` | Report long lines that could be broken, allow the rest. | + +### `link-fragments` + +| Option | Type | Default | Description | +| ---------------- | ---------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ignoreCase` | boolean | `false` | Match fragments without regard to case. | +| `ignoredPattern` | string | `''` | A regex. Fragments that match it are not checked. | +| `crossFile` | boolean | `false` | Also check links and images to other files. See [Cross-file links](#cross-file-links). | +| `rootDir` | string or object | `''` | With `crossFile`, the folder that site-root links such as `/guides/intro` resolve against. An object maps a source folder prefix to its root for monorepos; the longest matching prefix wins. | +| `ignoredTargets` | [string] | `[]` | With `crossFile`, destination globs that are not checked, for routes a site generates from data. | + +#### Cross-file links + +With `crossFile: true`, the rule replaces an external link checker for links inside the repository: + +- A relative link or image target must exist on disk. +- A `file.md#anchor` fragment must exist in the target file's headings and anchors. +- Extensionless links resolve the way the Realm router does: `./page` tries `page.md`, and a folder link reads its `index.md`. +- Site-root links such as `/x/y` resolve against `rootDir`, and are skipped without it. +- Links to `
` sections resolve, with the id derived from the `` text when none is set. +- Markdoc tags in a heading do not change its anchor. +- External URLs and `mailto:` links are skipped. + +```yaml +recheck: + rules: + recheck/link-fragments: + severity: error + assertions: + link-fragments: + crossFile: true + rootDir: docs + ignoredTargets: + - '/gateways/**' +``` + +### `link-image-reference-definitions` + +| Option | Type | Default | Description | +| -------------------- | -------- | -------- | --------------------------------------- | +| `ignoredDefinitions` | [string] | `['//']` | Definition labels that may stay unused. | + +### `link-image-style` + +| Option | Type | Default | Description | +| ----------- | ------- | ------- | --------------------------------------------------- | +| `autolink` | boolean | `true` | Allow ``. | +| `inline` | boolean | `true` | Allow `[text](url)`. | +| `full` | boolean | `true` | Allow `[text][label]`. | +| `collapsed` | boolean | `true` | Allow `[label][]`. | +| `shortcut` | boolean | `true` | Allow `[label]`. | +| `urlInline` | boolean | `true` | Allow `[https://example.com](https://example.com)`. | + +### `list-length` + +| Option | Type | Default | Description | +| ------ | ------ | ------- | ---------------------------------- | +| `min` | number | `2` | Minimum number of items in a list. | +| `max` | number | | Maximum number of items in a list. | + +### `list-marker-space` + +| Option | Type | Default | Description | +| ---------- | ------ | ------- | ------------------------------------------------------ | +| `ulSingle` | number | `1` | Spaces after a bullet in a list of single-line items. | +| `olSingle` | number | `1` | Spaces after a number in a list of single-line items. | +| `ulMulti` | number | `1` | Spaces after a bullet in a list with multi-line items. | +| `olMulti` | number | `1` | Spaces after a number in a list with multi-line items. | + +### `no-duplicate-heading` + +| Option | Type | Default | Description | +| ---------------------- | ------- | ------- | ------------------------------------------------------------------------------------------------ | +| `siblingsOnly` | boolean | `false` | Report duplicates only among headings with the same parent. | +| `respectSections` | boolean | `false` | Report duplicates only inside the same section. A Recheck extension. | +| `caseSensitive` | boolean | `true` | Compare heading text with regard to case. A Recheck extension. | +| `ignoreCommonHeadings` | boolean | `false` | Skip common headings such as "Overview", "Examples", and "Troubleshooting". A Recheck extension. | + +### `no-emphasis-as-heading`, `no-trailing-punctuation` + +| Option | Type | Default | Description | +| ------------- | ------ | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | +| `punctuation` | string | `.,;:!?。,;:!?` for `no-emphasis-as-heading`, the same without `?` and `?` for `no-trailing-punctuation` | The characters that count as punctuation. | + +### `no-hard-tabs` + +| Option | Type | Default | Description | +| --------------------- | -------- | ------- | ------------------------------------------- | +| `codeBlocks` | boolean | `true` | Check code blocks. | +| `ignoreCodeLanguages` | [string] | `[]` | Code block languages that may contain tabs. | +| `spacesPerTab` | number | `1` | Spaces that replace each tab with `--fix`. | + +### `no-inline-html` + +| Option | Type | Default | Description | +| ---------------------- | -------- | ------- | --------------------------------------------- | +| `allowedElements` | [string] | `[]` | HTML elements that are allowed. | +| `tableAllowedElements` | [string] | `[]` | HTML elements that are allowed inside tables. | + +### `no-multiple-blanks` + +| Option | Type | Default | Description | +| --------- | ------ | ------- | -------------------------------- | +| `maximum` | number | `1` | Consecutive blank lines allowed. | + +### `no-multiple-space-blockquote` + +| Option | Type | Default | Description | +| ----------- | ------- | ------- | ----------------------------------------- | +| `listItems` | boolean | `true` | Also check list items inside blockquotes. | + +### `no-trailing-spaces` + +| Option | Type | Default | Description | +| -------------------- | ------- | ------- | ---------------------------------------------------------------- | +| `brSpaces` | number | `2` | Trailing spaces that count as a hard line break and are allowed. | +| `codeBlocks` | boolean | `false` | Check code blocks. | +| `listItemEmptyLines` | boolean | `false` | Allow trailing spaces on empty lines inside list items. | +| `strict` | boolean | `false` | Report every trailing space, including hard line breaks. | + +### `ol-prefix` + +| Option | Type | Default | Description | +| ------- | ------ | ---------------- | ---------------------------------------------- | +| `style` | string | `one_or_ordered` | `one`, `ordered`, `one_or_ordered`, or `zero`. | + +### `proper-names` + +| Option | Type | Default | Description | +| -------------- | -------- | ------- | ---------------------------------------- | +| `names` | [string] | `[]` | Names with their correct capitalization. | +| `codeBlocks` | boolean | `true` | Check code blocks. | +| `htmlElements` | boolean | `true` | Check HTML elements. | + +### `reference-links-images` + +| Option | Type | Default | Description | +| ---------------- | -------- | ------- | ------------------------------------------------- | +| `shortcutSyntax` | boolean | `false` | Also check shortcut references such as `[label]`. | +| `ignoredLabels` | [string] | `['x']` | Labels that are not checked. | + +### `required-headings` + +| Option | Type | Default | Description | +| ----------- | -------- | ------- | -------------------------------------------------------------------------------------------------------- | +| `headings` | [string] | | The required headings in order. `*` matches zero or more headings, `+` one or more, and `?` zero or one. | +| `matchCase` | boolean | `false` | Compare heading text with regard to case. | + +### `table-column-style` + +| Option | Type | Default | Description | +| ------------------ | ------- | ------- | --------------------------------------------------------- | +| `style` | string | `any` | `any`, `aligned`, `compact`, or `tight`. | +| `alignedDelimiter` | boolean | `false` | Require the delimiter row to be aligned with the columns. | + +### `ul-indent` + +| Option | Type | Default | Description | +| --------------- | ------- | ------- | --------------------------------------------------------------------- | +| `indent` | number | `2` | Spaces per nesting level. | +| `startIndented` | boolean | `false` | Allow the first level to be indented. | +| `startIndent` | number | `2` | Spaces of indentation for the first level when `startIndented` is on. | + +## Front matter + +The `front-matter` rule validates front matter against a JSON Schema with the same validator that Redocly CLI uses for API descriptions. +Map file globs to schemas. +The first mapping that matches a file wins, and a file that matches no mapping is not checked. + +```yaml +recheck: + rules: + recheck/front-matter: + severity: error + assertions: + front-matter: + schemas: + - files: ['.changeset/**'] + schema: + type: object + patternProperties: + '^@redocly/': { enum: [major, minor, patch] } + additionalProperties: false + - files: ['docs/**'] + schema: realm + strict: true +``` + +Each mapping takes these keys: + +| Key | Type | Description | +| ------------ | ---------------- | ----------------------------------------------------------------------------------------------------- | +| `files` | [string] | File globs the mapping applies to. | +| `schema` | object or string | An inline JSON Schema, or the name of a built-in schema. `realm` is the only built-in schema. | +| `schemaFile` | string | Path to a YAML or JSON schema file, relative to the working directory. Used when `schema` is not set. | +| `strict` | boolean | Report keys the schema does not define. Default `false`. | + +A file with no front matter validates as an empty object, so the schema's `required` list decides whether front matter is mandatory. +Each finding points at the line of the offending top-level key. +Front matter that is not valid YAML is one finding at the start of the block. + +The built-in `realm` schema checks the type of every front matter option that a Realm page accepts, such as `title`, `description`, `slug`, `sidebar`, `excludeFromSearch`, and the options that override `redocly.yaml` on one page. +It does not check the inner shape of those option objects, because Realm evolves them independently. +`strict` is off by default, because pages often carry their own keys that Markdoc templates read back through `$frontmatter`. diff --git a/docs/@v2/recheck/suppress-findings.md b/docs/@v2/recheck/suppress-findings.md new file mode 100644 index 0000000000..f94f2515dc --- /dev/null +++ b/docs/@v2/recheck/suppress-findings.md @@ -0,0 +1,157 @@ +--- +seo: + title: Suppress Recheck findings + description: Limit which files and lines a Recheck rule reports, with excludes, exceptions, inline directives, and a baseline. +--- + +# Suppress findings + +Fix the content first. +When a finding is a true positive that must stay as written, or the content is generated or frozen, Recheck offers four ways to keep the rule on and quiet it where it does not apply. +In order of preference: + +1. **File targeting** in the configuration, with `appliesTo`, `excludes`, and `exceptions.files`, for generated or frozen files. +2. **Line exceptions** in the configuration, with `exceptions.lines`, for a recurring line that must stay as written. +3. **Inline directives** in the Markdown file, for a single line or region. +4. **A baseline**, to adopt a rule on a large document set before every existing finding is fixed. + +## File targeting + +Every rule accepts three lists of file globs: + +| Key | Effect | +| ------------------ | --------------------------------------------------------------------------------- | +| `appliesTo` | The rule runs only on files that match. Default: every Markdown file. | +| `excludes` | The rule skips files that match. | +| `exceptions.files` | The rule skips files that match. Same effect as `excludes`, kept for readability. | + +The `recheck` block also has a top-level `excludes` list that every rule skips, so a folder you never lint is declared once: + +```yaml +recheck: + excludes: + - '**/_partials/**' + - CHANGELOG.md + rules: + recheck/line-length: + severity: warn + appliesTo: + - 'docs/reference/**' + assertions: + line-length: + lineLength: 120 + recheck/us-spelling: + severity: error + message: 'Use the US spelling "%s" instead of "%s".' + excludes: + - 'docs/archive/**' + assertions: + swap: + pairs: + colour: color +``` + +A glob matches a file when it matches the file name, the path relative to the working directory, or any trailing part of that path. +So `style-guide.md` matches that file in any folder, `docs/style-guide.md` matches one path, and `docs/**` matches everything under `docs`. + +## Line exceptions + +`exceptions.lines` lists text fragments. +A line that contains any fragment is skipped by the rule. +Matching is case-sensitive. + +```yaml +recheck: + rules: + recheck/us-spelling: + severity: error + message: 'Use the US spelling "%s" instead of "%s".' + assertions: + swap: + pairs: + colour: color + exceptions: + lines: + - 'British spellings such as' +``` + +## Inline directives + +An HTML comment in the Markdown file turns rules off for a line, a region, or the whole file. +Name a rule by its full key or by the short name the report prints, and separate several names with spaces. +A directive without names applies to every rule. + +```markdown + + +This one long line is exempt from the line length rule; the rest of the file is not. + + + +These two rules are off from here on. + + + +The Microsoft rule is still off; the Google rule is back on. + + + +Nothing is checked from here on. + + + +Checking resumes here. +``` + +| Directive | Effect | +| -------------------------------------------- | ------------------------------------------------------------------------------------------------ | +| `` | Turns the rules off for the next line only. | +| `` | Turns the rules off from this line to the end of the file, or until a matching `recheck-enable`. | +| `` | Turns the rules back on from this line. | +| `` | Turns every rule off for the whole file, wherever the comment sits. | + +A directive must be real HTML in the document. +Inside a fenced code block it is text, and it does nothing. + +A directive that names a rule the configuration does not have disables nothing. +The command reports it as a `recheck-directive` warning on the directive's line, so a typo in a rule name is visible. + +## Use a baseline + +A baseline lets a team adopt Recheck on a large document set without fixing every existing finding first. +It records the errors that exist today, and later runs fail only on new ones. + +Generate it from the folder you lint: + +```bash +redocly recheck docs --generate-baseline +``` + +The command writes `.redocly.recheck-baseline.yaml` next to `redocly.yaml`. +Commit the file. +Every later run finds it by its presence; no configuration key is needed. + +The file stores one count per file per rule, for errors only, with sorted keys so that diffs stay stable: + +```yaml +# Generated by `redocly recheck --generate-baseline`. Do not edit by hand. +version: 1 +files: + docs/index.md: + recheck/single-h1: 1 + docs/guides/intro.md: + recheck/line-length: 3 +``` + +With a baseline in place, a run: + +- **suppresses** an error when the count for its file and rule is within the baseline, and prints how many findings matched; +- **fails** when a count rises, and prints the group's findings with `(baseline 3, found 5)` added to the message; +- **fails** when a count falls, because the baseline is stale. + Regenerate it and commit the smaller file, so the baseline equals reality at every green commit. + +Warnings and info findings are never baselined. +A run on a narrower path, or with `--rule`, compares only the files it scanned and the rules it ran, so it never reports a stale entry it could not see. +Line numbers are not stored, so a baseline survives unrelated edits, and its diff in a pull request reads as "this change pays down four findings". + +A renamed file is a new path with no budget, so its old findings report as new until you regenerate the baseline. diff --git a/docs/@v2/v2.sidebars.yaml b/docs/@v2/v2.sidebars.yaml index baebda623a..534c941845 100644 --- a/docs/@v2/v2.sidebars.yaml +++ b/docs/@v2/v2.sidebars.yaml @@ -14,8 +14,6 @@ page: commands/bundle.md - label: check-config page: commands/check-config.md - - label: recheck - page: commands/recheck.md - label: drift page: commands/drift.md - label: eject @@ -48,6 +46,8 @@ page: commands/push.md - label: push-status page: commands/push-status.md + - label: recheck + page: commands/recheck.md - label: respect page: commands/respect.md - label: score @@ -96,6 +96,7 @@ - page: guides/migrate-from-openapi-cli.md - page: guides/migrate-from-spectral.md - page: guides/migrate-from-swagger-cli.md + - page: guides/migrate-from-markdownlint.md - page: guides/migrate-to-v2.md - page: api-docs.md label: API docs @@ -220,6 +221,19 @@ - page: decorators/remove-unused-components.md - page: decorators/remove-x-internal.md - page: decorators/tag-description-override.md + - group: Markdown linting + page: recheck/index.md + items: + - label: Presets + page: recheck/presets.md + - label: Markdown rules + page: recheck/rules.md + - label: Prose rules + page: recheck/assertions.md + - label: Markdoc tags + page: recheck/markdoc.md + - label: Suppress findings + page: recheck/suppress-findings.md - page: file-management.md - group: Configuration page: configuration/index.md diff --git a/packages/recheck/README.md b/packages/recheck/README.md index 2a1d0ba804..68ef0a5608 100644 --- a/packages/recheck/README.md +++ b/packages/recheck/README.md @@ -15,7 +15,11 @@ recheck: ``` Run it with `npx @redocly/cli recheck`. -Its documentation is the [recheck command page](../../docs/@v2/commands/recheck.md). +Its documentation is the [Markdown and prose linting](../../docs/@v2/recheck/index.md) section and the [recheck command page](../../docs/@v2/commands/recheck.md). + +Two agent skills for AI coding assistants ship in [`skills/`](./skills): `recheck-lint` runs the command on touched Markdown, and `recheck-config` tunes the `recheck` block. +Install them with `npx skills add https://redocly.com`, or copy them from `node_modules/@redocly/recheck/skills/` into your project's `.claude/skills/`. +The repository keeps the same files under [`.claude/skills/`](../../.claude/skills), which is where the website publishes them from, so change both copies together. ## Programmatic use diff --git a/packages/recheck/skills/recheck-config/SKILL.md b/packages/recheck/skills/recheck-config/SKILL.md index e9bc3810ad..6bc423cfd9 100644 --- a/packages/recheck/skills/recheck-config/SKILL.md +++ b/packages/recheck/skills/recheck-config/SKILL.md @@ -1,11 +1,48 @@ --- name: recheck-config -description: Use when you create or tune the recheck block in redocly.yaml for a project — choose presets, set severities from measured counts, add exceptions, adopt a baseline, and validate the result. +description: Use when you create or tune the recheck block and recheck/* presets in redocly.yaml for a project — choose presets, set severities from measured counts, add exceptions, adopt a baseline, and validate the result. --- -# Configure the recheck block in redocly.yaml +# Configure recheck in redocly.yaml + +Configure `redocly recheck` for a project the way its maintainers would: measure first, decide per rule, and record intent. +The documentation is at [redocly.com/docs/cli/recheck](https://redocly.com/docs/cli/recheck); favor it over memory for rule names and options. + +## How the configuration is shaped + +Presets go in the root `extends`. +Adjustments go in the `recheck` block, with rules nested under `rules`. +The `lint` command ignores both, so they live next to the API ruleset: + +```yaml +extends: + - recommended + - recheck/markdown + - recheck/prose +recheck: + excludes: + - CHANGELOG.md + rules: + recheck/line-length: off + recheck/no-duplicate-heading: + severity: warn + assertions: + no-duplicate-heading: + siblingsOnly: true +``` + +Rules of the shape: -Configure recheck for a project the way its maintainers would: measure first, decide per rule, and record intent. +- A rule key is `/`: `recheck/line-length`, `google/no-via`, or your own, such as `acme/product-names`. +- A severity string (`off`, `info`, `warn`, `error`) adjusts a rule that a preset defines. + For a key no preset defines, it is a configuration error, because the rule has no assertions. +- A rule object merges over the preset's rule, but its `assertions` merge by assertion id: one option you set on a `swap` or `pattern` assertion replaces the preset's whole word list. + To reject one term from a preset's list, turn the rule off, restate the full list, or use an inline directive. +- A new prose rule needs `severity`, `message`, and `assertions`. + A new Markdown rule, such as `recheck/list-length`, needs only `severity` and `assertions`. +- The block does not accept `extends` or `baseline`; both are configuration errors. +- The command reads the root block only. + A `recheck` block under `apis.` does nothing and prints a warning. ## Start a new config @@ -17,36 +54,44 @@ Configure recheck for a project the way its maintainers would: measure first, de ``` ```bash - redocly recheck + npx @redocly/cli recheck docs + npx @redocly/cli check-config ``` - The command reports config errors before it lints anything. + The `recheck` command reports configuration errors before it lints anything, and `check-config` validates the block's shape. + +2. Add prose presets deliberately, not wholesale. + `recheck/prose` is a small starter at `warn`. + `recheck/google` and `recheck/microsoft` are large style guides, detection-only, that need tuning before they help. + `recheck/plain-language`, `recheck/inclusive-language`, and `recheck/technical-english` compose onto either. -2. Add prose rules deliberately, not wholesale. - `recheck/prose` is a small starter; `recheck/google` and `recheck/microsoft` are large style guides that need tuning before they help. +3. If the project writes Markdoc tags, add `recheck/markdoc` to `extends` and `markdoc: true` to the block. + Without the flag, the Markdoc rules never report. ## Tune severities from measurements, not taste 1. Run the whole document set and count findings per rule: ```bash - redocly recheck docs --format json > findings.json + npx @redocly/cli recheck docs --summary=json --summary-path=recheck-summary.json ``` - Group the findings by `ruleName` and look at real examples of each before deciding anything. + The summary's `breakdown` has the count per rule and severity. + For examples of each rule's findings, run `--rule=` and read a few. 2. Decide per rule from the counts: - `error` — enforce now; the document set is clean or you fix it in the same change. - `warn` — a worklist; visible, not blocking. - - `off` — decided against; keep a comment that says why, or the decision is lost. + - `off` — decided against; keep a YAML comment that says why, or the decision is lost. 3. Prefer fixing content over configuring around it. Run `--fix` first; most structural findings repair themselves. + Review the fix list before you commit: fixes that re-indent lines can move content out of an indented Markdoc tag body. ## Exceptions, in order of preference 1. Fix the content. -2. `exceptions.files` for generated or frozen content (archives, vendored docs) — with a comment naming the reason. +2. The top-level `excludes` list, or a rule's `excludes` or `appliesTo`, for generated or frozen content such as archives and vendored docs — with a comment naming the reason. 3. `exceptions.lines` for a recurring true positive that must stay as written. 4. An inline `` for a single line. @@ -55,16 +100,17 @@ Configure recheck for a project the way its maintainers would: measure first, de When the document set has too many errors to fix at once, record them and gate only new ones: ```bash -redocly recheck --generate-baseline +npx @redocly/cli recheck docs --generate-baseline ``` Commit `.redocly.recheck-baseline.yaml`. -The command writes it next to `redocly.yaml`, and later runs pick it up automatically. +The command writes it next to `redocly.yaml`, and later runs pick it up by presence; there is no configuration key. +It records errors only, as one count per file per rule. -Counts only step down: when findings get fixed, regenerate the baseline and commit the diff. +Counts only step down: when findings get fixed, the run reports stale entries, so regenerate the baseline and commit the diff. ## Finish -- `redocly recheck` must report no config errors. +- `npx @redocly/cli recheck docs` and `npx @redocly/cli check-config` report no configuration errors. - Every `off` and every exception carries a comment with its reason. -- The config's README section or the project docs say how contributors run recheck locally. +- The project's contributing docs say how contributors run recheck locally, and CI runs it with `--format=github-actions`. diff --git a/packages/recheck/skills/recheck-lint/SKILL.md b/packages/recheck/skills/recheck-lint/SKILL.md index 1fc9cc4538..c98379314d 100644 --- a/packages/recheck/skills/recheck-lint/SKILL.md +++ b/packages/recheck/skills/recheck-lint/SKILL.md @@ -1,37 +1,55 @@ --- name: recheck-lint -description: Use when you write or edit markdown in a project that has a recheck block in redocly.yaml — run redocly recheck on the touched files before you commit them or hand them over, and fix what it finds. +description: Use when you write or edit Markdown in a project whose redocly.yaml has a recheck block or a recheck/* preset in extends — run redocly recheck on the touched files before you commit them or hand them over, and fix what it finds. --- -# Lint markdown with recheck before you commit it +# Lint Markdown with recheck before you commit it -This project lints markdown and prose with recheck. -Your markdown must pass it, the same as code must pass its tests. +This project lints Markdown structure and prose with `redocly recheck`. +Your Markdown must pass it, the same as code must pass its tests. ## When this applies Look for a `redocly.yaml` in the project root or above the files you touched. -This skill applies when that file has a `recheck` block, or a `recheck/*` preset in its root `extends`. +This skill applies when that file has a `recheck` block, or a `recheck/*` preset in its root `extends`, for example: + +```yaml +extends: + - recommended + - recheck/markdown +recheck: + rules: + recheck/line-length: off +``` + If neither exists, skip this skill. +The command checks nothing in that case and says so. ## What to do -1. After you write or edit markdown files, run recheck on them: +1. After you write or edit Markdown files, run recheck on them: ```bash - redocly recheck + npx @redocly/cli recheck ``` + Pass the files you touched, not the whole repository, so the report is yours to act on. + 2. If it reports errors, run the auto-fix first — most structural findings repair themselves: ```bash - redocly recheck --fix + npx @redocly/cli recheck --fix ``` + The command lists every edit it made. + Read the list: a fix inside a Markdoc tag body or a code sample deserves a look. + 3. Fix the remaining errors by hand. Read each message; the rule name links the finding to its intent. + To see one rule at a time, add `--rule=` with the name the report prints. 4. Re-run until it reports no errors, and only then commit or output the content. + The command exits with code 1 while errors remain. ## Rules of conduct @@ -42,10 +60,14 @@ If neither exists, skip this skill. ``` + Name the rule by its full key or by the short name the report prints. Never disable a rule project-wide to make your change pass, and never edit the `recheck` block to get past a finding. -- Warnings do not block you. +- Warnings and info findings do not block you. Leave them unless the task asks for cleanup. -- If the project has a `.redocly.recheck-baseline.yaml` file next to redocly.yaml, the baseline already covers old findings; only new findings are yours to fix. - Do not regenerate the baseline to absorb findings your change introduced. +- If a `.redocly.recheck-baseline.yaml` file sits next to `redocly.yaml`, the baseline already covers old findings; only new findings are yours to fix. + Do not run `--generate-baseline` to absorb findings your change introduced. + If your change removed old findings, the run reports stale baseline entries; regenerate the baseline then, and commit the smaller file with your change. - A finding you believe is a false positive is worth reporting to the maintainer, not silently suppressing. +- Do not add `markdownlint` or `vale` comments. + Recheck does not read them. From 6db140f15e8c8099980301432bc734c7712e3cac Mon Sep 17 00:00:00 2001 From: Adam Altman Date: Sat, 3 Oct 2026 00:40:16 -0500 Subject: [PATCH 2/2] docs(recheck): document includeCode for the repetition and consistency assertions --- docs/@v2/recheck/assertions.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/@v2/recheck/assertions.md b/docs/@v2/recheck/assertions.md index 21d3076efd..ffe9abc9f0 100644 --- a/docs/@v2/recheck/assertions.md +++ b/docs/@v2/recheck/assertions.md @@ -77,7 +77,7 @@ For example, `'~blockquote & ~heading'` selects everything except blockquotes an `all` and `raw` cover the whole file, so they cannot be combined with another scope. Prose scopes never include inline code, and when [Markdoc parsing](./markdoc.md) is on, they do not include the tag syntax either. -Set `includeCode: true` on `swap` or `pattern` to match inside inline code. +Set `includeCode: true` on `swap`, `pattern`, `repetition`, or `consistency` to match inside inline code. ## Assertions @@ -180,10 +180,11 @@ assertions: repetition: {} ``` -| Option | Type | Required | Description | -| ------------ | ------- | -------- | ------------------------------------------------------------------------------------- | -| `pattern` | string | No | The regex that defines a word. Default `\w+`. | -| `ignoreCase` | boolean | No | Compare without regard to case. Default `true`, because "The the" is the common typo. | +| Option | Type | Required | Description | +| ------------- | ------- | -------- | ------------------------------------------------------------------------------------- | +| `pattern` | string | No | The regex that defines a word. Default `\w+`. | +| `ignoreCase` | boolean | No | Compare without regard to case. Default `true`, because "The the" is the common typo. | +| `includeCode` | boolean | No | Also look for repeats inside inline code spans. Default `false`. | Message placeholders: the repeated word. @@ -202,10 +203,11 @@ assertions: color: colour ``` -| Option | Type | Required | Description | -| ------------ | ------- | -------- | ------------------------------------------------ | -| `either` | object | Yes | Pairs of variants. Each pair has its own winner. | -| `ignoreCase` | boolean | No | Match without regard to case. Default `false`. | +| Option | Type | Required | Description | +| ------------- | ------- | -------- | ----------------------------------------------------- | +| `either` | object | Yes | Pairs of variants. Each pair has its own winner. | +| `ignoreCase` | boolean | No | Match without regard to case. Default `false`. | +| `includeCode` | boolean | No | Also match inside inline code spans. Default `false`. | Message placeholders: the later variant, then the variant that appeared first.