From f7632451f45178c93c5338c800324a929446c861 Mon Sep 17 00:00:00 2001 From: Nick Vigilante Date: Wed, 8 Jul 2026 11:43:29 -0400 Subject: [PATCH] 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.
Implementation plan and decision log ### 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.
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.* --- docs/.style/styles/Coder/BrandNames.yml | 28 +++++++++++++++++++ docs/.style/styles/Coder/README.md | 19 ++++++------- .../integrations/multiple-kube-clusters.md | 3 +- docs/admin/integrations/opentofu.md | 3 +- .../extending-templates/workspace-tags.md | 10 ++----- docs/admin/templates/index.md | 10 ++----- docs/manifest.json | 4 +-- docs/tutorials/template-from-scratch.md | 4 +-- 8 files changed, 49 insertions(+), 32 deletions(-) create mode 100644 docs/.style/styles/Coder/BrandNames.yml diff --git a/docs/.style/styles/Coder/BrandNames.yml b/docs/.style/styles/Coder/BrandNames.yml new file mode 100644 index 0000000000..a5a73f61bb --- /dev/null +++ b/docs/.style/styles/Coder/BrandNames.yml @@ -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 diff --git a/docs/.style/styles/Coder/README.md b/docs/.style/styles/Coder/README.md index 4023b22f53..05d50605b4 100644 --- a/docs/.style/styles/Coder/README.md +++ b/docs/.style/styles/Coder/README.md @@ -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 diff --git a/docs/admin/integrations/multiple-kube-clusters.md b/docs/admin/integrations/multiple-kube-clusters.md index 4040ec3254..32d5ced73b 100644 --- a/docs/admin/integrations/multiple-kube-clusters.md +++ b/docs/admin/integrations/multiple-kube-clusters.md @@ -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. diff --git a/docs/admin/integrations/opentofu.md b/docs/admin/integrations/opentofu.md index b35f105dd0..b078f16af4 100644 --- a/docs/admin/integrations/opentofu.md +++ b/docs/admin/integrations/opentofu.md @@ -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 diff --git a/docs/admin/templates/extending-templates/workspace-tags.md b/docs/admin/templates/extending-templates/workspace-tags.md index 3511601761..f2c5ac874c 100644 --- a/docs/admin/templates/extending-templates/workspace-tags.md +++ b/docs/admin/templates/extending-templates/workspace-tags.md @@ -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 diff --git a/docs/admin/templates/index.md b/docs/admin/templates/index.md index 0aec788a54..36b2cb7d15 100644 --- a/docs/admin/templates/index.md +++ b/docs/admin/templates/index.md @@ -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 diff --git a/docs/manifest.json b/docs/manifest.json index 363601b080..ffa3db6368 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -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" }, { diff --git a/docs/tutorials/template-from-scratch.md b/docs/tutorials/template-from-scratch.md index e30f1d7bfd..9872b7cfb5 100644 --- a/docs/tutorials/template-from-scratch.md +++ b/docs/tutorials/template-from-scratch.md @@ -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: