learn-tech-writing-with-phoebe / Session 7 of 8
Learn Tech Writing with Phoebe · Session 7 of 8

Docs as code: milestone 3

Pulse now has an audience statement, a README, a how-to, a reference, an ADR and a changelog - all markdown, all in git. Surprise: you've been doing docs as code since Session 1. Today we add the machinery that keeps those docs alive after you stop paying attention: linting, CI checks, and a review culture with teeth.

🟠 Deeper Practitioners Data & AI teams 45 min live + self-study depth
0-3 · Welcome 3-18 · Concepts (live cards) 18-40 · Build-along on Pulse 40-45 · Q&A
Part 0

Why this session exists

Every doc you've written in this course is plain text, in the repo, versioned by git. That IS docs as code - you just haven't been collecting the benefits yet. The benefits arrive when the machinery around the text starts working for you: a linter that catches style drift while you sleep, a CI gate that refuses to merge features without their docs, and a review habit that treats a doc bug exactly like a code bug. Without the machinery, docs rot the moment the author gets busy. With it, the repo defends its own documentation.

Live - presented in session Self-study - read after class ★ Try it now exercise Sources: Write the Docs + Vale + Google docguide
★ What you walk out with today Milestone 3: Vale linting Pulse's docs with Google's own style rules, a doc-review exercise done PR-style, and a four-line team contract that makes "no doc, no merge" a norm instead of a wish. Cards marked "Live" are what we do together; "Self-study" cards give you full source depth at your own pace.
Part 1 · covers Write the Docs "Docs as Code"

The philosophy and the loop 8 min live

Write the Docs defines docs as code as "a philosophy that you should be writing documentation with the same tools as code": issue trackers, version control, plain-text markup, code reviews, automated tests. It is not a tool you install. It is a decision that documentation gets the same production pipeline your code already earned.

1 · Write in a branch docs are plain text in the repo 2 · PR + peer review docs reviewed exactly like code 3 · CI checks run build · links · prose lint · spell 4 · Merge deploys publish happens on push 5 · Reader hits a gap a doc bug is a real bug 6 · Issue filed queued like any other fix Every step already exists for your code. Docs as code means docs ride the same loop, not a wiki beside it.
🔍 Click to zoom - the docs-as-code loop: the doc change rides the exact rails your code changes already use
LiveThe definition and the six-step workflow3 min▶

The whole workflow, end to end - notice that every step is one you already know from shipping code:

  • Docs live as plain text in the repo (Markdown, reST, AsciiDoc), next to the code they describe.
  • A change is a branch and a commit - no edit button on a live page, no "who changed this?" mystery. Git remembers.
  • A PR opens and the docs get peer review, at the same bar as code. A reviewer can block a misleading sentence the same way they block a misleading variable name.
  • CI runs checks: the site builds, links resolve, prose passes the linter, spelling passes the dictionary.
  • Merge triggers build and deploy. The published docs are always exactly what's on main.
  • Doc updates ship in the SAME PR as the feature. This is the load-bearing rule: "no doc, no merge". Everything else is tooling; this one is culture.

Write the Docs names the payoff plainly: writers integrate into product teams instead of orbiting them, developers write first drafts because the doc file is right there in their editor, and doc requirements become enforceable before merge instead of negotiable after release.

★ Try it nowScore your team's current doc home against the six steps. One point each: 1. Plain text in version control? 2. Changes go through branches? 3. Docs get reviewed before publish? 4. Any automated check at all? 5. Publish happens automatically? 6. Docs required in the same change as the code? 0-2: you have a wiki. 3-4: halfway there. 5-6: docs as code - guard it.
LiveWhy the loop beats the wiki3 min▶

Wikis fail predictably, and each failure maps to a loop step the wiki is missing:

  • No review. Wiki edits publish instantly, so the first reader of a wrong sentence is a user in trouble. In the loop, a reviewer sees it first.
  • No history you can use. Git blame tells you which commit, which PR, and which discussion changed a line. Wiki history tells you a name and a date, if that.
  • No dead-doc detection. When code moves, a doc in the repo shows up in the same grep, the same refactor, the same PR diff. A wiki page just quietly stops being true.
  • Docs live next to the code they describe. The person editing metrics.py sees docs/reference-config.md in the same file tree. Proximity is the cheapest freshness mechanism ever invented.
