Skill: anderskev/adr-writing (version 1.0.2)

--- name: adr-writing description: "Use when writing or formatting an ADR document using the MADR template, applying Definition of Done (E.C.A.D.R.) criteria, or verifying ADR completeness. Triggers on \"write the ADR\", \"format as MADR\", \"check ADR quality\", \"mark gaps in ADR\". Also triggers when a decision has been extracted and needs to become a document. Does NOT extract decisions from conversations (use adr-decision-extraction) or orchestrate the full extract-confirm-write workflow (use write-adr)." --- # ADR Writing ## Overview Generate Architectural Decision Records (ADRs) following the MADR template with systematic completeness checking. ## Quick Reference ``` ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ SEQUENCE │ ──▶ │ EXPLORE │ ──▶ │ FILL │ │ (get next │ │ (context, │ │ (template │ │ number) │ │ ADRs) │ │ sections) │ └─────────────┘ └──────────────┘ └─────────────┘ │ │ │ ▼ │ ┌─────────────┐ │ │ VERIFY │ │ │ (DoD │ └─────────────────────────────────│ checklist)│ └─────────────┘ ``` ## When To Use - Documenting architectural decisions from extracted requirements - Converting meeting notes or discussions to formal ADRs - Recording technical choices from PR discussions - Creating decision records from design documents ## Workflow ### Gates (objective pass conditions) Advance to the next step only when the **pass condition** holds. These replace “I explored” / “I verified” with checkable artifacts. | After | Pass condition | |-------|----------------| | Step 2 | **Pass:** You have a written list (bullets in draft preamble, scratch notes, or the ADR body) of **≥0** paths under `docs/adrs/` you consulted for related/superseded ADRs, **or** you explicitly record that `docs/adrs/` is missing or empty after checking. **And** you list **≥1** repo path for related code **or** `N/A` with one-line reason. | | Step 5 | **Pass:** For each E, C, A, D, R in `references/definition-of-done.md`, the draft either meets that letter’s checklist **or** contains an `[INVESTIGATE: …]` marker scoped to that gap. | | Step 7 | **Pass:** The ADR file exists at `docs/adrs/NNNN-slugified-title.md`, and a read of the file shows line 1 is `---` and frontmatter parses as YAML. | ### Step 1: Get Sequence Number **If a number was pre-assigned** (e.g., when called from `/beagle:write-adr` with parallel writes): - Use the pre-assigned number directly - Do NOT call the script - this prevents duplicate numbers in parallel execution **If no number was pre-assigned** (standalone use): ```bash python scripts/next_adr_number.py ``` This outputs the next available ADR number (e.g., `0003`). For parallel allocation (used by parent commands): ```bash python scripts/next_adr_number.py --count 3 # Outputs: 0003, 0004, 0005 (one per line) ``` ### Step 2: Explore Context Before writing, gather additional context: 1. **Related code** - Find implementations affected by this decision 2. **Existing ADRs** - Check `docs/adrs/` for related or superseded decisions 3. **Discussion sources** - PRs, issues, or documents referenced in decision **Gate:** Meet the Step 2 row in **Gates (objective pass conditions)** before Step 3. ### Step 3: Load Template Load `references/madr-template.md` for the official MADR structure. ### Step 4: Fill Sections Populate each section from your decision data: | Section | Source | |---------|--------| | Title | Decision summary (imperative mood) | | Status | Always `draft` initially | | Context | Problem statement, constraints | | Decision Drivers | Prioritized requirements | | Considered Options | All viable alternatives | | Decision Outcome | Chosen option with rationale | | Consequences | Good, bad, neutral impacts | ### Step 5: Apply Definition of Done Load `references/definition-of-done.md` and verify E.C.A.D.R. criteria: - **E**xplicit problem statement - **C**omprehensive options analysis - **A**ctionable decision - **D**ocumented consequences - **R**eviewable by stakeholders **Gate:** Meet the Step 5 row in **Gates (objective pass conditions)** before Step 6 (use `[INVESTIGATE: …]` where data is missing). ### Step 6: Mark Gaps For sections that cannot be filled from available data, insert investigation prompts: ```markdown * [INVESTIGATE: Review PR #42 discussion for additional drivers] * [INVESTIGATE: Confirm with security team on compliance requirements] * [INVESTIGATE: Benchmark performance of Option 2 vs Option 3] ``` These prompts signal incomplete sections for later follow-up. ### Step 7: Write File **IMPORTANT: Every ADR MUST start with YAML frontmatter.** The frontmatter block is REQUIRED and must include at minimum: ```yaml --- status: draft date: YYYY-MM-DD --- ``` Full frontmatter template: ```yaml --- status: draft date: 2024-01-15 decision-makers: [alice, bob] consulted: [] informed: [] --- ``` **Validation:** Before writing the file, verify the content starts with `---` followed by valid YAML frontmatter. If frontmatter is missing, add it before writing. **Gate:** After write, meet the Step 7 row in **Gates (objective pass conditions)** (file on disk, YAML frontmatter present). Save to `docs/adrs/NNNN-slugified-title.md`: ``` docs/adrs/0003-use-postgresql-for-user-data.md docs/adrs/0004-adopt-event-sourcing-pattern.md docs/adrs/0005-migrate-to-kubernetes.md ``` ### Step 8: Verify Frontmatter After writing, confirm the file: 1. Starts with `---` on the first line 2. Contains `status: draft` (or other valid status) 3. Contains `date: YYYY-MM-DD` with actual date 4. Ends frontmatter with `---` before the title ## File Naming Convention Format: `NNNN-slugified-title.md` | Component | Rule | |-----------|------| | `NNNN` | Zero-padded sequence number from script | | `-` | Separator | | `slugified-title` | Lowercase, hyphens, no special characters | | `.md` | Markdown extension | ## Reference Files - `references/madr-template.md` - Official MADR template structure - `references/definition-of-done.md` - E.C.A.D.R. quality criteria ## Output Example ```markdown --- status: draft date: 2024-01-15 decision-makers: [alice, bob] --- # Use PostgreSQL for User Data Storage ## Context and Problem Statement We need a database for user account data... ## Decision Drivers * Data integrity requirements * Query flexibility needs * [INVESTIGATE: Confirm scaling projections with infrastructure team] ## Considered Options * PostgreSQL * MongoDB * CockroachDB ## Decision Outcome Chosen option: PostgreSQL, because... ## Consequences ### Good * ACID compliance ensures data integrity ### Bad * Requires more upfront schema design ### Neutral * Team has moderate PostgreSQL experience ```

