diff --git a/CLAUDE.md b/CLAUDE.md index 86062c6..d4d85df 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,6 +16,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co `tests/test_unit.py` and `tests/test_fake.py` need no Postgres — runnable with `uv run pytest tests/test_unit.py` directly. `tests/test_integration.py` requires Postgres at `POSTGRES_DSN` (default `postgresql+asyncpg://outbox:outbox@localhost:5432/outbox`); the `pg_engine` fixture skips if unreachable. Coverage is on by default (`pyproject.toml` `addopts`) with a strict `--cov-fail-under=100` ratchet — partial runs (`pytest -k name`, a single test file, etc.) will fail that gate. Pass `--no-cov` or `--cov-fail-under=0` when iterating locally on a subset; the full `just test` run satisfies the gate. +## Workflow + +Per-feature workflow: brainstorming → spec in `planning/specs/YYYY-MM-DD--design.md` → writing-plans → plan in `planning/plans/YYYY-MM-DD--plan.md` → executing-plans / subagent-driven-development → requesting-code-review → finishing-a-development-branch. + +Topic slugs are kebab-case descriptions (e.g. `dlq-on-terminal-failure`), not story IDs. + ## Architecture The package wires a FastStream `Broker`/`Registrator`/`Subscriber` trio whose transport is Postgres rows, not a message bus. diff --git a/planning/plans/.gitkeep b/planning/plans/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/planning/plans/2026-06-03-all-extra-and-planning-dir-plan.md b/planning/plans/2026-06-03-all-extra-and-planning-dir-plan.md new file mode 100644 index 0000000..5d81ed6 --- /dev/null +++ b/planning/plans/2026-06-03-all-extra-and-planning-dir-plan.md @@ -0,0 +1,380 @@ +# `all` extra and `planning/` workflow dir — 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:** Add a `faststream-outbox[all]` aggregate extra and bootstrap the `planning/` directory convention (`specs/` + `plans/`) used by the sister `httpware` project. + +**Architecture:** No runtime code change. Three artifacts touched: `pyproject.toml` (one new line under `[project.optional-dependencies]`), `planning/specs/.gitkeep` + `planning/plans/.gitkeep` (zero-byte placeholders), and `CLAUDE.md` (one new `## Workflow` section between `## Commands` and `## Architecture`). All changes land in a single commit. + +**Tech Stack:** `uv` (lock + sync), `just` (lint + test recipes), Python 3.13+, Postgres 17 (test target — not exercised by this plan but the test suite must still pass). + +**Spec:** `planning/specs/2026-06-03-all-extra-and-planning-dir-design.md` (committed as `91201a2`). + +--- + +## File map + +| Path | Action | Responsibility | +|---|---|---| +| `pyproject.toml` | Modify (add one entry to `[project.optional-dependencies]`) | Expose `all` aggregate extra | +| `planning/specs/.gitkeep` | Create (zero bytes) | Keep dir tracked even if all specs are ever removed | +| `planning/plans/.gitkeep` | Create (zero bytes) | Keep dir tracked (this plan file will live here too, but `.gitkeep` is defensive per spec §2) | +| `CLAUDE.md` | Modify (insert new `## Workflow` section between line 18 and line 19) | Document the per-feature workflow lifecycle so future contributors find it | + +No source files (`src/...`) or test files (`tests/...`) change. There are no new Python symbols. + +--- + +## Task 1: Add `all` extra to `pyproject.toml` + +**Files:** +- Modify: `pyproject.toml` (insert after line 21, the `opentelemetry` entry, inside the `[project.optional-dependencies]` table) + +- [ ] **Step 1: Verify current state of the extras table** + +Run: +```bash +sed -n '16,22p' /Users/kevinsmith/src/pypi/faststream-outbox/pyproject.toml +``` + +Expected output (exact): +```toml +[project.optional-dependencies] +asyncpg = ["asyncpg>=0.29"] +validate = ["alembic>=1.13"] +fastapi = ["fastapi>=0.95"] +prometheus = ["prometheus-client>=0.19"] +opentelemetry = ["opentelemetry-api>=1.20", "opentelemetry-sdk>=1.20"] +``` + +If the output differs, STOP — the file has drifted from the spec's assumptions and the spec needs revisiting. + +- [ ] **Step 2: Add the `all` entry** + +Use the Edit tool to append after the `opentelemetry` line. The new content immediately following `opentelemetry = [...]` should be: + +```toml +all = ["faststream-outbox[asyncpg,validate,fastapi,prometheus,opentelemetry]"] +``` + +Concretely, the `old_string` for Edit is: +```toml +opentelemetry = ["opentelemetry-api>=1.20", "opentelemetry-sdk>=1.20"] +``` + +And the `new_string` is: +```toml +opentelemetry = ["opentelemetry-api>=1.20", "opentelemetry-sdk>=1.20"] +all = ["faststream-outbox[asyncpg,validate,fastapi,prometheus,opentelemetry]"] +``` + +- [ ] **Step 3: Verify TOML still parses** + +Run: +```bash +cd /Users/kevinsmith/src/pypi/faststream-outbox && uv run python -c "import tomllib, pathlib; data = tomllib.loads(pathlib.Path('pyproject.toml').read_text()); print(data['project']['optional-dependencies']['all'])" +``` + +Expected output: +``` +['faststream-outbox[asyncpg,validate,fastapi,prometheus,opentelemetry]'] +``` + +If it errors with a `tomllib.TOMLDecodeError`, the edit broke the file — re-open and fix. + +- [ ] **Step 4: Verify `uv` can resolve `--extra all`** + +Run: +```bash +cd /Users/kevinsmith/src/pypi/faststream-outbox && uv sync --extra all 2>&1 | tail -20 +``` + +Expected: completes without errors. Look for `Resolved packages` and `Installed` / `Audited` lines mentioning `alembic`, `asyncpg`, `fastapi`, `prometheus-client`, `opentelemetry-api`, `opentelemetry-sdk`. (If the env already had them via `dev` group, the output may just say `Audited packages in ` — that's also success.) + +If it fails with `failed to resolve`, the most likely cause is the self-referential extra name not matching the project name. Verify the project name with: +```bash +grep '^name =' /Users/kevinsmith/src/pypi/faststream-outbox/pyproject.toml +``` +It must read `name = "faststream-outbox"` (hyphen, not underscore). + +- [ ] **Step 5: Verify `--extra all` matches `--all-extras`** + +Run: +```bash +cd /Users/kevinsmith/src/pypi/faststream-outbox && \ + uv sync --extra all --quiet && uv pip list --format=freeze | sort > /tmp/with-all.txt && \ + uv sync --all-extras --quiet && uv pip list --format=freeze | sort > /tmp/with-all-extras.txt && \ + diff /tmp/with-all.txt /tmp/with-all-extras.txt && echo "MATCH" +``` + +Expected: `MATCH` on stdout, no diff output. If diff prints lines, the `all` extra is missing or has extras the broad form doesn't — re-check the entry against the spec. + +Cleanup: +```bash +rm -f /tmp/with-all.txt /tmp/with-all-extras.txt +``` + +**Do not commit yet — single commit at the end of Task 4.** + +--- + +## Task 2: Create `planning/` placeholder files + +**Files:** +- Create: `planning/specs/.gitkeep` (zero bytes) +- Create: `planning/plans/.gitkeep` (zero bytes) + +The directories `planning/specs/` and `planning/plans/` already exist on disk (created during the brainstorming step that produced the spec) and `planning/specs/2026-06-03-all-extra-and-planning-dir-design.md` is already tracked. Only the `.gitkeep` placeholders need adding. + +- [ ] **Step 1: Confirm the directories exist** + +Run: +```bash +ls -la /Users/kevinsmith/src/pypi/faststream-outbox/planning/ +``` + +Expected: shows `specs/` and `plans/` subdirectories. If either is missing, run `mkdir -p /Users/kevinsmith/src/pypi/faststream-outbox/planning/{specs,plans}` first. + +- [ ] **Step 2: Create both `.gitkeep` files** + +Run: +```bash +touch /Users/kevinsmith/src/pypi/faststream-outbox/planning/specs/.gitkeep && \ +touch /Users/kevinsmith/src/pypi/faststream-outbox/planning/plans/.gitkeep +``` + +- [ ] **Step 3: Verify both files exist and are empty** + +Run: +```bash +wc -c /Users/kevinsmith/src/pypi/faststream-outbox/planning/specs/.gitkeep /Users/kevinsmith/src/pypi/faststream-outbox/planning/plans/.gitkeep +``` + +Expected output (byte counts of 0): +``` +0 .../planning/specs/.gitkeep +0 .../planning/plans/.gitkeep +0 total +``` + +If `eof-fixer` later complains about the files (it generally leaves zero-byte files alone, but if it doesn't), accept whatever it does — the file existing under the name `.gitkeep` is what matters; the byte count is incidental. + +**Do not commit yet.** + +--- + +## Task 3: Add `## Workflow` section to `CLAUDE.md` + +**Files:** +- Modify: `CLAUDE.md` (insert a new `## Workflow` section between line 18 and line 19, i.e. between the blank line that ends `## Commands` and the `## Architecture` heading) + +- [ ] **Step 1: Verify the insertion point** + +Run: +```bash +sed -n '15,21p' /Users/kevinsmith/src/pypi/faststream-outbox/CLAUDE.md +``` + +Expected output (exact): +``` +- `just build` / `just down` / `just sh` — image build, teardown, shell into the app container. + +`tests/test_unit.py` and `tests/test_fake.py` need no Postgres — runnable with `uv run pytest tests/test_unit.py` directly. `tests/test_integration.py` requires Postgres at `POSTGRES_DSN` (default `postgresql+asyncpg://outbox:outbox@localhost:5432/outbox`); the `pg_engine` fixture skips if unreachable. Coverage is on by default (`pyproject.toml` `addopts`) with a strict `--cov-fail-under=100` ratchet — partial runs (`pytest -k name`, a single test file, etc.) will fail that gate. Pass `--no-cov` or `--cov-fail-under=0` when iterating locally on a subset; the full `just test` run satisfies the gate. + +## Architecture + +The package wires a FastStream `Broker`/`Registrator`/`Subscriber` trio whose transport is Postgres rows, not a message bus. +``` + +If the output differs, the file has drifted — STOP and re-locate the insertion point manually. + +- [ ] **Step 2: Insert the `## Workflow` section** + +Use the Edit tool with this exact replacement. + +`old_string` (matches the blank-line / `## Architecture` boundary unambiguously because `## Architecture` only appears once in the file): + +``` +gate. + +## Architecture +``` + +`new_string`: + +``` +gate. + +## Workflow + +Per-feature workflow: brainstorming → spec in `planning/specs/YYYY-MM-DD--design.md` → writing-plans → plan in `planning/plans/YYYY-MM-DD--plan.md` → executing-plans / subagent-driven-development → requesting-code-review → finishing-a-development-branch. + +Topic slugs are kebab-case descriptions (e.g. `dlq-on-terminal-failure`), not story IDs. + +## Architecture +``` + +Note: the surrounding `gate.` line is the tail of the long Commands paragraph (the last word of line 17). Anchoring on it makes `old_string` unique even though the markdown structure (blank line + heading) is otherwise generic. + +- [ ] **Step 3: Verify the section landed** + +Run: +```bash +grep -n '^## ' /Users/kevinsmith/src/pypi/faststream-outbox/CLAUDE.md +``` + +Expected output (order matters): +``` +5:## Project +9:## Commands +19:## Workflow +25:## Architecture +... +``` + +(Architecture's new line number will be 25 or thereabouts depending on whitespace — the key check is that `## Workflow` appears between `## Commands` and `## Architecture`.) + +- [ ] **Step 4: Verify the section body is correct** + +Run: +```bash +sed -n '/^## Workflow/,/^## Architecture/p' /Users/kevinsmith/src/pypi/faststream-outbox/CLAUDE.md +``` + +Expected output: +``` +## Workflow + +Per-feature workflow: brainstorming → spec in `planning/specs/YYYY-MM-DD--design.md` → writing-plans → plan in `planning/plans/YYYY-MM-DD--plan.md` → executing-plans / subagent-driven-development → requesting-code-review → finishing-a-development-branch. + +Topic slugs are kebab-case descriptions (e.g. `dlq-on-terminal-failure`), not story IDs. + +## Architecture +``` + +**Do not commit yet.** + +--- + +## Task 4: Verify and commit + +**Files:** none new — this task just runs verification + creates the commit. + +- [ ] **Step 1: Run `just lint-ci`** + +Run: +```bash +cd /Users/kevinsmith/src/pypi/faststream-outbox && just lint-ci +``` + +Expected: all checks pass (`eof-fixer`, `ruff format --check`, `ruff check`, `ty check`). No Python files were touched in this plan, so `ruff` and `ty` are effectively no-ops on the diff; `eof-fixer` may report something about `.gitkeep` or `CLAUDE.md` — if it modifies them, re-run `just lint-ci` and confirm clean. + +If `just` is unavailable, the equivalent is: +```bash +cd /Users/kevinsmith/src/pypi/faststream-outbox && \ + uv run eof-fixer --check . && \ + uv run ruff format --check . && \ + uv run ruff check . && \ + uv run ty check . +``` + +- [ ] **Step 2: Run the test suite** + +Run: +```bash +cd /Users/kevinsmith/src/pypi/faststream-outbox && just test +``` + +Expected: full suite passes with 100% coverage (the `--cov-fail-under=100` gate). No behavior change is expected — this confirms `pyproject.toml` is still syntactically valid and the package still installs cleanly in the docker compose env. + +If `just test` is impractical (docker not available), at minimum run the no-Postgres subset: +```bash +cd /Users/kevinsmith/src/pypi/faststream-outbox && uv run pytest tests/test_unit.py tests/test_fake.py --no-cov +``` +…and note in the commit description that the integration suite was not exercised locally. + +- [ ] **Step 3: Inspect the full diff one last time** + +Run: +```bash +cd /Users/kevinsmith/src/pypi/faststream-outbox && git status && echo '---' && git diff && echo '---' && git diff --stat +``` + +Expected `git status` shows: +- Modified: `CLAUDE.md` +- Modified: `pyproject.toml` +- (Possibly) Modified: `uv.lock` if the `--extra all` sync touched it +- Untracked: `planning/plans/.gitkeep`, `planning/specs/.gitkeep` + +(The plan file `planning/plans/2026-06-03-all-extra-and-planning-dir-plan.md` should already have been committed in a separate `docs: plan ...` commit ahead of execution — analogous to how the spec was committed at `91201a2`. If it still shows as untracked, commit it on its own *before* proceeding to Step 4.) + +`git diff --stat` should show ~3 modified lines in `pyproject.toml` (one added entry, possibly reformatted), and a small CLAUDE.md insertion (~6 lines). + +If `uv.lock` shows a diff, that's fine — include it in the commit (lock files belong with the change that produced them). + +- [ ] **Step 4: Stage the changes** + +Run: +```bash +cd /Users/kevinsmith/src/pypi/faststream-outbox && git add \ + pyproject.toml \ + CLAUDE.md \ + planning/specs/.gitkeep \ + planning/plans/.gitkeep +``` + +If `uv.lock` is also modified: +```bash +git add uv.lock +``` + +Verify staged set: +```bash +git status +``` + +Expected: all of the above under `Changes to be committed`, nothing under `Changes not staged for commit` (other than possibly untracked files outside this work). + +- [ ] **Step 5: Commit** + +Run: +```bash +cd /Users/kevinsmith/src/pypi/faststream-outbox && git commit -m "$(cat <<'EOF' +chore: add 'all' extra and planning/ workflow directory + +- pyproject.toml: self-referential `all` aggregate so users can + `pip install faststream-outbox[all]` for the full feature set + (asyncpg + validate + fastapi + prometheus + opentelemetry). +- planning/{specs,plans}/: adopt the layout used by the sister + httpware project for superpowers spec/plan artifacts. +- CLAUDE.md: document the per-feature workflow lifecycle in a new + `## Workflow` section between `## Commands` and `## Architecture`. + +Spec: planning/specs/2026-06-03-all-extra-and-planning-dir-design.md. + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +- [ ] **Step 6: Confirm the commit landed** + +Run: +```bash +cd /Users/kevinsmith/src/pypi/faststream-outbox && git log -1 --stat +``` + +Expected: one new commit with the message above and the file list from step 4. + +--- + +## Acceptance criteria (spec §"Verification checklist") + +After Task 4 completes, all of these must be true: + +- [ ] `uv sync --extra all` succeeds and pulls in alembic, asyncpg, fastapi, prometheus-client, opentelemetry-api, opentelemetry-sdk. (Task 1, Step 4.) +- [ ] `uv sync --extra all` and `uv sync --all-extras` produce the same installed set. (Task 1, Step 5.) +- [ ] `just lint-ci` passes. (Task 4, Step 1.) +- [ ] `just test` passes. (Task 4, Step 2.) +- [ ] `planning/specs/` and `planning/plans/` exist and are tracked by git (the design doc, this plan, and both `.gitkeep` files appear in `git ls-files`). Verify with: `git ls-files planning/`. +- [ ] `CLAUDE.md` `## Workflow` heading appears between `## Commands` and `## Architecture`. (Task 3, Step 3.) diff --git a/planning/specs/.gitkeep b/planning/specs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/planning/specs/2026-06-03-all-extra-and-planning-dir-design.md b/planning/specs/2026-06-03-all-extra-and-planning-dir-design.md new file mode 100644 index 0000000..13dcc99 --- /dev/null +++ b/planning/specs/2026-06-03-all-extra-and-planning-dir-design.md @@ -0,0 +1,145 @@ +# Design: `all` aggregate extra + `planning/` workflow directory + +**Date:** 2026-06-03 +**Status:** Approved +**Slug:** `all-extra-and-planning-dir` + +## Summary + +Two independent project-hygiene changes: + +1. Add a self-referential `all` aggregate to `[project.optional-dependencies]` + so users can `pip install faststream-outbox[all]` for the full feature set. +2. Adopt the `planning/` directory convention (`planning/specs/`, + `planning/plans/`) for superpowers spec/plan artifacts, mirroring the sister + `httpware` project. Document the workflow in `CLAUDE.md`. + +Neither change touches runtime code, test code, or public API. + +## Motivation + +- **`all` extra:** `faststream-outbox` currently exposes five optional extras + (`asyncpg`, `validate`, `fastapi`, `prometheus`, `opentelemetry`). Users + wanting the full feature set must enumerate every extra. An `all` aggregate + is the standard PyPI convention and is one line to maintain. +- **`planning/` directory:** Superpowers skills default to + `docs/superpowers/specs/` for design artifacts. The user's sister project + (`httpware`) uses `planning/specs/` and `planning/plans/` as a flat, top-level + workflow root, and has documented the per-feature lifecycle there. Adopting + the same layout keeps the two repos navigable in the same way and surfaces + the workflow earlier than burying it under `docs/`. + +## Design + +### 1. `all` extra (pyproject.toml) + +Append one entry to `[project.optional-dependencies]`: + +```toml +all = ["faststream-outbox[asyncpg,validate,fastapi,prometheus,opentelemetry]"] +``` + +**Rationale for self-reference over an explicit dep list:** the per-extra pin +sets (`asyncpg>=0.29`, `alembic>=1.13`, etc.) live in their own entries; an +explicit `all` list would duplicate them and risk drift when an individual +extra's pin is bumped. The self-referential form references the names, not the +versions, so version updates flow through automatically. + +**Future-extra discipline:** adding a sixth extra still requires updating `all`. +Acceptable trade — the alternative (`--all-extras` everywhere) is not a +package-level surface, and a CI step that diffs `all` against the union of +other extras is overkill for a five-extra package. + +**Verification command:** `uv sync --extra all` should resolve to the same +package set as `uv sync --all-extras`. + +### 2. `planning/` directory layout + +Create: + +``` +planning/ +├── specs/ +│ └── .gitkeep # (this design doc populates the dir, but .gitkeep stays +│ # so the dir survives if all specs are ever removed) +└── plans/ + └── .gitkeep +``` + +`.gitkeep` files are zero-byte. `planning/specs/` will hold this design doc on +first commit, so the `.gitkeep` there is defensive but not strictly required; +`planning/plans/.gitkeep` is necessary because no plan exists yet. + +No `deferred-work.md` scaffold. Will be added when there is actual deferred +work to track, not preemptively. + +### 3. `.gitignore` + +No change. The existing `plan.md` entry targets a file literally named +`plan.md` (legacy artifact), not the new `planning/plans/*.md` shape. Gitignore +patterns without wildcards or slashes match on exact basename at any depth, so +`plan.md` matches only `plan.md` — never `2026-06-03--plan.md`. + +### 4. `CLAUDE.md` — new `## Workflow` section + +Insert between `## Commands` and `## Architecture`: + +```markdown +## Workflow + +Per-feature workflow: brainstorming → spec in +`planning/specs/YYYY-MM-DD--design.md` → writing-plans → +plan in `planning/plans/YYYY-MM-DD--plan.md` → +executing-plans / subagent-driven-development → +requesting-code-review → finishing-a-development-branch. + +Topic slugs are kebab-case descriptions (e.g. `dlq-on-terminal-failure`), +not story IDs. +``` + +**Placement rationale:** `## Commands` is the second section a contributor +reads after `## Project`; placing `## Workflow` immediately after gives the +lifecycle convention visibility without breaking the long, narrative +`## Architecture` section that follows. + +**Slug example chosen:** `dlq-on-terminal-failure` — matches the most recent +PR (#40) merged into this repo, so the example resolves to something a reader +can `git log --grep` for. + +## Order of operations (single commit) + +1. Edit `pyproject.toml` — add `all` line. +2. `mkdir -p planning/specs planning/plans` — already done as part of writing + this spec. +3. `touch planning/specs/.gitkeep planning/plans/.gitkeep`. +4. Edit `CLAUDE.md` — insert `## Workflow` section. +5. `git add` the four touched paths + this spec file. +6. Single commit: `chore: add 'all' extra and planning/ workflow directory`. + +## Out of scope + +- No retroactive migration of historical design notes (none exist outside git + commit messages). +- No changes to any superpowers skill files (`~/.claude/...`); the per-call + `args` to `/superpowers:brainstorming` plus the new `CLAUDE.md` line are the + only steering mechanism we need. +- No `bumpversion`, no changelog entry — version is currently `"0"` (sentinel), + and there is no `CHANGELOG.md` in the repo. +- No `mkdocs.yml` change — `planning/` is contributor-facing, not user-facing + docs. + +## Verification checklist (for the plan / executing-plans phase) + +- [ ] `uv sync --extra all` succeeds and pulls in alembic, asyncpg, fastapi, + prometheus-client, opentelemetry-api, opentelemetry-sdk. +- [ ] `uv sync --extra all` and `uv sync --all-extras` produce the same + installed package set (diff `uv pip list` output). +- [ ] `just lint-ci` still passes (no Python files touched, but `eof-fixer` + runs over `.gitkeep` and `CLAUDE.md`). +- [ ] `just test` still passes (no behavior change expected, but confirms no + pyproject.toml syntax regression). +- [ ] `planning/specs/` and `planning/plans/` exist in the working tree and + are tracked by git. +- [ ] `CLAUDE.md` renders correctly — `## Workflow` heading shows up in the + table-of-contents order: Project, Commands, Workflow, Architecture, + Conventions. diff --git a/pyproject.toml b/pyproject.toml index 2a06885..96c5173 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ readme = "README.md" requires-python = ">=3.13,<4" license = "MIT" dependencies = [ - "faststream~=0.6", + "faststream>=0.6,<0.7", "sqlalchemy[asyncio]>=2.0", ] @@ -19,6 +19,7 @@ validate = ["alembic>=1.13"] fastapi = ["fastapi>=0.95"] prometheus = ["prometheus-client>=0.19"] opentelemetry = ["opentelemetry-api>=1.20", "opentelemetry-sdk>=1.20"] +all = ["faststream-outbox[asyncpg,validate,fastapi,prometheus,opentelemetry]"] [dependency-groups] dev = [