docs: scaffold docs/.style for the prose style guide (#25466)

Adds a private contributor-tooling directory at `docs/.style/` that will
host the canonical prose style guide and the custom Vale rules used to
enforce it. The directory's contents do not deploy to `coder.com/docs`.

This PR is the scaffold only. The Vale configuration, the rule set, and
the per-rule style-guide sections all land in follow-up PRs.

## What changes

- New `docs/.style/` directory with:
  - `README.md` explaining the convention
  - `style-guide.md` as a table-of-contents scaffold
- `styles/Coder/README.md` placeholder so Git tracks the empty Vale
rules dir
- `.github/workflows/deploy-docs.yaml`: skip the workflow on
`.style`-only pushes, and exclude `.style` paths from the
surgical-reindex git diff on mixed commits. Defense-in-depth on top of
the manifest-driven coder.com routing.
- `.github/.linkspector.yml`: add `docs/.style` to `excludedDirs`
- `AGENTS.md` and `.claude/docs/DOCS_STYLE_GUIDE.md`: cross-link to the
new style guide for agents

## Verification

- `make pre-commit-light` clean (`fmt/markdown`, `lint/markdown`,
`lint/typos`, `lint/emdash`, `lint/actions/actionlint`,
`lint/shellcheck`).
- `markdown-table-formatter --check` and `markdownlint-cli2` both
process the new files (existing globs are `find docs -name '*.md'`).
- `actionlint` clean on the modified workflow.
- coder.com exclusion works because route discovery and Algolia indexing
are manifest-driven; this directory is not in `docs/manifest.json`. The
workflow changes are defense in depth.

<details>
<summary>Implementation plan and decision log</summary>

### Decisions

- **Location**: `docs/.style/` (leading dot, mirrors `.github/`,
`.vscode/`, `.claude/`). Vale's `StylesPath` will be
`docs/.style/styles/`; `.vale.ini` lands at repo root in a follow-up.
- **Existing public page `docs/about/contributing/documentation.md`**:
untouched in this PR. Nick's separate information-architecture rework
will redirect it to GitHub at the right time.
- **Placeholder for empty `styles/Coder/`**: real `README.md`, not
`.gitkeep`. Discoverable on GitHub, lints with the existing tooling,
lists the planned starter rules.
- **CONTRIBUTING.md**: not touched. It's a 2-line redirect to
`coder.com/docs/CONTRIBUTING`; bloating it would defeat the redirect.
- **`.claude/docs/DOCS_STYLE_GUIDE.md`**: kept as the structure/research
companion. A blockquote at the top points at the new canonical prose
guide.

### coder.com exclusion mechanism (verified by inspection)

Direct inspection of `coder/coder.com`:

- Route discovery in
[`src/utils/docs/docs.ts`](https://github.com/coder/coder.com/blob/master/src/utils/docs/docs.ts)
iterates `routes` from `docs/manifest.json`. Files not in the manifest
never become routes.
- The Algolia surgical indexer at
[`src/utils/algoliaDocs/surgical.ts`](https://github.com/coder/coder.com/blob/master/src/utils/algoliaDocs/surgical.ts)
explicitly skips paths not in the manifest, incrementing `pathsSkipped`.

Net result: not adding anything from `docs/.style/` to `manifest.json`
is the only thing that has to be true for the exclusion to work. The
`deploy-docs.yaml` tweaks are defense in depth.

### deploy-docs.yaml changes (pre-mortem)

1. Trigger path negation `!docs/.style/**` skips the workflow on
`.style`-only pushes. GitHub Actions only suppresses when every changed
file matches a negation, so mixed commits still trigger.
2. The git-diff pathspec `:(exclude)docs/.style/**` drops `.style` paths
from the surgical-reindex payload on mixed commits.

Risks considered:

- **Test contract**: `.github/workflows/test-deploy-docs-diff.sh` only
exercises the downstream awk parser, not the git-diff invocation. The
exclusion happens at git-diff time; the parser sees the same
`<status>\0<path>\0` format. No test change needed.
- **First push to a brand-new branch**: the workflow falls back to
whole-branch reindex when `BEFORE_SHA` is all zeros. Whole-branch
reindex re-extracts records from the manifest, which still excludes
`.style` files because they are not in the manifest.
- **Workflow-dispatch**: takes the whole-branch path; same reasoning.
Safe.

### Why a real README in `styles/Coder/` instead of `.gitkeep`

It explains intent, lists the upcoming rules, and lints with the
existing tooling. The cost is one extra Markdown file; the upside is
that a contributor browsing GitHub sees the plan without clicking
around.

</details>

---

*Filed via [Coder Agents](https://coder.com/docs/ai-coder/agents) on
Nick's behalf.*


Linear: DOCS-180
This commit is contained in:
Nick Vigilante
2026-06-17 13:19:37 +00:00
committed by GitHub
parent 8d725969bf
commit 182bdc871a
8 changed files with 217 additions and 15 deletions
+9 -3
View File
@@ -1,13 +1,19 @@
# Documentation Style Guide
This guide documents prose, structure, and formatting patterns for documentation files in the `docs/` directory. It complements, and does not replace, the canonical content rules.
This guide documents structure, research, and content patterns for documentation files in the `docs/` directory. It complements, and does not replace, the canonical content rules or the prose style guide.
> [!IMPORTANT]
> **What belongs in the docs (and what doesn't)** is governed by
> [`docs/.style/content-guidelines.md`](../../docs/.style/content-guidelines.md).
> Read that first. When this style guide conflicts with the content
> guidelines, the content guidelines govern. This file covers prose,
> formatting, and structural conventions only.
> guidelines, the content guidelines govern.
>
> **For prose rules**, the canonical Coder documentation style guide lives
> at [`docs/.style/style-guide.md`](../../docs/.style/style-guide.md) and
> will be enforced by the Vale rules under `docs/.style/styles/Coder/`.
> That guide is currently a scaffold; continue using the **Writing Style**
> section below until it is populated. This file also remains authoritative
> for structure, research, and content patterns.
See [CONTRIBUTING.md](../../docs/about/contributing/CONTRIBUTING.md) for general contribution guidelines.
+3
View File
@@ -6,6 +6,9 @@ excludedDirs:
- docs/reference
# Older changelogs may contain broken links
- docs/changelogs
# Contributor-facing style guide and Vale config. Not deployed to
# coder.com/docs; chasing external links here is overkill.
- docs/.style
ignorePatterns:
- pattern: "localhost"
- pattern: "example.com"
+12 -1
View File
@@ -45,7 +45,14 @@ on:
# Intentionally only docs/**. Edits to this workflow file must not
# auto-trigger a production reindex; use workflow_dispatch instead.
# See DOCS-121 (incident) and DOCS-124 (fix).
#
# docs/.style/** is contributor tooling and never deploys to
# coder.com/docs. Negating it here skips the workflow on .style-only
# commits. GitHub Actions only suppresses when every changed file
# matches a negation, so mixed commits still trigger; the surgical
# diff step below drops .style paths from the payload.
- "docs/**"
- "!docs/.style/**"
release:
# Fires when a draft release is published, when a release goes from
# prerelease to non-prerelease, or when a release is created already
@@ -174,7 +181,11 @@ jobs:
# + save) from deleted/renamed-old-side (delete only), and
# so paths containing whitespace or quotes survive intact.
DIFF_FILE=$(mktemp)
git diff --name-status -z "$BEFORE_SHA" "$AFTER_SHA" -- 'docs/**/*.md' > "$DIFF_FILE"
# 'docs/**/*.md' to keep markdown-only paths, ':(exclude)docs/.style/**'
# so contributor-tooling pages never reach the surgical-reindex payload
# on mixed commits. The trigger filter already short-circuits .style-only
# pushes; this is defense in depth.
git diff --name-status -z "$BEFORE_SHA" "$AFTER_SHA" -- 'docs/**/*.md' ':(exclude)docs/.style/**' > "$DIFF_FILE"
# Parse the NUL-delimited diff into <path>\t<status> lines.
# `--name-status -z` uses NUL between fields and between
# records, with a special twist for renames: the record is
+16 -3
View File
@@ -24,6 +24,12 @@ on:
- reopened
paths:
- "docs/**"
# docs/.style/** is contributor tooling and never deploys to coder.com.
# Skipping the workflow on .style-only PRs avoids posting a preview
# link that 404s, since manifest-driven coder.com routing rejects
# paths under .style. Mixed PRs still trigger; the selection logic
# below filters .style files out of the preview-target pick.
- "!docs/.style/**"
concurrency:
group: docs-preview-${{ github.event.pull_request.number }}
@@ -69,11 +75,18 @@ jobs:
"repos/${REPO}/pulls/${PR_NUMBER}/files" \
--jq '.[] | select(.status != "removed") | .filename')
# Pick the first Markdown file under docs/. `|| true` keeps
# the pipeline from failing when grep finds no matches or
# head triggers SIGPIPE under `set -o pipefail`.
# Pick the first Markdown file under docs/, excluding the
# contributor-tooling subtree at docs/.style/**. Mixed PRs that
# touch both .style and a public docs page should land the
# preview link on the public page; .style-only PRs already get
# short-circuited by the trigger filter above and never reach
# this code path.
#
# `|| true` keeps the pipeline from failing when grep finds no
# matches or head triggers SIGPIPE under `set -o pipefail`.
first_doc=$(printf '%s\n' "$all_files" \
| grep -E '^docs/.*\.md$' \
| grep -v '^docs/\.style/' \
| head -n 1) || true
if [ -z "$first_doc" ]; then
+1
View File
@@ -12,6 +12,7 @@ Rule #1: If you want exception to ANY rule, YOU MUST STOP and get explicit permi
- Docs content scope: Use [Coder Docs Content Guidelines](docs/.style/content-guidelines.md) to decide whether a piece of content belongs in `docs/` at all. The Documentation Style Guide above covers prose and formatting; the content guidelines govern scope and routing and supersede the style guide on conflicts.
- Compatibility: `.agents/docs` symlinks to `.claude/docs` for agent runtimes that look there.
- Frontend: Read [Frontend Development Guidelines](site/AGENTS.md) before changing anything under `site/`.
- Docs prose: When editing anything under `docs/`, see the prose style guide at [`docs/.style/style-guide.md`](docs/.style/style-guide.md). It is currently a scaffold; until it is populated, use the **Writing Style** section in [`.claude/docs/DOCS_STYLE_GUIDE.md`](.claude/docs/DOCS_STYLE_GUIDE.md). That file also covers structure, research, and content patterns.
## Foundational rules
+33 -8
View File
@@ -9,22 +9,24 @@ Nothing under this directory is published to
| Path | Purpose |
|-------------------------|---------------------------------------------------------------------|
| `content-guidelines.md` | Canonical content rules: what belongs in `docs/`, what doesn't, why |
| `style-guide.md` | Canonical prose style guide for `docs/` |
| `styles/Coder/` | Custom Vale rules specific to Coder (product voice, terms) |
See [`content-guidelines.md`](content-guidelines.md) for the canonical
rules on what content belongs in `docs/` and what should be routed
elsewhere (blog, changelog, Support KB, etc.).
> [!NOTE]
> This directory is the home for the docs scaffold being built out under
> DOCS-180. The prose style guide and the Vale rules that enforce it land
> in that work and will appear in this table when they merge.
See [`style-guide.md`](style-guide.md) for the prose style guide. The
`styles/Coder/` directory holds the custom Vale rules that enforce parts
of the guide; Vale's `StylesPath` in the repo-root `.vale.ini` (added in
a follow-up PR) points at `docs/.style/styles/`.
## Why a hidden directory
The leading dot mirrors the `.github/`, `.vscode/`, and `.claude/`
convention already used in this repo for tooling-internal directories.
The structural Markdown linters still pick it up; coder.com's docs site
does not.
Vale and the structural Markdown linters still pick it up; coder.com's
docs site does not.
## How exclusion from coder.com works
@@ -38,10 +40,14 @@ manifest-driven:
do not become routes.
- The Algolia surgical indexer at
[`coder/coder.com:src/utils/algoliaDocs/surgical.ts`](https://github.com/coder/coder.com/blob/master/src/utils/algoliaDocs/surgical.ts)
explicitly skips paths that are not in the manifest.
explicitly skips paths that are not in the manifest, incrementing
`pathsSkipped`.
Net result: not adding anything from `docs/.style/` to `docs/manifest.json`
gives us no route, no Algolia record, and no sidebar entry.
gives us no route, no Algolia record, and no sidebar entry. Two
defense-in-depth changes in `.github/workflows/deploy-docs.yaml` keep the
deploy workflow from running on `.style`-only commits and exclude the
directory from the surgical-reindex payload on mixed commits.
## What still runs against this directory
@@ -50,9 +56,28 @@ gives us no route, no Algolia record, and no sidebar entry.
`markdownlint-cli2 --fix $(find docs -name '*.md')`.
- `make fmt/markdown` (markdown-table-formatter) reflows tables here for
the same reason.
- Vale, once `.vale.ini` lands in a follow-up PR, lints the entire
`docs/**/*.md` set including `docs/.style/style-guide.md`.
## What does not run against this directory
- `linkspector`: excluded via `excludedDirs` in `.github/.linkspector.yml`.
External-link checking is overkill for contributor tooling.
- The `deploy-docs` workflow: its `paths:` filter negates `docs/.style/**`,
and the surgical-reindex git-diff invocation excludes the same path. See
`.github/workflows/deploy-docs.yaml`.
- The `docs-preview` workflow: its `paths:` filter negates `docs/.style/**`,
so `.style`-only PRs produce no preview comment. The selection logic also
skips `.style` files when picking the preview target on mixed PRs. See
`.github/workflows/docs-preview.yaml`.
## Editing the content guidelines
Open a PR against `docs/.style/content-guidelines.md`. The rules in that
file apply to humans and AI-assisted workflows alike; when it conflicts
with another style or contributing doc in the repo, it governs.
## Editing the style guide
Open a PR against `docs/.style/style-guide.md`. Follow-up PRs add each
rule and the matching style-guide section together.
+103
View File
@@ -0,0 +1,103 @@
# Coder documentation style guide
This is the canonical style guide for the Coder documentation. It is the
source of truth that the Vale rules in `docs/.style/styles/Coder/` enforce.
Status: scaffold. Sections below are populated by follow-up PRs; this
page starts as a table of contents and grows as those PRs land.
## How to use this guide
This page is a scaffold while follow-up PRs land. Sections marked "To be
filled in" are placeholders. For anything not yet covered, see the
public summary at
[`docs/about/contributing/documentation.md`](../about/contributing/documentation.md).
- **Contributors**: read the section that matches what you are writing.
Each rule notes the Vale rule ID, if any, so you can reproduce the
warning locally.
- **Reviewers**: cite the section in a review comment. Reviews are easier
when the guidance is in one place.
- **AI agents**: read this page in full before editing anything under
`docs/`. The Coder Agents and Claude Code guides
([`AGENTS.md`](../../AGENTS.md),
[`.claude/docs/DOCS_STYLE_GUIDE.md`](../../.claude/docs/DOCS_STYLE_GUIDE.md))
link here.
## Voice and tone
To be filled in by follow-up PRs. Planned coverage:
- Active voice
- Second person
- Plural nouns and pronouns where number is uncertain
- Product voice (`stop` over `kill`, `turn off` over `disable` in
user-facing copy)
- Limiting "we"
## Word choice
To be filled in by follow-up PRs. Planned coverage:
- Inclusive-language substitutions
- HashiCorp casing
- Dev Container terminology
- "Setup" vs "set up" and Quickstart casing
- "Next steps" vs "Learn more"
- Weasel words
## Capitalization and punctuation
To be filled in by follow-up PRs. Planned coverage:
- Sentence case in titles and headings
- General capitalization policy
- Em-dash and en-dash ban (use comma, semicolon, or period)
## Formatting
To be filled in by follow-up PRs. Planned coverage:
- Bold for UI elements
- Italics for parameter names and version variables
- Code font for user input, command-line utility names, filenames,
environment variables, HTTP verbs and status codes, placeholder
variables
- Code blocks with explicit language fences
## Vale enforcement
The repo-root `.vale.ini` configures Vale to read styles from
`docs/.style/styles/`. The starter configuration combines:
- Google's developer-docs base style
- A curated subset of `alex` (inclusive-language)
- A curated subset of `write-good` (wordiness)
- Coder-specific custom rules in `docs/.style/styles/Coder/`
The rationale for the cherry-picked base styles and the severity
policy lives in the follow-up PR that lands `.vale.ini`.
## Editor setup
To be filled in by a follow-up PR. Will cover VS Code, Cursor,
JetBrains, and Neovim.
## Relationship to `docs/about/contributing/documentation.md`
A public-facing prose summary lives today at
[`docs/about/contributing/documentation.md`](../about/contributing/documentation.md).
A follow-up PR will redirect that page to this guide; until then,
follow the public summary for anything the scaffolded sections above do
not yet cover. New prose rules land here; the public page is frozen
pending the redirect.
## Third-party references
When this guide does not cover something, consult:
| Type of guidance | Reference |
|---------------------|-----------------------------------------------------------------------------------------|
| Spelling | [Merriam-Webster](https://www.merriam-webster.com/) |
| Style, nontechnical | [The Chicago Manual of Style](https://www.chicagomanualofstyle.org/home.html) |
| Style, technical | [Microsoft Writing Style Guide](https://learn.microsoft.com/en-us/style-guide/welcome/) |
+40
View File
@@ -0,0 +1,40 @@
# Coder custom Vale rules
Custom Vale rules specific to Coder live here. Each rule is a YAML file
that Vale loads through the `BasedOnStyles = Coder` setting in the
repo-root `.vale.ini`.
This directory is intentionally empty for now. Follow-up PRs add rules
incrementally. Planned starter rules:
- Dev Container terminology
- HashiCorp casing
- Limit "we"
- Setup vs set up, Quickstart casing
- Next steps vs Learn more
- Vale substitution rule scaffold
- Weasel words
- Em-dash and en-dash mirror in Vale
- Inclusive-language substitutions
- Product-voice rules
## Authoring a new rule
1. Write a YAML file under this directory. Name it after the rule's
intent, for example `InclusiveLanguage.yml` or `ProductVoice.yml`.
2. Each rule's `message:` should link to the matching section in
`docs/.style/style-guide.md`, ideally with a deep-link anchor, so a
contributor reading a Vale warning can jump straight to the guidance.
3. Land at `level: warning` first. Promote to `level: error` only after
both conditions hold:
- The rule is objectively correct (typo, brand-name casing, banned
substitution).
- The existing-content violation count for the rule reaches zero.
4. A follow-up PR will add a parity CI check that verifies every rule
here has a matching section in `style-guide.md`. Add the section in
the same PR as the rule.
## Reference
- Vale docs: <https://vale.sh/docs/>
- Vale rule types: <https://vale.sh/docs/topics/styles/>