Real world

The wiki nobody could delete. A data team kept a wiki with three conflicting versions of the same onboarding page - nobody knew which was current, so nobody dared remove any. After migrating docs into the repo, the duplicate problem became impossible: one file, one path, one git history. The wiki stayed up for a year as a read-only museum, then someone finally turned it off and nothing broke.

Self-studyThe tooling landscape3 min read▶

Every docs-as-code stack is the same four choices; Write the Docs' guidance on choosing tools boils down to "pick what your team already uses for code":

  • Version control: git, hosted on GitHub or GitLab. The PR/MR machinery is the review system - you don't build one.
  • Markup: Markdown for approachability, reStructuredText or AsciiDoc when you need cross-references and structured metadata at scale.
  • Generator: Sphinx (Python-native, reST-first), MkDocs (Markdown, fast start), Docusaurus (React sites), Hugo (speed at volume). All turn plain text into a website on push.
  • Checks: prose linters (Vale, next section), link checkers, spell checkers - wired into the same CI that runs your tests. The operational discipline of running docs like a production system even has a name: DocOps.
Meta note The course site you are reading right now is docs as code: static HTML files in a public GitHub repo, published by GitHub Pages on every push. No CMS, no edit button, full git history. The medium has been practicing the message all along.
Part 2 · covers Vale + Google documentation best practices

Enforcement: linting and review culture 7 min live

A philosophy without enforcement is a poster on a wall. Enforcement comes in two layers: a machine layer (Vale, catching style drift on every commit) and a human layer (Google's review-culture rules, catching everything the machine can't). You need both, and they catch different bugs.

PR #42 · feature + its docs, one change code · metrics.py +14 -3 docs · reference-config.md +9 Peer review human judgment Vale CI gate errors block merge Merged deployed PR #43 · code only, "docs later" code · metrics.py +14 -3 Changes requested: where are the docs? "docs later" is where docs go to die One PR carries both chips. The reviewer checks the prose; Vale checks the style; the gate checks the pairing.
🔍 Click to zoom - the same-PR rule as a pipeline: code and docs travel together, or the PR doesn't travel at all
LiveVale in 5 minutes3 min▶

Vale (vale.sh) is a prose linter: it does for your sentences what ruff does for your Python. Rules live in YAML, config lives in a .vale.ini at the repo root, and it runs anywhere - your editor, a pre-commit hook, or a GitHub Action in CI.

  • It ships the famous style guides as packages. One line in .vale.ini and you're linting against Google's or Microsoft's published style guide - the same rules their own writers follow, with zero rule-writing on day one.
  • It's syntax-aware. Vale knows Markdown, so it lints your prose and skips your code blocks. Your snake_case_variables never get flagged as spelling errors.
  • Three alert levels - suggestion, warning, error - and CI can be set to fail on errors only. That's the pragmatic setting: hard rules block the merge, soft rules educate without nagging.
Adoption tip Start with everything at suggestion level for two weeks. Let the team see what Vale sees without a single blocked PR, then promote the rules everyone already agrees with to error. A linter imposed overnight gets uninstalled; a linter phased in gets defended.
LiveGoogle's three review-culture rules3 min▶

Google's documentation best practices guide is short and ruthless. Three rules do most of the work:

  • Docs change in the same PR as the code, and get reviewed at the same bar. Not a follow-up ticket, not "I'll circle back". The diff that changes the behavior changes the description of the behavior, and a reviewer signs off on both.
  • Minimum viable documentation. "A small set of fresh and accurate docs is better than a large assembly of documentation in various states of disrepair." Write less, keep it true, and prune like a bonsai - deliberate small cuts, continuously, instead of a heroic yearly rewrite.
  • Delete dead docs. Wrong docs are not neutral; they actively harm, because readers trust them and act on them. Google's default on any migration is deletion, backed by regular team doc audits ("doc fixits") to find the pages nobody wants to admit are dead.
Real world

The runbook that paged the wrong team. An on-call engineer followed a stale runbook to escalate an alert - to a team that had been dissolved eight months earlier. Forty minutes of 3am confusion later, the fix wasn't updating the runbook. It was deleting it: the alert itself had been superseded, and the doc's only remaining function was to mislead. Deletion is a documentation act.

