feat(docs): add Coder.BrandNames Vale rule, enforce HashiCorp casing (#25501)

Lands the first concrete rule under the `Coder` style:
`Coder.BrandNames`, a bundled `substitution` rule that enforces
canonical brand casing in prose. HashiCorp is the first entry;
[DOCS-188](https://linear.app/codercom/issue/DOCS-188) extends it with
GitHub, OpenTofu, Kubernetes, Terraform, JetBrains, and VS Code.

## What changes

Four commits, ordered so each is independently valid:

1. **`docs: fix HashiCorp casing in prose and sidebar`**
([06d769dad1](https://github.com/coder/coder/pull/25501/commits/06d769dad179cf85c535b058df3b6bafdc1f9565)).
5 Markdown files plus 2 `docs/manifest.json` entries. Drives the corpus
violation count to zero.
2. **`feat(docs/.style/styles/Coder): add Coder.BrandNames Vale rule`**
([e00fc780a7](https://github.com/coder/coder/pull/25501/commits/e00fc780a7a20dcf82105d997af5cfcddd4b1855)).
New `BrandNames.yml` with the HashiCorp swap at `level: error`, plus a
new `### Brand names` subsection in `docs/.style/style-guide.md`.
3. **`docs(.style/styles/Coder/README.md): scrub planned-rules notes
obsoleted by Coder.BrandNames`**
([af8833b9f5](https://github.com/coder/coder/pull/25501/commits/af8833b9f58dd617732ae533bbf53eb4fc2e816a)).
Removes the README's "intentionally empty for now" lead-in and the
obsolete HashiCorp casing bullet from the planned-coverage list.
4. **`docs: apply semantic line breaks and fix Vale findings on
PR-touched files`**
([e9f11df188](https://github.com/coder/coder/pull/25501/commits/e9f11df1886fdf0d5efc5e8a2cab95fecbd898f9)).
Pre-review pass on every Markdown file this PR modifies. Full sembr and
Vale-warning cleanup on the style-guide infrastructure
(`style-guide.md`, `Coder/README.md`); sembr applied to the HashiCorp
swap paragraph only on the five product docs, per scoping discussion
with @nickvigilante.

## Severity rationale

`error` from day one. HashiCorp's brand owner publishes a canonical
casing; any other casing in prose is wrong, not a judgment call. Matches
the `error = low FPs x high gravity` framework. False-positive rate is
effectively zero because Vale's `substitution` rule skips inline code,
fenced code blocks, and URLs by default, so `hashicorp/kubernetes`
(Terraform provider source) and `developer.hashicorp.com` stay
untouched.

## Verification

- `make lint/markdown`: 0 errors across 487 files.
- `make lint/prose`: 1 error, 1 warning, 1 suggestion in 468 files. All
three findings are the intentional `Coder.DemoError`,
`Coder.DemoWarning`, and `Coder.DemoSuggestion` annotations on
`docs/.style/style-guide/demo/demo.md` (added on main as part of the
[DOCS-425](https://linear.app/codercom/issue/DOCS-425) inline-annotation
demo), not real findings. `Coder.BrandNames` fires zero times against
the cleaned-up corpus.
- `make pre-commit-light`: passed (7s).
- Self-test: ran the rule against an unmodified `docs/` and confirmed it
flags the 7 prose instances the cleanup commit fixes, then re-ran
against the post-cleanup state and confirmed zero alerts.

## Known future conflict

When [#26632](https://github.com/coder/coder/pull/26632)
([DOCS-434](https://linear.app/codercom/issue/DOCS-434)) merges, the
monolithic `docs/.style/style-guide.md` is split into the
`docs/.style/style-guide/` multi-page structure. The `### Brand names`
subsection added in commit 2 will need to land in
`docs/.style/style-guide/word-choice.md` (which already references the
rule), and the `link:` in `docs/.style/styles/Coder/BrandNames.yml` will
need to update from `style-guide.md#brand-names` to
`style-guide/word-choice.md#brand-names`. Resolution path documented in
an inline comment on this PR.

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

### Why bundle into Coder.BrandNames rather than one file per brand

Vale's convention (mirrored by `Google.WordList` with ~70 swaps in a
single file) is to bundle `substitution` rules when they share severity,
message template, and link. All brand-name rules share that shape:
`error`, `Use '%s' instead of '%s'`, link to the style guide section.
Bundling reduces "add a brand" to a one-line YAML diff and keeps
`CODEOWNERS` and blame coherent. Per-rule performance is irrelevant at
this scale; Vale's per-rule overhead is sub-millisecond and dwarfed by
Markdown parsing.

### Why the cleanup lands first

Commits are ordered cleanup-then-rule so each commit is a known-good
state:

- After commit 1: corpus is HashiCorp-clean, but no rule exists yet.
- After commit 2: rule exists and lints a clean corpus.

Reversing the order would land the rule at commit 1 (firing 7 errors on
uncleaned content) and resolve them at commit 2. Under `--no-exit` the
CI job still passes, but the inline annotations on commit 1 would be
misleading.

### Why HashiCorp first instead of all brands at once

Proof-of-concept value. HashiCorp is the smallest cleanup (7 prose lines
plus 2 sidebar lines = 9 lines), zero FPs, zero ambiguity. Once the loop
(rule plus cleanup plus style-guide section) is proven,
[DOCS-188](https://linear.app/codercom/issue/DOCS-188) appends the other
brands as additional commits to the same bundle.

### Brand-token sensitivity

The `swap:` table only matches:

- `Hashicorp` (capital H, lowercase rest), the actual wrong form in the
corpus.
- `HASHICORP` (all caps), defensive; doesn't appear in current corpus
but cheap to include.

`hashicorp` (all lowercase) is **not** in the swap table. The lowercase
form appears 49 times in URLs (`developer.hashicorp.com`,
`registry.terraform.io/providers/hashicorp/...`,
`github.com/hashicorp/...`) and 6 times as Terraform provider sources
(`source = "hashicorp/kubernetes"`), all of which are correct lowercase
by convention. Vale's substitution rule scope ensures URLs and code
blocks are skipped, but skipping the rule entirely for `hashicorp`
(lowercase) is the explicit decision; if a prose typo of lowercase
"hashicorp" ever shows up, we'd catch it through `Vale.Spelling`
([DOCS-187](https://linear.app/codercom/issue/DOCS-187)) instead.

### Self-reference in the style guide

The `### Brand names` section's example table needed `Hashicorp` and
`HashiCorp` as literal demonstration tokens. Wrapping them in backticks
(`` `Hashicorp` ``, `` `HashiCorp` ``) keeps Vale from flagging the
wrong-case example as a real violation. This is correct typography too:
demonstration tokens get code formatting.

### Manifest.json

Vale doesn't lint JSON, so the two `docs/manifest.json` entries are
fixed by direct edit rather than tool enforcement. The sidebar `path`
(`./admin/integrations/vault.md`) is unchanged; the title change does
not affect the page URL on coder.com. No redirect needed in
`coder/coder.com:redirects.json`.

### Pre-mortem

- **Generated docs noise**: `Coder.BrandNames` does not fire on
auto-generated `docs/reference/` content because no codersdk identifier
matches the swap pattern. Zero risk.
- **Future-additions friction**: adding GitHub to the swap table is one
YAML line and a cleanup commit. The bundling shape pays off here.
- **Disable footgun**: if a contributor needs to write the wrong casing
on purpose (quoting an external bug report verbatim, for example), they
can wrap the literal in backticks (already correct typography) or use
the per-line Vale skip comment.

</details>

Closes [DOCS-34](https://linear.app/codercom/issue/DOCS-34).

---

*Filed via [Coder Agents](https://coder.com/docs/ai-coder/agents) on
Nick's behalf.*
This commit is contained in:
Nick Vigilante
2026-07-08 11:43:29 -04:00
committed by GitHub
parent 1cc230b43a
commit f7632451f4
8 changed files with 49 additions and 32 deletions
+28
View File
@@ -0,0 +1,28 @@
# Coder.BrandNames - enforce canonical brand-name casing in prose.
#
# Vale's substitution rule applies in prose only; it skips fenced code
# blocks, inline code, and URLs by default. That means `hashicorp/kubernetes`
# (Terraform provider source) and `developer.hashicorp.com` (URL) stay
# untouched. The rule fires on body text and headings where the wrong
# casing appears as a normal word.
#
# Level: error. Each swap targets a brand whose owner publishes a
# canonical casing; "Hashicorp Vault" instead of "HashiCorp Vault" is
# objectively wrong, not a judgment call. The existing-content violation
# count is zero (cleanup landed in the previous commit), so this rule
# can ship at error from day one.
#
# Adding a brand: append a key/value to the swap table below and audit
# the docs corpus for the wrong-casing variant. Keep the message template
# unchanged; the `%s` token interpolates the matched (wrong) text.
extends: substitution
message: "Use '%s' instead of '%s' (brand-name casing)."
link: https://github.com/coder/coder/blob/main/docs/.style/style-guide.md#brand-names
level: error
ignorecase: false
nonword: false
action:
name: replace
swap:
Hashicorp: HashiCorp
HASHICORP: HashiCorp
+9 -10
View File
@@ -1,15 +1,15 @@
# 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`.
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:
Active rules ship as YAML files in this directory.
See the matching sections in `docs/.style/style-guide.md` for the user-facing policy each rule enforces.
Follow-up PRs add rules incrementally.
Planned coverage:
- Dev Container terminology
- HashiCorp casing
- Limit "we"
- Limit `we`
- Setup vs set up, Quickstart casing
- Next steps vs Learn more
- Vale substitution rule scaffold
@@ -28,9 +28,8 @@ incrementally. Planned starter rules:
- 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.
4. A follow-up PR adds 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
@@ -94,8 +94,7 @@ Alternatively, you can authenticate with remote clusters with ServiceAccount
tokens. Coder can store these secrets on your behalf with
[managed Terraform variables](../templates/extending-templates/variables.md).
Alternatively, these could also be fetched from Kubernetes secrets or even
[Hashicorp Vault](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/data-sources/generic_secret).
Alternatively, these could also be fetched from Kubernetes secrets or even [HashiCorp Vault](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/data-sources/generic_secret).
This guide assumes you have a `coder-workspaces` namespace on your remote
cluster. Change the namespace accordingly.
+1 -2
View File
@@ -11,8 +11,7 @@ Coder deployments support any custom Terraform binary, including
[OpenTofu](https://opentofu.org/docs/) - an open source alternative to
Terraform.
You can read more about OpenTofu and Hashicorp's licensing in our
[blog post](https://coder.com/blog/hashicorp-license) on the Terraform licensing changes.
You can read more about OpenTofu and HashiCorp's licensing in our [blog post](https://coder.com/blog/hashicorp-license) on the Terraform licensing changes.
## Using a custom Terraform binary
@@ -106,13 +106,9 @@ Passing template tags in from other data sources or resources is not permitted.
### HCL syntax
When importing the template version with `coder_workspace_tags`, the Coder
provisioner server extracts raw partial queries for each workspace tag and
stores them in the database. During workspace build time, the Coder server uses
the [Hashicorp HCL library](https://github.com/hashicorp/hcl) to evaluate these
raw queries on-the-fly without processing the entire Terraform template. This
evaluation is simpler but also limited in terms of available functions,
variables, and references to other resources.
When importing the template version with `coder_workspace_tags`, the Coder provisioner server extracts raw partial queries for each workspace tag and stores them in the database.
During workspace build time, the Coder server uses the [HashiCorp HCL library](https://github.com/hashicorp/hcl) to evaluate these raw queries on-the-fly without processing the entire Terraform template.
This evaluation is simpler but also limited in terms of available functions, variables, and references to other resources.
#### Supported syntax
+3 -7
View File
@@ -11,13 +11,9 @@ Terraform.
## Learn the concepts
While templates are written in standard Terraform, it's important to learn the
Coder-specific concepts behind templates. The best way to learn the concepts is
by
[creating a basic template from scratch](../../tutorials/template-from-scratch.md).
If you are unfamiliar with Terraform, see
[Hashicorp's Tutorials](https://developer.hashicorp.com/terraform/tutorials) for
common cloud providers.
While templates are written in standard Terraform, it's important to learn the Coder-specific concepts behind templates.
The best way to learn the concepts is by [creating a basic template from scratch](../../tutorials/template-from-scratch.md).
If you are unfamiliar with Terraform, see [HashiCorp's Tutorials](https://developer.hashicorp.com/terraform/tutorials) for common cloud providers.
## Create a template
+2 -2
View File
@@ -833,8 +833,8 @@
"path": "./admin/integrations/dx-data-cloud.md"
},
{
"title": "Hashicorp Vault",
"description": "Integrate Coder with Hashicorp Vault",
"title": "HashiCorp Vault",
"description": "Integrate Coder with HashiCorp Vault",
"path": "./admin/integrations/vault.md"
},
{
+2 -2
View File
@@ -28,8 +28,8 @@ server essentially runs a `terraform apply` every time a workspace is created,
started, or stopped.
> [!TIP]
> Haven't written Terraform before? Check out Hashicorp's
> [Getting Started Guides](https://developer.hashicorp.com/terraform/tutorials).
> Haven't written Terraform before?
> Check out HashiCorp's [Getting Started Guides](https://developer.hashicorp.com/terraform/tutorials).
Here's a simplified diagram that shows the main parts of the template we'll
create: