diff --git a/.github/vale-problem-matcher.json b/.github/vale-problem-matcher.json deleted file mode 100644 index bedf0e0b3e3da..0000000000000 --- a/.github/vale-problem-matcher.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "problemMatcher": [ - { - "owner": "vale", - "severity": "warning", - "pattern": [ - { - "regexp": "^(.+):(\\d+):(\\d+):([^:]+):(.+)$", - "file": 1, - "line": 2, - "column": 3, - "code": 4, - "message": 5 - } - ] - } - ] -} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 321c647bdf61b..4a01e04dcf523 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -162,15 +162,19 @@ jobs: - name: Check docs run: pnpm check-docs - # Vale prose linter, advisory only. Scoped to changed Markdown under - # docs/. Every Vale step is `continue-on-error` so this section can - # never block the required `lint-docs` job: a `vale sync` network - # blip or a baseline rule violation surfaces as an annotation, not a - # merge gate. Only markdownlint/table-formatter above stay blocking. - # `vale --no-exit` additionally keeps the baseline error count from - # un-overridden upstream Google rules from failing the step. Lives - # here rather than a standalone workflow so docs lint stays in the - # single required CI umbrella (see #25608). See DOCS-40. + # Vale prose linter. Scoped to changed Markdown under docs/. The + # surrounding setup steps (cache restore, sync, cache save) stay + # `continue-on-error` so cache or network flakes never block merge; + # only the prose lint step itself is allowed to fail the job. + # Severity model: error-level Vale findings exit non-zero and fail + # the step, which fails the required `lint-docs` job and blocks + # merge. Warning- and suggestion-level findings render as PR + # annotations but do not affect the exit code. This matches Vale's + # native exit semantics (only errors trigger non-zero exit) and the + # repo's style-guide doctrine that any rule we enable ships with + # zero baseline noise. Lives here rather than a standalone workflow + # so docs lint stays in the single required CI umbrella (see + # #25608). See DOCS-40, DOCS-425, DOCS-426. - name: Detect changed Markdown id: changed-md continue-on-error: true @@ -216,16 +220,18 @@ jobs: - name: Vale prose lint if: steps.changed-md.outputs.any_changed == 'true' - continue-on-error: true env: ALL_CHANGED_FILES: ${{ steps.changed-md.outputs.all_changed_files }} # Non-interactive: let mise auto-install the pinned Vale on first use. MISE_YES: "1" run: | + # pipefail propagates Vale's exit code through the jq pipe so + # an error-level finding fails the step. Without it, jq's + # success would mask Vale's non-zero exit. + set -o pipefail # all_changed_files is ACMRD and so lists paths this PR deleted. - # Vale errors on a missing file (--no-exit only suppresses alert - # exits, not runtime errors), so keep only docs/ paths still on - # disk. See DOCS-40. + # Vale errors on a missing file, so keep only docs/ paths still + # on disk. See DOCS-40. files=$(printf '%s\n' "$ALL_CHANGED_FILES" \ | tr ',' '\n' \ | grep -E '^docs/' \ @@ -234,9 +240,29 @@ jobs: echo "No changed Markdown files under docs/ on disk; skipping Vale." exit 0 fi - echo "::add-matcher::.github/vale-problem-matcher.json" - printf '%s\n' "$files" | xargs -d '\n' mise exec "aqua:errata-ai/vale" -- vale --no-exit --output=line - echo "::remove-matcher owner=vale::" + # Vale's --output=line strips per-finding severity, so the + # previous problem-matcher approach collapsed every finding to + # a single hard-coded severity. Use --output=JSON instead and + # emit GitHub workflow commands directly so error/warning/ + # suggestion render with their actual Vale severities. URL- + # encode message bodies for `%`, `\r`, and `\n` per the + # GitHub Actions workflow command spec. See DOCS-426. + # Vale exits non-zero only on error-level alerts (warnings and + # suggestions never trigger non-zero exit), so dropping + # --no-exit lets error-level findings fail the step while + # leaving lower-severity findings advisory. See DOCS-425. + printf '%s\n' "$files" \ + | xargs -d '\n' mise exec "aqua:errata-ai/vale" -- vale --output=JSON \ + | jq -r ' + to_entries[] + | .key as $file + | .value[] + | (if .Severity == "suggestion" then "notice" + elif .Severity == "warning" then "warning" + else "error" end) as $level + | (.Message | gsub("%"; "%25") | gsub("\r"; "%0D") | gsub("\n"; "%0A")) as $msg + | "::\($level) file=\($file),line=\(.Line),col=\(.Span[0]),title=\(.Check)::\($msg)" + ' - name: Save Vale styles # Only the default branch is trusted to write the cache, so PR diff --git a/docs/.style/_vale-annotation-demo.md b/docs/.style/_vale-annotation-demo.md new file mode 100644 index 0000000000000..facbf2487eab7 --- /dev/null +++ b/docs/.style/_vale-annotation-demo.md @@ -0,0 +1,25 @@ +# Vale annotation rendering demo + +> [!NOTE] +> This page exists only to verify how GitHub renders Vale annotations +> at each severity level. +> The three `Coder.Demo*` rules under +> [`docs/.style/styles/Coder/`](styles/Coder/) fire on the marker +> strings below. +> This page and its rules disappear in a follow-up commit on the same +> PR once the rendering check completes; see DOCS-426. + +## Markers + +Each marker fires exactly one Vale annotation when this page lints: + +- Suggestion (rendered as GitHub `notice`): + vale-demo-suggestion-marker. +- Warning (rendered as GitHub `warning`): + vale-demo-warning-marker. +- Error (rendered as GitHub `error`): + vale-demo-error-marker. + +The rules use Vale's `existence` extension type and target a single +literal token each, so each marker produces a single annotation at its +exact location. diff --git a/docs/.style/styles/Coder/DemoError.yml b/docs/.style/styles/Coder/DemoError.yml new file mode 100644 index 0000000000000..366736701a5fa --- /dev/null +++ b/docs/.style/styles/Coder/DemoError.yml @@ -0,0 +1,11 @@ +# DemoError - canary rule used to verify how GitHub renders error-level +# Vale annotations in PR diffs. Fires on the literal phrase +# `vale-demo-error-marker`, which appears only in +# docs/.style/_vale-annotation-demo.md. Demo rules drop in a follow-up +# commit before the parent PR merges; see DOCS-426. +extends: existence +message: "[Demo] Error-level Vale annotation." +link: https://github.com/coder/coder/blob/main/docs/.style/_vale-annotation-demo.md +level: error +tokens: + - vale-demo-error-marker diff --git a/docs/.style/styles/Coder/DemoSuggestion.yml b/docs/.style/styles/Coder/DemoSuggestion.yml new file mode 100644 index 0000000000000..dd6dda6a34220 --- /dev/null +++ b/docs/.style/styles/Coder/DemoSuggestion.yml @@ -0,0 +1,11 @@ +# DemoSuggestion - canary rule used to verify how GitHub renders +# suggestion-level Vale annotations in PR diffs. Fires on the literal +# phrase `vale-demo-suggestion-marker`, which appears only in +# docs/.style/_vale-annotation-demo.md. Demo rules drop in a follow-up +# commit before the parent PR merges; see DOCS-426. +extends: existence +message: "[Demo] Suggestion-level Vale annotation." +link: https://github.com/coder/coder/blob/main/docs/.style/_vale-annotation-demo.md +level: suggestion +tokens: + - vale-demo-suggestion-marker diff --git a/docs/.style/styles/Coder/DemoWarning.yml b/docs/.style/styles/Coder/DemoWarning.yml new file mode 100644 index 0000000000000..bd03e1c8db36f --- /dev/null +++ b/docs/.style/styles/Coder/DemoWarning.yml @@ -0,0 +1,11 @@ +# DemoWarning - canary rule used to verify how GitHub renders +# warning-level Vale annotations in PR diffs. Fires on the literal phrase +# `vale-demo-warning-marker`, which appears only in +# docs/.style/_vale-annotation-demo.md. Demo rules drop in a follow-up +# commit before the parent PR merges; see DOCS-426. +extends: existence +message: "[Demo] Warning-level Vale annotation." +link: https://github.com/coder/coder/blob/main/docs/.style/_vale-annotation-demo.md +level: warning +tokens: + - vale-demo-warning-marker