Self-studyWhat to automate, what needs humans2 min read▶

The trap after installing Vale is believing the green check means the doc is good. Linters and reviewers catch disjoint bug classes:

  • Machines catch what's checkable: spelling, passive voice, "simply" and friends, sentence length, banned words, broken links, a site that won't build. Fast, tireless, and consistent - perfect for the style layer this course built in Sessions 2 and 3.
  • Only humans catch what's judgeable: the doc is written for the wrong audience, the non-scope statement is missing, the example no longer matches the code's behavior, the steps are correct but in a baffling order. Everything Sessions 1, 4, 5 and 6 taught lives above the linter's ceiling.
  • Division of labor: let Vale own the mechanical comments so human reviewers never type "passive voice" again - their whole attention goes to the questions only they can answer. That is Session 8's editing pass, institutionalized.
Build-along 1 of 3

Point Vale at Pulse's docs ★ 8 min · everyone follows

We give the Pulse repo a .vale.ini, pull in Google's packaged style, and lint the oldest prose in the doc set: docs/notes-rewrite.md from Session 2. Watching a linter grade your own writing is the fastest style lesson there is.

Create .vale.ini at the repo root with the config below. It says: rules live in .vale/styles, report everything down to suggestions, fetch the Google package, and apply Vale's core rules plus Google's to every markdown file.

Run vale sync once - it downloads the Google style package into .vale/styles.

Run vale docs/notes-rewrite.md and read the output bottom-up: the summary line first, then the errors, then the warnings.

Fix one error, rerun, watch the count drop. That tight loop - lint, fix, relint - is the whole developer experience of docs as code.

.vale.ini - the whole configStylesPath = .vale/styles MinAlertLevel = suggestion Packages = Google [*.md] BasedOnStyles = Vale, Google
What Vale's output looks likedocs/notes-rewrite.md 3:18 warning Try to avoid using Google.We first-person plural like 'we'. 11:1 error Did you really mean Vale.Spelling 'metrcs'? 14:22 warning 'is triggered' may be Google.Passive passive voice. 20:9 suggestion In general, use active Google.Acronyms voice... spell out 'UTC', if it's unfamiliar. ✖ 1 error, 2 warnings, 1 suggestion in 1 file.
CI wiring, one sentence each GitHub Action: the official errata-ai/vale-action annotates the PR diff with Vale's findings inline. Pre-commit hook: Vale runs on staged markdown before the commit even exists. Gate setting: fail CI on errors only, so suggestions educate and errors enforce.
Build-along 2 of 3

Review a doc like a PR ★ 8 min · build your own

Vale would pass most of the paragraph below - and it's still a bad doc. This is the human layer: review the paragraph the way you'd review code, with line comments that name the problem and propose the fix. Everything you need was taught in Sessions 1-5.

Read the flawed paragraph below once as a newcomer. Note where you'd stop, scroll back, or DM the author.

Write review comments PR-style: quote the line, name the problem, propose the concrete rewrite. Aim for four comments before you peek at ours.

Compare with the sample review. The goal isn't matching wording - it's that every comment is actionable. "This is unclear" is not a review comment; "unclear WHICH script - name it" is.

The paragraph under review - from a fictional PR to docs/how-to-rerun.md## Rerunning the pipeline The pipeline is run by executing the scripts. Simply activate the environment first and it will work. After that is done, the metrics can be found in the output folder and it should be checked before telling stakeholders the numbers are final.
Review comments, PR-styleLine 3 · "The pipeline is run by executing the scripts." Passive voice hides the actor and the order. Rewrite: "Run the three scripts in order: ingest.py, clean.py, metrics.py." Line 3 · "Simply activate the environment" Two problems. "Simply" is a curse-of-knowledge alarm - delete it. And WHICH environment? Missing prerequisite. Add: "Prerequisite: the pulse conda env, activated (conda activate pulse)." Line 4 · "After that is done" After WHAT is done - the activation or the run? Vague pronoun. Name the step: "After metrics.py finishes..." Line 5 · "it should be checked" Who checks it, checked for what, and which file is "it"? Rewrite: "Check daily_metrics.csv for LOW flags before sharing (see docs/how-to-diagnose-low-day.md)." Verdict: request changes. Structure is fine; every sentence needs a named actor and a named object.
★ Notice what just happened Not one of those four comments could have come from Vale - well, it flags "simply" and the passive, but it cannot know the env name, the script order, or which doc to cross-link. That's why the review culture matters even after the linter is green: machines check style, humans check truth.
Build-along 3 of 3

