feat(docs): add Coder.GerundHeading Vale rule to flag -ing-leading headings#25502
Draft
nickvigilante wants to merge 9 commits into
Draft
feat(docs): add Coder.GerundHeading Vale rule to flag -ing-leading headings#25502nickvigilante wants to merge 9 commits into
nickvigilante wants to merge 9 commits into
Conversation
Adds a private contributor-tooling directory at docs/.style/ that holds: - README.md explaining the convention and the no-deploy guarantee - style-guide.md as a scaffold for the canonical prose style guide - styles/Coder/ as the home for custom Vale rules (filled by follow-ups) Defense-in-depth tweaks to .github/workflows/deploy-docs.yaml exclude docs/.style/** from the push trigger and from the surgical-reindex git diff. coder.com/docs route discovery is already manifest-driven, so nothing under docs/.style/ becomes a route or an Algolia record. Also: - .github/.linkspector.yml: skip external-link checks under docs/.style/ - AGENTS.md: point agents at the new style guide - .claude/docs/DOCS_STYLE_GUIDE.md: cross-link to the canonical prose guide; this file remains the structure/research companion The Vale configuration that consumes docs/.style/styles/ lands in a follow-up PR (DOCS-40). Closes DOCS-180.
Lands the Vale prose linter as a non-blocking docs CI step. Builds on the scaffold from DOCS-180 (#25466): - .vale.ini at the repo root configures Google + curated write-good + cherry-picked alex rules. Disables Google.Spacing (false positives on codersdk type names in the auto-generated API reference), Google.EmDash (conflicts with our em-dash ban), Google.Latin (i.e. and e.g. are fine), write-good.Passive and write-good.E-Prime (judgment-heavy). - mise.toml pins Vale 3.7.1 via aqua. - Makefile adds build/vale-$VERSION install, docs/.style/.vale-synced sentinel that runs 'vale sync' once per .vale.ini change, and a lint/prose target wrapped in '|| true' for v1 non-blocking severity. - .github/workflows/docs-ci.yaml adds a 'prose' step that lints only the changed Markdown files under docs/, with continue-on-error: true and a cache for the synced styles and binary. - .gitignore excludes the synced upstream styles and the sentinel. - .markdownlint-cli2.jsonc ignores the synced styles so local markdownlint runs do not lint upstream READMEs. - docs/.style/README.md and style-guide.md document how to run Vale locally and what the active rule set is. Severity policy (v1): every rule lands at 'warning'. CI is non-blocking through continue-on-error: true. A rule promotes to 'error' only when (a) it is objectively correct and (b) the existing-content violation count reaches zero. Judgment rules stay at 'suggestion'. Local run on the full docs corpus produces 0 blocking failures, 391 errors, 5202 warnings, 7682 suggestions across 458 files in ~20s. Refs DOCS-40.
… filter The 'docs/**.md' glob in tj-actions/changed-files skips dot-prefixed directories by default, so the changed-md-docs filter silently dropped docs/.style/README.md and docs/.style/style-guide.md. The Vale prose step never fired on PR #25467 as a result. Drop the second changed-files step and post-filter the changed-md output in shell. grep '^docs/' keeps only docs paths; grep -v '^docs/.style/styles/' excludes the synced upstream packages. The early exit handles PRs that only touched non-docs markdown. Refs DOCS-40.
Vale's release archive places 'vale' at the archive root with no leading ./ (unlike typos), so 'tar -xzf - ./vale' matched nothing and produced 'tar: ./vale: Not found in archive' on CI. Switch to 'vale'. The local Makefile invocation worked before because build/vale-3.7.1 was already present from the spike artifacts. CI hit the cold path and exposed the bug.
Resolves the 12 findings from review id 4314529409 on PR #25467. DEREM-1 (P2): fix `./build/vale-*/vale` glob in docs/.style/README.md; the binary is the file itself, not a directory. DEREM-2 (P3): drop the inaccurate `make lint` claim in style-guide.md; point readers at README's Running Vale section instead. DEREM-3 (P2): split the prose step into `Prepare Vale styles` (no continue-on-error) and `prose` (no continue-on-error; --no-exit), so the job fails on sync failures but lints non-blocking. DEREM-4 (P2): replace `|| true` with Vale's native --no-exit in the Makefile and rewrite the severity-policy comment to match the measured exit-code semantics (Vale exits non-zero only on error alerts, regardless of MinAlertLevel). DEREM-5 (P2): add `Coder` to BasedOnStyles so the empty starter style is loaded and ready to receive rule files. DEREM-6 (P3): replace hardcoded per-package paths in .gitignore and the workflow cache step with `styles/*` plus a `!styles/Coder` negation, reducing the places future packages need to be listed. DEREM-7 (P3): already addressed by c3e9e2f (prose filter matches docs/.style paths) and 6054d9e (tar layout). DEREM-8 (P3): remove the duplicate Running Vale block in style-guide.md and cross-reference README.md as the single source. DEREM-9 (P3): add .github/vale-problem-matcher.json and wire the prose step with `::add-matcher::`/`--output=line`/`::remove-matcher::` so alerts surface as inline PR annotations. DEREM-10 (P4): trim the Active rule set section in style-guide.md to a policy summary plus a pointer to .vale.ini. DEREM-11 (Nit): rephrase the Makefile sync-sentinel comment to describe Make's behavior instead of suggesting `touch`. DEREM-12 (Nit): swap "(Vale spike, 2026-05-18)" for the more neutral "(measured 2026-05-18)" in the Google.Spacing rationale. Two-layer defense on the cache step protects the hand-authored Coder rules: the negation excludes styles/Coder from the cached paths, and the cache key hashes styles/Coder/** so any rule change invalidates the cache even if the negation behavior ever regresses (actions/toolkit#713, actions/cache#494). Verification: - make lint/actions: clean (zizmor, actionlint). - make fmt/markdown: no changes. - make lint/markdown: 0 errors across 463 files. - make lint/prose: exit 0; baseline 391 errors / 5201 warnings / 7673 suggestions across 458 files (consistent with the pre-review run).
Three follow-ups from review id 4320840969. DEREM-13 (P3): drop the redundant `grep -v '^docs/\.style/styles/'` filter from the prose step. Synced upstream packages are gitignored, so tj-actions/changed-files never lists them; the filter only ever rejects tracked Coder/**.md files, which is the opposite of what we want. The inline comment now documents why no second filter exists. DEREM-14 (P3): fix the README's --no-exit explanation. The DEREM-4 fix chain reached .vale.ini, the Makefile, and style-guide.md but missed docs/.style/README.md. The README now matches the rest: --no-exit suppresses the exit from the baseline error count produced by un-overridden Google error-level rules, not from warnings/suggestions (which never trigger non-zero exit regardless of --no-exit). DEREM-15 (Nit): same model fix in the docs-ci.yaml prose-step comment. Verification: make lint/actions, make fmt/markdown, make lint/markdown, make lint/prose all clean. Vale baseline unchanged (391 errors / 5201 warnings / ~7673 suggestions across 458 files).
DEREM-16 (Nit) from review id 4321386259. `./build/vale-* docs/` is fine when only one vale-X.Y.Z binary exists in build/, but if a developer bumps the version in mise.toml without running `make clean`, multiple binaries coexist and the glob expands to multiple positional args, breaking the command. Low-risk path because three conditions have to align (version bump + no clean + manual invocation), but worth a sentence so a developer who hits it knows to `make clean`.
Adds a warning-level Vale rule that flags headings beginning with an -ing word (typically a gerund or present participle, like 'Installing' or 'Configuring'). The rule uses extends: existence with scope: heading and a regex anchored to the start of the heading text, since Vale's sequence rule type (which would let us condition on the POS tag VBG) is documented as sentence-scoped and does not honor scope: heading. Google and Microsoft style packages both use existence + scope: heading + regex for all their heading-targeted rules; this rule follows the same pattern. A small exceptions list covers words that end in 'ing' but are not gerunds (Bring, String, Spring, King, Ring, Sting, Sing, Thing, Wing). Concept-noun gerunds (Logging, Networking, Monitoring, Troubleshooting) are intentionally NOT in the exceptions list: those headings often read better as imperatives or full nouns and writers should see the warning and decide. Adds a 'Headings' section to docs/.style/style-guide.md with the 'Gerund headings' subsection explaining the rule, examples, and how to silence individual instances. Updates docs/.style/styles/Coder/README.md to list DOCS-191. Closes DOCS-191.
Docs preview📖 View docs preview for |
…-code reason sequence rules cannot reach headings
After a question from a reviewer about whether this rule should
use `extends: sequence` with the VBG POS tag rather than a regex
existence rule, dig into Vale 3.14.1 source and add the
architectural reason to the rule's YAML comment.
Two facts in Vale's source force the existence-based approach for
heading-targeted rules:
1. internal/check/sequence.go:75 ends NewSequence with
`rule.Definition.Scope = []string{"sentence"}`. The Run
method's comment at line 247 reads
"This is *always* sentence-scoped." Any user-supplied
`scope:` on a sequence rule is silently overwritten.
2. internal/lint/ast.go::lintScope dispatches heading content
with scope `text.heading.h2.md` to lintBlock directly,
skipping the lintProse path. Only lintProse calls nlp.Compute,
which is the function that produces `sentence.*`-scoped
sub-blocks. Heading text therefore never appears as a block
whose scope contains `sentence`.
Empirically verified against Vale 3.14.1: an `extends: sequence`
rule with `pattern: '\\w+ing'` + `tag: VBG` + `scope: heading`
fires on paragraph text but is silent on H1-H6.
Behavior of the rule is unchanged. Only the YAML comment is
updated to capture the investigation so the next reviewer doesn't
have to redo it.
Contributor
Author
|
Reopening. The stale bot auto-closed this on 2026-06-07 from inactivity, but the work is still active, tracked in Linear as DOCS-191 (In Review) under the Vale / docs style-guide initiative. This PR is stacked on Reopened by Coder Agents on behalf of @nickvigilante. |
Base automatically changed from
vigilante/docs-40-add-vale-to-docs-ci-with-starter-microsoftgoogle-style-coder
to
main
June 22, 2026 18:14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
Coder.GerundHeading, a warning-level Vale rule that flags headings beginning with an -ing word (typically a gerund or present participle, likeInstallingorConfiguring).Gerund-leading headings are a stylistic anti-pattern: imperative (
Install Coder) reads better for task headings, and the noun form (Installation) reads better for concept headings. The right choice is context-dependent, so the rule lands atwarning, noterror.Closes DOCS-191.
Corpus impact
Bring,String) are correctly skipped by the exception list. The list also seedsSpring,King,Ring,Sting,Sing,Thing,Wingfor future content.make lint/prose). The rule adds 270 warnings.Why existence + scope: heading and not sequence
The first prototype used Vale's
sequencerule withtag: VBG(Penn Treebank gerund/present-participle). The rule fired zero times. The Vale docs explain why: every sequence rule requires at least onepatterntoken as an anchor, and sequence rules are sentence-scoped, not heading-scoped. Soscope: headinghad no effect.Google and Microsoft style packages both use
extends: existence+scope: heading+ regex for every heading-targeted rule (HeadingAcronyms,HeadingColons,HeadingPunctuation, etc.). This rule follows the same pattern:^[A-Z][a-z]+ing\b, anchored to the start of the heading text. The POS tagger is unused, but the regex is precise enough that we measured 100% true positives in the corpus.Workflow disclosure
This is a docs-only change (one Vale rule, one style-guide section, one README ticket-list entry). Per the user's workflow rule, docs-only changes do not trigger
/coder-agents-review. Human review only.Implementation notes and decision log
Decisions
Logging,Networking,Monitoring,Troubleshooting) are legitimate heading leads. Flagging them aserrorwould force rewrites that don't always improve readability.warningsurfaces them for review and lets writers decide.^, which caughtBin## 🛡 Bulletproofingmid-heading. Anchoring to start dropped the corpus count from 320 to 270 and eliminated all mid-heading matches.Bring,String, etc.). Concept-noun gerunds stay in the flagged set on purpose.tag: VBGwould have been more principled, but Vale's sequence rules don't supportscope: heading. The regex approach matches Google/Microsoft conventions.Corpus sample (top 10 by frequency)
Files changed
docs/.style/styles/Coder/GerundHeading.yml(new): the rule.docs/.style/style-guide.md: adds a## Headingssection with a### Gerund headingssubsection (rule ID, examples, exception-list policy, how to silence individual instances).docs/.style/styles/Coder/README.md: adds DOCS-191 to the ticket list.Verification
Baseline before this PR (PR #25467 tip):
Delta: 391 → 391 errors (unchanged, as expected for a warning-level rule), 5201 → 5471 warnings (+270 from the new rule).
Base
Based on PR #25467 (DOCS-40, Vale wiring). Will need to retarget to
mainafter #25467 merges.Filed via Coder Agents on Nick's behalf.