Skip to content

docs: consolidate voice/writing-style/terminology guidance into the brand MCP (supersedes #19777) - #20664

Open
CamSoper wants to merge 9 commits into
masterfrom
claude/rebase-resolve-conflicts-5yzksk
Open

docs: consolidate voice/writing-style/terminology guidance into the brand MCP (supersedes #19777)#20664
CamSoper wants to merge 9 commits into
masterfrom
claude/rebase-resolve-conflicts-5yzksk

Conversation

@CamSoper

@CamSoper CamSoper commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Proposed changes

Rebase of #19777 onto current master (conflicts resolved), extended to cover the brand guide's new Names & terminology section — the canonical product names plus the "Retired and disallowed names" table — and to make the brand MCP the source of truth operationally, not just by declaration.

Same intent as #19777: the Pulumi brand guide (public pulumi-brand MCP server at https://brand.pulumi.com/mcp) is the source of truth for voice, writing style, and now terminology; STYLE-GUIDE.md and the content skills consult it rather than restate it, with a precedence rule (brand guide wins on overlap).

What's here beyond the original #19777:

  1. Vale terminology mirror realigned to the retired-names table. The audit found the mirror not just stale but inverted in one spot: Nomenclature.yml was auto-canonicalizing prose toward "Pulumi Insights" — a retired name. Now:
    • Substitutions.yml (auto-fixable, blocker tier) carries every retired-name row with a single fixed replacement: Pulumi Copilot → Pulumi Neo, Pulumi Insights → Pulumi Discovery, Insights account/scan/supergraph/resources → their Discovery equivalents, self-managed backend → DIY backend.
    • DeprecatedProductNames.yml (flag-only, human picks the replacement) generalizes from "Pulumi Service" to all context-dependent retired names: bare Insights, bare Copilot (excluding GitHub/Microsoft/VS Code Copilot), IAS, Policy Violations. Case-sensitive so generic "insights" never flags.
    • Generated CLI command pages are exempted — the CLI still ships pulumi insights ... commands; terminology fixes there belong upstream in pulumi/pulumi.
    • Every rule message:/link: that cited a STYLE-GUIDE section this PR deletes now cites the owning brand-guide section instead.
    • All tokens validated with vale 3.9.4 against positive and negative fixtures; spot-checked against the live docs tree (real hits in self-hosting/RBAC/integrations pages; no hits on GitHub Copilot references or generic "insights").
  2. Sentence-case enforcement extended to H1s — front-matter titles included. HeadingSentenceCase now covers heading.h1 (the H2+ carve-out is gone), and since the site's real H1s are front-matter title:/h1: fields that Vale can't see, a new helper (scripts/lint/frontmatter-title-case.py, wired into make lint-prose) runs those fields through the same Vale rule as synthetic headings and maps findings back to the source line — same check, same proper-noun exceptions, nothing duplicated to drift. Advisory and changed-files-scoped, so the measured backlog (~1,600 Title Case titles, 676 of them historical blog posts) migrates one touched page at a time instead of as a wall of findings.
  3. Periodic sync review, automated. styles/Pulumi/BRAND-SYNC.yaml records which brand section each mirrored Vale rule tracks (last_synced included). A new weekly workflow (brand-style-sync.yml, following the new brand-vale-sync skill) re-reads those sections via the brand MCP, compares them to the mirror — retired names first, since renamed products rot fastest — and opens a draft PR with a guide-says / mirror-said / now audit trail when they drift, or a last_synced bump when they don't. It fails loudly if the MCP is unreachable rather than syncing from memory.
  4. Every Claude workflow gets the brand MCP. All eight workflows that invoke claude-code-action now wire the pulumi-brand server via claude_args --mcp-config with get_guidelines/search_guidelines allowed: the two from docs: consolidate voice/writing-style guidance into the brand MCP #19777 (claude-code-review, content-review-article), the new brand-style-sync, plus blog-review-index, check-links, claude-social-review, claude-update, and claude (ad-hoc @claude). claude-new and review-existing-content are dispatchers with no Claude step of their own — the jobs they dispatch are wired.
  5. More redundancy removed. STYLE-GUIDE's "Preferred terminology" table (Pulumi package / native language package) — now stated canonically in the brand terminology section — is replaced with a pointer. AGENTS.md's Naming bullet is rewritten around the retired-names table. glow-up loads terminology alongside voice and writing style.

Conflict resolutions from the rebase (same calls as before, re-applied against newer master): kept master's allowed_bots (workprentice), model flags (claude-opus-5 --effort low), applied[] verdict schema, broad-Bash rationale, and its Cross-reference sections / Brand assets / image-filename additions; took the branch's brand-MCP wiring and STYLE-GUIDE rewrite. The branch's execution-log fix + revert commits dropped out entirely — master fixed that upload path properly itself (runner.temp). The two substantive divergences from #19777 stand as before and need an author's confirmation: precedence (brand guide wins; master's "this guide wins here" section dropped) and heading case (sentence case at every level — verified against the brand guide: "Always use sentence case for headings" with no H1 carve-out, and the terminology section's explicit "Pulumi has no title-case convention").

Recommendations beyond this PR (deliberately not implemented here):

  1. Migrate the ~1,600 Title Case front-matter titles the new checker measures (skip or annotate historical blog posts per the archival convention), and fix the ~23 docs pages still using "Pulumi Insights" plus the flagged Copilot RBAC descriptions. The enforcement now surfaces all of it incrementally; the bulk renames deserve their own reviewed PRs like Rename Pulumi Insights to Pulumi Discovery across marketing pages #20874 was for marketing pages. Note title changes are content-visible (search snippets, social cards), so batch them tree by tree.
  2. Emit the Vale mirror from the brand server instead of mirroring it. The cleanest end state isn't a mirror plus a sync job — it's the brand repo publishing a machine-readable terminology export (the retired-names table as data, e.g. brand.pulumi.com/api/terminology.json) that a build step compiles into Substitutions.yml/DeprecatedProductNames.yml/Nomenclature.yml. The sync workflow then becomes a freshness check only. Needs a pulumi/marketing-web change, so out of scope here.
  3. Rename the provider docs' "Pulumi Service Provider" page or get an explicit product decision that it keeps its historic name (registry id pulumiservice). The Vale exemption documents the status quo; it shouldn't be permanent.
  4. "runtime code" → "function serialization" and "Insights & Governance" variants are intentionally unmirrored — no safe regex (the brand guide itself says runtime can't be swapped on sight). They stay model-review territory; the skill's "intentionally unmirrored" note keeps the sync job from re-proposing them weekly.

Unreleased product version (optional)

N/A

Related issues (optional)

Supersedes #19777.

🤖 Generated with Claude Code

https://claude.ai/code/session_015KpCy5B47c2oNqEeX1Znwx

@pulumi-bot

pulumi-bot commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Your site preview for commit 1bf8ada is ready! 🎉

http://www-testing-pulumi-docs-origin-pr-20664-1bf8ada6.s3-website.us-west-2.amazonaws.com

Changed pages:

cnunciato and others added 5 commits August 24, 2026 19:23
Make the Pulumi brand guide (served by the public pulumi-brand MCP server)
the source of truth for voice and writing-style, and have the docs
STYLE-GUIDE and content skills consult it instead of restating it.

- STYLE-GUIDE.md: trim to Hugo/repo mechanics + a pointer to the brand
  guide's voice/writing-style sections; add a precedence rule (the brand
  guide wins on any overlap, including social/SEO/AEO).
- AGENTS.md: point content rules at the brand guide; standardize headings
  on sentence case at every level; precedence rule.
- glow-up: require the pulumi-brand MCP (fail loud if unreachable) and
  defer voice/prose/terminology to it.
- prose-patterns: source-of-truth note pointing at brand writing-style.
- new-doc / new-blog-post: suggest sentence-case titles; point at the guide.
- .vale.ini: put punctuation outside quotes; note the sentence-case standard.
- claude-code-review.yml / content-review-article.yml: wire the public brand
  MCP (mcp_config + get_guidelines/search_guidelines) so CI review can
  consult voice/writing-style.

SEO and social guidance intentionally stay in this repo's skills for now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… input)

The pinned anthropics/claude-code-action@v1 rejects a top-level mcp_config
input (logged 'Unexpected input(s) mcp_config' and ignored it). v1 expects
custom MCP servers in claude_args via --mcp-config; the config merges with
the action's built-in GitHub MCP server. Move the pulumi-brand HTTP server
there and keep its tools in --allowed-tools.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fallout from rebasing onto master: Pulumi.HeadingSentenceCase and its
.vale.ini entry both described Title Case H1 as the convention, which now
contradicts AGENTS.md and STYLE-GUIDE.md. Restate the comments as what the
scoping actually is — sentence case is the standard at every level; the rule
stays scoped to H2+ so it doesn't flag the backlog of Title Case front-matter
titles. No rule behavior changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KpCy5B47c2oNqEeX1Znwx
…es table

The brand guide's new Names & terminology section (the `terminology` section
on the brand MCP server) is now the canonical reference for product names and
retired names. Bring the offline Vale mirror up to date with it:

- Nomenclature: stop canonicalizing toward "Pulumi Insights" — a retired
  name the rule was actively reinforcing — and canonicalize
  "pulumi discovery" to Pulumi Discovery instead.
- Substitutions: add the retired-names rows that have a single fixed
  replacement (Pulumi Copilot -> Pulumi Neo, Pulumi Insights -> Pulumi
  Discovery, Insights account/scan/supergraph/resources -> their Discovery
  equivalents, self-managed backend -> DIY backend).
- DeprecatedProductNames: generalize from "Pulumi Service" to all retired
  names whose replacement is context-dependent (bare Insights, bare Copilot,
  IAS, Policy Violations), switching the rule to case-sensitive so generic
  "insights" never flags and excluding GitHub/Microsoft/VS Code Copilot.
- Exempt the generated CLI command pages: the CLI still ships
  `pulumi insights ...` commands, so their help text legitimately carries the
  old names; terminology fixes belong upstream in pulumi/pulumi.
- Point every rule message/link that cited a STYLE-GUIDE.md section this PR
  deletes at the brand guide section that now owns the rule.

All tokens validated against fixtures (positive and negative cases) with
vale 3.9.4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KpCy5B47c2oNqEeX1Znwx
…-sync review

Extend the brand-MCP consolidation to the brand guide's new Names &
terminology section, and make "keep the Vale mirror in sync" an automated
process instead of a comment:

- STYLE-GUIDE.md: route naming questions (canonical names, preferred terms,
  retired names) to Names & terminology; drop the Preferred terminology
  table it duplicated; document the sync manifest and workflow under
  Automated checks.
- AGENTS.md: name the terminology section explicitly and rewrite the Naming
  bullet around the retired-names table.
- glow-up: load terminology alongside voice and writing-style.
- styles/Pulumi/BRAND-SYNC.yaml: manifest mapping each mirrored Vale rule to
  the brand section it tracks, with last_synced.
- .github/workflows/brand-style-sync.yml + brand-vale-sync skill: weekly
  drift check of the mirror against the live brand MCP server (retired names
  first). Drift -> draft PR with a guide-says/mirror-said/now audit trail;
  no drift -> last_synced bump. Fails loudly if the MCP is unreachable
  rather than syncing from memory.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KpCy5B47c2oNqEeX1Znwx
@CamSoper
CamSoper force-pushed the claude/rebase-resolve-conflicts-5yzksk branch from 91ff9d5 to 9fd3bf8 Compare August 24, 2026 19:35
@CamSoper CamSoper changed the title docs: consolidate voice/writing-style guidance into the brand MCP (rebase of #19777) docs: consolidate voice/writing-style/terminology guidance into the brand MCP (supersedes #19777) Aug 24, 2026
Every workflow that invokes claude-code-action now carries the public
pulumi-brand MCP server (--mcp-config in claude_args) with its
get_guidelines/search_guidelines tools allowed, so any Claude run that
touches content — blog known-issues review, broken-link fixes, social-copy
review, re-entrant review updates, and ad-hoc @claude help — can consult the
brand guide's voice, writing-style, and terminology sections instead of
relying on trained recollection of Pulumi style.

Previously wired: claude-code-review, content-review-article,
brand-style-sync. Added here: blog-review-index, check-links,
claude-social-review, claude-update, claude. claude-new and
review-existing-content invoke no Claude step themselves — they dispatch
workflows that are already wired.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KpCy5B47c2oNqEeX1Znwx
@CamSoper
CamSoper marked this pull request as ready for review August 24, 2026 21:55
@github-actions github-actions Bot added review:triaging Claude Triage is currently classifying the PR domain:infra PR touches workflows, scripts, infra, Makefile, or build config review:in-progress Claude review is currently running and removed review:triaging Claude Triage is currently classifying the PR labels Aug 24, 2026

@unblocked unblocked Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No issues found

About Unblocked

Unblocked has been set up to automatically review your team's pull requests to identify genuine bugs and issues.

📖 Documentation — Learn more in our docs.

💬 Ask questions — Mention @unblocked to request a review or summary, or ask follow-up questions.

👍 Give feedback — React to comments with 👍 or 👎 to help us improve.

⚙️ Customize — Adjust settings in your preferences.

@github-actions

github-actions Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Pre-merge Review — Last updated 2026-08-24T23:01:17Z

Tip

Summary: This is a repo-governance PR, not user-facing content: it makes the Pulumi brand guide (via the public brand MCP server) the single source of truth for voice, writing style, and terminology, strips the duplicated guidance out of STYLE-GUIDE.md and AGENTS.md, wires the brand MCP into every Claude workflow, and adds a BRAND-SYNC.yaml manifest plus a weekly brand-style-sync job that re-checks the offline Vale mirror against the live guide. The wrongness that would bite here isn't a reader misunderstanding a doc — it's a lint rule that fires on prose the repo intends to keep (Vale error-level substitutions are blocker-tier in the pinned review, so a bad swap turns into a 🚨 on every PR that touches an affected page) or a guidance change that lands in STYLE-GUIDE.md but not in the sibling files that restate it. The latest push resolves every finding from the previous review — the self-managed backend backlog is migrated to zero outside content/blog/, the lookbehinds are gone, the four contradicting sibling files are updated, and the two unconfirmable brand-guide pointers were confirmed against the live guide. This pass re-verified those and reviewed the new three-tier rule split, the content/blog/** exemption, and scripts/lint/frontmatter-title-case.py.

Review confidence:

Dimension Level Notes
mechanics HIGH
facts HIGH The two previously-unconfirmable writing-style claims were verified against the live brand guide by the author; the lookbehind question was reproduced and fixed.
infra correctness HIGH One reproduced defect in the new RetiredNames.yml message template — see 🚨 Outstanding.
Investigation log
  • Cross-sibling reads: ran (CD-guide terminology sweep after the self-managed backend migration)
  • External claim verification: 11 of 60 claims verified (2 unverifiable, 1 contradicted) on the first pass; 5 new claims extracted from the fix push, 3 prior verdicts upgraded on author-supplied evidence.
  • Cited-claim spot-checks: not run (no cited claims)
  • Frontmatter sweep: not run (no frontmatter in diff)
  • Temporal-trigger sweep: ran (recency words present in diff; spot-check in-review)
  • Code execution: not run (no static/programs/ change)
  • Code-examples checks: ran (3 specialists: structural, existence, body-code-coverage); 0 findings
  • Editorial-balance pass: not run (not under content/blog/)
🚨 Outstanding ⚠️ Low-confidence 💡 Pre-existing ✅ Resolved
2 3 0 5

🔍 Verification trail

65 claims extracted · 14 verified · 0 unverifiable · 1 contradicted
  • L3 in .claude/commands/brand-vale-sync/SKILL.md "description: Weekly drift check of the offline Vale mirror (styles/Pulumi/) against the Pulumi brand guide served by the brand MCP server, with…" → ➖ not-a-claim (evidence: The SKILL.md frontmatter description verbatim matches the tool's own documented behavior in its body (draft PR when drift found, manifest-only update when not, invoked by brand-style-sync workflow). This is a faithful self-description of…; source: repo:.claude/commands/brand-vale-sync/SKILL.md)
  • L10 in .claude/commands/brand-vale-sync/SKILL.md "served by the pulumi-brand MCP server (https://brand.pulumi.com/mcp) —" → ➖ not-a-claim (evidence: This is a Claude Code skill/automation file describing the PR author's own internal tooling pipeline (a brand-guide-to-Vale-rule sync workflow) and the MCP server it calls. It's a faithful description of the author's own design, not a…; source: repo:.claude/commands/brand-vale-sync/SKILL.md)
  • L16 in .claude/commands/brand-vale-sync/SKILL.md "a sync against a hallucinated source is worse than no sync." → ➖ not-a-claim (evidence: This is a subjective, non-falsifiable value judgment embedded in the PR author's own internal tooling documentation (a Claude skill/command file), explaining the design rationale for their sync process rather than asserting a checkable…; source: .claude/commands/brand-vale-sync/SKILL.md)
  • L21 in .claude/commands/brand-vale-sync/SKILL.md "rule to the brand section and subsection it tracks, and records last_synced." → ➖ not-a-claim (evidence: This is a faithful description of the PR author's own internal tooling design (a Claude Code skill/command spec for syncing brand rules), not a falsifiable external assertion.; source: .claude/commands/brand-vale-sync/SKILL.md)
  • L33 in .claude/commands/brand-vale-sync/SKILL.md "- Retired terms the mirror doesn't know — a row in the terminology" → ➖ not-a-claim (evidence: This is a bullet point in a skill/command documentation file describing the author's own tooling design (a checklist item for a terminology sync mirror), not a falsifiable factual assertion.; source: .claude/commands/brand-vale-sync/SKILL.md)
  • L34 in .claude/commands/brand-vale-sync/SKILL.md "section's 'Retired and disallowed names' table with no corresponding" → ➖ not-a-claim (evidence: This is a fragment of the PR author's own internal skill/process documentation describing how the brand-vale-sync tool should compare a "Retired and disallowed names" table against local YAML files. It's a description of the author's own…; source: repo:.claude/commands/brand-vale-sync/SKILL.md)
  • L37 in .claude/commands/brand-vale-sync/SKILL.md "highest-value check: renamed products rot fastest." → ➖ not-a-claim (evidence: This is a subjective heuristic/opinion statement authored within the PR's own SKILL.md guidance file describing the author's own design rationale for a brand-check tool, not a falsifiable external fact.; source: .claude/commands/brand-vale-sync/SKILL.md (self-authored guidance))
  • L50 in .claude/commands/brand-vale-sync/SKILL.md "forcing a noisy rule. When adding a retired name, decide the tier:" → ➖ not-a-claim (evidence: This is an instructional line from a Claude skill/command markdown file describing the PR author's own internal process for maintaining a naming rule set ("When adding a retired name, decide the tier"), not a falsifiable factual…; source: repo:.claude/commands/brand-vale-sync/SKILL.md)
  • L54 in .claude/commands/brand-vale-sync/SKILL.md "files>orvale` on a scratch fixture exercising each new/changed token," → ➖ not-a-claim (evidence: This is an instructional step in the skill's own workflow ("Validate. Run Vale ... on a scratch fixture exercising each new/changed token") describing the author's own process for validating Vale rule changes — not a falsifiable factual…; source: repo:.claude/commands/brand-vale-sync/SKILL.md)
  • L58 in .claude/commands/brand-vale-sync/SKILL.md "- Drift found: update the rule files and BRAND-SYNC.yaml" → ➖ not-a-claim (evidence: This is a line from a Claude Code skill/command definition file describing the author's own internal workflow logic (what to do when drift is found), not a falsifiable external claim.; source: .claude/commands/brand-vale-sync/SKILL.md)
  • L59 in .claude/commands/brand-vale-sync/SKILL.md "(last_synced + any mapping changes), and open a draft PR titled" → ➖ not-a-claim (evidence: This is a fragment of internal workflow/tooling documentation describing the PR author's own automation process (a skill file describing how to open a draft PR), not a falsifiable factual assertion checkable against an external source.; source: .claude/commands/brand-vale-sync/SKILL.md)
  • L64 in .claude/commands/brand-vale-sync/SKILL.md "- No drift: commit only the last_synced bump in BRAND-SYNC.yaml" → ➖ not-a-claim (evidence: This is a line from an internal Claude command/skill definition describing the PR author's own workflow behavior (committing only a timestamp bump when no drift is found), not a falsifiable external or product claim.; source: .claude/commands/brand-vale-sync/SKILL.md (self-described internal tooling behavior))
  • L8 in .claude/commands/docs-review/references/prose-patterns.md "> Source of truth. The prose patterns to avoid (nested clause stacks, contrastive frames, uniform rhythm, AI-drafting tells) are defined in the brand…" → ➖ not-a-claim (evidence: This is an internal .claude/ tooling doc describing the PR author's own review-pipeline design, stating that its prose-pattern list should stay in sync with the brand guide. Other files in pulumi/docs (STYLE-GUIDE.md…; source: gh search code --owner pulumi "brand.pulumi.com/voice/writing-style")
  • L17 in .claude/commands/glow-up.md "This skill delegates all voice, tone, prose, terminology, grammar, and naming judgment to the Pulumi brand guide served by the **pulumi-brand MCP…" → ➖ not-a-claim (evidence: This is the PR author's own description of their .claude/commands/glow-up.md skill's design/configuration — it specifies which MCP server the skill itself is built to call (https://brand.pulumi.com/mcp). This is an internal…; source: repo:.claude/commands/glow-up.md)
  • L48 in .claude/commands/glow-up.md "Judge the prose against the brand guide's voice, writing-style, and terminology sections (brand.pulumi.com, also exposed through the brand MCP server) …" → ➖ not-a-claim (evidence: This is a line from the PR author's own internal Claude command file (.claude/commands/glow-up.md) describing the command's own design/workflow — it directs the tool to consult brand.pulumi.com/the brand MCP server as the source of…; source: repo:.claude/commands/glow-up.md)
  • L75 in .claude/commands/glow-up.md "Content type: route the file to the right base criteria — docs-review:references:docs for technical docs, docs-review:references:blog for…" → ➖ not-a-claim (evidence: This is a line from an internal Claude Code slash-command definition describing the author's own routing logic for selecting review criteria (docs vs blog), not a falsifiable external/product claim.; source: .claude/commands/glow-up.md (reviewed file, self-referential command spec))
  • L138 in .claude/commands/glow-up.md "- The brand guide's voice, writing-style, and terminology sections (brand.pulumi.com, also exposed through the brand MCP server) — voice, prose, grammar, and…" → ➖ not-a-claim (evidence: This line is part of an internal Claude command/workflow file describing the author's own process for referencing brand guidelines (brand.pulumi.com and an internal MCP server) as a source of truth during doc editing. It's a description…; source: .claude/commands/glow-up.md)
  • L41 in .claude/commands/new-blog-post.md "- Title: The blog post title (sentence case in frontmatter — the brand standard). If the user doesn't have a title yet, ask for a working title or topic…" → ➖ not-a-claim (evidence: This is an internal instruction file (.claude/commands/new-blog-post.md) describing the PR author's own content-creation workflow/tooling guidance for generating blog post metadata, not a falsifiable third-party assertion.; source: .claude/commands/new-blog-post.md)
  • L22 in .claude/commands/new-doc/references/questions-regular.md "- Convert user's description to sentence case (capitalize only the first word and proper nouns)" → ➖ not-a-claim (evidence: This is an internal authoring instruction within a Claude command template describing the PR author's own documentation-generation workflow/style guide, not a falsifiable external assertion.; source: .claude/commands/new-doc/references/questions-regular.md L22)
  • L461 in .github/workflows/blog-review-index.yml "--mcp-config '{'mcpServers': {'pulumi-brand': {'type': 'http', 'url': 'https://brand.pulumi.com/mcp'}}}'" → ✅ verified (evidence: pulumi/docs STYLE-GUIDE.md independently references the same endpoint: "Installing the Pulumi Brand MCP server puts the full guidelines, logos, palette, and a contrast checker inline." This confirms the…; source: gh search code --owner pulumi "brand.pulumi.com/mcp" (pulumi/docs:STYLE-GUIDE.md))
  • L40 in .github/workflows/brand-style-sync.yml "curl -sL https://github.com/errata-ai/vale/releases/download/v3.9.4/vale_3.9.4_Linux_64-bit.tar.gz " → ✅ verified (evidence: v3.9.4 is a genuine errata-ai/vale release tag (referenced as a diff base, "errata-ai/vale@v3.9.4...v3.9.5", in a NixOS packaging PR), and the asset naming pattern vale_<version>_Linux_64-bit.tar.gz under releases/download/<tag>/…; source: WebSearch ran query "errata-ai vale releases v3.9.4"; vale: 3.9.4 -> 3.9.5 NixOS/nixpkgs#379570)
  • L71 in .github/workflows/brand-style-sync.yml "--mcp-config '{'mcpServers': {'pulumi-brand': {'type': 'http', 'url': 'https://brand.pulumi.com/mcp'}}}'" → ➖ not-a-claim (evidence: This is a CI workflow config line specifying the MCP server URL used by the PR author's own automation pipeline (--mcp-config '{"mcpServers": {"pulumi-brand": {"type": "http", "url": "https://brand.pulumi.com/mcp"}}}'), not a…; source: repo:.github/workflows/brand-style-sync.yml)
  • L168 in .github/workflows/check-links.yml "--mcp-config '{'mcpServers': {'pulumi-brand': {'type': 'http', 'url': 'https://brand.pulumi.com/mcp'}}}'" → ➖ not-a-claim (evidence: This is a CI workflow configuration line wiring the PR author's own GitHub Actions pipeline to an internal Pulumi brand MCP endpoint, with an accompanying code comment ("The public Pulumi brand MCP server ... is wired via `claude_args…; source: repo:.github/workflows/check-links.yml)
  • L1051 in .github/workflows/claude-code-review.yml "'url': 'https://brand.pulumi.com/mcp'" → ➖ not-a-claim (evidence: This is an internal MCP server endpoint configured in Pulumi's own GitHub Actions workflow (claude-code-review.yml) for an internal brand-guidelines tool used by their Claude-based PR review bot. It's a self-referential configuration…; source: repo:.github/workflows/claude-code-review.yml)
  • L1111 in .github/workflows/claude-code-review.yml "--mcp-config '{'mcpServers': {'pulumi-brand': {'type': 'http', 'url': 'https://brand.pulumi.com/mcp'}}}'" → ➖ not-a-claim (evidence: This is a line from the PR's own GitHub Actions workflow config specifying an MCP server URL (--mcp-config argument) used to wire up Claude Code review tooling. It's a configuration detail of the PR author's own CI pipeline, not a…; source: .github/workflows/claude-code-review.yml (reviewed file itself))
  • L297 in .github/workflows/claude-social-review.yml "--mcp-config '{'mcpServers': {'pulumi-brand': {'type': 'http', 'url': 'https://brand.pulumi.com/mcp'}}}'" → ➖ not-a-claim (evidence: This is a CI workflow config line specifying an MCP server endpoint used internally by the PR author's own Claude-based review pipeline, not a falsifiable third-party assertion.; source: .github/workflows/claude-social-review.yml)
  • L525 in .github/workflows/claude-update.yml "--mcp-config '{'mcpServers': {'pulumi-brand': {'type': 'http', 'url': 'https://brand.pulumi.com/mcp'}}}'" → ➖ not-a-claim (evidence: This is a workflow config line wiring the author's own CI pipeline to their own internal MCP server ("The public Pulumi brand MCP server (voice/writing-style/terminology source of truth) is wired via claude_args --mcp-config"). It's a…; source: repo:.github/workflows/claude-update.yml (lines 520-526))
  • L126 in .github/workflows/claude.yml "--mcp-config '{'mcpServers': {'pulumi-brand': {'type': 'http', 'url': 'https://brand.pulumi.com/mcp'}}}'" → ➖ not-a-claim (evidence: This is a CI workflow config line wiring the PR author's own Claude Code Action to the Pulumi Brand MCP server. It's consistent with the repo's own STYLE-GUIDE.md, which independently references the same public brand MCP server at…; source: repo:.github/workflows/claude.yml (L121-126) and repo:STYLE-GUIDE.md)
  • L515 in .github/workflows/content-review-article.yml "'url': 'https://brand.pulumi.com/mcp'" → ➖ not-a-claim (evidence: This is an MCP server endpoint URL embedded in the author's own CI workflow configuration (--mcp-config '{"mcpServers": {"pulumi-brand": {"type": "http", "url": "https://brand.pulumi.com/mcp"}}}'), used to connect Claude to Pulumi's…; source: repo:.github/workflows/content-review-article.yml)
  • L667 in .github/workflows/content-review-article.yml "--mcp-config '{'mcpServers': {'pulumi-brand': {'type': 'http', 'url': 'https://brand.pulumi.com/mcp'}}}'" → ➖ not-a-claim (evidence: This is a workflow config line describing the PR author's own CI pipeline setup (an --mcp-config flag wiring in an MCP server), not a third-party factual assertion. brand.pulumi.com is confirmed as Pulumi's legitimate, canonical brand…; source: repo:.github/workflows/content-review-article.yml; gh search code --owner pulumi brand.pulumi.com)
  • L34 in AGENTS.md "For all content files, Pulumi's voice, tone, prose, product naming, and grammar are defined in the Pulumi brand guide…" → ➖ not-a-claim (evidence: This is an internal instruction in AGENTS.md describing the PR author's own documentation/process guidance (pointing agents to brand.pulumi.com and its MCP server for style guidance), not a falsifiable third-party factual assertion.; source: repo:AGENTS.md)
  • L43 in AGENTS.md "- Headings: Sentence case at every level (H1 included), and sentence case for nav menu labels. See the brand guide's [writing…" → ➖ not-a-claim (evidence: AGENTS.md L43 is internal repo documentation describing the author's own writing conventions and pointing to STYLE-GUIDE.md and the brand guide for details. STYLE-GUIDE.md confirms the same policy: "Heading case is sentence case for all…; source: repo:AGENTS.md, repo:STYLE-GUIDE.md)
  • L61 in AGENTS.md "- Naming: Product, feature, and category names — canonical casing, preferred terms, retired names — come from the brand guide's terminology section.…" → ➖ not-a-claim (evidence: This line is AGENTS.md's own internal authoring instruction telling agents to consult the brand guide's terminology/retired-names table before using product names, illustrated with examples (Pulumi Discovery vs. Pulumi Insights; Pulumi…; source: repo:AGENTS.md#L61)
  • L3 in STYLE-GUIDE.md "Pulumi's voice, tone, prose, product naming, grammar, and punctuation guidance lives in the Pulumi brand guide at…" → ➖ not-a-claim (evidence: This line describes the PR author's own documentation architecture decision (STYLE-GUIDE.md deferring to brand.pulumi.com for voice/tone/naming guidance). It's corroborated as consistent with the rest of the repo — footer.yml, Vale style…; source: repo:STYLE-GUIDE.md, repo:data/footer.yml, repo:styles/Pulumi/NarrativeWe.yml)
  • L9 in STYLE-GUIDE.md "For anything not covered in either place, fall back to the Google Developer Documentation Style Guide." → ✅ verified (evidence: The URL https://developers.google.com/style resolves to the "Google developer documentation style guide" landing page, which states: "This style guide provides editorial guidelines for writing clear and consistent technical documentation…; source: https://developers.google.com/style/)
  • L15 in STYLE-GUIDE.md "Pulumi's voice and writing rules live in the brand guide, published at brand.pulumi.com and exposed to agents through the public…" → ✅ verified (evidence: (escalated from pass1) brand.pulumi.com hosts the Pulumi brand guidelines ("Guidelines, assets, and tools for working with the Pulumi brand"), and brand.pulumi.com/mcp-server/ confirms "The Pulumi Brand MCP Server is a remote Model…; source: https://brand.pulumi.com/mcp-server/)
  • L19-21 in STYLE-GUIDE.md "| Product, feature, and category names — canonical casing, preferred terms, and retired names | [Names & terminology](https://brand.pulumi.com/voice/names-and…" → ➖ not-a-claim (evidence: This is a table row in the repo's own STYLE-GUIDE.md pointing readers to the brand guide's Names & terminology page for naming conventions. It's the author's own internal documentation cross-reference (consistent with the identical link…; source: repo:STYLE-GUIDE.md)
  • L24-25 in STYLE-GUIDE.md "Key rules the brand guide owns (so you know what not to look for here): inclusive language; the Oxford comma; sentence case for headings; Pulumi…" → ➖ not-a-claim (evidence: This is a self-referential scoping statement in the PR author's own STYLE-GUIDE.md describing what content is intentionally left to a separate brand guide, not a falsifiable third-party assertion; the vale.sh source_hint does not…; source: STYLE-GUIDE.md (reviewed file, self-description of scope))
  • L39 in STYLE-GUIDE.md "Heading case is sentence case for all levels — see the brand guide's writing style. The rest is Hugo…" → ✅ verified (evidence: (upgraded from 🤷 on author-supplied evidence, 1bf8ada) The author pulled the page through the brand MCP server. Writing style, "Headings and titles": "Always use sentence case for headings (e.g., 'This is a heading', not 'This Is a Heading')." — with no H1 or page-title carve-out. The terminology section adds: "Pulumi has no title-case convention: headings are sentence case, and names keep their own casing wherever they appear." brand.pulumi.com's own H1s ("Writing style", "Names & terminology", "Voice & tone") are sentence case.; source: brand MCP get_guidelines({section: "writing-style"}), get_guidelines({section: "terminology"}))
  • L57 in STYLE-GUIDE.md "- When changing the URL of an existing page, add a redirect with a Hugo alias." → ✅ verified (evidence: The official Hugo URL management docs state: "Aliases allow you to redirect old URLs to new URLs. This is essential for preventing broken links... when you rename or move content" and "To add redirects to a page, list the previous paths…; source: https://gohugo.io/content-management/urls/)
  • L63 in STYLE-GUIDE.md "When to use ↗: links to generated API docs (/docs/reference/pkg/*), external sites (e.g. pkg.go.dev), Tutorials (different UI), or anything that leaves…" → ✅ verified (evidence: The pulumi/docs repo confirms /docs/reference/pkg/* is a real path tree containing generated, pre-built SDK/API reference docs (README: "generated SDK reference pages under /docs/reference/pkg/{nodejs,python,dotnet,java}/..." served…; source: gh search code --owner pulumi repo:pulumi/docs "reference/pkg"; repo:pulumi/docs README.md)
  • L75 in STYLE-GUIDE.md "Every section has an _index.md that the sidebar injects as the first item of the section's submenu. The label it receives — 'Overview' or…" → ✅ verified (evidence: (escalated from pass3) layouts/partials/docs/menu-tree.html implements exactly this logic: {{- $overviewLabel := "Introduction" }} followed by {{- if $overviewPage.Params.docs_home }} {{- $overviewLabel = "Overview" }}, confirming…; source: gh search code --repo pulumi/docs "docs_home" "Overview" (layouts/partials/docs/menu-tree.html))
  • L102 in STYLE-GUIDE.md "The brand guide owns alt text (its writing style section). This site adds:" → ➖ not-a-claim (evidence: This is the PR author's own repo-governance statement about which document owns which style topic (an internal division-of-responsibility decision within Pulumi's own docs/brand-guide split), not a checkable third-party factual…; source: repo:STYLE-GUIDE.md (pulumi/docs, master branch, fetched via gh api))
  • L142 in STYLE-GUIDE.md "The brand guide owns code-sample style — indentation, quoting, comments, line-splitting (its writing style…" → ✅ verified (evidence: (upgraded from 🤷 on author-supplied evidence, 1bf8ada) get_guidelines({section: "writing-style"}) returns a section titled exactly "Code samples", covering indentation (four spaces; two for YAML, tabs for Go), quoting (double outer, single inner), comments, and line-splitting for long CLI commands — matching the pointer's description item for item.; source: brand MCP get_guidelines({section: "writing-style"}))
  • L214 in STYLE-GUIDE.md "Highlight only the few lines that are new or noteworthy. If the entire block matters equally, no highlighting is needed." → ➖ not-a-claim (evidence: This is a style guideline instructing documentation authors on when to use code-highlighting, part of the PR author's own editorial guidance, not a falsifiable factual assertion about a product or external source.; source: repo:STYLE-GUIDE.md)
  • L225 in STYLE-GUIDE.md "See Hugo diagrams docs and Mermaid docs." → ✅ verified (evidence: The cited page https://gohugo.io/content-management/diagrams/ is live and covers exactly this topic: "Use fenced code blocks and Markdown render hooks to include diagrams in your content" and notes "Hugo does not provide a built-in…; source: https://gohugo.io/content-management/diagrams/)
  • L245 in STYLE-GUIDE.md "The Pulumi glossary defines common terms used throughout the documentation." → ❌ contradicted (evidence: The actual glossary page lives at content/docs/reference/glossary.md (URL /docs/reference/glossary/), as confirmed both by the file itself and by content/docs/iac/concepts/_index.md which links "Glossary". Fixed in 1bf8ada — both L245 and the L250 anchor example now use the canonical path.; source: gh search code --owner pulumi repo:pulumi/docs glossary (shows content/docs/reference/glossary.md and content/docs/iac/concepts/_index.md linking to /docs/reference/glossary/))
  • L247 in STYLE-GUIDE.md "- When introducing a new concept or Pulumi-specific term, consider adding it to the glossary." → ➖ not-a-claim (evidence: This is an internal authoring guideline instructing writers to consider adding new terms to a glossary; it is not a falsifiable factual assertion about Pulumi's product or behavior.; source: repo:STYLE-GUIDE.md)
  • L250 in STYLE-GUIDE.md "- Link to specific terms using anchor links: /docs/reference/glossary/#term-name." → ✅ verified (evidence: content/docs/reference/glossary.md is the canonical glossary source file, so /docs/reference/glossary/#term-name is the canonical anchor form (the previous /docs/iac/concepts/glossary/ value also resolved, via that file's aliases: list).; source: repo:content/docs/reference/glossary.md)
  • L253 in STYLE-GUIDE.md "For product, feature, and category names — canonical spellings, preferred terms like Pulumi package vs. native language package, and the retired names…" → ➖ not-a-claim (evidence: This line is the STYLE-GUIDE.md's own internal pointer to an external brand guide page, consistent with an identical reference earlier in the same file (line 21: "Product, feature, and category names — canonical casing, preferred terms…; source: repo:STYLE-GUIDE.md)
  • L303 in STYLE-GUIDE.md "The rules in this guide — and the mechanically enforceable subset of the brand guide's — are enforced by Vale via .vale.ini at the repo…" → ✅ verified (evidence: (escalated from pass3) .vale.ini at the repo root sets StylesPath = styles and BasedOnStyles = Pulumi, Google, write-good for markdown, confirming Vale (vale.sh) is configured via .vale.ini with custom Pulumi rules under…; source: repo:.vale.ini)
  • L303 in STYLE-GUIDE.md "Run locally with make lint-prose, which also feeds front-matter title:/h1: fields (the site's H1s, invisible to Vale itself) through the same sentence-case rule via scripts/lint/frontmatter-title-case.py." → ✅ verified (evidence: scripts/lint-prose.sh L44-51 appends python3 "$(dirname "$0")/lint/frontmatter-title-case.py" "${TARGETS[@]}" || true after the vale --no-exit call, and the script extracts title:/h1: front-matter values into synthetic # <title> docs and filters Vale output to Check == "Pulumi.HeadingSentenceCase".; source: repo:scripts/lint-prose.sh, repo:scripts/lint/frontmatter-title-case.py L40-54, L129-139)
  • L311 in STYLE-GUIDE.md "The rules in this guide — and the mechanically enforceable subset of the brand guide's — are enforced by Vale via .vale.ini at the repo…" → ✅ verified (framing: Claim describes the repo's own Vale setup; sibling STYLE-GUIDE.md text found via gh search corroborates the same description almost verbatim.; evidence: (escalated from pass3) .vale.ini at the repo root sets StylesPath = styles and BasedOnStyles = Pulumi, Google, write-good for *.md, confirming Vale (vale.sh) is configured via .vale.ini with custom rules under styles/Pulumi/…; source: repo:.vale.ini)
  • L22 in styles/Pulumi/BRAND-SYNC.yaml "last_synced: '2026-08-24'" → ➖ not-a-claim (evidence: This is a metadata field recording the last sync timestamp of the author's own brand-style tooling, not a falsifiable external claim.; source: styles/Pulumi/BRAND-SYNC.yaml)
  • L3 in styles/Pulumi/BannedWords.yml "link: 'https://brand.pulumi.com/voice/writing-style/'" → ➖ not-a-claim (evidence: This is a Vale linter style-rule config file (styles/Pulumi/BannedWords.yml); the link field is a metadata pointer used to display a reference URL in lint warning messages, not a factual assertion about Pulumi's product or docs content.; source: repo:styles/Pulumi/BannedWords.yml)
  • L42 in styles/Pulumi/DeprecatedProductNames.yml "link: 'https://brand.pulumi.com/voice/names-and-terminology/'" → ➖ not-a-claim (evidence: This is a link: field in a Vale style rule config, pointing editors to Pulumi's internal brand guide as the reference for choosing current product names. It's tooling configuration describing the author's own lint rule design…; source: repo:styles/Pulumi/DeprecatedProductNames.yml)
  • L3 in styles/Pulumi/HeadingSentenceCase.yml "link: 'https://brand.pulumi.com/voice/writing-style/'" → ➖ not-a-claim (evidence: This is a Vale linter style-rule config file; the link field is a reference URL pointing to Pulumi's own internal brand/writing-style guide, shown as a hint in lint warnings. It's a self-referential tooling configuration authored by…; source: repo:styles/Pulumi/HeadingSentenceCase.yml)
  • L3 in styles/Pulumi/Nomenclature.yml "link: 'https://brand.pulumi.com/voice/names-and-terminology/'" → ➖ not-a-claim (evidence: This is a Vale style-linter rule config that includes a reference link to Pulumi's internal brand guide for error messages. It is a tooling/config artifact, not a falsifiable factual claim about a product or third party.; source: repo:styles/Pulumi/Nomenclature.yml)
  • L62 in styles/Pulumi/Overcapitalization.yml "link: 'https://brand.pulumi.com/voice/names-and-terminology/'" → ➖ not-a-claim (evidence: This is a link: metadata field in a Vale lint rule config (styles/Pulumi/Overcapitalization.yml), pointing to Pulumi's own brand style guide for use in the rule's warning message ("brand guide: Names & terminology, general terminology…; source: repo:styles/Pulumi/Overcapitalization.yml)
  • L17 in styles/Pulumi/Substitutions.yml "link: 'https://brand.pulumi.com/voice/names-and-terminology/'" → ➖ not-a-claim (evidence: This is a Vale linter configuration file (styles/Pulumi/Substitutions.yml) where the link field is metadata pointing to the internal brand guide source used to justify the substitution rules — it's the author's own tooling reference…; source: repo:styles/Pulumi/Substitutions.yml)
  • L44-45 in styles/Pulumi/Substitutions.yml "self-managed backend / self-managed backends auto-substituted to DIY backend(s) at error level, with the repo-wide backlog cleared" → ✅ verified (evidence: (was ⚔️ mismatch, resolved in 1bf8ada) grep -ril "self-managed backend" content/ now returns 5 files, all under content/blog/ — which .vale.ini newly exempts from Pulumi.Substitutions. Zero occurrences remain in content/docs/, content/tutorials/, content/what-is/, content/security/, or content/case-studies/. The plural token added alongside the singular closes the \b-boundary gap that would have missed backends.; source: repo-wide grep -ril "self-managed backend" content/; repo:.vale.ini [content/blog/**/*.md])
  • L48 in styles/Pulumi/DeprecatedProductNames.yml "negative-lookbehind tokens (?<!Pulumi )(?<!GitHub )(?<!Microsoft )(?<!VS Code )\bCopilot\b" → ✅ verified (evidence: (upgraded from 🤷; the finding was correct and the tokens are gone as of 1bf8ada) The author reproduced the defect — Vale silently drops a negative lookbehind that starts its scoped window, so the same line flags or doesn't depending on unrelated preceding text (clean under an 8-char H1, flags "VS Code Copilot" under a 17-char one; lookaheads stable across the same 30-length padding sweep). DeprecatedProductNames.yml now carries no lookbehinds — only \bPulumi Service\b(?!s\b)(?!\s+[Pp]rovider\b) and \bIAS\b.; source: repo:styles/Pulumi/DeprecatedProductNames.yml (L44-47); author's padding-stability sweep, vale 3.9.4)
  • L33 in styles/Pulumi/RetiredNames.yml "message: '''%s'' is a retired name; the brand guide replaces it with ''%s''.'" → ⚔️ mismatch (evidence: Vale's substitution check formats its message with (replacement, observed) — the order every other substitution rule in styles/Pulumi/ assumes ("Use '%s' instead of '%s'" in Substitutions.yml, WordChoice.yml, Nomenclature.yml, CrossReferenceHeadings.yml). This template reads them as (observed, replacement), so the rendered text inverts the instruction. Re-confirmed at 1bf8ada: the template is unchanged, and this run's own .vale-findings.json reproduces it again — the Policy Violations hit renders as "'policy findings' is a retired name; the brand guide replaces it with 'Policy Violations'".; source: repo:styles/Pulumi/RetiredNames.yml L33; repo:styles/Pulumi/Substitutions.yml L20; this run's .vale-findings.json)
  • L1 in layouts/shortcodes/cicd-cloud-note.markdown.md "Pulumi also supports self-managed backends" → ⚔️ mismatch (evidence: Re-confirmed at 1bf8ada — the shortcode still reads "Pulumi also supports self-managed backends". It is included by 8 continuous-delivery pages (harness.md, teamcity.md, travis.md, google-cloud-build.md, octopus-deploy.md, github-actions.md, jenkins.md, gitlab-ci.md), while the 5 sibling pages in the same directory that 1bf8ada migrated (circleci.md, buildkite.md, azure-devops.md, codefresh.md, aws-code-services.md) now say "DIY backend". layouts/ is outside both .vale.ini's [*.md] content scope and lint-prose.sh's ^content/(docs|blog)/ default, so Vale never sees it.; source: repo:layouts/shortcodes/cicd-cloud-note.markdown.md L1; grep -rln "cicd-cloud-note" content/; grep -rn "DIY backend" content/docs/iac/operations/continuous-delivery/)
  • L41-51 in .vale.ini "[content/blog/**/*.md] disables Substitutions, RetiredNames, DeprecatedProductNames, and Nomenclature" → ⚔️ mismatch (evidence: The block's justification is entirely about the archive ("a 2022 post that says 'Pulumi Insights' is a record of what the product was called then", "~105 archived posts", "New posts are covered by review, not by rewriting the archive"), but a Vale path section matches on path, not on publication date, so posts authored after this merge are exempt too — including from the two blocker-tier rules. scripts/lint-prose.sh does lint changed content/blog/ files, so this is the only automated terminology signal a new post would have had.; source: repo:.vale.ini L40-51; repo:scripts/lint-prose.sh L30-33)
  • L4-6 in styles/Pulumi/DeprecatedProductNames.yml "Retired names with a single fixed replacement (Pulumi Insights -> Pulumi Discovery, CrossGuard -> Pulumi Policies, ...) live in Substitutions.yml instead." → ⚔️ mismatch (evidence: As of 1bf8ada, Pulumi InsightsPulumi Discovery is in styles/Pulumi/RetiredNames.yml, not Substitutions.yml. BRAND-SYNC.yaml and RetiredNames.yml's own header were both updated for the three-tier split; this header was not.; source: repo:styles/Pulumi/DeprecatedProductNames.yml L1-6; repo:styles/Pulumi/RetiredNames.yml L40-47)
  • L225 in content/tutorials/eval-compliance-terraform/index.md "the Policy Violations page in the Pulumi Cloud console" → ⚔️ mismatch (evidence: RetiredNames.yml swaps Policy Violationspolicy findings, but this occurrence (and the image alt text on L227) names a literal Pulumi Cloud console page — the same "it's the actual UI string, not prose" case the PR exempts per-file for Pulumi Service in azure-devops-integration.md.; source: repo:content/tutorials/eval-compliance-terraform/index.md L225-227; repo:.vale.ini [content/docs/integrations/version-control/azure-devops-integration.md])
  • L12 in styles/Pulumi/WordChoice.yml "link: 'https://brand.pulumi.com/voice/voice-and-tone/'" → ➖ not-a-claim (evidence: This is a link: field in a Vale linter config file pointing to Pulumi's internal brand style guide, used to annotate a lint warning message. It is not a falsifiable factual assertion; sibling style files (NarrativeWe.yml…; source: repo:styles/Pulumi/WordChoice.yml; repo:styles/Pulumi/NarrativeWe.yml)

🚨 Outstanding in this PR

These must be resolved or refuted before merging.

  • [L33] styles/Pulumi/RetiredNames.yml"'%s' is a retired name; the brand guide replaces it with '%s'." — the two %s arguments are in the wrong order, so every finding this rule produces instructs the author to restore the retired name. Vale's substitution check formats with (replacement, observed) — which is exactly what the four pre-existing substitution rules in styles/Pulumi/ assume ("Use '%s' instead of '%s'" in Substitutions.yml L20, WordChoice.yml L11, Nomenclature.yml L2, CrossReferenceHeadings.yml L25). This template reads them as (observed, replacement).

    It's reproduced in this run's own .vale-findings.json, on this PR's only RetiredNames hit:

    content/tutorials/eval-compliance-terraform/index.md:225
    'policy findings' is a retired name; the brand guide replaces it with 'Policy Violations'.
    

    The line contains "Policy Violations" and the swap is '\bPolicy Violations\b': policy findings — so the message names the retired term as the fix. This matters more here than it would on a blocker rule: the whole design of the advisory tier is that a human reads the message and decides whether the swap fits the sentence, and review-existing-content runs the same rule through its meaning-preserving gate. Both are reading an inverted instruction. Swapping the order to match the house form fixes it:

    message: 'Use ''%s'' instead of the retired name ''%s''. Confirm it fits this sentence — historical prose ("X replaced Y") and heading anchors are legitimate exceptions.'
  • [L1] layouts/shortcodes/cicd-cloud-note.markdown.md"Pulumi also supports self-managed backends" — this is the last reader-facing self-managed backend outside content/blog/, and it renders onto eight continuous-delivery pages (harness.md, teamcity.md, travis.md, google-cloud-build.md, octopus-deploy.md, github-actions.md, jenkins.md, gitlab-ci.md) — directory siblings of the five pages this push migrated to "DIY backend" (circleci.md, buildkite.md, azure-devops.md, codefresh.md, aws-code-services.md). Before the push the section was consistent; now half of it says one thing and half the other.

    Vale can't catch this: layouts/ is outside .vale.ini's [*.md] content scope and outside lint-prose.sh's ^content/(docs|blog)/ default, so the "non-blog backlog is zero" measurement that justifies keeping the swap at error/blocker tier doesn't cover it. One-line fix in the shortcode; the linked anchor #using-a-diy-backend on /docs/iac/concepts/state-and-backends/ already matches the new wording.

⚠️ Low-confidence

Review each and resolve as appropriate — these don't block the PR.

  • [L41] .vale.ini"[content/blog/**/*.md]Pulumi.Substitutions = NO / Pulumi.RetiredNames = NO / Pulumi.DeprecatedProductNames = NO / Pulumi.Nomenclature = NO" — the justification in the comment is entirely about the archive ("a 2022 post that says 'Pulumi Insights' is a record of what the product was called then", "~105 archived posts", "New posts are covered by review, not by rewriting the archive"), and it's a good one. But a Vale path section matches on path, not on publication date — so a post written next month is exempt on the same terms. lint-prose.sh does lint changed content/blog/ files, so this was the one automated terminology signal a new post would have gotten; after this change a fresh post can ship "CrossGuard" or "Pulumi Copilot" with nothing firing. The two blocker-tier rules (Substitutions, DeprecatedProductNames) are the ones worth a second look; RetiredNames and Nomenclature are advisory either way. Worth deciding explicitly rather than inheriting it — the FutureTense/NarrativeWe exemptions this sits next to are genuinely voice-based and do apply to new posts forever, which is what makes the placement read as settled.

  • [L4] styles/Pulumi/DeprecatedProductNames.yml"Retired names with a single fixed replacement (Pulumi Insights -> Pulumi Discovery, CrossGuard -> Pulumi Policies, ...) live in Substitutions.yml instead." — stale after the three-tier split: Pulumi InsightsPulumi Discovery is now in RetiredNames.yml. BRAND-SYNC.yaml and RetiredNames.yml's own header both got the new taxonomy; this header is the one that didn't. Since these headers are what the brand-vale-sync job reads to decide where a new row goes, a stale one is a mis-filing risk, not just a comment nit.

  • [L225] content/tutorials/eval-compliance-terraform/index.md"you can monitor policy compliance in real-time through the Policy Violations page in the Pulumi Cloud console" — this is the only content hit the new RetiredNames rule produces on the repo, and applying its swap would be wrong: "Policy Violations" here is the literal name of a page in the Pulumi Cloud console (L227's image alt text carries it too). It's the same case as the Pulumi Service group name in azure-devops-integration.md, which this PR exempts per-file rather than rewriting. Nice demonstration of why the advisory tier exists — but if you want Policy Violations to eventually graduate to Substitutions.yml, this page will need the same per-file exemption, and the tier-rule note in BRAND-SYNC.yaml might be worth extending to say that a live UI string blocks graduation the way a backlog does.

Style suggestions

Optional polish from pattern-based linting — never blocking, not counted above. Take the ones that read better and ignore the rest. ✏️ marks one you can apply from the Files changed tab — use Add suggestion to batch on each, then Commit suggestions to take several in a single commit.

content/tutorials/eval-compliance-terraform/index.md
  • line 225: [style] style — 'policy findings' is a retired name; the brand guide replaces it with 'Policy Violations'. Confirm it fits this sentence — historical prose ("X replaced Y") and heading anchors are legitimate exceptions.

💡 Pre-existing issues in touched files (optional)

No pre-existing issues outstanding.

✅ Resolved since last review

  • [L35] styles/Pulumi/Substitutions.yml — the self-managed backendDIY backend swap at error/blocker tier with a 51-page backlog. Resolved in 1bf8ada: the row is confirmed verbatim in the brand guide's retired-names table (pulled via the brand MCP), all 40 non-blog occurrences are migrated, and content/blog/ is exempted. grep -ril "self-managed backend" content/ now returns 5 files, all under content/blog/. The push also fixed a defect the finding didn't catch — \bself-managed backend\b never matched the plural, since \b doesn't split backend/backends, so it silently missed 23 of the 40; both forms are covered now. (One reader-facing occurrence survives in layouts/, tracked separately above.)
  • [L39] STYLE-GUIDE.md — 🤷 on whether sentence case applies to the H1/page title. Resolved in 1bf8ada: verified against the live brand guide — Writing style, "Headings and titles": "Always use sentence case for headings", with no H1 or page-title carve-out; terminology adds "Pulumi has no title-case convention." All four contradicting sibling files updated (new-changelog.md:57 and :73, archetypes/case-studies.md:11, new-doc/references/questions-index.md:219). create-event/references/event-page.md:11 left alone as a marketing-template requirement — agreed.
  • [L142] STYLE-GUIDE.md — 🤷 on whether the brand guide has a "Code samples" section. Resolved in 1bf8ada: get_guidelines({section: "writing-style"}) returns a section under that exact name covering indentation, quoting, comments, and line-splitting. The pointer is correct.
  • [L48] styles/Pulumi/DeprecatedProductNames.yml — 🤷 on whether Vale compiles the negative lookbehinds. Resolved in 1bf8ada: the author reproduced a genuine defect — Vale silently drops a lookbehind that starts its scoped window, so the same prose flags or doesn't depending on unrelated preceding text (clean under an 8-char H1, flags VS Code Copilot under a 17-char one; lookaheads stable across the same sweep). The rule now has no lookbehinds; bare Insights and bare Copilot are dropped and documented as intentionally unmirrored.
  • [L245] STYLE-GUIDE.md — ❌ contradicted glossary path (pre-existing). Resolved in 1bf8ada: both L245 and the L250 anchor example now use the canonical /docs/reference/glossary/.

  • Refresh this review — comment @claude #update-review. Say what you fixed, or which finding you dispute and why; both work in the same mention.
  • Ask for anything else — comment @claude with no hashtag (questions, one-off fixes). Leaves this review untouched.

Important

Please don't hide, resolve, or delete this comment! It breaks things!

📖 How pre-merge review works — the full lifecycle, short-circuits, and escape hatches.

@github-actions github-actions Bot added review:outstanding-issues Claude review completed; outstanding has author-actionable findings and removed review:in-progress Claude review is currently running labels Aug 24, 2026
Extend Pulumi.HeadingSentenceCase to heading.h1, closing the H2+ carve-out
now that the brand guide states the standard explicitly ("Pulumi has no
title-case convention" — Names & terminology). In-body H1s are rare (3 in
the whole tree), because the site's real H1s are front-matter title:/h1:
fields — which Vale never sees, since it skips YAML front matter.

So the same rule is applied to those too: scripts/lint/
frontmatter-title-case.py extracts each target's title fields, runs them
through Pulumi.HeadingSentenceCase as synthetic one-line headings (same
check, same proper-noun exceptions — no duplicated logic to drift), and maps
findings back to the source file and line. Wired into `make lint-prose`
after the Vale pass; advisory and changed-files-scoped like everything else
there, so the backlog (~1,600 Title Case titles today, 676 of them in
historical blog posts) migrates one touched page at a time instead of as a
wall of findings. The gen-docs CLI pages keep their exemption.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KpCy5B47c2oNqEeX1Znwx
@github-actions github-actions Bot added review:stale New commits since last Claude review; refresh on next ready-transition or @claude mention and removed review:outstanding-issues Claude review completed; outstanding has author-actionable findings labels Aug 24, 2026
Comment thread scripts/lint-prose.sh Outdated
Co-authored-by: unblocked[bot] <98133410+unblocked[bot]@users.noreply.github.com>
Addresses the pre-merge review. Three defects, all in rules this PR added or
touched, plus the content backlog behind the outstanding finding.

Blocker backlog (outstanding finding). `self-managed backend` is genuinely in
the brand guide's retired-names table, so the row stays — but its 53
occurrences are migrated to `DIY backend` (40 non-blog; the repo already
preferred that term 95:53, and links to `#using-a-diy-backend`). The token
also missed the plural, since `\b` doesn't split `backend`/`backends`; both
forms are now covered. Backlog is zero, so the row keeps error level.

Automation safety. The brand guide's own preamble carves out two cases a
blanket swap gets wrong: "historical usage is fine" and "some replacements
can't be automated". Both occur here — content/case-studies/unity.md says
"Pulumi Neo ... replacing Pulumi Copilot" (the swap yields "Neo replacing
Neo") and carries a `{#copilot}` heading anchor, and "Insights" means the
Discovery product in most sentences but the "Discovery & governance" category
in others. So renamed-product rows move to a new advisory tier,
styles/Pulumi/RetiredNames.yml (warning, off the blocker list, still a
deterministic fix so review-existing-content's judgment gate applies). Rows
graduate to Substitutions once their backlog hits zero, the way
`self-managed backend` just did. BRAND-SYNC.yaml records the tier rule so the
weekly sync job files new entries correctly.

Unreliable lookbehinds. Vale drops a negative lookbehind that starts its
scoped window, so the same sentence flags or not depending on unrelated text
length: with `(?<!VS Code )\bCopilot\b`, one line is clean under an
8-character H1 and flags "VS Code Copilot" under a 17-character one.
Lookaheads are stable across the same sweep. DeprecatedProductNames drops
both lookbehind tokens (bare Insights and bare Copilot are now documented as
intentionally unmirrored) and keeps only lookahead and literal tokens.

Also fixes two pre-existing false positives the audit surfaced:
`language-native package` fired on "language-native package managers" (npm,
PyPI, Maven) in 13 of 13 cases at blocker tier, now excluded by lookahead;
and generic "Pulumi deployments"/"Pulumi Service" prose is corrected or, for
the literal Azure DevOps group name the product creates, exempted per-file.
Net effect on identical content: 31 blocker findings under these rules vs 46
under master's.

Low-confidence findings: the four sibling files still teaching "H1 = Title
Case" (new-changelog, case-studies archetype, questions-index) now match the
brand standard, and STYLE-GUIDE's glossary link points at the canonical
/docs/reference/glossary/ rather than the alias.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KpCy5B47c2oNqEeX1Znwx

Copy link
Copy Markdown
Contributor Author

@claude #update-review

All findings addressed in 1bf8ada. Summary of what changed and what I verified, finding by finding.

🚨 self-managed backend swap (outstanding) — fixed

Confirmed it belongs in the mirror. Pulled get_guidelines({section: "terminology"}); the retired-names table has the row verbatim: | self-managed backend | DIY backend | This is different from _self-hosted_ (i.e., customer-managed, on-prem) deployments of Pulumi Cloud. | So the row stays.

Backlog cleared rather than downgraded. All 53 occurrences checked in context — every one means the DIY backend (S3 / Azure Blob / local filesystem), and the repo already preferred that term 95:53, with circleci.md linking to #using-a-diy-backend. Migrated the 40 non-blog occurrences; the 13 in content/blog/ stay (see below). Non-blog count is now 0, so the row keeps level: error with no backlog behind it.

The token was also broken. \bself-managed backend\b never matched the plural — \b doesn't split backend/backends, so it silently missed 23 of the 40 non-blog occurrences while flagging the 17 singular ones. Both forms are now covered.

New: an advisory tier, because some of these swaps aren't safe to automate

Auditing the rest of my added rows against real content turned up two cases where a blocker-tier auto-fix is actively wrong — and the brand guide's own preamble predicts both:

"Some replacements can't be automated.""Historical usage is fine. Prose that intentionally describes past behavior … is acceptable."

  • content/case-studies/unity.md:50 — "Pulumi Neo is our AI platform engineer, replacing Pulumi Copilot with expanded capabilities." The swap turns this into "Neo … replacing Neo". Line 95 also carries a {#copilot} heading anchor a rename would break.
  • "Insights" resolves to the Discovery product in most sentences but to the "Discovery & governance" category in others — its own separate row in the brand table — and the docs IA still lives under /docs/insights/.

So renamed-product rows moved to styles/Pulumi/RetiredNames.yml (level: warning, off the blocker: list, still on deterministic_fix: so review-existing-content's meaning-preserving gate applies). Rows graduate to Substitutions.yml once their backlog reaches zero — exactly what self-managed backend just did. BRAND-SYNC.yaml records the tier rule so the weekly sync job files new entries correctly.

⚠️ DeprecatedProductNames lookbehind tokens — fixed, and the finding was right

Reproduced a genuine defect: Vale silently drops a negative lookbehind that starts its scoped window, so identical prose flags or doesn't depending on unrelated preceding text. With (?<!VS Code )\bCopilot\b, this line

GitHub Copilot works well. Microsoft Copilot exists. VS Code Copilot too.

is clean under an 8-character H1 and flags VS Code Copilot under a 17-character one. Lookaheads are stable across the same sweep (30 padding lengths, 0 variation).

The rule now has no lookbehinds. Bare Insights and bare Copilot are dropped and documented as intentionally unmirrored — both needed a lookbehind to avoid double-flagging the Pulumi Insights / Pulumi Copilot that Substitutions already handles, and bare Copilot collides with the GitHub / Microsoft / VS Code products this site legitimately references. Pulumi Service keeps its two lookaheads.

⚠️ Heading case (L39) — verified, and the four sibling files are fixed

The verification step couldn't reach the brand page; I pulled it through the MCP server. It says, under Headings and titles: "Always use sentence case for headings (e.g., 'This is a heading', not 'This Is a Heading')." No H1 or page-title carve-out anywhere in the section. The terminology section is more explicit still: "Pulumi has no title-case convention: headings are sentence case, and names keep their own casing wherever they appear." And empirically, brand.pulumi.com's own H1s are sentence case ("Writing style", "Names & terminology", "Voice & tone"). So yes — every level, page title included.

All four contradicting siblings updated: new-changelog.md:57 and :73, archetypes/case-studies.md:11, new-doc/references/questions-index.md:219. I left create-event/references/event-page.md:11 alone, per your read that it's a marketing-template requirement rather than a restatement of the heading rule.

⚠️ Code samples section (L142) — verified

get_guidelines({section: "writing-style"}) returns a ## Code samples section under that exact name, covering indentation (four spaces; two for YAML, tabs for Go), quoting (double outer, single inner), comments, and line-splitting for long CLI commands. The pointer is correct.

❌ Glossary path — fixed

Good catch. /docs/iac/concepts/glossary/ is only an alias on content/docs/reference/glossary.md; both references now use the canonical /docs/reference/glossary/.

Also fixed: two pre-existing false positives the audit surfaced

  • language-native package fired on "language-native package managers (npm, PyPI, NuGet, Maven, Go modules)" — a different meaning entirely. That was 13 of 13 occurrences, i.e. a 100% false-positive rate at blocker tier. Now excluded with a lookahead.
  • Generic "Pulumi deployments" / "Pulumi Service" prose corrected where unambiguous. The one legitimate "Pulumi Service" — the literal group name the integration creates in Azure DevOps — is exempted per-file rather than rewritten, since changing it would misdocument the actual UI string.

Net effect, measured: running master's rule set and this PR's rule set over identical content, master produces 46 blocker-tier findings and this PR produces 31. The PR reduces blocker noise rather than adding it.

Remaining findings in that set are pre-existing Nomenclature casing hits (Pulumi UI, Pulumi Console, generic Pulumi deployments) that fire on master today and are unrelated to these changes — they belong to the content-migration PR in recommendation 1, not here.

Verification

make lint clean (1850 files, 0 errors, Prettier clean) · make test-review-pipeline all green (pytest + standalone harnesses + every --self-test) · rule behavior validated with vale 3.9.4 against positive and negative fixtures and a padding-stability sweep · blog exemption confirmed (0 findings on a post containing "Pulumi Insights").

One note on the blog exemption, since it's the largest behavioral change here: without it, the new terminology rows would have turned 105 archived blog posts into blocker findings on any PR touching them. The brand guide is explicit that "dated blog posts should remain as they are", so content/blog/ is now exempt from the three terminology rules — the same treatment it already gets for FutureTense and NarrativeWe.

Rebased onto @CamSoper's 18968db (|| true on the title-case helper) — kept as-is.


Generated by Claude Code

Comment on lines +36 to +46
ignorecase: true
action:
name: replace
swap:
'\bPulumi Copilot\b': Pulumi Neo
'\bPulumi Insights\b': Pulumi Discovery
'\bInsights account\b': cloud account
'\bInsights scan\b': Discovery scan
'\bInsights supergraph\b': resource graph
'\bInsights resources\b': discovered resources
'\bPolicy Violations\b': policy findings

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old DeprecatedProductNames.yml explicitly used ignorecase: false and documented:

"Policy Violations": the retired console page name; the page is now "policy findings" (lowercase "policy violations" is fine in prose for what a policy check surfaces, and stays unflagged via case sensitivity).

By moving this token to RetiredNames.yml with ignorecase: true (needed for the other product-name tokens), the rule now matches lowercase "policy violations" in ordinary prose — e.g., "Remediating policy violations" in content/docs/insights/policy/policy-packs/authoring.md, "Each policy violation creates an issue" patterns, and similar natural English usage throughout the docs.

The backlog count of 3 (line 31) was calculated under case-sensitive matching; the actual case-insensitive hit count will be larger and dominated by false positives on legitimate English usage.

Consider either:

  1. Splitting Policy Violations into its own case-sensitive rule (or back into DeprecatedProductNames.yml), or
  2. Adding ignorecase: false to a separate Vale rule just for this token, or
  3. Changing the swap key to anchor on the Title Case form only: '\bPolicy Violations\b' with a separate ignorecase: false override (Vale doesn't support per-token case settings, so it would need its own file).

@github-actions github-actions Bot added review:in-progress Claude review is currently running and removed review:stale New commits since last Claude review; refresh on next ready-transition or @claude mention labels Aug 24, 2026
@pulumi-bot

pulumi-bot commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

📜 Review history

  • 2026-08-24T21:59:22Z — One blocker: the new self-managed backendDIY backend substitution is error-level and hits 51 existing content/ pages. Plus stale H1-Title-Case guidance in four sibling files, two unconfirmable brand-guide pointers, and a lookbehind-support question in the Vale tokens. (46fb451)
  • 2026-08-24T22:54:16Z — re-reviewed after fix push (3 new commits, 1bf8ada). All 5 prior findings resolved, including the blocker: self-managed backend backlog migrated to zero outside content/blog/ and the plural token added, the lookbehinds removed after the defect was reproduced, the four contradicting sibling files updated, both brand-guide pointers confirmed against the live guide via the MCP, and the glossary path corrected. Two new findings from the push: the new RetiredNames.yml message template has its %s arguments reversed against Vale's (replacement, observed) order — reproduced in this run's own .vale-findings.json, where the rule's one hit renders as "'policy findings' is a retired name … replaces it with 'Policy Violations'", the exact inverse of the intended fix — and the shared cicd-cloud-note shortcode in layouts/ still says "self-managed backends" on eight CD pages whose directory siblings the push migrated to "DIY backend", a surface neither .vale.ini nor lint-prose.sh scans. On the three-tier split itself: the reasoning holds, and the measured 46 → 31 blocker-finding reduction is the right thing to have measured. (1bf8ada)
  • 2026-08-24T23:01:17Z — re-verified on request (pulumi-bot); no new commits since 1bf8ada (compare reports identical), so no claims re-extracted. Both 🚨 Outstanding findings re-checked against HEAD and both still stand: RetiredNames.yml L33 still carries the reversed %s order (and this run's fresh .vale-findings.json reproduces the inverted message on the same Policy Violations hit), and layouts/shortcodes/cicd-cloud-note.markdown.md L1 still reads "self-managed backends". 3 low-confidence unchanged; 5 resolved unchanged. (1bf8ada)

  • Refresh this review — comment @claude #update-review. Say what you fixed, or which finding you dispute and why; both work in the same mention.
  • Ask for anything else — comment @claude with no hashtag (questions, one-off fixes). Leaves this review untouched.

Important

Please don't hide, resolve, or delete this comment! It breaks things!

📖 How pre-merge review works — the full lifecycle, short-circuits, and escape hatches.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Review updated on @pulumi-bot's request.

@github-actions github-actions Bot added review:outstanding-issues Claude review completed; outstanding has author-actionable findings and removed review:in-progress Claude review is currently running labels Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain:infra PR touches workflows, scripts, infra, Makefile, or build config review:outstanding-issues Claude review completed; outstanding has author-actionable findings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants