feat(docs/.style): enable Coder.GerundHeading - #25502
Merged
nickvigilante merged 11 commits intoJul 23, 2026
Merged
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.
Contributor
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 comment has been minimized.
This comment has been minimized.
Catch up to main's restructured docs/.style/ (style-guide directory, Vale in ci.yaml). Tree matches main; the rule lands in the next commit.
nickvigilante
marked this pull request as ready for review
July 8, 2026 15:50
bpmct
approved these changes
Jul 23, 2026
nickvigilante
enabled auto-merge (squash)
July 23, 2026 17:57
nickvigilante
deleted the
vigilante/docs-191-add-coder-gerundheading-rule
branch
July 23, 2026 20:38
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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, awarning-level Vale rule that flags headings and titles whose first word ends in-ing(a gerund or present participle used as a verb form, likeInstallingorConfiguring).Task headings read better in the imperative (
Install Coder); concept headings read better as nouns (Installation). The choice is context-dependent, so the rule is awarning: it annotates without blocking CI.The style-guide section this rule enforces already lives on
mainatcapitalization-and-punctuation.md#no-gerund-leading-headings. This PR adds the matching rule and nothing else: the net diff is a single file.What's in this PR
docs/.style/styles/Coder/GerundHeading.yml(new). Heading-scopedexistencerule, anchored regex^[A-Z][a-z]+ing\b,level: warning.exceptions:mirror the style guide's Exceptions section:-ingwords that name a feature, category, or attribute (Logging,Monitoring,Networking,Tracing,Troubleshooting,Pricing,Billing, ...) plus words that only look like gerunds (Bring,String, ...).This branch was rebuilt onto
main's restructureddocs/.style/(the singlestyle-guide.mdbecame astyle-guide/directory and Vale moved intoci.yaml), which is why the diff is now just the rule.Scope: rule only
The rule ships as a
warning, so it surfaces the existing-ingtask headings (~200) as advisory annotations rather than failing CI. De-gerunding those headings (imperative rewrites plus internal anchor fixes) is a corpus-wide content change and lands in a dedicated follow-up PR, tracked separately. Splitting keeps this PR to the rule and keeps the content churn reviewable on its own.Decision log
existence+scope: heading, notsequence+tag: VBG. Vale's POS-tagging sequence rules are hardcoded to sentence scope and never reach heading text, so aVBGsequence rule fires on paragraphs and stays silent on H1-H6. Google's and Microsoft's heading rules all use the existence+regex pattern; this rule follows it.Exceptions align to the committed style guide, not the original branch design. The first draft of this rule intentionally left concept-noun gerunds (
Logging,Monitoring, ...) in the flagged set. Since then,main's style guide declared exactly those as non-violations. The rule now excepts them so the rule and the guide agree. An excepted first word is allowed everywhere, which is a deliberate precision trade-off for a first-word regex:Monitoring Coder(a task) is not flagged, but the standalone concept headingMonitoringstays clean.Severity = warning. The imperative-vs-noun choice is judgment-bound, which is the case the
warningtier exists for: strong guidance, legitimate human-judgment exceptions, no CI block.Verification.
make lint/proseloads the rule cleanly; the excepted words (Troubleshooting,Monitoring,Networking,Logging,Contributing,Styling,Scaling,Routing,Pricing,Billing,Tracing) each produce zero findings.Opened via Coder Agents on @nickvigilante's behalf.