path: references/definition-of-done.md
content: # Definition of Done: E.C.A.D.R. Criteria

An ADR is complete when it meets all five E.C.A.D.R. criteria.

## E.C.A.D.R. Checklist

### E - Explicit Problem Statement

| Check | Criteria |
|-------|----------|
| [ ] | Context describes a real, specific problem |
| [ ] | Problem is scoped (not too broad, not too narrow) |
| [ ] | Constraints and requirements are stated |
| [ ] | Reader understands WHY a decision is needed |

**Anti-patterns:**
- "We need to choose a database" (too vague)
- Problem buried in decision outcome section
- Missing business or technical context

### C - Comprehensive Options Analysis

| Check | Criteria |
|-------|----------|
| [ ] | At least 2 options considered |
| [ ] | Options are genuinely viable (not strawmen) |
| [ ] | Each option has pros AND cons listed |
| [ ] | "Do nothing" considered if applicable |

**Anti-patterns:**
- Single option presented as foregone conclusion
- Options listed without analysis
- Missing obvious alternatives

### A - Actionable Decision

| Check | Criteria |
|-------|----------|
| [ ] | Chosen option is clearly stated |
| [ ] | Decision is specific enough to implement |
| [ ] | Rationale links to decision drivers |
| [ ] | No ambiguity about what was decided |

**Anti-patterns:**
- "We will use a modern approach" (vague)
- Decision contradicts stated constraints
- Missing implementation guidance

### D - Documented Consequences

| Check | Criteria |
|-------|----------|
| [ ] | Good consequences listed |
| [ ] | Bad consequences listed (honest tradeoffs) |
| [ ] | Operational impacts considered |
| [ ] | Future implications noted |

**Anti-patterns:**
- Only positive consequences (overselling)
- Generic consequences that apply to any option
- Missing security, performance, or cost impacts

### R - Reviewable by Stakeholders

| Check | Criteria |
|-------|----------|
| [ ] | Status is set appropriately |
| [ ] | Decision-makers are identified |
| [ ] | Language is accessible (not jargon-heavy) |
| [ ] | Sufficient context for outsiders to understand |

