diff --git a/CLAUDE.md b/CLAUDE.md index 49334b2..1d2a9e5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,7 @@ source of truth for recipes — run `just --list` or read it. The non-obvious bi forward to pytest. `tests/test_unit.py` + `tests/test_fake.py` need no Redis; the rest (the integration suites) do. - `just lint` / `just lint-ci` — autofix vs non-mutating; `lint-ci` also runs the - planning-bundle validator (`planning/index.py --check`). + planning-change validator (`planning/index.py --check`). - Verifying a Python-version / interpreter-compat change: run the **full** suite on the target interpreter (the CI matrix, or `just test` in docker), not just the no-Redis subset. An integration-only failure — e.g. a broker-shutdown hang @@ -21,14 +21,14 @@ source of truth for recipes — run `just --list` or read it. The non-obvious bi ## Workflow Planning uses a portable convention — `architecture/` (repo root) is the living -**truth home** and promotion target; `planning/changes/` holds the per-change -bundles. Start at the +**truth home** and promotion target; `planning/changes/` holds the flat +change files. Start at the [Quick path](planning/README.md#quick-path-start-here) in [`planning/README.md`](planning/README.md) (the authoritative spec) to pick a -lane — **Full** (`design.md` + `plan.md`), **Lightweight** (single `change.md`), -or **Tiny** (just a commit) — and ship. `just check-planning` validates bundles; -`just index` prints the change + decision listing; `planning/_templates/` are -copy-and-fill starting points. +lane — **Full** (design template), **Lightweight** (change template), +or **Tiny** (just a commit) — and ship. `just check-planning` validates +change files; `just index` prints the change + decision listing; +`planning/_templates/` are copy-and-fill starting points. ## Architecture diff --git a/Justfile b/Justfile index 8b66705..d141a5a 100644 --- a/Justfile +++ b/Justfile @@ -33,7 +33,7 @@ lint-ci: index: uv run python planning/index.py -# Validate planning bundles + decisions; CI runs this. +# Validate planning changes + decisions; CI runs this. check-planning: uv run python planning/index.py --check diff --git a/architecture/README.md b/architecture/README.md index 2dce1fb..d99e7d5 100644 --- a/architecture/README.md +++ b/architecture/README.md @@ -34,5 +34,5 @@ system's behavior is documented. Shipping a change hand-edits the affected capability file(s) here to match the new reality, in the same PR as the code. When a change alters a capability's behavior, update the matching `architecture/.md` in the same PR. The -change bundle stays in place under +change file stays in place under [`../planning/changes/`](../planning/changes/) — no folder move. diff --git a/planning/.convention-version b/planning/.convention-version index 9084fa2..227cea2 100644 --- a/planning/.convention-version +++ b/planning/.convention-version @@ -1 +1 @@ -1.1.0 +2.0.0 diff --git a/planning/README.md b/planning/README.md index 776d8f7..18527db 100644 --- a/planning/README.md +++ b/planning/README.md @@ -13,25 +13,22 @@ truth about *what the system does now* lives in **1. Choose a lane — first matching rule wins:** 1. Any of: needs design judgment · new file/module · public-API change · - cross-cutting or multi-file · non-trivial test design → **Full** - (`design.md` + `plan.md`) + cross-cutting or multi-file · non-trivial test design → **Full** (design template) 2. Purely mechanical: typo · dep bump · linter/formatter/CI tweak · - mechanical rename · single-line config → **Tiny** (no bundle, conventional + mechanical rename · single-line config → **Tiny** (no change file, conventional commit) 3. Small-but-real, none of the above: ≲30 LOC net · ≤2 files · no new file · - no public-API change · one straightforward test → **Lightweight** - (`change.md`) + no public-API change · one straightforward test → **Lightweight** (change template) -Ambiguous between two? Take the heavier. A `change.md` that outgrows its lane -splits into `design.md` + `plan.md`. +Ambiguous between two? Take the heavier. A lightweight change file that outgrows its lane is rewritten from the design template. -**2. Create the bundle** (Full / Lightweight only): -`planning/changes/YYYY-MM-DD.NN-/`, where `.NN` is a zero-padded -intra-day counter. Copy the matching template from +**2. Create the change file** (Full / Lightweight only): +`planning/changes/YYYY-MM-DD.NN-.md`, where `.NN` is a zero-padded +intra-day counter — copied from the matching template (design or change) in [`_templates/`](_templates/). **3. Ship in the implementing PR:** hand-edit the affected -`architecture/.md`, finalize the bundle's `summary:` to the +`architecture/.md`, finalize the change file's `summary:` to the realized result, and run `just check-planning` before pushing. ## Conventions @@ -47,14 +44,14 @@ realized result, and run `just check-planning` before pushing. - **`architecture/` (repo root) — the present.** One file per capability, plus a single `glossary.md` (the ubiquitous language); living prose, updated in the same PR that ships the change. The truth home. -- **`planning/changes/` — the past-and-pending.** One folder per change, +- **`planning/changes/` — the past-and-pending.** One file per change, kept in place after ship. A change **promotes** its conclusions into the affected `architecture/.md` by hand **in the implementing PR, alongside the code** — the edit rides in the same diff and is reviewed with it, never applied as a separate post-merge step. That hand-edit is what keeps `architecture/` -true; the bundle stays in `changes/` as the *why*. +true; the change file stays in `changes/` as the *why*. ### Glossary @@ -76,9 +73,9 @@ Keep it a glossary, not a spec — no implementation detail. A change that introduces or sharpens a term updates `glossary.md` in the same PR, the same way a behavior change promotes into a capability file. -### Change bundles +### Change files -A change is a folder `changes/YYYY-MM-DD.NN-/`: +A change is a file `changes/YYYY-MM-DD.NN-.md`: - `YYYY-MM-DD` — proposal date; `.NN` — zero-padded intra-day counter (`.01`, `.02`, …) that breaks same-date ties so the timeline sorts stably. @@ -87,25 +84,44 @@ A change is a folder `changes/YYYY-MM-DD.NN-/`: `summary` is written when the change is created (the intent one-liner) and **finalized at ship** to state the realized result — set in the implementing PR, alongside the code and the `architecture/` promotion. No post-merge -bookkeeping, no folder move. `date` and `slug` are never written — they are -read from the bundle's directory name. +bookkeeping, no file move. `date` and `slug` are never written — they are +read from the file name. ### Three lanes | Lane | Artifacts | Use when | |------|-----------|----------| -| **Full** | `design.md` + `plan.md` | design judgment; new file/module; public-API change; cross-cutting/multi-file; non-trivial test design | -| **Lightweight** | `change.md` | small-but-real: ≲30 LOC net, ≤2 files, no new file, no public-API change, single straightforward test | +| **Full** | one change file from the design template | design judgment; new file/module; public-API change; cross-cutting/multi-file; non-trivial test design | +| **Lightweight** | one change file from the change template | small-but-real: ≲30 LOC net, ≤2 files, no new file, no public-API change, single straightforward test | | **Tiny** | none — conventional commit | typo, dep bump, linter/formatter/CI tweak, mechanical rename, single-line config | -Heavier lane wins on ambiguity. A `change.md` that outgrows its lane splits -into `design.md` + `plan.md`. +Heavier lane wins on ambiguity. A lightweight change file that outgrows its lane is rewritten from the design template. + +### Plans are ephemeral + +The executable plan — task checklists, embedded code, commit sequences, +whatever the executor needs — is a working artifact, not history. Keep it out +of `changes/` and out of version control (git-ignored scratch, e.g. +`.superpowers/`). Once the change ships, the diff and the PR are the record +of execution; a committed plan duplicates them. `check-planning` rejects +anything in `changes/` that is not a flat change file. + +### Lean specs + +The change file is the single home of a change's rationale: + +- The PR body summarizes and links to the change file — it never restates it. +- Rejected alternatives live in `decisions/` and are referenced, not retold. +- Show a sketch when the design needs code; never the full diff-to-be. +- Delete template sections that don't apply — an empty section is ceremony. +- Most designs fit well under ~700 words; length must buy information. ### Artifacts at a glance -- **`design.md`** — the spec: the *thinking* (why, design, trade-offs, scope). -- **`plan.md`** — the plan: the *sequencing* (the executor's task checklist). -- **`change.md`** — both, condensed, for the lightweight lane. +- **design template** — the spec: the *thinking* (why, design, trade-offs, + scope); the change file it produces is the single home of rationale (see + [Lean specs](#lean-specs)). +- **change template** — the condensed spec for the lightweight lane. - **`releases/.md`** — per-release user-facing notes. - **`audits/-.md`** — findings from a code/docs/bug-hunt sweep; spawns fix changes. @@ -119,11 +135,10 @@ Templates live in [`_templates/`](_templates/). ### Frontmatter -`date` and `slug` are **derived from the directory / file name** — never +`date` and `slug` are **derived from the file name** — never repeated in frontmatter. So: -- `design.md` / `change.md`: `summary` (single line) only. -- `plan.md`: **no frontmatter** — its identity is the bundle directory. +- `changes/*.md`: `summary` (single line) only. - `decisions/*.md`: `status` (accepted|superseded), `summary`, and optional `supersedes` / `superseded_by`. - Files in `architecture/` carry **no** frontmatter — living prose, dated by git. @@ -136,9 +151,9 @@ only field the index renders. The listing is **generated**, not maintained — run `just index` to print it: a flat, newest-first list of changes, then decisions newest-first. The frontmatter -in each bundle / decision file is the single source of truth; there is no -committed copy to drift. Run `just check-planning` to validate every bundle and -decision. +in each change / decision file is the single source of truth; there is no +committed copy to drift. Run `just check-planning` to validate every change +and decision. ## Other @@ -147,8 +162,8 @@ decision. authored over time as the system grows. - **[`architecture/glossary.md`](../architecture/glossary.md)** — the ubiquitous language: the domain terms code, specs, and capability pages share. -- **[changes/](changes/)** — the per-change bundles (`design.md` + `plan.md`, - or a single `change.md`). +- **[changes/](changes/)** — the per-change files (one change file from the + design template, or one from the change template). - **[decisions/](decisions/)** — design decisions taken (and alternatives rejected), each with a revisit trigger; indexed by `just index`. - **[releases/](releases/)** — per-release user-facing notes; the release diff --git a/planning/_templates/change.md b/planning/_templates/change.md index d4c8962..5aa7e81 100644 --- a/planning/_templates/change.md +++ b/planning/_templates/change.md @@ -5,8 +5,8 @@ summary: One line — shown in the generated index. Written at creation; finaliz # Change: One-line capitalized title **Lane:** lightweight — ≲30 LOC net, ≤2 files, no new file, no public-API -change, a single straightforward test. If it outgrows this, split into -`design.md` + `plan.md`. +change, a single straightforward test. If it outgrows this, rewrite it from +the design template. ## Goal diff --git a/planning/_templates/design.md b/planning/_templates/design.md index d63e22d..17dbee1 100644 --- a/planning/_templates/design.md +++ b/planning/_templates/design.md @@ -4,6 +4,10 @@ summary: One line — shown in the generated index. Written at creation; finaliz # Design: One-line capitalized title + + ## Summary One paragraph. What changes, at the level a reader needs to decide if this @@ -12,37 +16,23 @@ spec is worth reading in full. ## Motivation Why now. What is broken or missing. Concrete observations / numbers, not -abstract complaints. Link to memory entries or earlier specs when relevant. - -## Non-goals - -What is deliberately out of scope and (when nontrivial) why. Each item is -a sentence; one line each. +abstract complaints. ## Design -### 1. - What changes, in enough detail that a reader who has not seen the codebase -can follow. Code samples / diagrams welcome. +can follow. Sketches and interface fragments welcome; never the full +diff-to-be. Reference rejected alternatives in `decisions/` instead of +retelling them. -### 2. - -... - -## Operations - -Out-of-repo steps (DNS, infra, external account changes). Omit if none. - -## Out of scope +## Non-goals -Already covered above under Non-goals if appropriate. Repeat-list of -explicitly-excluded follow-ups belongs here when the list is long. +What is deliberately out of scope and (when nontrivial) why. One line each. ## Testing -How we know it landed correctly. New pytest? Smoke check on live URL? -Lint pass? Be specific. +How we know it landed correctly. Be specific: the command and the expected +signal. ## Risk diff --git a/planning/_templates/plan.md b/planning/_templates/plan.md deleted file mode 100644 index 132d720..0000000 --- a/planning/_templates/plan.md +++ /dev/null @@ -1,46 +0,0 @@ -# — implementation plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use -> superpowers:subagent-driven-development (recommended) or -> superpowers:executing-plans to implement this plan task-by-task. Steps -> use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** One sentence — what shipping this plan achieves. No design -rationale; link to the spec for that. - -**Spec:** [`design.md`](./design.md) - -**Branch:** `feat/my-change` (or `fix/`, `chore/`, etc.) - -**Commit strategy:** Per-task commits / single commit / squash on merge. -Whichever fits. - ---- - -### Task 1: - -**Files:** -- Modify: `path/to/file.py` -- Create: `path/to/new.py` - -One sentence on what this task accomplishes. No deeper reasoning — that's -in the spec. - -- [ ] **Step 1: ** - - Run / edit / verify command. Expected output. - -- [ ] **Step 2: ** - - ... - -- [ ] **Step 3: Commit** - - ```bash - git add path/to/file.py - git commit -m ": " - ``` - ---- - -### Task 2: ... diff --git a/planning/changes/2026-06-03.01-faststream-0.7-migration/design.md b/planning/changes/2026-06-03.01-faststream-0.7-migration.md similarity index 100% rename from planning/changes/2026-06-03.01-faststream-0.7-migration/design.md rename to planning/changes/2026-06-03.01-faststream-0.7-migration.md diff --git a/planning/changes/2026-06-03.01-faststream-0.7-migration/plan.md b/planning/changes/2026-06-03.01-faststream-0.7-migration/plan.md deleted file mode 100644 index 8b618b8..0000000 --- a/planning/changes/2026-06-03.01-faststream-0.7-migration/plan.md +++ /dev/null @@ -1,1128 +0,0 @@ -# FastStream 0.7 migration — PR1 (defensive pin + planning scaffold) Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Tighten the FastStream pin to `>=0.6,<0.7`, adopt `--cov-fail-under=100`, and scaffold the `planning/` workflow directory mirroring `faststream-outbox`. Lands the PR2 design doc in the same commit so PR2 has a home. - -**Architecture:** Pure config + docs change. No runtime code touched. Single commit on `chore/pin-faststream-pre-0.7`. The coverage gate is conditional: if `just test` is not at 100% today, the gate addition gets reverted and deferred to PR2 (recorded as decision in commit body). - -**Tech Stack:** `pyproject.toml`, `uv`, `pytest-cov`, markdown. - -**Related spec:** `planning/specs/2026-06-03-faststream-0.7-migration-design.md`. - ---- - -## Task 1: Create the working branch - -**Files:** None (git operation). - -- [ ] **Step 1: Confirm clean tree** - -Run: `git status` -Expected: `nothing to commit, working tree clean` (a fresh `planning/` dir from the brainstorming session is acceptable; we'll stage it later). - -- [ ] **Step 2: Create branch** - -Run: `git switch -c chore/pin-faststream-pre-0.7` -Expected: `Switched to a new branch 'chore/pin-faststream-pre-0.7'` - -## Task 2: Establish coverage baseline (precondition for gate adoption) - -**Files:** None (read-only test run). - -The spec's R8 flags that adopting `--cov-fail-under=100` in PR1 requires the suite to already hit 100%. This task discovers the baseline. - -- [ ] **Step 1: Run full suite via Justfile** - -Run: `just test` -Expected: All tests pass. Final coverage line at bottom of output shows total `%` covered. - -- [ ] **Step 2: Record baseline** - -Note the total coverage percentage from the report. There are three branches: - -- **If `100%`:** Continue to Task 3 — gate adoption is safe. -- **If `<100%` AND missing lines are trivially testable:** Continue to Task 3 but write the backfill tests in Task 3a (insert before Task 4) before adding the gate. -- **If `<100%` AND gaps are non-trivial:** Skip the gate adoption entirely; remove "adopt `--cov-fail-under=100`" from this PR's scope; record decision in commit body; gate moves to PR2 where the orphaned-branch removal naturally lifts coverage. - -## Task 3: Tighten the FastStream pin - -**Files:** -- Modify: `pyproject.toml` (line 12) - -- [ ] **Step 1: Read current dependencies block** - -Run: `sed -n '9,14p' pyproject.toml` -Expected output: -``` -requires-python = ">=3.13,<4" -license = "MIT" -dependencies = [ - "faststream~=0.6", - "redis>=5.0", -] -``` - -- [ ] **Step 2: Edit the pin** - -Change `"faststream~=0.6"` to `"faststream>=0.6,<0.7"` on line 12. - -- [ ] **Step 3: Verify edit** - -Run: `grep -n "faststream" pyproject.toml | grep -v module` -Expected: `12: "faststream>=0.6,<0.7",` - -## Task 4: Adopt `--cov-fail-under=100` (conditional on Task 2) - -**Files:** -- Modify: `pyproject.toml` (line 67) - -**Skip this task if Task 2 step 2 selected the third branch (`<100%`, non-trivial gaps).** - -- [ ] **Step 1: Read current pytest config** - -Run: `sed -n '66,70p' pyproject.toml` -Expected output: -``` -[tool.pytest.ini_options] -addopts = "--cov=. --cov-report term-missing" -asyncio_mode = "auto" -asyncio_default_fixture_loop_scope = "function" -``` - -- [ ] **Step 2: Append the coverage gate to addopts** - -Change `addopts = "--cov=. --cov-report term-missing"` to `addopts = "--cov=. --cov-report term-missing --cov-fail-under=100"`. - -- [ ] **Step 3: Verify edit** - -Run: `grep -n "addopts" pyproject.toml` -Expected: `addopts = "--cov=. --cov-report term-missing --cov-fail-under=100"` - -## Task 5: Regenerate lockfile - -**Files:** -- Touched (gitignored, not committed): `uv.lock` - -`uv.lock` is gitignored in this repo, so the file is local-only. The regeneration confirms the resolver is happy with the tightened pin. - -- [ ] **Step 1: Regenerate lock** - -Run: `uv lock` -Expected: `Resolved N packages in