adr: align the ADR tooling with MADR 4 exactly (docs/decisions/, YAML frontmatter, full MADR template) #483

Closed
opened 2026-09-11 01:08:55 +00:00 by hexajon · 3 comments
Owner

Problem

madt_adr / madtea adr only knows the house layout: docs/adr/, a # ADR NNNN - title heading, - Status: / - Date: / - Tracking: bullet lines, and empty Context / Decision / Consequences sections (internal/service/adr/adr.go:422-434). The check action assumes the same layout.

I am standardising every sixfold.space repo on MADR 4 (https://adr.github.io/madr/), and I want the tooling to produce and check MADR exactly, not a house dialect of it. Today madt_adr refuses a repo that has no docs/adr/ tree, so a MADR repo gets no numbering and no drift check.

Decision

MADR 4 is the format. The tool follows the spec, not the other way round.

What changes

Layout

  • Directory: docs/decisions/ (the MADR 4 default). docs/adr/ stays supported for existing trees; detect which one the repo carries. A repo with both is an error.
  • Filename: NNNN-kebab-title.md, four-digit zero-padded, monotonic, never reused. Unchanged.
  • Index: MADR 4 keeps no mandatory index file. Keep writing the index row when a README.md exists in the tree, skip when it does not, and make check tolerate both.

Template written by adr new (the MADR 4 full template, verbatim section names):

---
status: proposed
date: YYYY-MM-DD
decision-makers: 
consulted: 
informed: 
---
# {title}

## Context and Problem Statement

## Decision Drivers

## Considered Options

## Decision Outcome

### Consequences

### Confirmation

## Pros and Cons of the Options

## More Information
  • status values are the MADR set: proposed | rejected | accepted | deprecated | superseded by ADR-NNNN.
  • Optional tracking= lands under ## More Information as a link line, not in the frontmatter, because MADR frontmatter has no tracking key.
  • The heading is the bare title. The number lives in the filename only, as in MADR.

adr check

  • Parse the YAML frontmatter. Fail on a missing or unknown status, a missing or malformed date, and a superseded by that names a record that does not exist.
  • Keep the existing checks: unique numbers, resolvable intra-tree links, file/index bijection when an index exists.
  • Fail when a record with status: superseded by ... has no corresponding pointer back from the newer record (bidirectional supersedence).

Numbering

  • Unchanged: next number resolved from the origin default branch, offline=true fallback.

Acceptance criteria

  • madtea adr new <slug> in a repo with docs/decisions/ writes a file that matches the MADR 4 full template above byte-for-byte apart from the filled title and date.
  • madtea adr check passes on a clean MADR 4 tree and fails, naming the file, on each of: unknown status, missing date, dangling superseded by, one-way supersedence, duplicate number, broken link.
  • Existing docs/adr/ trees in this repo and in cheshirecube-cloud-infra still pass check unchanged.
  • madt_help topic=adr and the tool description describe both layouts and name MADR 4 as the format.
  • Tests cover both layouts.

Out of scope

  • Migrating this repo's own docs/adr/ tree to MADR. Separate issue if I want it.
  • Generating an index or summary document from frontmatter. Separate issue.
## Problem `madt_adr` / `madtea adr` only knows the house layout: `docs/adr/`, a `# ADR NNNN - title` heading, `- Status:` / `- Date:` / `- Tracking:` bullet lines, and empty Context / Decision / Consequences sections (`internal/service/adr/adr.go:422-434`). The check action assumes the same layout. I am standardising every sixfold.space repo on MADR 4 (https://adr.github.io/madr/), and I want the tooling to produce and check MADR exactly, not a house dialect of it. Today `madt_adr` refuses a repo that has no `docs/adr/` tree, so a MADR repo gets no numbering and no drift check. ## Decision MADR 4 is the format. The tool follows the spec, not the other way round. ## What changes **Layout** - Directory: `docs/decisions/` (the MADR 4 default). `docs/adr/` stays supported for existing trees; detect which one the repo carries. A repo with both is an error. - Filename: `NNNN-kebab-title.md`, four-digit zero-padded, monotonic, never reused. Unchanged. - Index: MADR 4 keeps no mandatory index file. Keep writing the index row when a `README.md` exists in the tree, skip when it does not, and make `check` tolerate both. **Template written by `adr new`** (the MADR 4 full template, verbatim section names): ``` --- status: proposed date: YYYY-MM-DD decision-makers: consulted: informed: --- # {title} ## Context and Problem Statement ## Decision Drivers ## Considered Options ## Decision Outcome ### Consequences ### Confirmation ## Pros and Cons of the Options ## More Information ``` - `status` values are the MADR set: `proposed | rejected | accepted | deprecated | superseded by ADR-NNNN`. - Optional `tracking=` lands under `## More Information` as a link line, not in the frontmatter, because MADR frontmatter has no tracking key. - The heading is the bare title. The number lives in the filename only, as in MADR. **`adr check`** - Parse the YAML frontmatter. Fail on a missing or unknown `status`, a missing or malformed `date`, and a `superseded by` that names a record that does not exist. - Keep the existing checks: unique numbers, resolvable intra-tree links, file/index bijection when an index exists. - Fail when a record with `status: superseded by ...` has no corresponding pointer back from the newer record (bidirectional supersedence). **Numbering** - Unchanged: next number resolved from the origin default branch, `offline=true` fallback. ## Acceptance criteria - `madtea adr new <slug>` in a repo with `docs/decisions/` writes a file that matches the MADR 4 full template above byte-for-byte apart from the filled title and date. - `madtea adr check` passes on a clean MADR 4 tree and fails, naming the file, on each of: unknown status, missing date, dangling `superseded by`, one-way supersedence, duplicate number, broken link. - Existing `docs/adr/` trees in this repo and in cheshirecube-cloud-infra still pass `check` unchanged. - `madt_help topic=adr` and the tool description describe both layouts and name MADR 4 as the format. - Tests cover both layouts. ## Out of scope - Migrating this repo's own `docs/adr/` tree to MADR. Separate issue if I want it. - Generating an index or summary document from frontmatter. Separate issue.
Author
Owner

Scope addition for adr check: undecided records must be tracked by issues.

  • An ADR is undecided when its status is not one of accepted, rejected, deprecated, or superseded by ADR-NNNN.
  • I add one house frontmatter key on top of MADR 4: impacts: [repo, repo], the repos the decision constrains. Empty means the record's own repo only.
  • An undecided ADR must link, under ## More Information, an open issue in the top-level repo and an open issue in every repo named in impacts. check fails when a link is missing, when a linked issue is closed, or when a repo in impacts has no linked issue.
  • When the status moves to a decided value, check requires those issues closed and the closing comment to cite the ADR.
  • This needs the forge, not only file parsing. check already runs in a repo with an origin, so it resolves issue state through the same client. A --offline run skips the issue checks and says so.
  • Which repo is "top-level" is a per-workspace setting (adr.top-level-repo in git config), not a hardcoded name. Unset means the record's own repo.

Acceptance criteria added: one test per failure mode above, plus one for the offline skip.

Scope addition for `adr check`: undecided records must be tracked by issues. - An ADR is undecided when its `status` is not one of `accepted`, `rejected`, `deprecated`, or `superseded by ADR-NNNN`. - I add one house frontmatter key on top of MADR 4: `impacts: [repo, repo]`, the repos the decision constrains. Empty means the record's own repo only. - An undecided ADR must link, under `## More Information`, an open issue in the top-level repo and an open issue in every repo named in `impacts`. `check` fails when a link is missing, when a linked issue is closed, or when a repo in `impacts` has no linked issue. - When the status moves to a decided value, `check` requires those issues closed and the closing comment to cite the ADR. - This needs the forge, not only file parsing. `check` already runs in a repo with an origin, so it resolves issue state through the same client. A `--offline` run skips the issue checks and says so. - Which repo is "top-level" is a per-workspace setting (`adr.top-level-repo` in git config), not a hardcoded name. Unset means the record's own repo. Acceptance criteria added: one test per failure mode above, plus one for the offline skip.
Author
Owner

Implementation spec

This comment is the complete spec. Where it differs from the template sketch in the issue body, this comment wins. The earlier comment about undecided records is folded in here (section 6).

0. Source of truth

MADR 4.0.0, upstream adr/madr at tag 4.0.0. Three upstream files matter. Vendor them verbatim under internal/service/adr/madr/ with a LICENSE note (MIT OR CC0-1.0, the upstream dual licence):

Upstream file Blob sha Used by
template/adr-template-bare.md 26f6598f70239f173239290beb7aa5fb71483f52 adr new writes this, with substitutions (section 3)
template/adr-template.md 3dea2440cae85e408bd281a1a5b14399f0d21b4a adr init copies it as the explained template
template/0000-use-markdown-architectural-decision-records.md d7c7b10b63ffe4a71aa4aaf9458f45a9fcd0e412 adr init writes it as record 0000

Verbatim adr-template-bare.md, the exact bytes adr new starts from:

---
status:
date: 
decision-makers: 
consulted: 
informed: 
---

# <!-- short title, representative of solved problem and found solution -->

## Context and Problem Statement



## Decision Drivers

* <!-- decision driver -->

## Considered Options

* <!-- option -->

## Decision Outcome

Chosen option: "", because

### Consequences

* Good, because
* Bad, because

### Confirmation



## Pros and Cons of the Options

### <!-- title of option -->

* Good, because
* Neutral, because
* Bad, because

## More Information


Upstream conventions I follow exactly: directory docs/decisions/, filename NNNN-title-with-dashes.md, status grammar proposed | rejected | accepted | deprecated | superseded by ADR-0123 (identifier only, no link, per upstream 4.0.0-beta change #150), status value quoted as a YAML string (upstream #91), list bullets are *.

1. Layout detection

  • docs/decisions/ exists: MADR mode. Sections 2 to 7 apply.
  • docs/adr/ exists and docs/decisions/ does not: legacy mode. Today's behaviour, unchanged.
  • Both exist: every action fails with two ADR trees: docs/adr/ and docs/decisions/; keep one.
  • Neither exists: new and check fail and point at adr init.
  • A record is a file matching ^[0-9]{4}-[a-z0-9-]+\.md$. Any other file in the tree (README.md, adr-template.md, adr-template-bare.md) is ignored by numbering and by every check.

2. New action init

madt_adr action="init" / madtea adr init. Creates docs/decisions/ with four files. Refuses when the directory already exists.

  1. 0000-use-markdown-architectural-decision-records.md: the upstream file, with this frontmatter prepended so it passes check:
    ---
    status: "accepted"
    date: <today>
    decision-makers: [<decision_makers arg, default git user.name>]
    consulted: 
    informed: 
    ---
    
  2. adr-template.md: upstream, verbatim.
  3. adr-template-bare.md: upstream, verbatim.
  4. README.md:
    # Decisions
    
    Architecture decision records for this repository, in [MADR 4.0.0](https://adr.github.io/madr/) format.
    New record: `madtea adr new <slug>`. Templates: [adr-template.md](adr-template.md) (explained), [adr-template-bare.md](adr-template-bare.md) (empty).
    
    ## Index
    
    | Record | Title | Status | Date |
    |---|---|---|---|
    | [0000](0000-use-markdown-architectural-decision-records.md) | Use Markdown Architectural Decision Records | accepted | <today> |
    

Args: decision_makers (list, optional). Nothing else.

3. new in MADR mode

Args: slug (required), title, tracking, impacts (list of owner/repo or bare repo names), decision_makers (list, default git user.name), offline. Status is always proposed on creation; there is no status arg. A record is born undecided.

Writes NNNN-slug.md as adr-template-bare.md with exactly these substitutions and nothing else:

  • status: line becomes status: "proposed".
  • date: line becomes date: <YYYY-MM-DD, local date>.
  • decision-makers: line becomes decision-makers: [<names>] (YAML flow list).
  • When impacts is given, one line impacts: [<repos>] is inserted after informed: . When absent, no impacts line. This is the only key on top of MADR 4.0.0.
  • # <!-- short title, ... --> becomes # <title> (title arg, else title-cased slug).
  • When tracking is given, the empty body under ## More Information becomes Tracking: <tracking>.

Numbering: unchanged. Next number from the origin default branch; offline=true numbers from the local tree.

Index: when README.md exists and contains a ## Index heading with the table above, append a row | [NNNN](NNNN-slug.md) | <title> | proposed | <date> |. When there is no ## Index, write no index row and say so in the result.

Result text names the file, the number, and whether an index row was written.

4. Frontmatter grammar

YAML between the first line --- and the next ---. Keys:

Key Required Type Rule
status yes string exact match, after stripping quotes, against proposed, rejected, accepted, deprecated, superseded by ADR-[0-9]{4}
date yes string YYYY-MM-DD, a valid calendar date
decision-makers no string or list free
consulted no string or list free
informed no string or list free
impacts no list of strings each owner/repo or bare repo name (house key)

Unknown keys: warning, not a failure. MADR allows extra metadata.

Decided statuses: rejected, accepted, deprecated, superseded by ADR-NNNN. Undecided: proposed. Anything else fails rule C3.

5. check in MADR mode

Per record, in order. Each failure prints one line <path>: <rule>: <message>. A clean tree prints OK: <n> records, <k> proposed records checked against the forge. Any failure exits non-zero.

Rule Check Message
C1 filename matches the record pattern filename must be NNNN-kebab-title.md
C2 frontmatter present and parses as YAML missing or unparseable YAML frontmatter
C3 status present and in the grammar status "<v>" is not one of proposed | rejected | accepted | deprecated | superseded by ADR-NNNN
C4 date present and a valid YYYY-MM-DD date "<v>" is not a valid YYYY-MM-DD date
C5 first non-blank line after the frontmatter is # <title> and the title is not the template placeholder comment record has no title
C6 the three mandatory MADR sections exist in order: ## Context and Problem Statement, ## Considered Options, ## Decision Outcome missing mandatory section "<name>"
C7 number unique across the tree number NNNN is also used by <other file>
C8 every relative link inside the tree resolves link "<target>" does not resolve
C9 superseded by ADR-NNNN: the target record exists, and the target's body contains a link to this record (two-way supersedence) superseded by ADR-NNNN but <target> does not link back
C10 a decided record contains no unfilled template placeholder: <!-- option -->, <!-- decision driver -->, <!-- title of option -->, Chosen option: "", because decided record still contains template placeholder "<text>"
C11 undecided record issue tracking, section 6 see section 6
T1 when README.md has ## Index: every record has one row and every row resolves to a record index row missing for <file> / index row "<n>" has no record

Legacy mode keeps only today's checks (numbers unique, index bijection, links resolve).

6. Issue tracking for undecided records (C11)

An undecided record must be tracked by open issues.

  • Recognised issue links, anywhere under ## More Information: https://<forge-host>/<owner>/<repo>/issues/<n>, <owner>/<repo>#<n>, or #<n> for the record's own repo.
  • Required repo set = the top-level repo plus every repo in impacts. Top-level repo comes from git config madtea.adr.topLevelRepo (owner/repo). Unset means the record's own repo. A bare repo name in impacts resolves to the same owner as the record's own repo.
  • Rule on a proposed record: every repo in the required set has at least one linked issue, and that issue is open. Failures: proposed record has no issue link for <owner/repo>; proposed record links <owner/repo>#<n> but it is closed.
  • Rule on a decided record that carries issue links: every linked issue is closed, and its comments (including the close comment) contain the record filename stem NNNN-slug. Failures: record is decided but <owner/repo>#<n> is still open; <owner/repo>#<n> closed without citing <NNNN-slug>.
  • Forge access: the same client and credentials every other madt_* call uses for that checkout. A forge error is a failure: could not read <owner/repo>#<n>: <error>. It is not a silent pass.
  • offline=true: skip C11 and print SKIPPED (offline): issue tracking not checked for <k> proposed records. The skip does not change the exit code.

7. CLI parity

madtea adr init [--decision-makers a,b], madtea adr new <slug> [--title] [--tracking] [--impacts a,b] [--decision-makers a,b] [--offline], madtea adr check [--offline]. Flag names equal the MCP arg names (#444).

8. Docs and help

  • Tool description: "Create and consistency-check architecture decision records (ADRs) in any repo that carries a docs/decisions/ (MADR 4.0.0) or docs/adr/ (legacy) tree."
  • madt_help topic=adr: document both modes, the frontmatter grammar, every check rule with its message, and the issue-tracking rule.
  • CLI help and the README section: same content.
  • This change gets its own record in this repo's docs/adr/ tree (legacy mode; migrating this repo is out of scope).

9. Tests

  • Golden test: adr new output equals the vendored adr-template-bare.md with only the section 3 substitutions applied. Byte comparison.
  • Golden test: adr init writes the four files; 0000 equals the vendored upstream file plus the frontmatter.
  • One fixture tree per check rule C1 to C10 and T1 under internal/service/adr/testdata/madr/, each failing on exactly that rule with the exact message.
  • C11: one test per failure line in section 6, plus the offline skip, through the existing fake forge client.
  • Legacy fixtures: the existing tests pass unchanged.
  • Both-trees fixture: every action fails with the section 1 message.

10. Acceptance criteria

This list replaces the one in the issue body.

  • madtea adr init creates the four files in section 2 and refuses an existing directory.
  • madtea adr new <slug> in MADR mode writes a file that is byte-identical to the vendored bare template apart from the section 3 substitutions.
  • madtea adr check passes on a clean MADR tree and fails, naming the file, rule, and message, on every rule in sections 5 and 6.
  • madtea adr check --offline skips C11 with the stated line.
  • This repo's own docs/adr/ tree and every other legacy tree pass check unchanged.
  • Tool description, madt_help topic=adr, CLI help, and README updated.
  • Tests in section 9 pass.
  • A record for this change exists in this repo's docs/adr/.

11. Out of scope

  • Migrating this repo's docs/adr/ tree to MADR.
  • A generated cross-repo index or summary document.
  • An adr status action to move a record between statuses. Status is edited by hand; check enforces the consequences.
## Implementation spec This comment is the complete spec. Where it differs from the template sketch in the issue body, this comment wins. The earlier comment about undecided records is folded in here (section 6). ### 0. Source of truth MADR 4.0.0, upstream `adr/madr` at tag `4.0.0`. Three upstream files matter. Vendor them verbatim under `internal/service/adr/madr/` with a `LICENSE` note (`MIT OR CC0-1.0`, the upstream dual licence): | Upstream file | Blob sha | Used by | |---|---|---| | `template/adr-template-bare.md` | `26f6598f70239f173239290beb7aa5fb71483f52` | `adr new` writes this, with substitutions (section 3) | | `template/adr-template.md` | `3dea2440cae85e408bd281a1a5b14399f0d21b4a` | `adr init` copies it as the explained template | | `template/0000-use-markdown-architectural-decision-records.md` | `d7c7b10b63ffe4a71aa4aaf9458f45a9fcd0e412` | `adr init` writes it as record 0000 | Verbatim `adr-template-bare.md`, the exact bytes `adr new` starts from: ``` --- status: date: decision-makers: consulted: informed: --- # <!-- short title, representative of solved problem and found solution --> ## Context and Problem Statement ## Decision Drivers * <!-- decision driver --> ## Considered Options * <!-- option --> ## Decision Outcome Chosen option: "", because ### Consequences * Good, because * Bad, because ### Confirmation ## Pros and Cons of the Options ### <!-- title of option --> * Good, because * Neutral, because * Bad, because ## More Information ``` Upstream conventions I follow exactly: directory `docs/decisions/`, filename `NNNN-title-with-dashes.md`, status grammar `proposed | rejected | accepted | deprecated | superseded by ADR-0123` (identifier only, no link, per upstream 4.0.0-beta change #150), `status` value quoted as a YAML string (upstream #91), list bullets are `*`. ### 1. Layout detection - `docs/decisions/` exists: MADR mode. Sections 2 to 7 apply. - `docs/adr/` exists and `docs/decisions/` does not: legacy mode. Today's behaviour, unchanged. - Both exist: every action fails with `two ADR trees: docs/adr/ and docs/decisions/; keep one`. - Neither exists: `new` and `check` fail and point at `adr init`. - A record is a file matching `^[0-9]{4}-[a-z0-9-]+\.md$`. Any other file in the tree (`README.md`, `adr-template.md`, `adr-template-bare.md`) is ignored by numbering and by every check. ### 2. New action `init` `madt_adr action="init"` / `madtea adr init`. Creates `docs/decisions/` with four files. Refuses when the directory already exists. 1. `0000-use-markdown-architectural-decision-records.md`: the upstream file, with this frontmatter prepended so it passes `check`: ``` --- status: "accepted" date: <today> decision-makers: [<decision_makers arg, default git user.name>] consulted: informed: --- ``` 2. `adr-template.md`: upstream, verbatim. 3. `adr-template-bare.md`: upstream, verbatim. 4. `README.md`: ``` # Decisions Architecture decision records for this repository, in [MADR 4.0.0](https://adr.github.io/madr/) format. New record: `madtea adr new <slug>`. Templates: [adr-template.md](adr-template.md) (explained), [adr-template-bare.md](adr-template-bare.md) (empty). ## Index | Record | Title | Status | Date | |---|---|---|---| | [0000](0000-use-markdown-architectural-decision-records.md) | Use Markdown Architectural Decision Records | accepted | <today> | ``` Args: `decision_makers` (list, optional). Nothing else. ### 3. `new` in MADR mode Args: `slug` (required), `title`, `tracking`, `impacts` (list of `owner/repo` or bare repo names), `decision_makers` (list, default git `user.name`), `offline`. Status is always `proposed` on creation; there is no `status` arg. A record is born undecided. Writes `NNNN-slug.md` as `adr-template-bare.md` with exactly these substitutions and nothing else: - `status:` line becomes `status: "proposed"`. - `date: ` line becomes `date: <YYYY-MM-DD, local date>`. - `decision-makers: ` line becomes `decision-makers: [<names>]` (YAML flow list). - When `impacts` is given, one line `impacts: [<repos>]` is inserted after `informed: `. When absent, no `impacts` line. This is the only key on top of MADR 4.0.0. - `# <!-- short title, ... -->` becomes `# <title>` (`title` arg, else title-cased slug). - When `tracking` is given, the empty body under `## More Information` becomes `Tracking: <tracking>`. Numbering: unchanged. Next number from the origin default branch; `offline=true` numbers from the local tree. Index: when `README.md` exists and contains a `## Index` heading with the table above, append a row `| [NNNN](NNNN-slug.md) | <title> | proposed | <date> |`. When there is no `## Index`, write no index row and say so in the result. Result text names the file, the number, and whether an index row was written. ### 4. Frontmatter grammar YAML between the first line `---` and the next `---`. Keys: | Key | Required | Type | Rule | |---|---|---|---| | `status` | yes | string | exact match, after stripping quotes, against `proposed`, `rejected`, `accepted`, `deprecated`, `superseded by ADR-[0-9]{4}` | | `date` | yes | string | `YYYY-MM-DD`, a valid calendar date | | `decision-makers` | no | string or list | free | | `consulted` | no | string or list | free | | `informed` | no | string or list | free | | `impacts` | no | list of strings | each `owner/repo` or bare repo name (house key) | Unknown keys: warning, not a failure. MADR allows extra metadata. Decided statuses: `rejected`, `accepted`, `deprecated`, `superseded by ADR-NNNN`. Undecided: `proposed`. Anything else fails rule C3. ### 5. `check` in MADR mode Per record, in order. Each failure prints one line `<path>: <rule>: <message>`. A clean tree prints `OK: <n> records, <k> proposed records checked against the forge`. Any failure exits non-zero. | Rule | Check | Message | |---|---|---| | C1 | filename matches the record pattern | `filename must be NNNN-kebab-title.md` | | C2 | frontmatter present and parses as YAML | `missing or unparseable YAML frontmatter` | | C3 | `status` present and in the grammar | `status "<v>" is not one of proposed \| rejected \| accepted \| deprecated \| superseded by ADR-NNNN` | | C4 | `date` present and a valid `YYYY-MM-DD` | `date "<v>" is not a valid YYYY-MM-DD date` | | C5 | first non-blank line after the frontmatter is `# <title>` and the title is not the template placeholder comment | `record has no title` | | C6 | the three mandatory MADR sections exist in order: `## Context and Problem Statement`, `## Considered Options`, `## Decision Outcome` | `missing mandatory section "<name>"` | | C7 | number unique across the tree | `number NNNN is also used by <other file>` | | C8 | every relative link inside the tree resolves | `link "<target>" does not resolve` | | C9 | `superseded by ADR-NNNN`: the target record exists, and the target's body contains a link to this record (two-way supersedence) | `superseded by ADR-NNNN but <target> does not link back` | | C10 | a decided record contains no unfilled template placeholder: `<!-- option -->`, `<!-- decision driver -->`, `<!-- title of option -->`, `Chosen option: "", because` | `decided record still contains template placeholder "<text>"` | | C11 | undecided record issue tracking, section 6 | see section 6 | | T1 | when `README.md` has `## Index`: every record has one row and every row resolves to a record | `index row missing for <file>` / `index row "<n>" has no record` | Legacy mode keeps only today's checks (numbers unique, index bijection, links resolve). ### 6. Issue tracking for undecided records (C11) An undecided record must be tracked by open issues. - **Recognised issue links**, anywhere under `## More Information`: `https://<forge-host>/<owner>/<repo>/issues/<n>`, `<owner>/<repo>#<n>`, or `#<n>` for the record's own repo. - **Required repo set** = the top-level repo plus every repo in `impacts`. Top-level repo comes from git config `madtea.adr.topLevelRepo` (`owner/repo`). Unset means the record's own repo. A bare repo name in `impacts` resolves to the same owner as the record's own repo. - **Rule on a `proposed` record**: every repo in the required set has at least one linked issue, and that issue is open. Failures: `proposed record has no issue link for <owner/repo>`; `proposed record links <owner/repo>#<n> but it is closed`. - **Rule on a decided record** that carries issue links: every linked issue is closed, and its comments (including the close comment) contain the record filename stem `NNNN-slug`. Failures: `record is decided but <owner/repo>#<n> is still open`; `<owner/repo>#<n> closed without citing <NNNN-slug>`. - **Forge access**: the same client and credentials every other `madt_*` call uses for that checkout. A forge error is a failure: `could not read <owner/repo>#<n>: <error>`. It is not a silent pass. - **`offline=true`**: skip C11 and print `SKIPPED (offline): issue tracking not checked for <k> proposed records`. The skip does not change the exit code. ### 7. CLI parity `madtea adr init [--decision-makers a,b]`, `madtea adr new <slug> [--title] [--tracking] [--impacts a,b] [--decision-makers a,b] [--offline]`, `madtea adr check [--offline]`. Flag names equal the MCP arg names (#444). ### 8. Docs and help - Tool description: "Create and consistency-check architecture decision records (ADRs) in any repo that carries a `docs/decisions/` (MADR 4.0.0) or `docs/adr/` (legacy) tree." - `madt_help topic=adr`: document both modes, the frontmatter grammar, every check rule with its message, and the issue-tracking rule. - CLI help and the README section: same content. - This change gets its own record in this repo's `docs/adr/` tree (legacy mode; migrating this repo is out of scope). ### 9. Tests - Golden test: `adr new` output equals the vendored `adr-template-bare.md` with only the section 3 substitutions applied. Byte comparison. - Golden test: `adr init` writes the four files; `0000` equals the vendored upstream file plus the frontmatter. - One fixture tree per check rule C1 to C10 and T1 under `internal/service/adr/testdata/madr/`, each failing on exactly that rule with the exact message. - C11: one test per failure line in section 6, plus the offline skip, through the existing fake forge client. - Legacy fixtures: the existing tests pass unchanged. - Both-trees fixture: every action fails with the section 1 message. ### 10. Acceptance criteria This list replaces the one in the issue body. - `madtea adr init` creates the four files in section 2 and refuses an existing directory. - `madtea adr new <slug>` in MADR mode writes a file that is byte-identical to the vendored bare template apart from the section 3 substitutions. - `madtea adr check` passes on a clean MADR tree and fails, naming the file, rule, and message, on every rule in sections 5 and 6. - `madtea adr check --offline` skips C11 with the stated line. - This repo's own `docs/adr/` tree and every other legacy tree pass `check` unchanged. - Tool description, `madt_help topic=adr`, CLI help, and README updated. - Tests in section 9 pass. - A record for this change exists in this repo's `docs/adr/`. ### 11. Out of scope - Migrating this repo's `docs/adr/` tree to MADR. - A generated cross-repo index or summary document. - An `adr status` action to move a record between statuses. Status is edited by hand; `check` enforces the consequences.
Author
Owner

The tooling landed in #484: adr init, MADR-mode adr new, and the MADR-mode adr check (frontmatter grammar, mandatory sections in order, two-way supersedence, and open-issue tracking for undecided records), with docs/decisions/ and legacy docs/adr/ layout detection. Legacy docs/adr/ trees keep the current checks.

This repo still keeps its own decisions in the legacy docs/adr/ tree. The remaining work under this issue: convert that tree to docs/decisions/ MADR 4.0.0 in one pass, so the repo uses MADR only, and add this change's own record there. It is one atomic pass, because the tool treats a repo with both trees as an error.

The tooling landed in #484: `adr init`, MADR-mode `adr new`, and the MADR-mode `adr check` (frontmatter grammar, mandatory sections in order, two-way supersedence, and open-issue tracking for undecided records), with `docs/decisions/` and legacy `docs/adr/` layout detection. Legacy `docs/adr/` trees keep the current checks. This repo still keeps its own decisions in the legacy `docs/adr/` tree. The remaining work under this issue: convert that tree to `docs/decisions/` MADR 4.0.0 in one pass, so the repo uses MADR only, and add this change's own record there. It is one atomic pass, because the tool treats a repo with both trees as an error.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sixfold-space/madtea#483
No description provided.