Instruction file imported from franklesniak/PSStyleGuide (
.github/instructions/docs.instructions.md). Copyright stays with the author.
Documentation Writing Style
Version: 1.6.20260904.0
Metadata
- Status: Active
- Owner: Repository Maintainers
- Last Updated: 2026-09-04
- Scope: Defines documentation standards for Markdown (
**/*.md) and Cursor Markdown rule (**/*.mdc) files in this repository, including specs, design docs, runbooks, ADRs, instruction files, and developer documentation. Does not cover code comments or inline documentation in source files. - Related: Repository Copilot Instructions
Purpose and Scope
Documentation in this repository is treated as a first-class engineering artifact, not an afterthought. Docs are expected to function as:
- A contract (what the system does, does not do, and why)
- A design record (how it works, constraints, trade-offs, failure modes)
- A maintenance tool (how to safely change and operate it without regressions)
This file governs all Markdown (**/*.md) and Cursor Markdown rule (**/*.mdc) documentation in this repository, including README.md, docs/**, ADRs, runbooks, release notes, instruction files under .github/instructions/, and Cursor project rules under .cursor/rules/.
Core Principles
- Contract-first: State behavior precisely. Prefer normative language: MUST, SHOULD, MAY, MUST NOT, SHOULD NOT.
- Deterministic and explicit: Avoid vague words like "simple," "fast," "robust," "soon," "etc." Replace with measurable claims or concrete boundaries.
- Traceable: Requirements, design decisions, and implementation details must connect via stable identifiers and links.
- Drift-resistant: Docs evolve with code; no "document later" in canonical docs.
- Explain "why," not just "what": Capture rationale and trade-offs so future changes can be made safely.
Documentation Taxonomy
- Normative PowerShell style:
STYLE_GUIDE.md(required rules and examples) - PowerShell style rationale:
STYLE_GUIDE_RATIONALE.md(rationale and trade-offs) - Developer documentation:
docs/(how to build, test, extend, and operate) - Decision records:
docs/decisions/(durable repository decisions)
If you introduce a new top-level documentation category (a bucket that represents a distinct kind of document), it MUST be added to this taxonomy section. Purely organizational subdirectories under an existing category (for example, grouping related developer docs under docs/<topic>/) are a filing convention, MUST NOT be treated as new top-level categories, and MUST NOT trigger an update to this section. When in doubt, prefer treating a new directory as a subdirectory of an existing category unless it represents a fundamentally different kind of document.
This taxonomy is specific to PSStyleGuide and MUST be updated when the repository's documentation ownership or tracked structure changes.
Canonical Source of Truth
STYLE_GUIDE.md is the authoritative source for normative PowerShell style requirements. STYLE_GUIDE_RATIONALE.md owns the rationale and trade-offs for those requirements and MUST remain aligned with the normative guide. Developer documentation under docs/ and durable decisions under docs/decisions/ MUST link to these sources rather than redefine conflicting PowerShell style requirements.
Metadata Header Block Policy (Tiered by Audience)
This repository classifies documents into two tiers based on their primary audience and content type. The tier determines whether a visible metadata header block is required.
Tier 1 — Required
The metadata header block is REQUIRED for documents whose primary purpose is governance, specification, instruction, process, runbook, or ADR-style design rationale. Tier 1 covers, for example:
- Repository-level instruction files matching
.github/instructions/*.instructions.md. The repository's.github/copilot-instructions.mdremains governed instruction content but is an explicit exception to the visible metadata-header requirement. - Root agent entry-point files such as
AGENTS.md,CLAUDE.md,GEMINI.md,.hermes.md, and future equivalents. - Cursor project rules matching
.cursor/rules/*.mdc. - ADR-style or formal design-decision records, regardless of where they live (
.github/,docs/, or another documented ADR/design-decision location). - Process and operational documents intended as durable repository policy, such as review guides, linting guides, testing guides, and release runbooks.
- READMEs whose content is itself authoritative repository policy, for example schema-policy READMEs, as opposed to reader-facing onboarding or starter content.
- Future project specifications, formal design docs, process docs, and runbooks.
The Tier 1 metadata header block consists of these fields:
- Status: Draft | Proposed | Active | Accepted | Superseded | Deprecated (REQUIRED)
- Owner: Person or team (REQUIRED)
- Last Updated: YYYY-MM-DD (REQUIRED)
- Scope: What this doc covers (and does not cover) (REQUIRED)
- Related: Links to related docs and relevant requirement IDs / ADR IDs (RECOMMENDED)
Status, Owner, Last Updated, and Scope MUST be present in every Tier 1 document. Related SHOULD be included when useful related documents, requirement IDs, ADR IDs, or policy references exist. It MAY be omitted when no meaningful related target exists. Authors MUST NOT invent placeholder or low-value links solely to populate the field.
Tier 2 — Not Required
The metadata header block is NOT REQUIRED for documents whose primary purpose is end-user onboarding, customization, community health, or shipping starter content for downstream consumers. Tier 2 documents SHOULD NOT add the metadata header block without a concrete consumer for the metadata. Tier 2 covers, for example:
- Top-level community-health files such as
README.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md, andSECURITY.md. - End-user onboarding, configuration, customization, and prompt/cookbook guides intended for repository consumers, whether downstream of a template or direct users of this repository.
- Repository-hosted request templates, when a project tracks them.
- Starter-content READMEs intended for downstream copy/paste, such as those matching
templates/**/README.md, unless the README's content itself meets the Tier 1 content criteria above.
Precedence: Tier 1 wins on content. Content classification is primary; file location and filename are secondary. A file is Tier 1 when its content is governance, specification, instruction, ADR-style, runbook, or process documentation, even when it lives under
docs/**,templates/**, or is namedREADME.md. Conversely, location alone does not promote a file to Tier 1 if the content is purely end-user-oriented. When location and content disagree, the audit must record the chosen tier and rationale in prose.Subdirectory READMEs that document files shipped for downstream copy/paste, for example
templates/**/README.md, are Tier 2 by default; promote to Tier 1 only when the README's content itself meets the Tier 1 content criteria above, not by virtue of file length or being a long-lived document.Audits must cover both
.mdand.mdcfiles and must traverse hidden directories such as.github/and.cursor/. Use a command such asfind . -type d \( -name node_modules -o -name .venv -o -name .git \) -prune -o -type f \( -name '*.md' -o -name '*.mdc' \) -printso Tier 1 files are not skipped without flooding the output with generated-directory noise (extend the-prunelist with other generated directories such asdist/,build/, or__pycache__/if your working tree contains them).
Length and durability MAY inform classification when a new document does not obviously fit either tier, but length alone does not require the metadata header block. A long Tier 2 onboarding guide remains Tier 2; a short ADR remains Tier 1.
Tier 2 Front-Matter Guidance (Optional)
Tier 2 documents MAY adopt YAML front matter later if a real tool consumes it, such as a docs site, a sync tool, or a search index. Do not introduce front matter without a concrete consumer. If adopted, fields such as last-updated SHOULD be generated from repository history (for example, from git log) where practical rather than maintained by hand.
Placement Rules for the Tier 1 Metadata Header Block
- The block MUST appear at the document level. It MUST NOT be placed inside fenced code blocks, quoted excerpts, block quotes, or examples.
- "Top of body" means the first line after any YAML front matter, or line 1 if there is no front matter. "First 30 lines" means lines 1-30 of the body, counting every line (including blank lines and HTML comment directives such as
<!-- markdownlint-disable ... -->). - If a top-level
#heading appears within the first 30 lines of the body, the block MUST be placed immediately after that heading. A single optional**Version:** ...line (with surrounding blank lines) MAY appear between the heading and the block; no other intervening content is permitted. - Otherwise, the block MUST be placed immediately after any leading
<!-- markdownlint-disable ... -->directive at the top of the body, or at the top of the body if no such directive is present. - For documents that already use a top-level
## Metadatasection to host the bullet list, that section MUST be the first##section after the H1 (and the optional**Version:** ...line, if present), and the bullet list MUST appear inside it. - This placement is compatible with the convention (see Markdown Conventions below) that Markdown files SHOULD include
<!-- markdownlint-disable MD013 -->immediately after any YAML front matter, or at the very top of the file if there is no front matter.
Synchronizing Last Updated and Version on Content Changes
This subsection applies to Tier 1 documents and to any other document that intentionally carries the metadata header block. The fields referenced in this subsection (Last Updated, and the optional Version line) are defined in the Tier 1 bullet list above; this subsection adds normative synchronization rules for those fields and does not redefine their semantics.
For this subsection:
-
The published baseline is the pre-change version already present on the branch where the change will land. For a pull request, it is the document on the pull request base branch. For a direct push, it is the document at the before revision of the pushed branch.
-
The finalization point is the last author- or agent-controlled update before the change is merged, added to an automated merge queue, or pushed directly to that branch. Metadata is evaluated between the published baseline and the finalization point. Internal topic, work-in-progress, and iteration commits are not separate published transitions. If automated merge machinery changes the target branch after the finalization point, the landed value is not a violation of this rule; correct any resulting metadata drift in a follow-up change.
-
When the change modifies the rendered content or documentation meaning of a document that carries the metadata header block, the
Last Updatedfield in the final document MUST be the current UTC date inYYYY-MM-DDform at the finalization point. -
If the document also carries a
**Version:** <major>.<minor>.<YYYYMMDD>.<revision>line, the embedded<YYYYMMDD>segment MUST match the finalLast Updatedvalue. -
Revision convention for the
<revision>segment of**Version:**:<revision>counts published updates relative to the published baseline. It is evaluated at the finalization point, not for each internal commit.- After setting
<major>.<minor>under the document's own conventions and<YYYYMMDD>to the current UTC date,<revision>MUST be0when the resulting<major>.<minor>.<YYYYMMDD>differs from the published baseline's<major>.<minor>.<YYYYMMDD>, including when no published baseline exists. - When the published baseline already carries the same
<major>.<minor>.<YYYYMMDD>at revisionN, the final<revision>MUST beN + 1. - Intra-PR iteration commits target the single correct final revision. Re-check the published baseline at the finalization point.
- Examples:
- Same-day published update that keeps the same
<major>.<minor>: published baseline1.6.20260502.0to final1.6.20260502.1. - Next-day update: published baseline
1.6.20260502.1, change finalized on 2026-05-03 UTC, to final1.6.20260503.0.
- Same-day published update that keeps the same
- This synchronization rule does not govern when
<major>or<minor>are incremented. Those segments continue to follow the document's own semantic-versioning conventions. For<revision>computation, treat**Version:**as an ordered four-segment tuple:<major>,<minor>,<YYYYMMDD>, and<revision>.<revision>is the lowest-precedence segment and MUST reset to0whenever a higher-order segment changes relative to the published baseline.
-
Exemption for trivial mechanical changes. The bump MAY be omitted when the published change does not alter rendered content or documentation meaning, including pure file-mode changes, line-ending normalization, end-of-file newline fixes, or trailing-whitespace-only fixes produced by pre-commit hooks. The trailing-whitespace exemption MUST NOT be applied when the change removes or alters a Markdown hard line break (two or more trailing spaces, or a trailing backslash, immediately before a newline), because such whitespace is rendering-significant; in that case the change alters rendered content and the bump is required.
-
This rule applies to all documents in the repository that carry the metadata header block, including but not limited to
.github/copilot-instructions.md,.github/instructions/*.instructions.md,CLAUDE.md,AGENTS.md,GEMINI.md,.hermes.md, and.cursor/rules/*.mdc.
Non-Normative Historical Artifacts
A non-normative historical artifact is a Markdown file committed to preserve provenance, such as a verbatim AI-assistant prompt, transcript excerpt, quoted source excerpt, or similar historical material used to produce or review another artifact. For these files, "preserved" means content-level fidelity; repository hooks can still normalize trailing whitespace and final newlines.
- Classification. Treat a non-normative historical artifact as Tier 2, so the metadata header block is NOT REQUIRED, unless its current, non-quoted framing independently meets the Tier 1 criteria. Copied prompts, requirements language, review text, or source excerpts inside preserved historical content do not promote the file to Tier 1. Classify the file by its current purpose and framing, not by copied historical text.
- Top-of-document label. The file MUST carry a clear top-of-document label identifying it as a non-normative historical artifact that defines no repository requirements. The H1 itself MUST include the non-normative marker; an explanatory note alone is insufficient because a
**Version:**line or metadata header block can separate the note from the H1. When a label format shows a placeholder such as<topic>, the placeholder MUST stay inside a code span or fenced block so it is not parsed as raw inline HTML. - Note placement. The explanatory note MUST appear immediately after the H1, or, when the file intentionally carries the metadata header block, immediately after that block. This follows the placement model above. Markdown files SHOULD still include the repository-standard
<!-- markdownlint-disable MD013 -->portability directive described in Markdown Conventions. - Optional metadata. The file SHOULD NOT carry the metadata header block without a concrete consumer for that metadata, and SHOULD NOT carry a standalone
**Version:**line unless a concrete consumer and synchronization convention are documented. When the metadata header block is intentionally present, authors MUST follow the placement and synchronization rules above, which govern any**Version:**line carried alongside it. A**Version:**line carried without the metadata header block has noLast Updatedfield to synchronize against and instead follows the documented synchronization convention recorded for that standalone line. Authors MUST NOT invent new metadataStatusvalues such asHistorical; use only the values allowed by the Tier 1 metadata policy. - Preserved content fencing. Authors SHOULD preserve prompts, transcripts, and excerpts inside fenced
textcode blocks. Authors MAY use amarkdownfence only when rendering and linting the preserved Markdown, including the repository's nested-Markdown check, is intentional. If the preserved content contains triple-backtick fences, authors MUST use a longer outer backtick fence. Authors MUST NOT switch to tilde fences because this repository's markdownlint configuration enforces backtick fences. - Fencing rationale. Fenced code blocks keep preserved content from being parsed as live headings or directives. For an artifact under
docs/, fenced code blocks also distinguish quotedTODO:,TBD, orFIXMEexamples from active normative text during manual authoring and review. Block quotes and other rendered quote forms are not equivalent because they can still read as live requirements. - Related sources. The file SHOULD link to canonical repository documents or inspectable public sources it relates to when they exist, such as the specification, ADR, issue, public upstream source, or quoted source it was used to produce or review. These references MUST follow the existing reproducible-source and repository self-containment rules. Required interpretation MUST NOT depend on machine-local paths, agent session routes, private repositories, internal-only resources, or any other non-public resource.
- Scope limit. This classification MUST NOT be used as a carve-out for active prompt or cookbook guides, runbooks, process docs, specifications, ADRs, or reusable operator guidance. Classify those documents by their current content under the ordinary Tier 1 and Tier 2 policy.
- Safety. Historical provenance MUST NOT override existing safety rules. Authors MUST NOT commit secrets, credentials, private-only context, personal data that should not be published, or source material that cannot be safely or lawfully included.
Compliant label and note example:
# Historical Artifact (Non-Normative): AI Review Prompt for ADR-0003
This file preserves the AI review prompt used while preparing ADR-0003. The preserved content is provenance/history only; it is not current specification, review, policy, requirements, or implementation guidance.
Writing Rules
Clarity and Structure
- Use informative headings that allow skimming.
- Prefer short paragraphs and bullet lists.
- Use tables only when they increase clarity (avoid tables for "pretty formatting").
- Every list of "things" should be complete or explicitly labeled as partial.
Normative Language
- Use MUST/SHOULD/MAY for requirements and guarantees.
- Use CAN only for capability, not obligation.
- Label assumptions explicitly as Assumption: and keep them testable.
- Scope conditional obligations. When a normative keyword constrains an action that is itself optional, explicitly scope the obligation to when that action occurs, for example, "When a document cites sources, it MUST cite only inspectable sources." This prevents readers from misreading the requirement as mandating the optional action.
- Cross-instruction-file normative-level alignment. When a document restates a normative requirement that is also defined in an applicable file under
.github/instructions/*, the document's requirement level (MUST,SHOULD,MAY, and their negations) MUST match the level used in the instruction file when the scope and context are the same, unless the document explicitly justifies a stricter or weaker level in prose immediately adjacent to the restatement. If the scope or context differs from the instruction file, the document SHOULD note that scope/context difference at the restatement. Implicit divergence (silently using a different level when the scope and context are the same as in the instruction file, with no adjacent justification) MUST NOT occur. - Intra-document normative-level consistency. Within a single document, the normative requirement level for the same keyword, field, rule, and scope MUST be consistent across sections. If two sections appear to attach different levels to the same item, reconcile the wording or explicitly explain why the scopes differ.
Status and Tense Consistency
When a tracked entry in an ADR, another decision or design record, a requirements table, or a similar status-bearing record carries a status indicating the described change is complete or realized (for example, Addressed, Implemented, Resolved, Fixed, Done, or Completed), the prose that summarizes that realized change MUST use present or past tense. For example, use "Section 25.7 defines ..." or "The spec requires ..." rather than future or pending phrasing such as "will define", "will require", or "does not yet ...". Future-tense phrasing and "not yet" phrasing remain acceptable only when the sentence explicitly describes genuinely pending work, future-scoped work, or future-triggered effects that have not occurred yet, including genuinely forward-looking ADR consequences. A status that records only that a decision was approved rather than realized, notably an ADR marked Accepted under this repository's Proposed | Accepted | Superseded | Deprecated lifecycle, does not by itself require present or past tense; match the tense to the actual state of the work. When a sentence must describe the pre-change state, scope it explicitly to that prior state (for example, "Before this change, ..." or "Prior to this revision, ...").
Examples:
Non-compliant (future tense under a completed status):
**Status:** Addressed. Section 25.7 will define the retry budget.
Compliant (realized change in present tense):
**Status:** Addressed. Section 25.7 defines the retry budget.
Compliant (explicitly scoped pre-change reference):
**Status:** Addressed. Prior to this revision, no retry budget existed; Section 25.7 defines it.
Examples
- When documenting behavior, include at least one example that shows:
- Input
- Output
- Explanation (why that output is correct)
- For edge cases, include at least one "failure or ambiguous input" example and the expected handling.
- When a code example intentionally uses an example-only helper function, method, type, module, or similarly non-obvious symbol (i.e., one invented for the example and not defined within the example or elsewhere in the same document), the example SHOULD orient the reader in at least one of these ways: label the symbol or example as illustrative, incomplete, or example-only (in surrounding prose, a language-appropriate comment, or a clear document-level or section-level note that applies to the example); or include a minimal declaration, signature, or definition. When the example-only symbol appears as a function call, method call, or type/constructor instantiation (for example, an undefined
do_thing(x),thing.do(),Widget(), ornew Widget()expression), the example MUST do at least one of these, because calls and instantiations read as runnable code more strongly than bare values. An obvious generic local variable or parameter name with clear local meaning (such astext,value,input,expected, orresult) does not by itself trigger this rule, and neither does a reference to a known real, documented API (for example, a standard-library API, documented third-party API, or repo-owned API referenced by the surrounding document), even when that API is not defined within the document.
Markdown Conventions
- Use fenced code blocks with language tags.
- Avoid trailing whitespace; keep blank lines truly blank.
- Prefer relative links within the repo (e.g.,
docs/decisions/0001-accept-in-repository-trust-root.md). - Avoid raw URLs in prose; use descriptive link text when possible.
- Markdown files in this repository SHOULD include
<!-- markdownlint-disable MD013 -->immediately after any YAML front matter (or at the very top of the file if there is no front matter), and before any other content, including badges, links, the H1 heading, and any prose. A single optional blank line MAY appear between the front matter terminator (---) and the directive for readability; blank lines are not "content" for this rule.- Placement matters: markdownlint's inline
<!-- markdownlint-disable RULE -->directive only suppresses the rule for content that follows it. Placing the directive after badges or other long lines leaves those lines unprotected when the file is processed with default markdownlint settings outside this repo. - This intentionally duplicates the repo-wide
"MD013": falsesetting in.github/workflows/.markdownlint.jsonc. - This is a deliberate portability convention for cases where a file is read or processed outside this repository, for example:
- sent to an external LLM for analysis or editing
- viewed by a tool that applies default markdownlint settings
- imported into another project
- The per-file directive helps ensure the file is interpreted with the same expectation that long lines (URLs, code samples, single-line paragraphs, tables) are acceptable.
- Per-file
<!-- markdownlint-disable RULE -->directives MUST NOT contradict the configuration in.github/workflows/.markdownlint.jsonc; their purpose is portability, not local override. - When a contributor wants an additional rule disabled, update
.github/workflows/.markdownlint.jsoncfirst. Per-file directives are only for mirroring repo-wide configuration where default enforcement would harm portability.
- Placement matters: markdownlint's inline
- Code-fence info strings MUST contain only a single language tag (e.g.,
powershell,text,json,bash). Do NOT embed file paths, URLs, or other metadata in the info string (for example,powershell name=src/Foo.ps1 url=https://...#L1-L9is not allowed). To cite the source of a code excerpt, place a line of the formSource: [`relative/path` (lines <start>-<end>)](relative/path#L<start>-L<end>).in prose immediately above the fence (for example,Source: [`src/Foo.ps1` (lines 1-9)](src/Foo.ps1#L1-L9).). This keeps the language tag standard, preserves syntax highlighting across Markdown renderers, and reinforces the existing rule to avoid raw URLs in prose.
Reproducible source citations
When committed Markdown documentation, including analysis write-ups, research notes, review artifacts, and other durable Markdown records, cites sources or evidence, it MUST cite only sources another reader can inspect or reproduce from repository contents or clearly linked public references. This subsection makes the repository's self-containment requirement concrete for Markdown citations; it does not create a separate source-of-truth rule.
When citing sources or evidence, Markdown authors MUST NOT use machine-local or ephemeral filesystem paths outside the repository, including temporary caches, agent work directories, per-host checkout directories, or tool cache paths. Authors MUST NOT use internal-only session/tool routes, including agent-internal skill names, session-scoped MCP resources, local connector aliases, or other non-public helper routes, as authoritative sources or evidence. Instead, cite stable sources such as repo-relative paths to committed files, canonical public documentation, or upstream repository references, following this file's existing link conventions.
This rule applies when a path or route is used as a citation, source, or evidence reference. It does not ban legitimate path examples, command examples, repo-relative links, or environment-specific paths when those paths are the documented subject.
Where a claim rests on a local check, describe the check generically and reproducibly, for example, "local CLI help output confirmed ..." or "local validation output confirmed ...", without naming ephemeral paths or session-only tooling.
Fenced code blocks inside list items
When a bullet item includes a fenced code block followed by continuation prose that should render as part of the same bullet, the fence and the continuation prose MUST both be indented to the column after the bullet marker. For this repository's unordered - bullets, that is 2 spaces after the marker (markdownlint's MD007 default, which is not overridden in .github/workflows/.markdownlint.jsonc). Within this pattern, blank lines between the bullet text, fence, and continuation prose SHOULD remain truly blank. Mixing an unindented fence with an indented continuation paragraph MUST NOT occur, because CommonMark-style renderers can end the list item at the unindented fence and then treat the continuation paragraph as a disconnected block.
When no continuation prose follows the fence before the next sibling bullet or section, a fenced code block that is intended as a standalone example associated with the preceding bullet MAY be left at 0 indent. This preserves the existing repository convention used in standalone style-guide examples. To make the fence render as part of the bullet item, use the 2-space-indented pattern (per the MUST rule above).
MUST-compliant example:
- Generate the local report.
```bash
./scripts/write-report.sh
```
The command prints the report path after it completes.
MUST NOT example:
- Generate the local report.
```bash
./scripts/write-report.sh
```
The command prints the report path after it completes.
Note: A renderer can end the list item at the unindented fence and render the continuation as a separate paragraph.
Shell command portability
- Scope. Applies to fenced
bash/shshell-command examples in this repository's Markdown documentation that a reader is expected to copy and run on a Unix-like target environment (Linux, macOS, FreeBSD, WSL, or Git Bash on Windows). Applies to fencedtextexamples only when the surrounding prose clearly presents the block as copyable shell commands or a shell session, not when the block is command output, logs, diagnostics, or plain text. Applies to inline-prose backtick references only when the surrounding prose clearly presents the inline content as a copyable shell command, not when it is command output, a diagnostic fragment, or a tool-name mention. Native PowerShell examples are out of scope and SHOULD use apowershellfence. Examples that surrounding prose explicitly labels as GNU-only, BSD-only, Bash-only, PowerShell-only, or otherwise platform-specific are allowed. - Rules.
- For alternation in
grep, MUST use extended regex (grep -E "P1|P2|P3") or multiple-epatterns (grep -e P1 -e P2 -e P3). MUST NOT use basic-regex\|alternation, which is a GNU extension and is not reliably supported in BSDgrep(the macOS default). - For in-place edits with
sed, prefer the attached non-empty backup-suffix form (for example,sed -i.bak 'SCRIPT' FILE), which works on both GNU and BSDsedand produces a.bakbackup file that surrounding prose SHOULD note so readers know to keep, delete, or.gitignoreit. Alternatives are to pipesedoutput to a temporary file and rename it over the original (which can drop file permissions, ownership, or extended attributes), or to explicitly state in surrounding prose that the example uses GNUsed -isemantics (where the suffix is optional and, when supplied, must be attached with no space) or BSDsed -i ''semantics (where an empty suffix is supplied as a separate argument so no backup is written; GNUsedwould misparse the''as the script). The baresed -iform (no suffix) and the separate-argumentsed -i ''form are not portable across both. - Avoid Bash-specific syntax (
[[ ... ]],(( ... )),<<<here-strings,mapfile/readarray, process substitution<(...)) in examples that should also run undersh,dash, or other POSIX-style shells. When Bash-specific syntax is required, the code fence MUST bebash(notsh) and surrounding prose MUST note the Bash dependency. - When a command is intentionally GNU-only, BSD-only, Bash-only, PowerShell-only, or otherwise platform-specific, surrounding prose MUST explicitly label it so readers know which
grep/sed/shell variant is required. - When a shell-command example depends on a third-party CLI tool that is not part of standard installations on the target environments listed in the Scope. paragraph (for example,
rg/ripgrep,fd/fdfind,jq,yq,eza/exa,bat,delta, or similar), the example MUST either document a portable fallback that uses tools available without extra installs (for example,grep -E -R,find,awk,sort, oruniq) or tools already established as prerequisites in the surrounding document (for example,git grep -Ewhengitis already a documented prerequisite, sincegititself is not a default install on every target environment listed in the Scope. paragraph), or explicitly label the tool dependency in surrounding prose so readers know to install it before running the example. Examples in downstream-facing setup, optional-configuration, template-update, and onboarding guides SHOULD prefer the portable form by default because adopters may be on any platform without prior tool installation.
- For alternation in
This template ships maintainer-facing scripts, onboarding steps, and verification commands intended for cross-platform use. macOS and BSD-family defaults for grep and sed differ from GNU tools, so a command that works on a Linux CI runner can fail on a maintainer's macOS workstation. The third-party-tool guidance above applies even when an example is intended only for maintainers, because maintainers also work across macOS, Windows, WSL, Git Bash, and minimal Linux environments.
Repository URL and placeholder safety
PSStyleGuide does not track request-template files or a placeholder-check workflow. Documentation MUST NOT attribute a live convention or enforcement guarantee to an absent repository source.
- Tracked Markdown SHOULD use relative links for repository-internal targets.
- When a durable document requires an absolute PSStyleGuide link, the link MUST use
https://github.com/franklesniak/PSStyleGuide/and MUST identify a real target. - The literal example
https://github.com/OWNER/REPO/...MAY appear only as clearly labeled didactic text in an inline code span or fenced code block. It MUST NOT appear as a live unresolved link target. - Placeholder text in copyable shell examples MUST remain literal and safe. The shell rules below prohibit command-substitution metacharacters in such placeholder text.
.github/instructions/docs.instructions.mdowns these documentation rules.
Template-substitution marker boundaries and replacement surfaces
When a template-substitution marker is embedded in a structured Markdown construct, such as an HTML comment, link target, fenced code block, or table cell, authors MUST design the substitution boundary so the post-substitution text is syntactically and semantically meaningful when read in isolation. Authors SHOULD prefer replacing the whole enclosing construct, for example the entire HTML comment line, over replacing only a substring inside that construct.
Authors who add or modify a template-substitution marker MUST keep every surface that references the marker consistent with it. This includes any automated substitution helper or allowlist, any setup-guide snippets that perform the substitution, any manual find-and-replace instructions, and any regression tests that exercise the substitution.
For example, this marker is embedded in an HTML comment:
<!-- TODO: Replace with your support contact address -->
If the substitution covers only TODO: Replace, the resulting comment is syntactically valid but semantically incoherent:
<!-- Support contact configured with your support contact address -->
The safer boundary is the whole comment line, so the substituted result is meaningful as a standalone construct:
<!-- Support contact configured -->
Decision Record Standards
Decision records exist to prevent re-litigating decisions.
- File naming pattern:
docs/decisions/NNNN-short-title.md - Decision records MUST include:
- The single Tier 1 Status metadata field. For decision records, its value MUST be Proposed | Accepted | Superseded | Deprecated. A decision record MUST NOT add a separate narrative status field or section.
- The Tier 1 Owner, Last Updated, and Scope metadata fields.
- Context
- Decision
- Consequences: positive and negative
- Alternatives Considered
- Date: YYYY-MM-DD
Published legacy decision records that predate this lifecycle rule MAY retain their existing status representation while their bytes remain unchanged. The next change to such a record MUST migrate it to the single Tier 1 Status metadata field and MUST remove each separate narrative status field or section.
ADRs MUST be short and specific. If an ADR grows into a design doc, split it.
Requirements Documentation Standards
Customize this section for your project. The patterns below are recommendations for projects that track formal requirements.
When writing or updating requirements in specification documents:
- Each requirement SHOULD have a stable identifier (example pattern):
PROJ-REQ-001,PROJ-REQ-002, ...
- Each requirement MUST be phrased as a testable statement:
- "The system MUST …"
- Each requirement MUST define required behavior, limits, defaults, and ownership without unresolved placeholder markers; see the placeholder-marker rule in Prohibited Patterns.
- Each requirement entry SHOULD include:
- Rationale: why it exists
- Acceptance Criteria: objective checks (bullets)
- Priority: P0/P1/P2 (or repo standard)
- Verification: how it will be tested (unit/integration/e2e/manual)
Avoid "implementation leakage" in requirements unless the constraint is truly required (e.g., "MUST NOT store secrets at rest").
Traceability to Implementation
For each non-trivial requirement, maintain a "Traceability" note that points to:
- An ADR (if it drove a durable decision)
- The implementation module/package path
- The primary test file(s)
This can be minimal, but it SHOULD exist for high-priority requirements.
Design Documentation Standards
Design docs SHOULD be written to survive refactors. They describe architecture and invariants, not incidental code structure.
A design section SHOULD include:
- Context: problem statement and why now
- Goals / Non-Goals: explicit boundaries
- Key Constraints: security, privacy, performance, portability, cost, toolchain
- System Overview: components and responsibilities
- Data Flow: what moves where, in what format, and why
- Interfaces and Contracts: inputs/outputs, error semantics, validation rules
- Failure Modes: what can fail, detection, recovery, and user-visible behavior
- Alternatives Considered: at least 2 credible alternatives and why rejected
- Open Questions: enumerated, each with an owner or next step
Design sections SHOULD reference requirement IDs they satisfy when applicable.
Runbook Standards
Runbooks MUST optimize for "2 a.m. usability."
- Symptoms: what the operator sees
- Immediate Triage: safe checks first
- Diagnostics: commands/steps with expected output patterns
- Mitigations: reversible actions first; call out risks explicitly
- Escalation: when to stop and who to contact
- Postmortem Notes: what to capture for later analysis
All commands in runbooks MUST be copy/paste safe and must not destroy data without an explicit warning.
Placeholder text embedded inside a fenced shell example MUST NOT contain shell metacharacters that the target shell would interpret. In fenced bash examples, runbook authors MUST NOT embed backticks or $() command-substitution syntax inside placeholder text, including inside double-quoted strings, because a user who pastes the command before substituting the placeholder may cause the shell to attempt command substitution instead of producing a clean unresolved-placeholder error. When a placeholder needs to refer to a command name, command output, or another identifier that benefits from monospace formatting, the inline-code reference MUST be kept in the surrounding prose, not inside the placeholder string in the code fence. When practical, runbook authors SHOULD validate shell examples in a safe context with the placeholder still present and confirm they fail as literal unresolved-placeholder errors rather than attempting unintended substitution, expansion, redirection, or command execution.
Change Hygiene and "Definition of Done" for Docs
A change is not complete unless docs remain correct.
For any PR/commit that changes externally observable behavior, at least one of the following MUST be updated:
- spec docs (if any contract/behavior/design constraints changed)
- a design doc section (if architecture/invariants changed)
- an ADR (if a durable decision changed)
- a runbook (if operational behavior changed)
- README / developer docs (if onboarding/build/test steps changed)
Before merging, verify:
- No contradictions across docs
- Examples still match actual behavior
- Open questions are either resolved or explicitly tracked
Mirrored Excerpts and Full-File Consistency Audits
Background. Mirrored excerpts that drift from their source are a recurring source of documentation inconsistency, and partial fixes that touch only reviewer-flagged lines can leave parallel copies of the same excerpt inconsistent and force repeat review rounds for the same root cause.
-
Mirrored excerpt policy. When a Markdown document includes a literal excerpt, whether inline or fenced, that mirrors a real load-bearing file in the repository (for example,
.pre-commit-config.yaml, a JSON schema, a workflow YAML, or a script), the excerpt MUST either:- Be a verbatim copy of the corresponding source-of-truth lines for the included literal content, without paraphrasing or selectively rewriting copied literals; if the excerpt is intentionally partial, the surrounding prose MUST make that scope clear and MUST NOT imply the excerpt is the complete file or complete configuration, OR
- Be replaced with a one-line pointer to the real file (for example,
See [.pre-commit-config.yaml](../../.pre-commit-config.yaml)from a citing document under.github/instructions/; adjust the relative path so it resolves from the citing document's own location) with no inline copy of the excerpt content.
When the verbatim-copy path is taken for a fenced or multi-line excerpt, authors SHOULD use the file's existing
Source:citation convention from theMarkdown Conventionssection to anchor the excerpt to its source-of-truth file with a line-range link. -
Full-file audit on consistency fixes. When fixing a code/doc consistency issue in a mirrored excerpt that was flagged on specific lines, the fix MUST audit the entire mirrored excerpt block and the affected Markdown file's other excerpts of the same source-of-truth file for all literal occurrences of the old pattern, not just the lines explicitly mentioned by the reviewer. The fix is not complete until every literal in every mirrored excerpt of the affected source-of-truth file within that Markdown file matches the source of truth.
Prohibited Patterns
- Unresolved requirement or specification content in normative or definitional text. Prohibited markers include, but are not limited to:
TODO: document later,TODO,FIXME, andXXXwhen used as unresolved requirement content.TBD,to be determined,(default ... to be determined), and other parentheticalto be determineddefaults inside requirement clauses.- Bare
unspecifiedorleft to the implementerlanguage when it leaves required behavior, limits, defaults, or ownership unresolved without an explicit**Open Question:**,**Assumption:**, measurable bound, or cross-reference. Replace these patterns with one of: - A measurable value, such as
(default 10 seconds; SHOULD be configurable). - An explicit
**Open Question:**labeled entry. - An explicit
**Assumption:**labeled entry. - A cross-reference to another requirement or section that defines the value. This rule applies to unresolved requirements/specification content only. It does not ban legitimate template-substitution placeholders, didactic examples, migration notes, or code-comment TODO examples elsewhere in the repository.
- Remediate normative occurrences with a measurable value, an explicit
**Open Question:**entry, an explicit**Assumption:**entry, or a cross-reference to another requirement. Fenced examples, HTML comments, changelog history, and clearly labeled didactic examples are allowed only when context makes clear that they are not unresolved repository requirements. - Contradictory statements between the spec and other docs
- Vague guarantees without measurable definitions
- Unowned open questions ("someone should figure out…")
AI Authorship Expectations
When generating or editing docs:
- Prefer correctness over eloquence.
- Do not invent requirements, interfaces, or behavior. If unknown, label as Open Question.
- Keep language neutral and engineering-focused; avoid marketing tone.