**Anti-patterns:**
- Missing metadata (date, status, authors)
- Assumes reader context not in document
- Dense technical prose without summaries

## Quality Rubric

| Score | Criteria Met | Status |
|-------|--------------|--------|
| 5/5 | All E.C.A.D.R. criteria | Ready for `proposed` |
| 4/5 | One minor gap | Add `[INVESTIGATE]` prompt |
| 3/5 | Two gaps | Needs revision before proposing |
| 2/5 | Major gaps | Incomplete draft |
| 1/5 | Minimal content | Placeholder only |

## Using [INVESTIGATE] Prompts

When a criterion cannot be met from available information, insert an investigation prompt:

```markdown
## Decision Drivers

* Performance under 100ms response time
* [INVESTIGATE: Confirm budget constraints with finance team]
* Compatibility with existing Python stack
```

These prompts:
1. Signal incomplete sections
2. Document what information is missing
3. Enable async follow-up
4. Prevent premature status advancement

## Status Progression

```
draft ──▶ proposed ──▶ accepted
  │          │
  │          ▼
  │       rejected
  │
  └──▶ [fix gaps, remove INVESTIGATE prompts]
```

Do not advance to `proposed` until all `[INVESTIGATE]` prompts are resolved.

## Review Checklist

Final pass before marking `proposed`:

- [ ] No `[INVESTIGATE]` prompts remain
- [ ] All E.C.A.D.R. criteria checked
- [ ] File named correctly (`NNNN-slugified-title.md`)
- [ ] Frontmatter complete (status, date, decision-makers)
- [ ] Links to related ADRs if superseding/related


path: references/madr-template.md
content: # MADR Template

> Markdown Any Decision Records (MADR) - https://adr.github.io/madr/

## Template Structure

```markdown
---
status: {draft | proposed | accepted | rejected | deprecated | superseded by [ADR-NNNN](NNNN-title.md)}
date: YYYY-MM-DD
decision-makers: [list of involved people]
consulted: [list of people whose opinions are sought]
informed: [list of people who are kept up-to-date]
---

# {Title: Short imperative statement of decision}

## Context and Problem Statement

{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. You may want to articulate the problem in form of a question.}

## Decision Drivers

* {decision driver 1, e.g., a force, facing concern, ...}
* {decision driver 2, e.g., a force, facing concern, ...}
* ...

## Considered Options

* {title of option 1}
* {title of option 2}
* {title of option 3}
* ...

## Decision Outcome

Chosen option: "{title of option 1}", because {justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | ... | comes out best (see below)}.

### Consequences

* Good, because {positive consequence, e.g., improvement of one or more desired qualities, ...}
* Bad, because {negative consequence, e.g., compromising one or more desired qualities, ...}
* Neutral, because {neutral consequence, neither positive nor negative}

### Confirmation

{Describe how the implementation of/compliance with the ADR is confirmed. E.g., by a review or an ArchUnit test. Although we classify this element as optional, it is recommended to include it.}

## Pros and Cons of the Options

### {title of option 1}

{example | description | pointer to more information | ...}

* Good, because {argument a}
* Good, because {argument b}
* Neutral, because {argument c}
* Bad, because {argument d}
* ...

### {title of option 2}

{example | description | pointer to more information | ...}

* Good, because {argument a}
* Good, because {argument b}
* Neutral, because {argument c}
* Bad, because {argument d}
* ...

### {title of option 3}

{example | description | pointer to more information | ...}

* Good, because {argument a}
* Good, because {argument b}
* Neutral, because {argument c}
* Bad, because {argument d}
* ...

## More Information

{You might want to provide additional evidence/confidence for the decision outcome here and/or document the team agreement on the decision and/or define when this decision should be re-considered and/or links to other decisions and resources.}
```

## Section Guide

### Status Values

| Status | Meaning |
|--------|---------|
| `draft` | Initial creation, not yet reviewed |
| `proposed` | Ready for team review |
| `accepted` | Approved and active |
| `rejected` | Considered but not adopted |
| `deprecated` | No longer recommended |
| `superseded by [ADR-NNNN]` | Replaced by newer decision |

### Title

