Skip to content

The Document Pipeline

FieldValue
TypeSkill Resource
Source~/.copilot/skills/technical-writing/references/pipeline.md
DescriptionNot specified

Source Content

The Document Pipeline

The full workflow for writing or editing a real document — mode, template, drafting, and the gates. The router in SKILL.md sends diagram-only and markdown-mechanics-only tasks straight to their own references; this file is for documents.

Pick the mode first (Diátaxis)

The first decision in any document is its mode, not its words. A tutorial that also tries to be reference fails at both. Each mode serves a different reader job — do not mix two in one document. Pick the mode, copy the template, write — then gate before shipping.

ModeReader’s jobThe question it answers
TutorialLearning by doing”I’m new — teach me by building something”
How-toAchieving a goal”I know the basics — give me the recipe”
ReferenceLooking something up”What are the exact parameters and behavior?”
ExplanationUnderstanding why”Help me understand the design and tradeoffs”

Composite forms — README, migration guide, blog post, troubleshooting, FAQ, release notes, runbook, status update, user survey — have their own skeletons, indexed in templates.md with one file each under templates/. Decision records go to the adr skill. A teaching MDX page built from design-system components goes to the docs-that-teach skill — pair it with this one for mode and house style.

The workflow

  1. Name the mode. If two seem to fit, it is two documents.
  2. Name the reader. Role, stack familiarity, and what they should be able to do after. Assume they landed mid-document from search — every page is page one.
  3. Copy the template. From templates/ — each file carries its skeleton plus its own rules.
  4. Outline the spine. A heading hierarchy where each heading is a one-line promise; the outline alone should tell the story. Any comparison table earns a discriminating column — one that turns each row into a testable condition.
  5. Draft the lede. Open with the why and the payoff — never “this document is about…”. If there is a TLDR, a reader of only the TLDR must get both the claim and the consequence.
  6. Open with a story; draw flows. The two-person story leads the document or section — picture first, explanation after — cast and rotated from the roster in personas.md. Mermaid (following mermaid.md) for every flow or decision.
  7. Fill with runnable, version-pinned code. Mark unverifiable claims [TODO]; never invent an API.
  8. Cut 20%. Replace adjectives with examples; delete throat-clearing. The load-bearing claim gets the space; corollaries get compressed.
  9. Gate. One command runs every registered lint over the files you touched:
Terminal window
python3 ~/.copilot/skills/technical-writing/scripts/lint.py --git-changed
# or explicit files:
python3 ~/.copilot/skills/technical-writing/scripts/lint.py docs/your-doc.md

The gates

scripts/lint.py dispatches every lint registered in lints.toml. It matches files to each lint’s globs, runs only the lints with at least one matching file, and skips (never fails) any lint whose tool isn’t installed. Exit is non-zero only when a required lint fails.

LintScript or toolBlocking?
readabilityscripts/readability.pyYes — adjusted grade ≤ 8.9, no paragraph over 4 sentences
markdownscripts/markdown_lint.pyYes — house Markdown/MDX rules
mermaidscripts/mermaid_lint.pyYes — passes when a file has no diagrams
prosescripts/prose_lint.pyOnly a dead external link fails; the rest is advisory
spellingcodespellNo — advisory; ignore list in scripts/codespell-ignore.txt

To add a lint, append a [[lint]] block to lints.toml — name, globs, command, required flag. No code changes needed. Every script also stays runnable standalone.

Four extra checks layer on top of the readability gate and the Markdown linter. Only a dead external link fails prose_lint.py’s exit code — everything else is advisory, printed for a person to weigh, because regex-based prose checks are inherently false-positive-prone (a rule like “flag the word easy” can’t tell “an easy fix” from “use simple words,” which is a fine use of “simple”). Internal (relative) links are validated elsewhere — see Internal links.

Install once per machine — brew bundle --file=Brewfile or scripts/install-tools.sh (see requirements.txt / Brewfile in the skill root).

Vale

