adr-writer · git:20260818.1172578 · 2026-08-18 · sha256 2d73d37f5aa6d6e1
adr-writer git:20260818.1172578A
Immutable. This exact content never changes and is served at /api/v1/blob/2d73d37f5aa6d6e1.
---
name: adr-writer
description: Record a design decision as an Architecture Decision Record (ADR) in the project's `docs/adr/` directory. Use this skill whenever the user says "let's ADR this", "record this decision", "write an ADR for X", or when context-sync Phase 3 needs to extract a buried decision. The skill resolves the target ADR directory from cwd, picks the next sequence number with no collision, delegates 7-section body generation (incl. `Review-when` expiry conditions) to the adr-writer agent, and updates the ADR index. Works across any repo — auto-detects or creates `docs/adr/` from the repo root.
user-invocable: true
origin: shimo4228
---
# ADR Writer
Capture a design decision as a numbered ADR with consistent structure. The skill handles the boilerplate (directory detection, sequence numbering, index update); the `adr-writer` agent handles the prose.
## Why a Skill + Agent Split
The skill owns deterministic concerns: where the ADR goes, what number it gets, which index needs updating. These are easy to get wrong silently (number collisions, sub-directory cwd confusion, index drift) so they live in scripted steps.
The agent owns **rendering**, not deciding: given a frozen, already-approved decision packet, it re-expresses each section into the template, calibrates against neighbouring ADRs' house style, resolves links, and writes the file. What the ADR *means* — the real Context, the actual Decision, why each Alternative was rejected, which Consequences follow — is a **semantic decision that stays with the caller (the main loop)**. The agent has no authority to infer or invent it; it refuses to write when the packet is incomplete. See [ADR-0016](../../docs/adr/0016-writer-agents-render-not-decide.md) — writer agents render, they do not decide. (This split does not exist merely to isolate context: rendering to a fixed house-style template is low-authority and cheaply verifiable, which is exactly what makes delegation safe here — unlike translation, whose prose carries the author's non-convergent voice and stays in the main loop.)
## When to Use
- The user says "let's ADR this", "record this decision", "write an ADR for X"
- `context-sync` Phase 3 has extracted a decision from CLAUDE.md / README that needs ADR form
- The user is about to merge a PR with an irreversible architectural change and wants a record
- A debate concluded in chat and the user wants the outcome durable
## When NOT to Use
- Bug fix records (use the commit message)
- Reversible refactors (commit message is enough)
- Personal preferences ("I like spaces over tabs") — those go in a style guide, not an ADR
## Workflow
### Step 1: Resolve the ADR directory
```bash
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || REPO_ROOT="$(pwd)"
ADR_DIR="$REPO_ROOT/docs/adr"
```
If `$ADR_DIR` does not exist, create it — the ADR request itself covers the directory, and
git makes it reversible. Mention the creation in your final report. Write a minimal
`README.md` index alongside:
```markdown
# Architecture Decision Records
| ID | Title | Status | Date |
|---|---|---|---|
## Template
ADRs in this repository use the 7-section template:
Status / Date / Context / Decision / Review-when / Alternatives Considered / Consequences.
`Review-when` holds the expiry conditions — an ADR is a dated hypothesis, not a permanent constraint.
File name: `NNNN-kebab-case-title.md` (zero-padded 4-digit sequence).
```
If the user declines, stop and explain that ADRs need a directory.
### Step 2: Pick the next sequence number
```bash
LAST_NUM=$(ls "$ADR_DIR"/[0-9]*-*.md 2>/dev/null | sort -V | tail -1 | sed -E 's|.*/([0-9]+)-.*|\1|')
if [ -z "$LAST_NUM" ]; then
NEXT_NUM="0001"
else
NEXT_NUM=$(printf "%04d" $((10#$LAST_NUM + 1)))
fi
```
Verify uniqueness right before writing (race safety):
```bash
[ -e "$ADR_DIR/$NEXT_NUM-"*.md ] && echo "COLLISION" || echo "OK"
```
If `COLLISION`, recompute `NEXT_NUM` from the latest state.
### Step 3: Gather the 7 inputs
Ask the user (or accept from caller) for:
1. **Title** — short, kebab-case-friendly (e.g., `context-sync-cascade-and-writer-agents`). The skill will compose the filename.
2. **Status** — `proposed | accepted | superseded | deprecated`. Default `accepted`.
3. **Context** — what problem prompted this decision (raw text OK).
4. **Decision** — what was decided (raw text OK).
5. **Review-when** — the expiry conditions (失効条件): which observation or premise failure would void or weaken this decision, 1-3 lines. It can only be captured at write time (ADR-0021); an ADR without it reads as permanent. If there genuinely is none, say so explicitly — the agent renders 「無し — 恒久判断ではなく記録」.
6. **Alternatives** — what else was considered and why rejected, or, for an alternative that stays live, 「未決 — 再訪条件: …」 (raw text or list). Keeping a rival open is allowed; a straw man is not.
7. **Consequences** — what becomes easier / harder (raw text or list).
If 3-7 are missing, request them — do not proceed. ADRs without these sections are noise.
**Assemble and approve the decision packet before delegating.** The main loop holds the semantic authority for this ADR, so it — not the agent — must settle the actual content: the real Context, the decision as decided, the Review-when triggers, why each Alternative was rejected (or under what condition it is revisited), which Consequences genuinely follow. Confirm this packet with the user (especially the Review-when, the rejection reasons and both sides of Consequences) *before* Step 4. The agent that follows only renders what you hand it; it will not fill a gap you leave. If the decision is still fuzzy, resolve it here in the main loop — do not expect the agent to infer it.
### Step 4: Delegate body generation to the adr-writer agent
Invoke the `adr-writer` agent via the Agent tool with:
- ADR number (`$NEXT_NUM`)
- Repo root (`$REPO_ROOT`)
- ADR directory (`$ADR_DIR`)
- Title (kebab-case slug)
- Status / Date / Context / Decision / Review-when / Alternatives / Consequences
The agent will:
- Read the 2 most recent ADRs in `$ADR_DIR` for style calibration
- Fill the 7 sections from your input (no invention)
- Write the file at `$ADR_DIR/$NEXT_NUM-<title>.md`
- Return a summary block
If the agent returns "needs more input", surface the missing-fields message to the user and stop. Do not push partial ADRs.
**Fidelity check (main loop, after the agent writes).** Because the agent only renders, verify it added nothing semantic: read the written file and confirm every Context fact, Decision clause, rejection reason, and Consequence traces back to the packet you approved in Step 3. If the agent introduced an inferred claim (a "logically entailed" consequence, an unstated rationale), strike it or send it back — the render must not exceed the packet.
### Step 5: Update the index
After the agent confirms file written, append a row to `$ADR_DIR/README.md` index table:
```bash
# Read current index
# Find the table block (lines between "| ID |" header and the next "##" heading)
# Append: | $NEXT_NUM | <title human-readable> | <status> | <date> |
```
If the index table is malformed or absent, regenerate it from the directory:
```bash
for f in "$ADR_DIR"/[0-9]*-*.md; do
num=$(basename "$f" | sed -E 's|([0-9]+)-.*|\1|')
title=$(head -1 "$f" | sed -E 's|^# ADR-[0-9]+: ||')
status=$(awk '/^## Status/{getline; getline; print; exit}' "$f")
date=$(awk '/^## Date/{getline; getline; print; exit}' "$f")
echo "| $num | $title | $status | $date |"
done
```
### Step 6: Report
Tell the user:
```
ADR written
---
File: docs/adr/<NNNN>-<title>.md
Number: <NNNN>
Status: <status>
Index: updated (+1 row)
```
## Edge cases
| Case | Handling |
|---|---|
| Called from a sub-directory of the repo | Use `git rev-parse --show-toplevel`; never trust raw cwd |
| Not a git repo | Fall back to cwd, warn the user that they should `git init` |
| ADR number was reserved verbally but not yet written ("I'll write ADR-0010 later") | Skill cannot know; ask the user whether to take the next free number or the reserved one |
| User wants to supersede an existing ADR | Update the old ADR's Status to `superseded by ADR-NNNN`, then create the new one. Two file writes. |
| New ADR **partially weakens** an old one (a premise expired, a Review-when trigger fired) but does not supersede it | Do not flip Status. Append under the affected section of the old ADR: `> **注記(YYYY-MM-DD, ADR-NNNN)**: <what changed and what still stands>`. Never delete the original text — the strength history stays readable in place (precedents: ADR-0018 §Consequences, ADR-0028). This is a main-loop step after the agent has written the new file. |
| Title contains spaces or non-ASCII | Skill normalizes to kebab-case ASCII for the filename; preserves original in the `# ADR-NNNN: ...` heading |
## Boundaries
- **Do not** invent missing sections. Refuse to write an ADR with `Context: [TBD]` or similar placeholders.
- **Do not** modify ADRs other than the new one and (optionally) the index, except the two explicit main-loop steps above (Status flip on full supersede; dated 注記 on partial weakening). The `adr-writer` agent itself never touches an existing ADR (ADR-0016).
- **Do not** infer the user's decision from chat history without confirming. Ask, even if the answer feels obvious.
- **Do not** commit the file. The user owns the commit step.
## Reference Files
- ADR template canonical source: read the target repo's existing ADRs to mirror their voice. For the harness itself, see `~/.claude/docs/adr/README.md`.
- Agent that fills the body: `~/.claude/agents/adr-writer.md`.
- Evals: `evals/evals.json` (3 scenarios — new ADR / missing docs-adr / sequence collision).