docs: AI-optimized docs restructure (Phases 1–8) (#61)

* docs(dialog-pattern): add generic entity-dialog spec

Introduceert docs/patterns/dialog.md als bron-of-truth voor elke
create/edit/detail-dialog in Scrum4Me, ongeacht het achterliggende
dataobject. Bevat 14 secties: uitgangspunten, stack, component-
architectuur, layout, validatie, drielaagse demo-policy, submission,
dialog-gedrag, theming, footer, triggers/URL-state, per-entiteit
profile-template, out-of-scope, en een verificatie-checklist.

Registreert het patroon in CLAUDE.md "Implementatiepatronen"-tabel
zodat Claude (en mensen) de spec verplicht raadplegen voor elke
nieuwe dialog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(dialog-pattern): convert task spec + add pbi/story entity-profiles

Reduceert docs/scrum4me-task-dialog.md van 507 naar ~140 regels: alle
gedeelde regels verhuisd naar docs/patterns/dialog.md, dit document
bevat nu alleen Task-specifieke velden, URL-pattern, status-veld,
server actions, triggers en bewuste out-of-scope-keuzes.

Voegt twee nieuwe entity-profielen toe voor bestaande dialogen:
- docs/scrum4me-pbi-dialog.md (PbiDialog: state-based, code+title-rij,
  PbiStatusSelect, geen delete in v1)
- docs/scrum4me-story-dialog.md (StoryDialog: state-based, header met
  status/priority badges, inline activity-log, demo-readonly-fallback,
  inline-delete-confirm i.p.v. AlertDialog)

Beide profielen documenteren expliciet de "Bekende gaps t.o.v.
generieke spec" zodat opvolgende PR's de afwijkingen kunnen
rechtzetten of bewust kunnen accorderen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Added pdevelopment docs

* docs(plans): add docs-restructure plan for AI-optimized lookup

Audit of existing 39 doc files (~10.700 lines) and a phased restructure
proposal aimed at minimising the tokens an AI agent has to read to find
the right reference. Captures resolved decisions on language (English),
ADR template (Nygard default with MADR escape-hatch), index generator
(node script), and folder taxonomy. Proposal status — fase 1 to follow.

* docs(adr): add ADR scaffolding (templates, README, meta-ADR)

Set up docs/adr/ as the canonical home for architecture decisions:

- templates/nygard.md — default four-section format (Status, Context,
  Decision, Consequences) for one-way-door decisions.
- templates/madr.md — MADR v4 with YAML front-matter and explicit
  Considered Options for decisions where rejected alternatives matter.
- README.md — naming convention (NNNN-kebab-case), template-selection
  guidance (Nygard default; MADR for auth, queue mechanics, agent
  integration), status lifecycle, and ADR roster.
- 0000-record-architecture-decisions.md — meta-ADR establishing the
  practice itself, in Nygard format.

Backfilling existing implicit decisions (base-ui-over-radix, float
sort_order, demo-user three-layer policy, etc.) is fase 6 of the
docs-restructure plan.

* feat(docs): add docs index generator + initial INDEX.md

scripts/generate-docs-index.mjs walks docs/**/*.md, parses YAML
front-matter (or first H1 fallback) and a Nygard-style ## Status
section, then writes docs/INDEX.md with grouped tables for ADRs,
Specs, Plans (with archive subsection), Patterns, and Other.

Pure Node 20 (no external deps); idempotent — running it twice
produces byte-identical output. Excludes adr/templates/, the ADR
README, INDEX.md itself, and any *_*.md sidecar file.

Wire-up:
- package.json: docs:index → node scripts/generate-docs-index.mjs

Initial run indexed 35 docs across the existing structure; the
generated INDEX.md is committed so the table is reviewable in the
PR before hooking generation into a pre-commit step.

* chore: ignore Obsidian vault and personal sidecar files

Add .obsidian/ (Obsidian vault config) and _*.md (personal sidecar
notes) to .gitignore so the docs/ tree can serve as canonical source
of truth while still being usable as an Obsidian vault for personal
authoring. The docs index generator already excludes the same _*.md
pattern from INDEX.md.

* docs(plans): add PBI bulk-create spec for docs-restructure

Machine-parseable spec for an executor that calls the scrum4me MCP
(create_pbi → create_story → create_task) to seed the docs-restructure
work into the DB.

- Section 1 (Context) is the PBI description; serves as task-context
  via mcp__scrum4me__get_claude_context.
- Section 2 lists the 6 resolved decisions (English, MD3+styling
  merged, solo-paneel merged, .Plans archived, Nygard ADR default,
  node index script).
- Section 3 records what already shipped on this branch so the
  executor doesn't duplicate the ADR scaffolding or index generator.
- Section 4 carries the structured YAML graph: 1 PBI, 8 stories
  (one per phase), 39 tasks. product_id is REPLACE_ME — fill before
  running.
- YAML validated with PyYAML; field schema sanity-checked.

* docs(junk-cleanup): remove stub patterns/test.md

* docs(junk-cleanup): archive .Plans/ to docs/plans/archive/

* docs(front-matter): add YAML front-matter to docs/ root

* docs(front-matter): add YAML front-matter to patterns/

* docs(front-matter): add YAML front-matter to plans + agent files

* docs(index): regenerate INDEX.md after front-matter pass

* docs(naming): drop scrum4me- prefix from doc filenames

* docs(naming): lowercase API.md and MD3 filenames

* docs(naming): rename plan file to kebab-case ASCII

* docs(naming): rename middleware.md to proxy.md (next 16)

* docs(naming): polish CLAUDE.md doc-index after renames

* docs(taxonomy): scaffold topical folders under docs/

* docs(taxonomy): move spec files into docs/specs/

* docs(taxonomy): move design/api/qa/backlog/assets into folders

* docs(taxonomy): move agent-instruction-audit into decisions/

* docs(split): break architecture.md into 6 topical files

* docs(split): merge solo-paneel-spec into specs/functional.md

* docs(split): merge md3-color-scheme into design/styling

* docs(trim): extract branch/commit rules into runbook

* docs(trim): extract MCP integration into runbook

* docs(adr): add 0001-base-ui-over-radix

* docs(adr): add 0002-float-sort-order

* docs(adr): add 0003-one-branch-per-milestone

* docs(adr): add 0004-status-enum-mapping

* docs(adr): add 0005-iron-session-over-nextauth

* docs(adr): add 0006-demo-user-three-layer-policy

* docs(adr): add 0007-claude-question-channel-design

* docs(adr): add 0008-agent-instructions-in-claude-md + update README index

* docs(index): regenerate after ADR 0001-0008

* docs(glossary): add docs/glossary.md

* chore(docs): regenerate INDEX.md in pre-commit hook

* docs(readme): link INDEX + glossary + agent instructions

* feat(docs): add doc-link checker script

* chore(docs): wire docs:check-links and docs npm scripts

* ci(docs): block merge on broken doc links

* docs(links): fix broken cross-references after restructure

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-03 03:21:59 +02:00 committed by GitHub
parent 289bcf9bf0
commit 7e45bbdbc0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
81 changed files with 12364 additions and 3154 deletions

View file

@ -0,0 +1,66 @@
# ADR-0000: Record architecture decisions
## Status
accepted
## Context
Scrum4Me makes several non-obvious architectural choices that aren't visible
from the code alone — for example, why we use `@base-ui/react` rather than
Radix, why drag-and-drop ordering uses float `sort_order` instead of integer
positions, why authentication runs on iron-session rather than NextAuth,
and why the demo-user policy is enforced in three layers. These decisions
are scattered across `CLAUDE.md`, individual pattern docs, plan files, and
commit messages. New contributors and AI agents working on the codebase
have no fast path to the *why*, which leads to one of two failure modes:
they either re-litigate decisions that were already settled, or they make
changes that violate constraints they didn't know about.
We want a single, predictable place to record significant architectural
choices, with enough context that a reader six months from now can decide
whether the decision still holds.
## Decision
We adopt Architecture Decision Records (ADRs) as the canonical format for
documenting significant architectural choices in this codebase. ADRs live
in `docs/adr/`, are numbered sequentially with a four-digit prefix
(`0001-...md`, `0002-...md`, …), and follow one of two templates:
- **Nygard** ([`templates/nygard.md`](./templates/nygard.md)) — default,
for one-way-door decisions with a clear motivating context.
- **MADR v4** ([`templates/madr.md`](./templates/madr.md)) — for
decisions where weighing multiple alternatives is part of the value
the record provides (auth, queue mechanics, agent integration).
The full conventions — file naming, status lifecycle, template selection
guidance — are documented in [`README.md`](./README.md).
ADRs are immutable once accepted: course corrections create a new ADR
that supersedes the old one rather than editing the original.
## Consequences
### Positive
- Architectural choices have a single, predictable home that an AI agent
or new contributor can find with one `ls docs/adr/`.
- The "why" of each decision is captured at the moment it's made, when
the context is fresh, rather than reconstructed later from commits.
- Superseded decisions remain readable, so future contributors can see the
history of a choice without git archaeology.
- The format scales: writing an ADR is a 15-minute activity for the
default Nygard template, low enough overhead to be worth doing every
time.
### Negative
- Adds a small ritual to every significant architectural decision — easy
to skip when moving fast, leading to a stale or incomplete record if
not enforced through review.
- Backfilling existing decisions requires writing 58 retrospective ADRs
for choices that were never recorded (planned in fase 6 of
[`../plans/docs-restructure-ai-lookup.md`](../plans/docs-restructure-ai-lookup.md)).
- Two templates means a per-decision choice about which to use. Mitigated
by making Nygard the explicit default in `README.md`.

View file

@ -0,0 +1,34 @@
# ADR-0001: Use @base-ui/react instead of Radix UI
## Status
accepted
## Context
shadcn/ui ships visual components that are typically built on Radix UI primitives. When we bootstrapped Scrum4Me with shadcn, the component wrappers in `components/ui/` were adapted to use `@base-ui/react` instead of the Radix packages. `@base-ui/react` exposes the same accessibility primitives but uses a `render` prop for composition instead of Radix's `asChild` pattern. Attempting to use `asChild` in our TypeScript-strict setup produced type errors because the prop is not declared in `@base-ui/react`'s API surface.
## Decision
We use `@base-ui/react` exclusively. No Radix UI package (`@radix-ui/*`) is imported anywhere in the codebase. Composition always uses the `render` prop:
```tsx
// ✅ correct
<TooltipTrigger render={<button />}>…</TooltipTrigger>
// ❌ wrong — asChild does not exist on @base-ui/react primitives
<TooltipTrigger asChild><button></button></TooltipTrigger>
```
## Consequences
### Positive
- TypeScript stays clean; no `any` casts or `asChild` workarounds.
- `@base-ui/react` is actively maintained by the MUI team with React 19 support.
- Composition pattern is explicit and grep-friendly.
### Negative
- AI agents trained on Radix-based shadcn documentation will default to `asChild` — they must be reminded of the `render`-prop pattern (this ADR exists for that reason).
- shadcn CLI-generated components may need manual adjustment when installed.

View file

@ -0,0 +1,26 @@
# ADR-0002: Use float sort_order for drag-and-drop ordering
## Status
accepted
## Context
The planning screens (PBI list, Story list, Task list, Solo board) all support drag-and-drop reordering. With integer positions, inserting an item between positions 3 and 4 requires renumbering every subsequent row — an O(N) write for every drag operation. At the scale of a sprint board this is tolerable, but it causes unnecessary lock contention and makes optimistic UI rollback harder. See `docs/patterns/sort-order.md` for the full implementation pattern.
## Decision
Every ordered collection uses a `Float` column named `sort_order`. Inserting between two items sets `sort_order = (prev + next) / 2`. New items appended to the end get `last + 1.0`.
## Consequences
### Positive
- Reorder writes are O(1) — only the moved item's row is updated.
- Optimistic UI updates map directly to the same midpoint calculation.
- No lock contention on adjacent rows during concurrent drags.
### Negative
- Repeated insertions between the same two items cause float precision drift. Mitigation: a periodic compaction job normalizes `sort_order` values to integers × 1000 when the gap drops below `0.001`. This is a known trade-off; compaction has not been needed in practice yet.
- Queries that return items in `sort_order` order must always include `ORDER BY sort_order` — there is no implicit ordering.

View file

@ -0,0 +1,61 @@
---
status: accepted
date: 2026-05-03
decision-makers: [janpetervisser]
---
# ADR-0003: One branch per milestone, push only after user test
## Context and Problem Statement
Every `git push` to a feature branch triggers a Vercel preview deployment. On the Hobby plan, preview builds are limited and cost money. How should we structure branches and pushes to minimize preview-build spend while still supporting a fast AI-driven development loop?
## Decision Drivers
- Vercel Hobby plan: preview builds are finite and billed per deployment.
- Small team (primarily solo developer + AI agent): branch overhead should be minimal.
- AI-driven flow: the agent commits frequently in small logical layers; we don't want a push per commit.
- User acceptance is done interactively per milestone, not per story.
## Considered Options
- **Branch per story** — one branch per story, PR per story.
- **Branch per milestone** — one branch for all stories in a milestone, single PR after user test.
- **Trunk-based development** — commit directly to `main` with feature flags.
## Decision Outcome
Chosen option: **Branch per milestone**, because it is the only option that keeps preview-build count proportional to milestones (not stories), while still enabling isolated review via a single PR.
### Consequences
- Good, because preview deployments are rare — only one per milestone reaching review.
- Good, because PR history maps to milestones, not micro-stories.
- Bad, because branches live longer; merge conflicts are larger but less frequent.
- Bad, because a single failed story blocks the milestone PR.
### Confirmation
Before pushing, the developer/agent must confirm explicitly. `git push` is never automated. See `docs/runbooks/branch-and-commit.md`.
## Pros and Cons of the Options
### Branch per story
- Good, because small, focused PRs are easy to review.
- Bad, because each push triggers a preview build — N stories = N builds per milestone.
### Branch per milestone
- Good, because minimal preview builds.
- Good, because the PR represents a coherent feature set.
- Bad, because long-lived branches.
### Trunk-based development
- Good, because no branch management overhead.
- Bad, because requires feature flags to hide incomplete work — too much infrastructure for this scale.
## More Information
Revisit this decision if/when the Vercel account upgrades to Pro (unlimited preview builds). At that point, branch-per-story is the preferred default. Update `docs/runbooks/branch-and-commit.md` and this ADR when that happens.

View file

@ -0,0 +1,28 @@
# ADR-0004: DB enums UPPER_SNAKE, API enums lowercase, mapped exclusively via lib/task-status.ts
## Status
accepted
## Context
Prisma generates TypeScript types from PostgreSQL enum values verbatim. Our DB enums use `UPPER_SNAKE_CASE` (e.g. `TO_DO`, `IN_PROGRESS`, `DONE`) because that is the PostgreSQL convention and it keeps Prisma-generated code readable. However, REST API consumers — including Claude Code via MCP and frontend fetch calls — expect lowercase, underscore-separated values (`todo`, `in_progress`, `done`). Without a single conversion boundary, ad-hoc `.toLowerCase()` calls scattered across route handlers and actions introduce silent mapping bugs when enum values change.
## Decision
- The database retains `UPPER_SNAKE_CASE` enum values. Prisma schema is the source of truth.
- The REST API (route handlers) and MCP server always expose and accept **lowercase** enum strings.
- All conversion happens exclusively in `lib/task-status.ts` via named mapper functions. No `.toLowerCase()`, `.toUpperCase()`, or inline string mapping anywhere else.
## Consequences
### Positive
- A single file to audit when enum values change.
- TypeScript types catch missing branches in mapper exhaustive checks.
- API contract is stable and grep-friendly.
### Negative
- Every developer (and AI agent) must know to use the mappers rather than string coercion. Violations compile fine but break the API contract at runtime.
- Mitigated by an ESLint rule that flags direct `.toLowerCase()` on known enum types (pending implementation).

View file

@ -0,0 +1,71 @@
---
status: accepted
date: 2026-05-03
decision-makers: [janpetervisser]
---
# ADR-0005: Use iron-session for authentication instead of NextAuth/Clerk/Supabase Auth
## Context and Problem Statement
Scrum4Me requires username/password login without email verification, a synchronous demo-user check on every request, and full control over the session cookie shape (including an `isDemo` flag). Which authentication solution fits these constraints at minimal complexity?
## Decision Drivers
- No email required — username/password only.
- Demo-user policy (ADR-0006) requires a synchronous `isDemo` check in both middleware and server actions.
- No third-party redirect chain — auth must stay in-process.
- Solo-developer project: minimal external dependencies preferred.
## Considered Options
- **NextAuth / Auth.js v5**
- **Clerk**
- **Supabase Auth**
- **iron-session + bcryptjs**
## Decision Outcome
Chosen option: **iron-session + bcryptjs**, because it is the only option that gives us full control over cookie contents, has zero external redirect dependency, and lets us embed `isDemo` directly in the session payload.
### Consequences
- Good, because session structure is fully controlled — we add any field we need.
- Good, because no external service dependency for auth; works offline and in CI.
- Good, because synchronous cookie read in `proxy.ts` middleware is trivial.
- Bad, because we own the password hashing, session rotation, and CSRF protection.
- Bad, because no OAuth/social login without building it ourselves.
### Confirmation
`lib/session.ts` defines the session type. `docs/patterns/iron-session.md` documents the pattern. Any new field on the session object must be added to the type there.
## Pros and Cons of the Options
### NextAuth / Auth.js v5
- Good, because OAuth, email magic links, and credentials all in one library.
- Bad, because credentials provider is discouraged in v5; session shape is opaque.
- Bad, because adding `isDemo` to the JWT requires custom callbacks.
### Clerk
- Good, because fully managed, beautiful UI, no session code to maintain.
- Bad, because requires third-party redirect; adds external dependency.
- Bad, because demo-user policy would require custom session metadata sync.
### Supabase Auth
- Good, because integrates with Supabase storage (but we use Neon).
- Bad, because username/password without email is not the primary use case.
- Bad, because adds a second database dependency just for auth.
### iron-session + bcryptjs
- Good, because minimal, explicit, and TypeScript-native.
- Good, because session payload is a plain object we fully control.
- Neutral, because we write our own password logic (bcrypt makes it safe).
## More Information
See `docs/patterns/iron-session.md` for implementation details. Revisit if multi-tenant or SSO requirements emerge.

View file

@ -0,0 +1,30 @@
# ADR-0006: Demo-user write protection enforced in three layers
## Status
accepted
## Context
Scrum4Me has a demo account that allows prospective users to explore the app without signing up. The demo user must never be able to create, update, or delete any data. A single guard at one layer is insufficient: a bug or a missing check in any one layer would expose a write path. See `docs/architecture/auth-and-sessions.md` and `docs/plans/ST-1110-demo-readonly.md` for implementation details.
## Decision
Write protection for the demo user is enforced at **three independent layers**:
1. **Network — `proxy.ts`:** The Next.js proxy middleware rejects all non-GET requests from demo sessions before they reach any route handler or server action.
2. **Server — every Server Action and Route Handler:** Each write endpoint checks `session.isDemo` and returns `403` immediately if true.
3. **UI — disabled buttons + `<DemoTooltip>`:** Write controls (create, edit, delete, reorder) are rendered as `disabled` with a tooltip explaining the demo restriction. No write request is ever sent.
## Consequences
### Positive
- Defense-in-depth: any single layer can fail independently without exposing a write path.
- Clear user feedback at the UI layer without relying on error responses.
- Straightforward to audit: search for `isDemo` to find all enforcement points.
### Negative
- Three enforcement sites for every new write operation — easy to miss one when adding a new feature.
- Mitigation: the `DemoTooltip` pattern is documented in `docs/patterns/` and enforced in code review.

View file

@ -0,0 +1,64 @@
---
status: accepted
date: 2026-05-03
decision-makers: [janpetervisser]
---
# ADR-0007: Agent ↔ user question channel via persistent table + LISTEN/NOTIFY
## Context and Problem Statement
When Claude Code is executing a task and needs human input, it must be able to pause, post a question, and receive an answer — potentially across separate sessions. The app must notify an active user that a question is waiting. How should this async communication channel be designed?
## Decision Drivers
- Questions must survive agent session restarts (persistent, not in-memory).
- The app user needs a real-time notification without polling from the client.
- The infrastructure already includes PostgreSQL with LISTEN/NOTIFY (used for M8 realtime updates).
- Answers must be readable by the agent in a future session without the original connection.
## Considered Options
- **Synchronous polling only** — agent polls an endpoint every N seconds.
- **Push via SSE without persistence** — agent opens SSE connection, user pushes answer over it.
- **Persistent `claude_questions` table + PostgreSQL LISTEN/NOTIFY**
## Decision Outcome
Chosen option: **Persistent table + LISTEN/NOTIFY**, because it is the only option that survives session restarts on both ends and reuses existing infrastructure.
### Consequences
- Good, because questions survive agent and user session restarts.
- Good, because reuses the `scrum4me_changes` LISTEN/NOTIFY channel already in place.
- Good, because any product member with access can answer, not just the original session.
- Bad, because adds a `claude_questions` table and trigger to the schema.
- Bad, because LISTEN/NOTIFY requires a persistent DB connection (`DIRECT_URL` env var).
### Confirmation
`docs/patterns/claude-question-channel.md` documents the full implementation. MCP tools: `ask_user_question`, `get_question_answer`, `list_open_questions`, `cancel_question`.
## Pros and Cons of the Options
### Synchronous polling only
- Good, because simple — no extra infrastructure.
- Bad, because agent blocks a CPU slot while polling; question lost if agent restarts.
- Bad, because no real-time notification to the user.
### Push via SSE without persistence
- Good, because low latency.
- Bad, because agent-side SSE connection is fragile across restarts.
- Bad, because no persistence — if the user isn't connected at the moment the question is posted, it is lost.
### Persistent table + LISTEN/NOTIFY
- Good, because fully durable.
- Good, because real-time notification to the user reuses existing M8 infrastructure.
- Neutral, because requires `DIRECT_URL` for a persistent PostgreSQL connection (already required for M8).
## More Information
See `docs/plans/M11-claude-questions.md` and `docs/patterns/claude-question-channel.md`.

View file

@ -0,0 +1,41 @@
---
title: "ADR-0008: Agent instructions in CLAUDE.md + topical runbooks"
status: accepted
date: 2026-05-03
---
# ADR-0008: Agent instructions in CLAUDE.md + topical runbooks
## Status
Accepted
## Context
Claude Code reads `CLAUDE.md` at session start and injects it verbatim into the system prompt. As the project grew, the file expanded to ~350 lines covering stack, patterns, MCP tools, commit strategy, branch policy, Vercel deployment, and demo-user rules. At that length the token cost was significant and the file was hard to navigate — both for humans and for AI agents skimming for a specific rule.
A separate `docs/agent-instruction-audit.md` tracked *why* each rule existed, but that meta-history was buried and rarely consulted. The result: rules were added but never removed, and the file drifted toward being a reference dump rather than a decision-forcing instrument.
## Decision
Split `CLAUDE.md` into a short ≤150-line orientation hub that hard-links to topical runbooks:
- `docs/runbooks/branch-and-commit.md` — branch policy, plan mode, commit strategy
- `docs/runbooks/mcp-integration.md` — all 18 MCP tools + batch-loop invariants
- `docs/runbooks/deploy-vercel.md` — Sharp, cron, env-var, preflight rules
`CLAUDE.md` retains only the rules an agent **must** have in active context on every turn: the orientation table, the 8 hardstop rules, stack table, patterns quickref, and env vars. Everything else is a hyperlink.
`AGENTS.md` (Codex entry-point) becomes a 10-line redirect stub pointing at `CLAUDE.md`.
## Consequences
**Positive**
- Active-context token cost drops ~60 % — the hub is ~114 lines vs 350.
- Each runbook is standalone and audience-tagged (`audience: [ai-agent]` etc.), so a future agent can fetch exactly the doc it needs.
- The split gives a natural place to add rules without polluting the root file.
- `docs/decisions/agent-instructions-history.md` (renamed from `agent-instruction-audit.md`) documents the *why* behind each rule as persistent institutional memory.
**Negative**
- An agent that reads only `CLAUDE.md` will miss branch/MCP/deploy rules unless it follows the links. Mitigation: critical one-liners (e.g., "PR only after user test") are kept as hardstops in the hub.
- The split increases the number of files to maintain; runbooks can drift from `CLAUDE.md` if authors forget to update both. Mitigation: docs-sync convention in `CLAUDE.md` Conventions section.

101
docs/adr/README.md Normal file
View file

@ -0,0 +1,101 @@
---
title: Architecture Decision Records
status: active
audience: ai-agent, maintainer, contributor
language: en
last_updated: 2026-05-02
---
# Architecture Decision Records
This directory contains the Architecture Decision Records (ADRs) for Scrum4Me.
## What is an ADR
An ADR is a short document that captures a single significant architectural
decision: the context that forced the decision, the choice we made, and the
consequences of that choice. ADRs are immutable once accepted — if a later
decision changes course, we write a new ADR that supersedes the old one.
We use ADRs because the project mixes several non-obvious choices (Next.js 16
specifics, `@base-ui/react` over Radix, float `sort_order` for drag-and-drop,
iron-session over NextAuth, demo-user three-layer policy, MCP integration
patterns) and an AI agent reading the codebase six months from now needs to
find the *why* without spelunking through commit history.
## File naming
```
NNNN-kebab-case-title.md
```
- `NNNN` — four-digit zero-padded sequential number, starting at `0001`
(`0000` is reserved for the meta-ADR that introduces the practice).
- `kebab-case-title` — lowercase, hyphen-separated, short noun phrase
echoing the decision (`base-ui-over-radix`, not `decided-to-use-baseui`).
- Always `.md`.
## Choosing a template
Two templates live in [`templates/`](./templates/). Default to Nygard.
### Nygard (default — [`templates/nygard.md`](./templates/nygard.md))
Use Nygard for the common case: a decision that is essentially a one-way
door with a clear motivating context and one obvious choice. Four sections:
**Title, Status, Context, Decision, Consequences (Positive / Negative)**.
Aim: ≤60 lines. Reads in under a minute.
### MADR v4 (when alternatives matter — [`templates/madr.md`](./templates/madr.md))
Use MADR when the decision involves weighing multiple alternatives that a
future reader would otherwise re-litigate. Triggers:
- **Authentication / session strategy** (NextAuth vs iron-session vs Clerk).
- **Queue / messaging mechanics** (LISTEN/NOTIFY vs Redis vs SQS).
- **Agent integration patterns** (REST polling vs MCP vs SSE channel).
- **Schema or data-model choices with non-trivial migration cost.**
- Any decision where you want to record the *rejected* options so future
contributors don't propose them again.
MADR adds: YAML front-matter (status, date, decision-makers, consulted,
informed), explicit Decision Drivers, Considered Options, Pros and Cons of
each option, Confirmation, and More Information.
## Status lifecycle
```
proposed → accepted → (optionally) superseded by NNNN
↘ (optionally) deprecated
```
- **proposed** — drafted, awaiting decision-maker sign-off.
- **accepted** — current binding decision; the codebase reflects this.
- **superseded by ADR-NNNN** — replaced. Keep the file; never edit the
Decision section. Add a one-line "Superseded by …" note at the top of
the Status section and link to the new ADR.
- **deprecated** — still current but no longer recommended; usually a
precursor to a future supersession.
Once an ADR is accepted, it is immutable except for the Status field and
typo fixes. Course corrections always create a new ADR.
## Index of ADRs
| # | Title | Status | Template |
|---|---|---|---|
| [0000](./0000-record-architecture-decisions.md) | Record architecture decisions | accepted | Nygard |
| [0001](./0001-base-ui-over-radix.md) | @base-ui/react over Radix UI | accepted | Nygard |
| [0002](./0002-float-sort-order.md) | Float sort_order for drag-and-drop reorder | accepted | Nygard |
| [0003](./0003-one-branch-per-milestone.md) | One branch per milestone (Hobby plan) | accepted | MADR |
| [0004](./0004-status-enum-mapping.md) | Status enum mapping via lib/task-status.ts | accepted | Nygard |
| [0005](./0005-iron-session-over-nextauth.md) | iron-session over NextAuth/Clerk | accepted | MADR |
| [0006](./0006-demo-user-three-layer-policy.md) | Demo-user three-layer write guard | accepted | Nygard |
| [0007](./0007-claude-question-channel-design.md) | Agent ↔ user question channel via persistent table + LISTEN/NOTIFY | accepted | MADR |
| [0008](./0008-agent-instructions-in-claude-md.md) | Agent instructions in CLAUDE.md + topical runbooks | accepted | Nygard |
When new ADRs are added, the docs index generator (`npm run docs:index`)
will list them in [`../INDEX.md`](../INDEX.md). Update this table by hand
when you add or supersede an ADR — the script aggregates across the whole
docs tree, this README is the canonical ADR-only roster.

View file

@ -0,0 +1,78 @@
---
status: {{proposed | rejected | accepted | deprecated | superseded by ADR-NNNN}}
date: {{YYYY-MM-DD when the decision was last updated}}
decision-makers: {{list everyone who participated in the decision}}
consulted: {{list everyone whose opinions were sought (typically subject-matter experts), and with whom there was a two-way communication}}
informed: {{list everyone who is kept up-to-date on progress, and with whom there is one-way communication}}
---
# ADR-{{NNNN}}: {{short title, representative of solved problem and found solution}}
## 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 and add links to
collaboration boards or issue management systems.}}
## 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 a knock-out criterion / which resolves a force / …
turned out best (see "Pros and Cons of the Options" 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, …}}
### Confirmation
{{Describe how the implementation of/compliance with the ADR can be
confirmed. E.g., a test, a peer review, a runtime check.}}
## 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}}
- Bad, because {{argument b}}
### {{title of option 3}}
{{example | description | pointer to more information | …}}
- Good, because {{argument a}}
- Bad, because {{argument b}}
## 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 the decision should be realized and if/when it
should be re-visited. Links to other decisions and resources might appear
here as well.}}

View file

@ -0,0 +1,31 @@
# ADR-{{NNNN}}: {{Short noun phrase describing the decision}}
## Status
{{proposed | accepted | superseded by ADR-NNNN | deprecated}}
## Context
{{What is the issue we're seeing that motivates this decision? Describe the
forces at play — technical, organizational, business — that make this choice
necessary now. State facts, not opinions. Keep it short: one or two
paragraphs is usually enough. If a reader needs background that lives
elsewhere, link to it instead of duplicating.}}
## Decision
{{The choice we've made, written in present tense as a declarative statement.
"We will use X." "We adopt Y." Avoid hedging language. One paragraph.}}
## Consequences
### Positive
- {{What becomes easier or possible because of this decision?}}
- {{What problem is no longer relevant?}}
### Negative
- {{What becomes harder, slower, or more expensive?}}
- {{What did we accept as a trade-off?}}
- {{What new risks does this introduce, and how do we mitigate them?}}