diff --git a/docs/decisions/011-update-management.md b/docs/decisions/011-update-management.md index 95552e0..0fcb088 100644 --- a/docs/decisions/011-update-management.md +++ b/docs/decisions/011-update-management.md @@ -2,9 +2,8 @@ ## Status -Accepted (2026-06-04) - -**Status: Proposed — draft for discussion (not yet accepted).** +Proposed (2026-06-04) — draft for discussion; not yet accepted. The core decisions +below are settled in intent, but several specifics remain open (see "Open questions"). ## Context diff --git a/docs/decisions/023-adr-structure.md b/docs/decisions/023-adr-structure.md index 9c7246d..5d5da67 100644 --- a/docs/decisions/023-adr-structure.md +++ b/docs/decisions/023-adr-structure.md @@ -30,7 +30,8 @@ keeps its number and file. A new ADR is registered as a row in the CLAUDE.md ### 2. Mandatory sections, in this order -- `## Status` — `Accepted (YYYY-MM-DD)`, plus an optional one-line note. +- `## Status` — a lifecycle line, usually `Accepted (YYYY-MM-DD)` (see §4), plus an + optional one-line note. - `## Context` — the forces, the problem, what exists today, why now. - `## Decision` — what we are doing; numbered sub-decisions for multi-part ADRs. - `## Consequences` — results, trade-offs explicitly accepted, follow-on work. @@ -42,10 +43,15 @@ keeps its number and file. A new ADR is registered as a row in the CLAUDE.md ### 4. Status lifecycle -Three states; no "Proposed" stage (boma is single-contributor and trunk-based with no -review gate, so an ADR is born committed-to). +Four states. Because boma is single-contributor and trunk-based with no review gate, +most ADRs are **born `Accepted (YYYY-MM-DD)`** — committed-to on writing. A +**`Proposed`** state exists for a genuine draft whose core direction is recorded but +whose specifics are still open for discussion (e.g. ADR-011); it is promoted to +`Accepted` once settled. -- Born **`Accepted (YYYY-MM-DD)`**. +- **`Proposed (YYYY-MM-DD)`** — drafted, under discussion, not yet committed-to. May + carry open questions. Promoted to `Accepted (YYYY-MM-DD)` when decided. +- **`Accepted (YYYY-MM-DD)`** — committed-to. The common starting state. - Replaced → old ADR's Status becomes **`Superseded by ADR-NNN (YYYY-MM-DD)`**; the new ADR records `Supersedes ADR-MMM` in its Status and `## Related`. The link is **bidirectional**. @@ -85,7 +91,6 @@ a consistent, legible decision history. ## What was ruled out -- **A "Proposed" draft stage** — no review gate exists for it to serve. - **A `make lint` / CI gate for ADR structure** — heavier than the risk warrants; the `/review-repo` check and the template suffice. - **Machine-enforcing section order** — brittle for marginal value; left as a diff --git a/docs/decisions/adr-template.md b/docs/decisions/adr-template.md index c3814a9..ae7c622 100644 --- a/docs/decisions/adr-template.md +++ b/docs/decisions/adr-template.md @@ -8,8 +8,10 @@ ## Status Accepted (YYYY-MM-DD) - ## Context diff --git a/docs/superpowers/plans/2026-06-10-adr-structure.md b/docs/superpowers/plans/2026-06-10-adr-structure.md index ea83f2b..eff627c 100644 --- a/docs/superpowers/plans/2026-06-10-adr-structure.md +++ b/docs/superpowers/plans/2026-06-10-adr-structure.md @@ -20,7 +20,7 @@ - **Mandatory sections, in this order:** `## Status`, `## Context`, `## Decision`, `## Consequences`. - **Optional sections:** `## Related`, `## Scope`, `## Guardrails` / `## Enforcement`, `## What was ruled out`, `## Verified facts (ADR-014)`. -- **Status lifecycle (3 states):** `Accepted (YYYY-MM-DD)` → optionally `Superseded by ADR-NNN (YYYY-MM-DD)` or `Deprecated (YYYY-MM-DD)`. No "Proposed" stage. +- **Status lifecycle (4 states):** `Proposed (YYYY-MM-DD)` (genuine drafts, e.g. ADR-011) → `Accepted (YYYY-MM-DD)` (the common starting state) → optionally `Superseded by ADR-NNN (YYYY-MM-DD)` or `Deprecated (YYYY-MM-DD)`. (`Proposed` was added on the evidence of ADR-011, which is a real draft with open questions.) - **No silent rewrites:** material reversal = new ADR + `Superseded by` marker; bidirectional link. - **Enforcement checks presence + parseable Status line, NOT section order.** Order is demonstrated by the template, not machine-enforced. - **Back-catalogue is fully restructured (no grandfathering)** — ADRs 001–018 are brought to all-four-section conformance. The restructure is **presentational**: relabel/regroup/demote existing headings, add a dated Status, assemble a Consequences section from implications the ADR already states. **The substance of no decision is changed.** If a faithful Consequences cannot be drawn from existing content, escalate that file rather than inventing one. @@ -305,7 +305,8 @@ keeps its number and file. A new ADR is registered as a row in the CLAUDE.md ### 2. Mandatory sections, in this order -- `## Status` — `Accepted (YYYY-MM-DD)`, plus an optional one-line note. +- `## Status` — a lifecycle line, usually `Accepted (YYYY-MM-DD)` (see §4), plus an + optional one-line note. - `## Context` — the forces, the problem, what exists today, why now. - `## Decision` — what we are doing; numbered sub-decisions for multi-part ADRs. - `## Consequences` — results, trade-offs explicitly accepted, follow-on work. @@ -317,10 +318,15 @@ keeps its number and file. A new ADR is registered as a row in the CLAUDE.md ### 4. Status lifecycle -Three states; no "Proposed" stage (boma is single-contributor and trunk-based with no -review gate, so an ADR is born committed-to). +Four states. Because boma is single-contributor and trunk-based with no review gate, +most ADRs are **born `Accepted (YYYY-MM-DD)`** — committed-to on writing. A +**`Proposed`** state exists for a genuine draft whose core direction is recorded but +whose specifics are still open for discussion (e.g. ADR-011); it is promoted to +`Accepted` once settled. -- Born **`Accepted (YYYY-MM-DD)`**. +- **`Proposed (YYYY-MM-DD)`** — drafted, under discussion, not yet committed-to. May + carry open questions. Promoted to `Accepted (YYYY-MM-DD)` when decided. +- **`Accepted (YYYY-MM-DD)`** — committed-to. The common starting state. - Replaced → old ADR's Status becomes **`Superseded by ADR-NNN (YYYY-MM-DD)`**; the new ADR records `Supersedes ADR-MMM` in its Status and `## Related`. The link is **bidirectional**. @@ -360,7 +366,6 @@ a consistent, legible decision history. ## What was ruled out -- **A "Proposed" draft stage** — no review gate exists for it to serve. - **A `make lint` / CI gate for ADR structure** — heavier than the risk warrants; the `/review-repo` check and the template suffice. - **Machine-enforcing section order** — brittle for marginal value; left as a diff --git a/docs/superpowers/specs/2026-06-10-adr-structure-design.md b/docs/superpowers/specs/2026-06-10-adr-structure-design.md index cd35dfe..23018f0 100644 --- a/docs/superpowers/specs/2026-06-10-adr-structure-design.md +++ b/docs/superpowers/specs/2026-06-10-adr-structure-design.md @@ -46,10 +46,12 @@ lifecycle, ships a template, and reconciles the back-catalogue. is presentational — relabel/regroup/demote existing content, add a dated Status, never alter what was decided); a `make lint` / CI gate for ADR structure (explicitly rejected in favour of the `/review-repo` check — consistent with boma's other doctrine - ADRs, which add no CI gate); a "Proposed" draft stage (rejected — boma is - single-contributor and trunk-based with no review gate, so ADRs are born Accepted); - grandfathering pre-convention ADRs from the check (rejected — the whole corpus is - brought to conformance instead). + ADRs, which add no CI gate); grandfathering pre-convention ADRs from the check + (rejected — the whole corpus is brought to conformance instead). + +The lifecycle uses four states — `Proposed / Accepted / Superseded / Deprecated`. An +earlier draft of this design omitted `Proposed`, but ADR-011 (a real draft with open +questions) is evidence boma occasionally needs it, so it was kept. ## Decision @@ -82,9 +84,13 @@ lifecycle, ships a template, and reconciles the back-catalogue. ### 3. Status lifecycle -Three states; no "Proposed" stage. +Four states. Most ADRs are **born `Accepted (YYYY-MM-DD)`** — the sole author commits +to it on writing (boma is single-contributor and trunk-based with no review gate). -- Born **`Accepted (YYYY-MM-DD)`** — the sole author commits to it on writing. +- **`Proposed (YYYY-MM-DD)`** — a genuine draft whose core direction is recorded but + whose specifics are still open (e.g. ADR-011, which carries open questions). Promoted + to `Accepted (YYYY-MM-DD)` once settled. +- **`Accepted (YYYY-MM-DD)`** — committed-to; the common starting state. - Replaced by a later decision → the old ADR's Status becomes **`Superseded by ADR-NNN (YYYY-MM-DD)`**; the superseding ADR records `Supersedes ADR-MMM` in its own `## Status` and `## Related`. The link is @@ -151,7 +157,8 @@ a parseable `## Status` line. The template carries the convention forward for ne ## Open questions -None outstanding — title/filename, the 3-state lifecycle, template name +None outstanding — title/filename, the **4-state lifecycle** (`Proposed / Accepted / +Superseded / Deprecated`; `Proposed` adopted on the evidence of ADR-011), template name (`adr-template.md`), enforcement (`/review-repo`, no CI gate), and the **full retroactive restructure** of 001–018 (no grandfathering) were all confirmed during -brainstorming. +brainstorming and execution. diff --git a/scripts/repo-scan.py b/scripts/repo-scan.py index d8bc5f0..58b3594 100644 --- a/scripts/repo-scan.py +++ b/scripts/repo-scan.py @@ -47,7 +47,8 @@ RESOLVE_WORD_RE = re.compile(r"\b(?:resolv\w*|decid\w*|address\w*|complet\w*|don ADR_FILE_RE = re.compile(r"^\d{3}-.*\.md$") ADR_REQUIRED_SECTIONS = ("Status", "Context", "Decision", "Consequences") ADR_STATUS_LINE_RE = re.compile( - r"^(Accepted \(\d{4}-\d{2}-\d{2}\)" + r"^(Proposed \(\d{4}-\d{2}-\d{2}\)" + r"|Accepted \(\d{4}-\d{2}-\d{2}\)" r"|Superseded by ADR-\d{3}" r"|Deprecated \(\d{4}-\d{2}-\d{2}\))") @@ -134,8 +135,9 @@ def adr_structure_findings(adr_files): if not ADR_STATUS_LINE_RE.match(status_text): out.append({"check": "adr-structure", "severity": "medium", "path": rpath, "line": headings["Status"] + 1, - "detail": "Status not parseable (want 'Accepted (YYYY-MM-DD)', " - "'Superseded by ADR-NNN', or 'Deprecated (YYYY-MM-DD)'); " + "detail": "Status not parseable (want 'Proposed (YYYY-MM-DD)', " + "'Accepted (YYYY-MM-DD)', 'Superseded by ADR-NNN', or " + "'Deprecated (YYYY-MM-DD)'); " f"got: {status_text[:60]!r}"}) return out diff --git a/tests/test_repo_scan.py b/tests/test_repo_scan.py index e8a0542..ac776c1 100644 --- a/tests/test_repo_scan.py +++ b/tests/test_repo_scan.py @@ -46,6 +46,13 @@ def test_superseded_status_is_accepted(): assert out == [] +def test_proposed_status_is_accepted(): + lines = [("Proposed (2026-06-04)\n" if ln == "Accepted (2026-06-10)\n" + else ln) for ln in GOOD] + out = _checks(rs.adr_structure_findings({"docs/decisions/099-example.md": lines})) + assert out == [] + + def test_non_numbered_file_is_skipped(): bare = ["# ADR template\n", "\n", "## Status\n", "\n", "\n"] out = _checks(rs.adr_structure_findings({"docs/decisions/adr-template.md": bare}))