Write the team norm: DOCS.md ★ 6 min · build your own

Machinery without a written norm decays into "that check we always skip". The last artifact of milestone 3 is four lines that make the rules explicit, visible, and quotable in a PR comment. Short on purpose: a contract nobody reads is not a contract.

Create DOCS.md at the Pulse repo root with the four rules below.

Read each rule and check it's enforceable: rule 1 by reviewers, rule 2 by CI, rules 3 and 4 by reviewers with a one-line comment. A rule nobody can enforce is a hope.

Adapt the wording for your real team and open it as a PR - the contract about reviews should itself get reviewed. If anyone pushes back, that conversation IS the adoption process working.

DOCS.md - the four-line contract# How we do docs 1. Docs change in the same PR as the code they describe. No doc, no merge. 2. Vale errors block merge. Warnings and suggestions are advisory. 3. Every alert or error a user can see links to a runbook or how-to. 4. Dead docs are deleted on sight. Wrong docs are worse than no docs.
Real world

The contract that survived its author. A data platform team wrote exactly this kind of four-line norm before their tech lead left. Two years and three team-composition changes later, PRs still get the comment "rule 1?" - from people who never met the person who wrote it. Culture you write down outlives culture you model.

Homework

Try it yourself - this week ◐ 30-45 min total

Source material

Official sources covered

This session teaches the working content of these sources. What stays with the originals: Vale's full rule-authoring syntax and the complete Write the Docs guide prose.

✓Write the Docs · Docs as CodePart 1: the definition, the six-step workflow, the benefits
✓Vale (vale.sh)Part 2 + Demo 1: .vale.ini, packaged Google/Microsoft styles, alert levels, CI wiring
✓Google · Documentation best practicesPart 2: same-PR rule, minimum viable documentation, delete dead docs
◐Write the Docs · DocOpsMentioned in the tooling card: running docs like a production system
◐Write the Docs · Choosing toolsSelf-study card summary; the full comparison essay is at writethedocs.org
Check yourself

Three questions before you go 🎯 ◐ 90 seconds

1 · Vale passes a doc with zero errors. What can you now be sure of?

Linters catch the checkable: spelling, passive voice, banned words. Wrong audience, missing non-scope, and stale facts all pass Vale with flying colors - only reviewers catch those.

2 · Why do docs change in the SAME PR as the code, rather than a follow-up ticket?

The merge gate is the one point where a reviewer can actually require the doc. Once the feature ships, the doc ticket competes with the sprint and loses - that's the lane where docs go to die.

3 · Google's guidance says to DELETE dead docs rather than leave them up "just in case". Why?

A missing doc sends the reader to a colleague; a wrong doc sends them confidently in the wrong direction - like the runbook that paged a dissolved team. Deletion is a documentation act, and git remembers everything anyway.

Session 7 cheat sheet · pin this

Docs as codeWrite documentation with the same tools as code: git, plain text, issues, reviews, automated tests. A philosophy, not a product.
The loopBranch → PR + review → CI checks → merge deploys → reader files gaps as issues. Same rails as your code.
No doc, no mergeDoc changes ship in the SAME PR as the feature. The merge gate is the only enforceable moment; "later" means never.
Vale setup.vale.ini at repo root: StylesPath, MinAlertLevel, Packages = Google, BasedOnStyles per file type. vale sync, then lint.
Alert levelssuggestion < warning < error. Gate CI on errors only: hard rules enforce, soft rules educate. Vale skips code blocks.
Minimum viable docsA small set of fresh, accurate docs beats a large assembly in disrepair. Prune like a bonsai - small cuts, continuously.
Delete dead docsWrong docs actively harm; missing docs just prompt questions. Default to deletion, audit regularly, trust git to remember.
Pulse statusFull doc set ✓ · Vale linting ✓ · DOCS.md contract ✓. One session left: the editing pass that ships v1.0.