- Use imperative mood ("Use X", "Adopt Y", "Migrate to Z")
- Keep concise (5-10 words)
- Start with verb

### Context and Problem Statement

- 2-4 sentences describing the situation
- Can be phrased as a question
- Include relevant constraints

### Decision Drivers

- List forces influencing the decision
- Prioritize by importance
- Include both technical and business drivers

### Considered Options

- Minimum 2 options (including chosen)
- Include "do nothing" if viable
- Brief titles, details in Pros/Cons section

### Decision Outcome

- State chosen option clearly
- Explain why it was chosen
- Reference decision drivers it satisfies

### Consequences

- Categorize as Good/Bad/Neutral
- Be honest about tradeoffs
- Include operational impacts

## Optional Sections

These sections enhance completeness but may be omitted for simpler decisions:

- **Confirmation** - How to verify compliance
- **Pros and Cons of the Options** - Detailed option analysis
- **More Information** - Links, references, caveats

## Minimal Template

For quick decisions, use this shortened form:

```markdown
---
status: draft
date: YYYY-MM-DD
---

# {Title}

## Context and Problem Statement

{description}

## Decision Drivers

* {driver 1}
* {driver 2}

## Decision Outcome

Chosen option: "{option}", because {reason}.

### Consequences

* Good, because {positive}
* Bad, because {negative}
```


path: scripts/next_adr_number.py
content: #!/usr/bin/env python3
"""Get the next ADR sequence number.

Scans docs/adrs/ for existing ADRs and returns the next available number.

Usage:
    python scripts/next_adr_number.py
    python scripts/next_adr_number.py --dir /path/to/docs/adrs
    python scripts/next_adr_number.py --count 3  # Pre-allocate 3 numbers for parallel writes
"""

import argparse
import re
import sys
from pathlib import Path


def find_adr_directory() -> Path:
    """Find the ADR directory by searching up from cwd."""
    candidates = [
        Path("docs/adrs"),
        Path("docs/adr"),
        Path("adr"),
        Path("adrs"),
        Path("doc/adr"),
        Path("doc/adrs"),
    ]

    # Search from current directory
    cwd = Path.cwd()
    for candidate in candidates:
        if (cwd / candidate).is_dir():
            return cwd / candidate

    # Search up to git root
    git_root = cwd
    while git_root != git_root.parent:
        if (git_root / ".git").exists():
            break
        git_root = git_root.parent

    for candidate in candidates:
        if (git_root / candidate).is_dir():
            return git_root / candidate

    return cwd / "docs/adrs"


def get_existing_numbers(adr_dir: Path) -> list[int]:
    """Extract ADR numbers from filenames in the directory."""
    pattern = re.compile(r"^(\d{4})-.*\.md$")
    numbers = []

    if not adr_dir.exists():
        return numbers

    for file in adr_dir.iterdir():
        if file.is_file():
            match = pattern.match(file.name)
            if match:
                numbers.append(int(match.group(1)))

    return sorted(numbers)


def next_number(existing: list[int]) -> int:
    """Calculate the next ADR number."""
    if not existing:
        return 1
    return max(existing) + 1


def format_number(num: int) -> str:
    """Format number as zero-padded 4-digit string."""
    return f"{num:04d}"


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Get the next ADR sequence number"
    )
    parser.add_argument(
        "--dir",
        type=Path,
        help="ADR directory (auto-detected if not specified)",
    )
    parser.add_argument(
        "--list",
        action="store_true",
        help="List existing ADR numbers",
    )
    parser.add_argument(
        "--count",
        type=int,
        default=1,
        help="Number of sequential ADR numbers to allocate (for parallel writes)",
    )
    args = parser.parse_args()

    adr_dir = args.dir or find_adr_directory()
    existing = get_existing_numbers(adr_dir)

    if args.list:
        if existing:
            print(f"ADR directory: {adr_dir}")
            print(f"Existing ADRs: {[format_number(n) for n in existing]}")
        else:
            print(f"No ADRs found in {adr_dir}")
        return 0

    next_num = next_number(existing)

    if args.count == 1:
        print(format_number(next_num))
    else:
        # Output multiple numbers, one per line, for parallel allocation
        allocated = [format_number(next_num + i) for i in range(args.count)]
        print("\n".join(allocated))

    return 0


if __name__ == "__main__":
    sys.exit(main())


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)