diff --git a/docs/docs-format.md b/docs/docs-format.md index d43465c..2deeee5 100644 --- a/docs/docs-format.md +++ b/docs/docs-format.md @@ -2,13 +2,13 @@ This is the complete specification for architecture decision records (ADRs) and implementation plans checked by `docs check`. It is standalone: a file that follows this document is valid without running the tool. The words MUST, MUST NOT and MAY are used in their RFC 2119 sense. -The ADR body follows the core sections of [MADR 4](https://adr.github.io/madr/). The frontmatter, naming and cross-reference rules are specific to this format. +The ADR body uses the [MADR 4](https://adr.github.io/madr/) layout; `Consequences` is mandatory. The frontmatter, naming and cross-reference rules are specific to this format. ## 1. Files and directories - ADRs live in `docs/adr/`, plans in `docs/plans/`, relative to the repository root. Both are flat: subdirectories are not scanned and are errors. - A missing directory is treated as empty. -- `README.md` in either directory is skipped. Every other entry MUST be a regular file whose name matches section 2 and ends in `.md`. Any other file, directory or name is an error. There are no other exceptions: no templates and no companion files; every `.md` file is a record. +- Entries whose name starts with `.` (such as `.DS_Store` or editor swap files) are ignored. `README.md` in either directory is skipped. Every other entry MUST be a regular file whose name matches section 2 and ends in `.md`. Any other file, directory or name is an error. There are no other exceptions: no templates and no companion files; every `.md` file is a record. - Files are UTF-8 without a byte order mark. LF and CRLF line endings are both accepted. ## 2. Names and IDs @@ -19,12 +19,14 @@ A record file is named `YYYYMMDDTHHMMSSmmmZ-slug.md`, for example `20261023T2045 - The slug is lowercase `a-z0-9` words joined by single hyphens: no leading, trailing or doubled hyphen, 1 to 80 characters. - The filename without `.md` (the stem) is the record's permanent ID. Never rename a record, even to fix its slug; change the title instead. - IDs MUST be unique across `docs/adr` and `docs/plans` (compared by the timestamp-and-slug stem). -- Tools create files exclusively (`O_EXCL`). If a file with the same millisecond exists, the creator advances by 1 ms until it is free. +- Tools create `docs//.md` exclusively (`O_EXCL`) and advance the timestamp by 1 ms on `EEXIST`, or while any record in either directory has the same stem. This is tool behavior and does not affect validity. ## 3. Frontmatter Every record starts with a YAML frontmatter block: the first line is exactly `---`, and the block ends at the next line that is exactly `---`. The block MUST be a mapping containing exactly the keys listed for the record type, no more, no fewer, each once. Key order is free. Empty lists are written `[]`. A key with no value (null) is an error. +Scalar values are read as their literal text, whether quoted or not; for example, `date` may be quoted or unquoted, and `title: 2026` is the string `2026`. Anchors, aliases, explicit tags and multiple YAML documents are errors. "Single line" means the parsed value contains no line break. + ### 3.1 ADR keys | Key | Type | Rule | @@ -60,12 +62,13 @@ Every list item is a string. No list may contain duplicates. Which kinds of refe | --- | --- | | `supersedes`, `superseded_by`, plan `adrs` | Repo-root paths to ADRs (`/docs/adr/.md`). | | `related` | Repo-root paths to any record (`/docs/adr/...` or `/docs/plans/...`). | -| `tracking` (ADR and plan) | `http` or `https` URLs. | +| ADR `tracking` | `http` or `https` URLs, or repo-root paths to plans (`/docs/plans/.md`). | +| Plan `tracking` | `http` or `https` URLs. | | `implemented_by`, `completion` | `http` or `https` URLs (pull request, commit, release), or repo-root paths to existing files. | -See section 7 for what a valid path or URL is. A record MUST NOT reference itself. +See section 7 for what a valid path or URL is. A record MUST NOT reference itself. An ADR whose delivery is tracked by a plan lists the plan in `tracking`; other record-to-record links go in `related`. -Metadata values MUST be durable: a bare issue number such as `#123` and the placeholders "this PR", "this pull request" or "this commit" (any case) are rejected. Use a full URL. +Reference lists MUST be durable: an item that is a bare issue number such as `#123` or `owner/repo#123`, or one of the placeholders "this PR", "this pull request" or "this commit" (any case), is rejected. Use a full URL. This applies to whole list items in frontmatter reference lists only; `title`, `summary` and body text are not inspected for it. ## 4. Status @@ -108,38 +111,44 @@ Plan `status`: - If ADR A lists B in `supersedes`, then B MUST list A in `superseded_by`, and the reverse. Both records exist and are ADRs. - Supersession MUST NOT form a cycle. - The superseding record (A, the one whose `supersedes` lists the other) MUST have status `accepted`, or `superseded` or `deprecated` (it was accepted once and has since moved on). It MUST NOT be `proposed` or `rejected`. +- While a replacement is `proposed`, list the ADR it would replace in `related`. On acceptance, in the same change, move it to `supersedes` and set the target's `status: superseded` and `superseded_by`. - A superseded record keeps its original `date` and filename; only its `status` and `superseded_by` change. ## 6. Body -The body is Markdown (CommonMark) after the frontmatter. Headings inside fenced code blocks or HTML comments do not count. Required section headings are matched case-sensitively and exactly, at level 2 (`##`). A required section MUST appear exactly once and contain non-empty prose: at least one paragraph, list item or table cell of text. HTML comments and code (fenced, indented or inline) do not count as prose. Extra sections are allowed anywhere. +The body is Markdown (CommonMark) after the frontmatter. Headings inside fenced code blocks or HTML comments do not count. Required section headings are matched case-sensitively and exactly, at level 2 (`##`). A section runs until the next heading of level 2 or lower, so its subsections belong to it. A required section MUST appear exactly once and contain non-empty prose: at least one paragraph (including paragraphs inside blockquotes), list item or table cell of text. HTML comments and code (fenced, indented or inline) do not count as prose. Extra sections are allowed anywhere. + +Heading text is the raw source after the `#` markers, trimmed, with any closing `#` run removed; it is compared byte for byte, with no inline-markup rendering. Setext headings are read as the equivalent ATX heading. ### 6.1 ADR -- The first heading is an H1 whose text equals `title` exactly. There is one H1. -- Required H2 sections: `Context and Problem Statement`, `Considered Options`, `Decision Outcome`, `Consequences`. +- The first heading is an H1 whose heading text equals `title`. There is one H1. +- Required H2 sections: `Context and Problem Statement`, `Considered Options`, `Decision Outcome`. +- `Decision Outcome` MUST contain exactly one `### Consequences` (H3) subsection with non-empty prose, and prose of its own outside that subsection. A level-2 `## Consequences` is an error. - Optional MADR sections (for example `Decision Drivers`, `Pros and Cons of the Options`, `More Information`) MAY be added. ### 6.2 Plan - The first heading is a non-empty H1: the plan title. There is one H1. - Required H2 sections: `Scope`, `Steps`, `Validation`. -- `Outcome` is required when `status` is `completed` or `cancelled` and MUST NOT appear otherwise. +- `## Outcome` is optional for every status. Closed plans still require `completion` (section 4). ### 6.3 Placeholders -A record is invalid if it still contains unfinished template text: `{{` or `}}` anywhere outside code, or the whole words `TODO` or `TBD` in prose outside code. +A record is invalid if it still contains the exact marker `{{docs:todo}}` anywhere outside code. `docs new` writes this marker into each section it leaves for you to fill; replace it with content. No other text is treated as a placeholder, so words like TODO or TBD are fine. To mention the marker itself, put it in inline or fenced code. ## 7. References and links -A reference is either a repo-root path or a URL. This applies to frontmatter reference lists and to every Markdown link and image destination in the body (inline, reference-style and autolinks; bare URLs in text are not checked). +A reference is either a repo-root path or a URL. This applies to frontmatter reference lists and to every Markdown link and image destination in the body (inline, reference-style and autolinks; bare URLs in text are not checked). Link rules apply only to Markdown link destinations: forge autolink shortcuts in plain text (`#123`, `owner/repo#123`, `@user`, commit SHAs) and raw HTML (``, ``) are not inspected. Every link reference definition is checked, whether or not it is used. Repo-root path: -- Starts with a single `/` and is relative to the repository root, for example `/docs/adr/20261023T204512345Z-use-example-queue.md`. +- Starts with a single `/` and is relative to the repository root, for example `/docs/adr/20261023T204512345Z-use-example-queue.md`. A leading `//` (protocol-relative `//host/...`) is rejected. +- Repo-root links render correctly on Git forges (GitHub, Gitea) but not in every Markdown viewer. +- The destination is percent-decoded before the existence check (`%20` is a space). A `?query` is an error. - The target MUST exist as a regular file inside the repository. Symlinks that resolve outside the repository are rejected. - In body links only, a `#fragment` suffix is allowed; the fragment is not verified. In frontmatter, a fragment is an error. -- Rejected: relative paths (`foo.md`, `./foo.md`), any `..` segment, backslashes, and machine paths (`/Users/...`, `/home/...`, `C:\...`, `file:` URLs). +- Rejected: relative paths (`foo.md`, `./foo.md`), any `..` segment and backslashes. Other non-existent targets, including machine paths such as `/Users/...`, `/home/...` or `C:\...`, fail the existence rule; `file:` URLs fail the scheme rule. URL: @@ -163,7 +172,7 @@ It reports violations of everything above: - missing, malformed or unterminated frontmatter; unknown, missing, duplicate or null keys; wrong types or values; `date` not matching the filename; - status/implementation/field combinations from section 4; - supersession problems from section 5, and self-references; -- missing, repeated or empty required sections; H1 problems; placeholders; +- missing, repeated or empty required sections; H1 problems; `{{docs:todo}}` markers; - invalid, unresolvable or non-durable references and links. It does not judge whether a decision is good, whether a record should have been written, or whether a link's fragment exists. @@ -203,7 +212,7 @@ Jobs currently run inline and block requests. Chosen option: Example Queue, because it decouples jobs from requests. -## Consequences +### Consequences * We operate one more service. ```