Checks voice and word choice against .vale.ini and the rules in vale-styles/TechnicalWriting/: words that assert difficulty instead of proving it, hedge words that add no information, possible passive voice, a handful of Latinate substitutions, section-intro headings, sentences over 30 words, and drift between common English spelling pairs (TermConsistency.yml). See the rule files for exact token lists and the documented exceptions each one carries (e.g. passive-voice adjectivals like “is done,” or compound modifiers like “well-known” that legitimately vary by position in a sentence).

Proselint

Catches grammar slips, cliches, and redundancy that a style linter doesn’t cover. Its findings carry no severity signal in the installed version, so they are always advisory here.

Term consistency (project-specific)

A generic heuristic, distinct from Vale’s fixed-list TermConsistency.yml: if a doc uses both plug-in and plug in for the same term, it flags the drift — no fixed word list required, so it catches project-specific vocabulary Vale can’t know about in advance. It skips known compound modifiers where English legitimately varies the hyphen by sentence position (well-known before a noun vs. well known after it) and anything starting with per-, since “$5 per user” and “a per-user limit” share words but aren’t the same construction.

Pulls every external (http/https) URL out of the doc and sends a HEAD request, flagging anything that doesn’t come back with a 2xx or 3xx. This is the one check that fails the exit code — a dead link is unambiguous, not a matter of taste.

Relative links between docs ([the style guide](style-guide.md)) are validated by the repo root’s scripts/validate.py, which already runs in CI on every PR — prose_lint.py does not duplicate that check.

CI

.github/workflows/validate.yml runs the readability gate as a blocking check, scoped to skills/technical-writing/** (the only directory audited against it so far — it now owns the former markdown skill’s docs too — while a repo-wide run today fails 109 of 261 skill docs, mostly pre-existing ADR references, and widening the gate is a separate cleanup, not something to force through unrelated PRs). The same workflow runs Vale, Proselint, and term consistency repo-wide as an advisory step. External link liveness runs on its own schedule in .github/workflows/docs-link-check.yml, since a transient network failure blocking an unrelated PR does more harm than a once-a-week check catching it a few days later.

Vale and Proselint silently skip when not installed, same as markdownlint-cli2 in markdown.md. The link checker always runs — a doc with a dead link fails this gate even with neither tool present.

Asking questions of users

When the document asks people things — surveys, feedback forms, research scripts, or your own clarifying questions — use question-craft.md. It distills the interviewers who get real answers: Didion’s silence, Gross’s researched empathy, Theroux’s deliberate naivety, Fallaci’s one unvarnished question, Ripley’s complicated narrative. The short version: one question per question, concrete beats abstract, never lead, and end with “what should we have asked about?”.

When unsure, ask

  • “Who is the reader — a new hire, a senior engineer, or a non-technical stakeholder?”
  • “Is this a tutorial (learn by doing) or a how-to (already knows the basics)?”
  • “What should the reader be able to do or believe by the end?”
  • “Walk me through the last time someone needed this doc — what were they stuck on?”

Elicitation tool order: see STANDARDS.md §6. Question phrasing: question-craft.md.

Self-rubric

  • One mode, not four; the right template from templates/ was the starting point.
  • The lede states the why and the payoff in the first two sentences.
  • The outline alone tells the story; headings are noun phrases.
  • The document opens with a two-person story — cast from personas.md, pair rotated; runbooks get a 2–3 sentence incident scene.
  • Every key: value pair sits on its own line.
  • Flows and decisions have a Mermaid diagram with a “what to notice” line.
  • Any multi-layer/system diagram (new or already in the doc being edited) passes the shape + muted-color distinctness hard rule in mermaid.md (palette in mermaid/gitops-patterns.md) — not a monochrome, one-shape diagram. Re-check diagrams you didn’t author this pass, not just ones you’re drawing fresh.
  • Every named model or pattern claim (continuous delivery, GitOps, blue-green, etc.) states its mechanism — what’s automatic vs. gated, and by whom — not just the label.
  • Every code block runs on the stated version; no invented APIs.
  • Questions to users follow question-craft.md — one per question, concrete, never leading.
  • Cut 20% — adjectives replaced by examples.
  • File saved under docs/<subfolder>/; images have real alt text or a deliberate empty ![]() for decorative ones.
  • scripts/lint.py exits 0 on every touched file — advisory findings are read and weighed, not blindly dismissed.