diff --git a/.gitignore b/.gitignore index d5967b0..c73839d 100644 --- a/.gitignore +++ b/.gitignore @@ -19,5 +19,4 @@ dist/ .python-version .venv uv.lock -plan.md site/ diff --git a/CLAUDE.md b/CLAUDE.md index e94ee9c..8ce52af 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,7 +38,7 @@ BaseBootstrapper (abc.ABC) ### Key design decisions -Recent design context, bugs, and convention rationale: see `planning/specs/2026-*-bug-audit-*.md` (audits + retros). +Recent design context, bugs, and convention rationale: see the bug-audit findings in `planning/audits/` and the post-work reflections in `planning/retros/` (the audit arcs themselves are bundled under `planning/changes/archive/`). - **Optional dependencies**: Each instrument checks for its optional package via `import_checker.py` (`importlib.util.find_spec`). Instruments are skipped silently if the package is absent. Optional packages are imported inside `if import_checker.is_X_installed:` blocks; static analyzers that don't model this guard will report spurious "possibly unbound" diagnostics — the project uses `ty` which handles the pattern correctly. - **Instrument skip ordering**: `BaseBootstrapper.__init__` runs `instrument_type.is_configured(config)` first (silent skip if the user's config indicates the instrument shouldn't run — populates `bootstrapper.skipped_instruments: list[tuple[type, str]]`); then `check_dependencies()` (emits `InstrumentDependencyMissingWarning` only for configured-but-dep-missing — the genuine deployment surprise); then instantiates. One `logger.info` summary line at the end lists configured + skipped instruments via `BaseBootstrapper.build_summary()`; that method is also publicly callable for post-construction debugging. Uses stdlib `logging` so it composes cleanly with the user's logging setup and with pytest's `caplog`. @@ -61,15 +61,15 @@ One file per instrument under `lite_bootstrap/instruments/`, one per framework u See `[project.optional-dependencies]` in `pyproject.toml` for the full extras matrix. -## Planning artifacts +## Workflow -Design docs and implementation plans live under `planning/` at the repo root, not under `docs/` (so they're excluded from the mkdocs site automatically): +Per-feature: brainstorming → spec in `planning/changes/active/YYYY-MM-DD.NN-/design.md` → writing-plans → plan in `planning/changes/active/YYYY-MM-DD.NN-/plan.md` → executing-plans / subagent-driven-development → requesting-code-review → finishing-a-development-branch. Each change is a folder bundle; `` is a kebab-case description, not a story ID; `.NN` is a zero-padded intra-day counter that breaks same-date ties so the timeline sorts stably. On merge, the bundle moves to `planning/changes/archive/` with `status: shipped`, `pr:`, and `outcome:` filled, **and the change promotes its conclusions into the affected `architecture/.md`** — that hand-edit is what keeps `architecture/` true. See [`planning/README.md`](planning/README.md) for the conventions + index and [`planning/_templates/`](planning/_templates/) for copy-and-fill starting points. -- `planning/specs/` — design docs / specs (output of brainstorming). Filename: `YYYY-MM-DD--design.md`. -- `planning/plans/` — step-by-step implementation plans (output of writing-plans). Filename: `YYYY-MM-DD--plan.md` or `YYYY-MM-DD-pr-.md` for per-PR plans. -- `planning/templates/` — local templates (e.g. `lightweight-plan-template.md`). +**Spec** (`design.md`) captures the *thinking* — why, what the design is, trade-offs, scope. Written before code; rarely revised after merge. **Plan** (`plan.md`) captures the *sequencing* — the ordered checklist an executor walks; references the spec for the "why". **`architecture/`** captures the *invariants* of shipped systems — the living truth, promoted from a change on merge. A plan paragraph that would still read correctly with all task numbers and checkboxes removed is design content and belongs in the spec. -When superpowers skills default to `docs/superpowers/specs/`, use `planning/specs/` here instead. +**Three lanes.** Scale the artifact to the change. **Full** — a `design.md` + `plan.md` bundle — for real design judgment, a new file/module, a public-API change, cross-cutting/multi-file work, or non-trivial test design. **Lightweight** — a single `change.md` — for small-but-real changes (≲30 LOC net, ≤2 files, no new file, no public-API change, a single straightforward test). **Tiny** — no bundle, just a conventional commit — for a typo, dep bump, linter/formatter/CI tweak, a mechanical rename, or a single-line config change. Heavier lane wins on ambiguity; a `change.md` that outgrows its lane splits into `design.md` + `plan.md`. + +Design docs and implementation plans live under `planning/` (not under `docs/`, so they're excluded from the mkdocs site automatically). When superpowers skills default to `docs/superpowers/specs/` or `docs/superpowers/plans/`, use the change bundle under `planning/changes/active/` here instead. ## Code style diff --git a/Justfile b/Justfile index 6783ea3..453eb7a 100644 --- a/Justfile +++ b/Justfile @@ -32,3 +32,7 @@ publish: # Manual invocation from a stale checkout will roll the live site back. docs-deploy: uvx --with-requirements docs/requirements.txt mkdocs gh-deploy --force + +# Strict local docs build (no deploy). Mirrors CI's link/strict checks. +docs-build: + uvx --with-requirements docs/requirements.txt mkdocs build --strict diff --git a/architecture/bootstrappers.md b/architecture/bootstrappers.md new file mode 100644 index 0000000..4644427 --- /dev/null +++ b/architecture/bootstrappers.md @@ -0,0 +1,76 @@ +# Bootstrappers + +A bootstrapper takes one framework config, decides which instruments apply, +and drives their lifecycle. It is the user-facing entry point. + +## The hierarchy + +`BaseBootstrapper` (`lite_bootstrap/bootstrappers/base.py`) is an `abc.ABC`, +generic over `ApplicationT`. Five concrete bootstrappers exist: + +- `FastAPIBootstrapper` +- `LitestarBootstrapper` +- `FastStreamBootstrapper` +- `FastMcpBootstrapper` +- `FreeBootstrapper` — no web framework; just the instruments. + +Each declares `instruments_types: ClassVar[list[type[BaseInstrument]]]` (the +instruments it can run) and implements `_prepare_application()`, `is_ready()`, +and the `not_ready_message` property. (`FreeBootstrapperConfig` exists as a +backward-compat alias for `FreeConfig`.) + +## Skip ordering at construction + +`BaseBootstrapper.__init__` loops over `instruments_types` and decides each +instrument's fate in a fixed order: + +1. **`is_configured(config)`** runs first. If False, the user's config indicates + the instrument should not run — it is **silently skipped** and appended to + `skipped_instruments: list[tuple[type[BaseInstrument], str]]` (the class plus + its `not_ready_message`). No warning. This runs before instantiation so a + missing optional dependency can't blow up in a dataclass default before we + even decide the user opted out. +2. **`check_dependencies()`** runs only for configured instruments. If the + optional package is missing, this is a genuine deployment surprise: the + bootstrapper emits an `InstrumentDependencyMissingWarning` (and a + `logger.warning`) and skips the instrument. +3. Otherwise it **instantiates** the instrument and appends it to `instruments`. + +`InstrumentDependencyMissingWarning` is a `UserWarning` subclass (under the base +`InstrumentSkippedWarning`); filter it like any warning category. + +## Instrument registry and teardown + +The bootstrapper holds `instruments` (live instances) and runs them as a +registry: + +- `bootstrap()` calls `bootstrap()` on each instrument **in order**, then + returns the prepared application. It is idempotent: if already bootstrapped it + re-runs `_prepare_application()` without re-bootstrapping instruments. +- `teardown()` calls `teardown()` on each instrument **in reverse**. It is + idempotent via the `is_bootstrapped` guard — it returns immediately when not + bootstrapped. Per-instrument teardown errors are collected and re-raised as a + `TeardownError` after all instruments have been attempted, so one failure does + not strand the rest. Cached runtime state in `LoggingInstrument` and + `OpenTelemetryInstrument` is reset inside `try/finally`. + +## Summary logging + +After the construction loop, `__init__` emits one INFO-level summary line listing +configured + skipped instruments. It uses stdlib `logging` (composes with the +user's logging setup and with pytest's `caplog`); default Python logging +suppresses INFO, so opt in via `logging.basicConfig(level=logging.INFO)`. + +The same string is produced by the public `build_summary()` method, callable at +any later point (REPL, health endpoint) regardless of log-level filtering. To +inspect skips programmatically, iterate `bootstrapper.skipped_instruments`. + +## App-tagging sentinel convention + +When a bootstrapper must tag a user-supplied framework app (FastAPI, FastMCP, +Litestar, FastStream) with internal state, it stores a direct attribute prefixed +`_lite_bootstrap_` rather than squatting in framework namespaces like Starlette's +`application.state`. Example: FastAPI's lifespan double-wrap guard reads +`getattr(application, "_lite_bootstrap_lifespan_attached", False)` (no SLF +violation) and writes +`application._lite_bootstrap_lifespan_attached = True # noqa: SLF001`. diff --git a/architecture/config-model.md b/architecture/config-model.md new file mode 100644 index 0000000..6c55566 --- /dev/null +++ b/architecture/config-model.md @@ -0,0 +1,77 @@ +# Config model + +Configs describe *what the user wants*. They are immutable, declarative, and +carry no runtime state. Every config in the project descends from `BaseConfig` +(`lite_bootstrap/instruments/base.py`). + +## BaseConfig + +`BaseConfig` is a `@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)`. +It holds the service-identity fields shared by every instrument (`service_name`, +`service_description`, `service_version`, `service_environment`, `service_debug`). + +Framework configs compose multiple instrument configs via **multiple +inheritance**. For example `FastAPIConfig` mixes `CorsConfig`, +`OpenTelemetryConfig`, `LoggingConfig`, `SentryConfig`, `PrometheusConfig`, +`SwaggerConfig`, `HealthChecksConfig`, etc. into one frozen dataclass, so a +single config object configures every instrument the framework supports. + +All `*Config` classes are frozen for user-facing immutability; only the +`*Instrument` classes are non-frozen (they cache runtime state — see +`architecture/instruments.md`). + +## from_dict vs from_object + +Two classmethods build a config from external data. They differ in how they +treat `None`: + +- `BaseConfig.from_dict(data)` — keeps unknown keys out (`{k: v for k, v in + data.items() if k in field_names}`) but passes through explicit `None`. So + `BaseConfig.from_dict({"service_name": None})` succeeds and **overrides** the + default with `None`. +- `BaseConfig.from_object(obj)` — pulls each field via `getattr(obj, field, + None)` and **filters out** any attribute that is `None` or missing, letting + the dataclass default take over. + +The asymmetry is load-bearing: pick `from_dict` when explicit-None override is +the semantic you want; pick `from_object` when missing/None should mean +"fall back to default." It is documented in both docstrings +(`instruments/base.py:25, 31`) and pinned by tests in `tests/test_config.py`. + +## UNSET sentinel and FastAPIConfig.application + +`lite_bootstrap/types.py` defines `UnsetType` and the singleton `UNSET` +(`typing.Final[UnsetType]`). It distinguishes "user did not supply this" from +"user explicitly passed `None`", which a plain `None` default cannot express. + +`FastAPIConfig.application` defaults to `UNSET`. In `__post_init__`, when the +value is still `UnsetType`, the config constructs a fresh `FastAPI()` and writes +it back: + +```python +if isinstance(self.application, UnsetType): + application = fastapi.FastAPI(...) + object.__setattr__(self, "application", application) +``` + +The `object.__setattr__` bypasses the frozen guard — the config stays +immutable to the user, but construction-time computed fields can still be set. +A one-line comment documents the trade-off (user-facing immutability vs. +construction-time mutation) at the bypass site. + +## __post_init__ cascade invariant + +Several configs override `__post_init__` (e.g. `OpenTelemetryConfig` emits a +security warning, `CorsConfig` validates, `FastAPIConfig` constructs the app). +Because they share an MRO under `FastAPIConfig` / `LitestarConfig` / +`FastStreamConfig` / `FreeConfig`, **every** config `__post_init__` must call +`super().__post_init__()` so the chain runs to completion. A class that returns +early before `super()` silently blocks the rest of the chain. + +`BaseConfig.__post_init__` is a deliberate no-op that **terminates** the +cascade; without it the chain would raise `AttributeError` on `object`. + +`FastAPIConfig` uses the explicit `super(FastAPIConfig, self).__post_init__()` +form rather than bare `super()`. Under `@dataclass(slots=True)` the decorator +replaces the class object after the body compiles, which breaks the bare-`super()` +`__class__` cell; the explicit form is required. diff --git a/architecture/instruments.md b/architecture/instruments.md new file mode 100644 index 0000000..429216c --- /dev/null +++ b/architecture/instruments.md @@ -0,0 +1,89 @@ +# Instruments + +An instrument is one observability or middleware concern (logging, tracing, +metrics, …). Each lives in its own file under `lite_bootstrap/instruments/`. +A bootstrapper owns a list of instrument instances and drives their lifecycle. + +## BaseInstrument + +`BaseInstrument[ConfigT]` (`lite_bootstrap/instruments/base.py`) is a generic, +**non-frozen** `@dataclasses.dataclass(kw_only=True, slots=True)` holding a +single `bootstrap_config: ConfigT`. Subclasses implement: + +- `bootstrap()` / `teardown()` — lifecycle hooks, called in order / reverse by + the bootstrapper. +- `is_configured(cls, bootstrap_config) -> bool` (classmethod) — return False + when the user's config means this instrument should not run. Default: always + True. Drives the silent-skip path. +- `check_dependencies() -> bool` (staticmethod) — return False when the + optional package is absent. Default: always True. +- class attributes `not_ready_message` and `missing_dependency_message` — + human-readable reasons surfaced in skip reporting and warnings. + +## Instrument catalog + +One file per instrument: + +- `logging_instrument.py` — structlog setup (`LoggingInstrument`), skipped when + `logging_enabled=False`. +- `opentelemetry_instrument.py` — OTel tracer provider + span export. +- `sentry_instrument.py` — Sentry SDK init, skipped when `sentry_dsn` empty. +- `prometheus_instrument.py` — Prometheus metrics; framework variants wrap it. +- `pyroscope_instrument.py` — continuous profiling, skipped when + `pyroscope_endpoint` empty. +- `cors_instrument.py` — CORS headers, requires an origins/regex setting. +- `swagger_instrument.py` — Swagger / offline docs. +- `healthchecks_instrument.py` — health-check route, gated by + `health_checks_enabled`. + +`logging_factory.py` was split out of `logging_instrument.py` to keep each file +scoped to one job. It holds `MemoryLoggerFactory`, `_MemoryLoggerFactoryConfig`, +the orjson structlog serializer, and the ASGI `AddressProtocol` / +`RequestProtocol` typing protocols. + +## Optional-dependency guard + +Optional packages stay optional. `lite_bootstrap/import_checker.py` exposes +booleans computed once at import via `importlib.util.find_spec` +(`is_opentelemetry_installed`, `is_sentry_installed`, `is_fastapi_installed`, +`is_pyroscope_installed`, …). Optional imports sit behind +`if import_checker.is_X_installed:` blocks. Code that references the optional +symbol is only reached after `check_dependencies()` has already returned True, +so the runtime invariant holds even though static analyzers that don't model the +guard may report spurious "possibly unbound" diagnostics. The project uses `ty`, +which handles the pattern correctly. + +## Why instruments are not frozen + +All `*Config` classes are frozen, but `*Instrument` classes drop `frozen=True` +because two of them cache mutable runtime state: `LoggingInstrument` caches a +`_logger_factory` (`MemoryLoggerFactory | None`) and `OpenTelemetryInstrument` +caches `_tracer_provider`. Python's dataclass rules require the whole hierarchy +to be non-frozen, so `BaseInstrument` is non-frozen too. Both caches are reset +to `None` inside a `try/finally` during `teardown()`, so a raised shutdown +leaves no stale references. + +## Cross-instrument integrations + +**Logging ↔ Sentry.** `logging_instrument.py` injects structlog context into +Sentry events. `sentry_instrument.py` chains the user's `before_send` after the +built-in structlog enricher via `wrap_before_send_callbacks()`. A `skip_sentry` +flag in the log context suppresses the event; the flag is also stripped from the +Sentry payload (it is in `IGNORED_STRUCTLOG_ATTRIBUTES`). + +**OTel ↔ Logging.** The logging instrument injects span/trace IDs from the +active OpenTelemetry context into every log record, so logs and traces correlate. + +**Pyroscope ↔ OTel.** When both are enabled, a `PyroscopeSpanProcessor` is added +to the tracer provider so traces and profiles link in Grafana. + +## OpenTelemetry single-instance-per-process + +`OpenTelemetryInstrument.bootstrap()` calls +`opentelemetry.trace.set_tracer_provider(...)`, which the OTel SDK enforces as +**set-once**. A second instance's call is ignored (the SDK logs "Overriding of +current TracerProvider is not allowed"). `teardown()` calls `shutdown()` on the +cached provider — flushing batched spans and closing exporters — and resets +`_tracer_provider = None`, but it **cannot** reset the process-global pointer. +Construct exactly one `OpenTelemetryInstrument` per process; do not bootstrap a +second. diff --git a/planning/README.md b/planning/README.md new file mode 100644 index 0000000..5abd272 --- /dev/null +++ b/planning/README.md @@ -0,0 +1,113 @@ +# Planning + +Specs, plans, and change history for `lite-bootstrap`. The living truth +about *what the system does now* lives in [`architecture/`](../architecture/) +at the repo root; this directory records *how it got there*. + +## Conventions + +> This section is the portable convention — identical across the +> modern-python repos. The Index below is repo-specific. To adopt elsewhere, +> copy this section plus [`_templates/`](_templates/) and point that repo's +> `CLAUDE.md` Workflow + truth home at it. + +### Two axes, never mixed + +- **`architecture/` (repo root) — the present.** One file per capability, + living prose, updated whenever a change ships. The truth home. +- **`planning/changes/` — the past-and-pending.** One folder per change, + frozen once shipped. + +Shipping a change **promotes** its conclusions into the affected +`architecture/.md` by hand, then archives the bundle. That +hand-edit is what keeps `architecture/` true; the archived bundle carries the +*why*. + +### Change bundles + +A change is a folder `changes/active/YYYY-MM-DD.NN-/`: + +- `YYYY-MM-DD` — proposal date; `.NN` — zero-padded intra-day counter + (`.01`, `.02`, …) that breaks same-date ties so the timeline sorts stably. +- `` — kebab-case description, not a story ID. + +On merge the folder moves to `changes/archive/` with `status: shipped`, `pr:`, +and `outcome:` filled, and its line moves from **Active** to **Archived** in +the Index below. + +### 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 | +| **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`. + +### 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. +- **`releases/.md`** — per-release user-facing notes. +- **`audits/-.md`** — findings from a code/docs/bug-hunt sweep; + spawns fix changes. +- **`retros/-.md`** — what we learned after a body of work. +- **`deferred.md`** — real-but-unscheduled items, each with a revisit trigger. + +Templates live in [`_templates/`](_templates/). + +### Frontmatter + +`design.md` / `change.md`: `status` (draft|approved|shipped|superseded), +`date`, `slug`, `supersedes`, `superseded_by`, `pr`, `outcome`. +`plan.md`: `status`, `date`, `slug`, `spec`, `pr`. Files in `architecture/` +carry **no** frontmatter — living prose, dated by git. + +## Index + +### Active + +_None._ + +### Archived (shipped) + +- **[portable-planning-convention](changes/archive/2026-06-13.01-portable-planning-convention/design.md)** + (#120, 2026-06-13) — Adopt the portable two-axis convention: `architecture/` + truth home + `changes/` bundles, per-arc bundling of the audit arcs, fresh + Index. +- **[mkdocs-github-pages](changes/archive/2026-06-09.01-mkdocs-github-pages/design.md)** + (#112–#115, 2026-06-09) — Docs hosting moved from Read the Docs to GitHub + Actions + Pages. +- **[bug-audit-v2](changes/archive/2026-06-05.01-bug-audit-v2/design.md)** + (#108–#110, 2026-06-05) — 26 findings (UX · logic · security · tests) shipped + across three themed PRs. +- **[deferred-refactors](changes/archive/2026-06-01.03-deferred-refactors/design.md)** + (#96–#103, 2026-06-01) — The 20 deferred items from the 2026-05-31 audit + (REF/TEST/LOW) across eight PRs. +- **[fastmcp-bootstrapper](changes/archive/2026-06-01.02-fastmcp-bootstrapper/design.md)** + (2026-06-01) — New `FastMcpBootstrapper` mirroring microbootstrap's fastmcp + support. +- **[instrument-skip-rework](changes/archive/2026-06-01.01-instrument-skip-rework/design.md)** + (2026-06-01) — Replace `InstrumentNotReadyWarning` with a pre-instantiation + config check + summary log. *Partially superseded by + [stdlib-logging-and-build-summary](changes/archive/2026-06-02.01-stdlib-logging-and-build-summary/design.md).* +- **[stdlib-logging-and-build-summary](changes/archive/2026-06-02.01-stdlib-logging-and-build-summary/design.md)** + (#107, 2026-06-02) — Stdlib `logging` in `bootstrappers/base.py` + public + `build_summary()`. +- **[audit-implementation](changes/archive/2026-05-31.01-audit-implementation/design.md)** + (#89–#95, 2026-05-31) — Criticals (CRIT-1..3) + design issues (DES-1..5) + + paired tests across seven sequenced PRs. + +## Other + +- **[`architecture/`](../architecture/)** at the repo root — the living + capability truth (config model, instruments, bootstrappers). The promotion + target on every ship. +- **[audits/](audits/)** — findings reports (2026-05-31 bug+refactor audit, + 2026-06-05 bug audit v2). +- **[retros/](retros/)** — what we learned after a body of work. +- **[deferred.md](deferred.md)** — the long-tail register of real-but- + unscheduled items with revisit triggers. diff --git a/planning/_templates/change.md b/planning/_templates/change.md new file mode 100644 index 0000000..0fe24c0 --- /dev/null +++ b/planning/_templates/change.md @@ -0,0 +1,38 @@ +--- +status: draft +date: YYYY-MM-DD +slug: my-change +supersedes: null +superseded_by: null +pr: null +outcome: null +--- + +# 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`. + +## Goal + +One or two sentences: what changes and why. + +## Approach + +The shape of the change in brief — enough that a reviewer sees the design +without a full spec. Link the truth home (`architecture/.md`) if a +capability contract moves. + +## Files + +- `path/to/file.py` — what changes +- `tests/test_x.py` — test added / updated + +## Verification + +- [ ] Failing test first — command + expected error. +- [ ] Apply the change. +- [ ] Test passes — command. +- [ ] `just test` — full suite green. +- [ ] `just lint` — clean. diff --git a/planning/_templates/design.md b/planning/_templates/design.md new file mode 100644 index 0000000..fb0fe5b --- /dev/null +++ b/planning/_templates/design.md @@ -0,0 +1,55 @@ +--- +status: draft +date: YYYY-MM-DD +slug: my-change +supersedes: null +superseded_by: null +pr: null +outcome: null +--- + +# Design: One-line capitalized title + +## Summary + +One paragraph. What changes, at the level a reader needs to decide if this +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. + +## Design + +### 1. + +What changes, in enough detail that a reader who has not seen the codebase +can follow. Code samples / diagrams welcome. + +### 2. + +... + +## Operations + +Out-of-repo steps (DNS, infra, external account changes). Omit if none. + +## Out of scope + +Already covered above under Non-goals if appropriate. Repeat-list of +explicitly-excluded follow-ups belongs here when the list is long. + +## Testing + +How we know it landed correctly. New pytest? Smoke check on live URL? +Lint pass? Be specific. + +## Risk + +What could go wrong, ranked by likelihood × impact. Mitigations. diff --git a/planning/_templates/plan.md b/planning/_templates/plan.md new file mode 100644 index 0000000..f2b90e8 --- /dev/null +++ b/planning/_templates/plan.md @@ -0,0 +1,56 @@ +--- +status: draft +date: YYYY-MM-DD +slug: my-change +spec: my-change +pr: null +--- + +# — 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 ": + + Co-Authored-By: Claude Opus 4.7 (1M context) " + ``` + +--- + +### Task 2: ... diff --git a/planning/specs/2026-05-31-bug-refactor-audit.md b/planning/audits/2026-05-31-bug-refactor-audit.md similarity index 100% rename from planning/specs/2026-05-31-bug-refactor-audit.md rename to planning/audits/2026-05-31-bug-refactor-audit.md diff --git a/planning/specs/2026-06-05-bug-audit-v2.md b/planning/audits/2026-06-05-bug-audit-v2.md similarity index 100% rename from planning/specs/2026-06-05-bug-audit-v2.md rename to planning/audits/2026-06-05-bug-audit-v2.md diff --git a/planning/changes/active/.gitkeep b/planning/changes/active/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/planning/specs/2026-05-31-audit-implementation-sequencing.md b/planning/changes/archive/2026-05-31.01-audit-implementation/design.md similarity index 98% rename from planning/specs/2026-05-31-audit-implementation-sequencing.md rename to planning/changes/archive/2026-05-31.01-audit-implementation/design.md index 8ef8200..dcd215a 100644 --- a/planning/specs/2026-05-31-audit-implementation-sequencing.md +++ b/planning/changes/archive/2026-05-31.01-audit-implementation/design.md @@ -1,7 +1,16 @@ +--- +status: shipped +date: 2026-05-31 +slug: audit-implementation +supersedes: null +superseded_by: null +pr: null +outcome: "shipped as #89–#95" +--- # Audit Implementation Sequencing **Date:** 2026-05-31 -**Parent spec:** [2026-05-31-bug-refactor-audit.md](2026-05-31-bug-refactor-audit.md) +**Parent spec:** [2026-05-31-bug-refactor-audit.md](../../audits/2026-05-31-bug-refactor-audit.md) **Scope:** Criticals (CRIT-1..3) + Design issues (DES-1..5) + paired tests (TEST-1/2/3/5/6) **Deliverable:** 7 sequenced PRs. Sequencing rationale, per-PR scope, decisions. diff --git a/planning/plans/2026-05-31-pr1-crit1-redoc-root-path.md b/planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr1-crit1-redoc-root-path.md similarity index 100% rename from planning/plans/2026-05-31-pr1-crit1-redoc-root-path.md rename to planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr1-crit1-redoc-root-path.md diff --git a/planning/plans/2026-05-31-pr2-crit2-otel-shutdown.md b/planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr2-crit2-otel-shutdown.md similarity index 100% rename from planning/plans/2026-05-31-pr2-crit2-otel-shutdown.md rename to planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr2-crit2-otel-shutdown.md diff --git a/planning/plans/2026-05-31-pr3-crit3-idempotent-teardown.md b/planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr3-crit3-idempotent-teardown.md similarity index 100% rename from planning/plans/2026-05-31-pr3-crit3-idempotent-teardown.md rename to planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr3-crit3-idempotent-teardown.md diff --git a/planning/plans/2026-06-01-pr4-des4-des5-small-cleanups.md b/planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr4-des4-des5-small-cleanups.md similarity index 100% rename from planning/plans/2026-06-01-pr4-des4-des5-small-cleanups.md rename to planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr4-des4-des5-small-cleanups.md diff --git a/planning/plans/2026-06-01-pr5-des3-config-method-semantics.md b/planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr5-des3-config-method-semantics.md similarity index 100% rename from planning/plans/2026-06-01-pr5-des3-config-method-semantics.md rename to planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr5-des3-config-method-semantics.md diff --git a/planning/plans/2026-06-01-pr6-des2-otel-fields-mixin.md b/planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr6-des2-otel-fields-mixin.md similarity index 100% rename from planning/plans/2026-06-01-pr6-des2-otel-fields-mixin.md rename to planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr6-des2-otel-fields-mixin.md diff --git a/planning/plans/2026-06-01-pr7-des1-generic-instruments.md b/planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr7-des1-generic-instruments.md similarity index 100% rename from planning/plans/2026-06-01-pr7-des1-generic-instruments.md rename to planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr7-des1-generic-instruments.md diff --git a/planning/specs/2026-06-01-instrument-skip-rework-design.md b/planning/changes/archive/2026-06-01.01-instrument-skip-rework/design.md similarity index 98% rename from planning/specs/2026-06-01-instrument-skip-rework-design.md rename to planning/changes/archive/2026-06-01.01-instrument-skip-rework/design.md index 44c452a..22d910b 100644 --- a/planning/specs/2026-06-01-instrument-skip-rework-design.md +++ b/planning/changes/archive/2026-06-01.01-instrument-skip-rework/design.md @@ -1,3 +1,12 @@ +--- +status: shipped +date: 2026-06-01 +slug: instrument-skip-rework +supersedes: null +superseded_by: stdlib-logging-and-build-summary +pr: null +outcome: shipped (partially superseded; see stdlib-logging-and-build-summary) +--- # Design: Instrument Skip Rework — Replace `InstrumentNotReadyWarning` With Pre-Instantiation Config Check + Summary Log **Date:** 2026-06-01 diff --git a/planning/plans/2026-06-01-instrument-skip-rework.md b/planning/changes/archive/2026-06-01.01-instrument-skip-rework/plan.md similarity index 99% rename from planning/plans/2026-06-01-instrument-skip-rework.md rename to planning/changes/archive/2026-06-01.01-instrument-skip-rework/plan.md index 6e428e2..27fa69b 100644 --- a/planning/plans/2026-06-01-instrument-skip-rework.md +++ b/planning/changes/archive/2026-06-01.01-instrument-skip-rework/plan.md @@ -1,3 +1,10 @@ +--- +status: shipped +date: 2026-06-01 +slug: instrument-skip-rework +spec: instrument-skip-rework +pr: null +--- # Instrument Skip Rework Implementation Plan > **Note (2026-06-02):** the `_get_logger()` fresh-per-call decision documented below was revised by `docs/superpowers/specs/2026-06-02-stdlib-logging-and-build-summary-design.md`. The summary-log goal is unchanged; the implementation switched to stdlib `logging` with a public `build_summary()` method. @@ -10,7 +17,7 @@ **Tech Stack:** Python 3.10+, dataclasses, stdlib `warnings` + `logging`, pytest (caplog), structlog (transitive). -**Parent spec:** `docs/superpowers/specs/2026-06-01-instrument-skip-rework-design.md` +**Parent spec:** `./design.md` --- diff --git a/planning/specs/2026-06-01-fastmcp-bootstrapper-design.md b/planning/changes/archive/2026-06-01.02-fastmcp-bootstrapper/design.md similarity index 99% rename from planning/specs/2026-06-01-fastmcp-bootstrapper-design.md rename to planning/changes/archive/2026-06-01.02-fastmcp-bootstrapper/design.md index b9bbb9d..5f4c559 100644 --- a/planning/specs/2026-06-01-fastmcp-bootstrapper-design.md +++ b/planning/changes/archive/2026-06-01.02-fastmcp-bootstrapper/design.md @@ -1,3 +1,12 @@ +--- +status: shipped +date: 2026-06-01 +slug: fastmcp-bootstrapper +supersedes: null +superseded_by: null +pr: null +outcome: shipped +--- # FastMCP Bootstrapper Design **Date:** 2026-06-01 diff --git a/planning/plans/2026-06-01-fastmcp-bootstrapper.md b/planning/changes/archive/2026-06-01.02-fastmcp-bootstrapper/plan.md similarity index 99% rename from planning/plans/2026-06-01-fastmcp-bootstrapper.md rename to planning/changes/archive/2026-06-01.02-fastmcp-bootstrapper/plan.md index 3c7c5ac..11fceec 100644 --- a/planning/plans/2026-06-01-fastmcp-bootstrapper.md +++ b/planning/changes/archive/2026-06-01.02-fastmcp-bootstrapper/plan.md @@ -1,3 +1,10 @@ +--- +status: shipped +date: 2026-06-01 +slug: fastmcp-bootstrapper +spec: fastmcp-bootstrapper +pr: null +--- # FastMCP Bootstrapper 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. @@ -8,7 +15,7 @@ **Tech Stack:** Python 3.10+ dataclasses, `fastmcp`, `structlog`, `prometheus_client`, `starlette.responses`, `pytest-asyncio` (auto mode), `httpx2` test client, `unittest.mock.MagicMock` / `pytest.MonkeyPatch`. -**Parent spec:** `docs/superpowers/specs/2026-06-01-fastmcp-bootstrapper-design.md` +**Parent spec:** `./design.md` **Reference PR:** [microbootstrap PR #141](https://github.com/community-of-python/microbootstrap/pull/141). --- @@ -1103,7 +1110,7 @@ gh pr create --title "feat: add FastMcpBootstrapper" --body "$(cat <<'EOF' Mirrors the instrument set merged upstream in [microbootstrap PR #141](https://github.com/community-of-python/microbootstrap/pull/141) and improves on it by wiring teardown. -Spec: `docs/superpowers/specs/2026-06-01-fastmcp-bootstrapper-design.md`. +Spec: `./design.md`. ## Test plan diff --git a/planning/specs/2026-06-01-deferred-refactors-sequencing.md b/planning/changes/archive/2026-06-01.03-deferred-refactors/design.md similarity index 97% rename from planning/specs/2026-06-01-deferred-refactors-sequencing.md rename to planning/changes/archive/2026-06-01.03-deferred-refactors/design.md index 90900d1..59d3ed0 100644 --- a/planning/specs/2026-06-01-deferred-refactors-sequencing.md +++ b/planning/changes/archive/2026-06-01.03-deferred-refactors/design.md @@ -1,8 +1,17 @@ +--- +status: shipped +date: 2026-06-01 +slug: deferred-refactors +supersedes: null +superseded_by: null +pr: null +outcome: "shipped as #96–#103" +--- # Deferred Refactors Sequencing **Date:** 2026-06-01 -**Parent audit:** [2026-05-31-bug-refactor-audit.md](2026-05-31-bug-refactor-audit.md) -**Sibling spec:** [2026-05-31-audit-implementation-sequencing.md](2026-05-31-audit-implementation-sequencing.md) (criticals + design issues; 7 PRs shipped as #89, #90, #91, #92, #93, #94, #95) +**Parent audit:** [2026-05-31-bug-refactor-audit.md](../../audits/2026-05-31-bug-refactor-audit.md) +**Sibling spec:** [2026-05-31-audit-implementation-sequencing.md](../2026-05-31.01-audit-implementation/design.md) (criticals + design issues; 7 PRs shipped as #89, #90, #91, #92, #93, #94, #95) **Scope:** All 20 deferred items — REF-1..7 (refactor opportunities), TEST-4/7/8 (test gaps), LOW-1..9 (cosmetic). Plus one finding surfaced during PR6 review: `Opentelemetry` → `OpenTelemetry` capitalization rename. **Deliverable:** 8 sequenced PRs. Sequencing rationale, per-PR scope, locked decisions. diff --git a/planning/plans/2026-06-01-pr10-test-gap-fill.md b/planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr10-test-gap-fill.md similarity index 100% rename from planning/plans/2026-06-01-pr10-test-gap-fill.md rename to planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr10-test-gap-fill.md diff --git a/planning/plans/2026-06-01-pr11-logging-cleanup.md b/planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr11-logging-cleanup.md similarity index 100% rename from planning/plans/2026-06-01-pr11-logging-cleanup.md rename to planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr11-logging-cleanup.md diff --git a/planning/plans/2026-06-01-pr12-base-layer-cleanup.md b/planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr12-base-layer-cleanup.md similarity index 100% rename from planning/plans/2026-06-01-pr12-base-layer-cleanup.md rename to planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr12-base-layer-cleanup.md diff --git a/planning/plans/2026-06-01-pr13-frozen-setattr.md b/planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr13-frozen-setattr.md similarity index 100% rename from planning/plans/2026-06-01-pr13-frozen-setattr.md rename to planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr13-frozen-setattr.md diff --git a/planning/plans/2026-06-01-pr14-faststream-timeout.md b/planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr14-faststream-timeout.md similarity index 100% rename from planning/plans/2026-06-01-pr14-faststream-timeout.md rename to planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr14-faststream-timeout.md diff --git a/planning/plans/2026-06-01-pr15-naming-pass.md b/planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr15-naming-pass.md similarity index 100% rename from planning/plans/2026-06-01-pr15-naming-pass.md rename to planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr15-naming-pass.md diff --git a/planning/plans/2026-06-01-pr16-post-retro-hygiene.md b/planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr16-post-retro-hygiene.md similarity index 94% rename from planning/plans/2026-06-01-pr16-post-retro-hygiene.md rename to planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr16-post-retro-hygiene.md index 10ed7af..579ef3e 100644 --- a/planning/plans/2026-06-01-pr16-post-retro-hygiene.md +++ b/planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr16-post-retro-hygiene.md @@ -6,7 +6,7 @@ - `pyproject.toml` — add upper bound to `uv_build` to silence the every-`just lint` warning - `lite_bootstrap/instruments/pyroscope_instrument.py` — add a runtime assert on `pyroscope_endpoint` to document the `is_ready()`-enforced invariant -**Parent docs:** Surfaced in the [audit retrospective](../specs/2026-06-01-audit-implementation-retro.md). Neither is an audit finding; both noticed during the retro action-item work (`just lint` warning persistence + Pyright's `reportArgumentType` on pyroscope's `server_address`). +**Parent docs:** Surfaced in the [audit retrospective](../../retros/2026-06-01-audit-implementation-retro.md). Neither is an audit finding; both noticed during the retro action-item work (`just lint` warning persistence + Pyright's `reportArgumentType` on pyroscope's `server_address`). This is the first PR using the [lightweight plan template](../templates/lightweight-plan-template.md). Eat your own dog food. diff --git a/planning/plans/2026-06-01-pr8-low-1-2-sentry-micro.md b/planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr8-low-1-2-sentry-micro.md similarity index 100% rename from planning/plans/2026-06-01-pr8-low-1-2-sentry-micro.md rename to planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr8-low-1-2-sentry-micro.md diff --git a/planning/plans/2026-06-01-pr9-otel-touch-ups.md b/planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr9-otel-touch-ups.md similarity index 100% rename from planning/plans/2026-06-01-pr9-otel-touch-ups.md rename to planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr9-otel-touch-ups.md diff --git a/planning/specs/2026-06-02-stdlib-logging-and-build-summary-design.md b/planning/changes/archive/2026-06-02.01-stdlib-logging-and-build-summary/design.md similarity index 98% rename from planning/specs/2026-06-02-stdlib-logging-and-build-summary-design.md rename to planning/changes/archive/2026-06-02.01-stdlib-logging-and-build-summary/design.md index 962656b..7a4f9e1 100644 --- a/planning/specs/2026-06-02-stdlib-logging-and-build-summary-design.md +++ b/planning/changes/archive/2026-06-02.01-stdlib-logging-and-build-summary/design.md @@ -1,3 +1,12 @@ +--- +status: shipped +date: 2026-06-02 +slug: stdlib-logging-and-build-summary +supersedes: instrument-skip-rework +superseded_by: null +pr: "107" +outcome: "merged as #107" +--- # Design: Stdlib Logging in `bootstrappers/base.py` + Public `build_summary()` Method **Date:** 2026-06-02 diff --git a/planning/plans/2026-06-02-stdlib-logging-and-build-summary.md b/planning/changes/archive/2026-06-02.01-stdlib-logging-and-build-summary/plan.md similarity index 99% rename from planning/plans/2026-06-02-stdlib-logging-and-build-summary.md rename to planning/changes/archive/2026-06-02.01-stdlib-logging-and-build-summary/plan.md index f6d403d..d7030ac 100644 --- a/planning/plans/2026-06-02-stdlib-logging-and-build-summary.md +++ b/planning/changes/archive/2026-06-02.01-stdlib-logging-and-build-summary/plan.md @@ -1,3 +1,10 @@ +--- +status: shipped +date: 2026-06-02 +slug: stdlib-logging-and-build-summary +spec: stdlib-logging-and-build-summary +pr: "107" +--- # Stdlib Logging + `build_summary()` 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. @@ -8,7 +15,7 @@ **Tech Stack:** Python 3.10+, stdlib `logging`, pytest (`caplog`), ruff, `ty`. -**Parent spec:** `docs/superpowers/specs/2026-06-02-stdlib-logging-and-build-summary-design.md` +**Parent spec:** `./design.md` --- diff --git a/planning/specs/2026-06-05-bug-audit-v2-sequencing.md b/planning/changes/archive/2026-06-05.01-bug-audit-v2/design.md similarity index 99% rename from planning/specs/2026-06-05-bug-audit-v2-sequencing.md rename to planning/changes/archive/2026-06-05.01-bug-audit-v2/design.md index 406e621..4291591 100644 --- a/planning/specs/2026-06-05-bug-audit-v2-sequencing.md +++ b/planning/changes/archive/2026-06-05.01-bug-audit-v2/design.md @@ -1,3 +1,12 @@ +--- +status: shipped +date: 2026-06-05 +slug: bug-audit-v2 +supersedes: null +superseded_by: null +pr: null +outcome: "shipped as #108–#110" +--- # Bug Audit v2 — Implementation Sequencing **Date:** 2026-06-05 diff --git a/planning/plans/2026-06-05-pr1-lifecycle.md b/planning/changes/archive/2026-06-05.01-bug-audit-v2/plan-pr1-lifecycle.md similarity index 100% rename from planning/plans/2026-06-05-pr1-lifecycle.md rename to planning/changes/archive/2026-06-05.01-bug-audit-v2/plan-pr1-lifecycle.md diff --git a/planning/plans/2026-06-05-pr2-config-security.md b/planning/changes/archive/2026-06-05.01-bug-audit-v2/plan-pr2-config-security.md similarity index 100% rename from planning/plans/2026-06-05-pr2-config-security.md rename to planning/changes/archive/2026-06-05.01-bug-audit-v2/plan-pr2-config-security.md diff --git a/planning/plans/2026-06-05-pr3-hygiene-ci.md b/planning/changes/archive/2026-06-05.01-bug-audit-v2/plan-pr3-hygiene-ci.md similarity index 100% rename from planning/plans/2026-06-05-pr3-hygiene-ci.md rename to planning/changes/archive/2026-06-05.01-bug-audit-v2/plan-pr3-hygiene-ci.md diff --git a/planning/specs/2026-06-09-mkdocs-github-actions-design.md b/planning/changes/archive/2026-06-09.01-mkdocs-github-pages/design.md similarity index 97% rename from planning/specs/2026-06-09-mkdocs-github-actions-design.md rename to planning/changes/archive/2026-06-09.01-mkdocs-github-pages/design.md index f4e5f7b..d37188f 100644 --- a/planning/specs/2026-06-09-mkdocs-github-actions-design.md +++ b/planning/changes/archive/2026-06-09.01-mkdocs-github-pages/design.md @@ -1,3 +1,12 @@ +--- +status: shipped +date: 2026-06-09 +slug: mkdocs-github-pages +supersedes: null +superseded_by: null +pr: null +outcome: shipped in the docs+CI modern-di mirror arc (#112–#115) +--- # Migrate docs from Read the Docs to GitHub Actions + Pages Date: 2026-06-09 diff --git a/planning/plans/2026-06-09-mkdocs-github-actions-plan.md b/planning/changes/archive/2026-06-09.01-mkdocs-github-pages/plan.md similarity index 98% rename from planning/plans/2026-06-09-mkdocs-github-actions-plan.md rename to planning/changes/archive/2026-06-09.01-mkdocs-github-pages/plan.md index 04aa2ff..900fcb1 100644 --- a/planning/plans/2026-06-09-mkdocs-github-actions-plan.md +++ b/planning/changes/archive/2026-06-09.01-mkdocs-github-pages/plan.md @@ -1,3 +1,10 @@ +--- +status: shipped +date: 2026-06-09 +slug: mkdocs-github-pages +spec: mkdocs-github-pages +pr: null +--- # MkDocs GitHub Actions Migration 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. @@ -8,7 +15,7 @@ **Tech Stack:** MkDocs + mkdocs-material, GitHub Actions, GitHub Pages, `uv`/`uvx`, `just`. -**Spec:** `planning/specs/2026-06-09-mkdocs-github-actions-design.md` +**Spec:** `./design.md` --- @@ -417,8 +424,8 @@ gh pr create --title "docs: migrate from Read the Docs to GitHub Actions + Pages - GitHub Pages serves `gh-pages` under `lite-bootstrap.modern-python.org` (`docs/CNAME`). - Mirrors the sibling [`modern-di`](https://github.com/modern-python/modern-di) project's docs deployment pattern verbatim. -Spec: `planning/specs/2026-06-09-mkdocs-github-actions-design.md` -Plan: `planning/plans/2026-06-09-mkdocs-github-actions-plan.md` +Spec: `./design.md` +Plan: `./plan.md` ## Operator follow-ups (post-merge) diff --git a/planning/changes/archive/2026-06-13.01-portable-planning-convention/design.md b/planning/changes/archive/2026-06-13.01-portable-planning-convention/design.md new file mode 100644 index 0000000..67e7824 --- /dev/null +++ b/planning/changes/archive/2026-06-13.01-portable-planning-convention/design.md @@ -0,0 +1,400 @@ +--- +status: shipped +date: 2026-06-13 +slug: portable-planning-convention +supersedes: null +superseded_by: null +pr: "120" +outcome: "ships in #120 — defines the convention; no architecture/ promotion applies" +--- + +# Design: Adopt the portable OpenSpec-shaped planning convention + +## Summary + +Replace `lite-bootstrap`'s ad-hoc `planning/` layout (`specs/` mixing design +specs + audits + retros + sequencing docs, a flat `plans/`, a bespoke +`templates/lightweight-plan-template.md`) with the **portable two-axis +convention** already shipped in `faststream-outbox`: a new `architecture/` +directory at the repo root holds the *living truth* — what the system does now, +one file per capability — and `planning/changes/` holds the *change history*, +each change a self-contained folder bundle, `active/` → `archive/`. Shipping a +change **promotes** its conclusions into the relevant `architecture/.md` +by hand, then archives the bundle. + +The convention prose (`planning/README.md` "Conventions" section) and the three +templates (`_templates/{design,plan,change}.md`) are copied **byte-identical** +from `faststream-outbox`; only the repo-specific "Index" is authored fresh. The +existing `planning/` artifacts are migrated into the new shape: design+plan +pairs become bundles, audits go to `audits/`, retros go to `retros/`, +sequencing docs become the `design.md` of per-arc bundles, and the +lightweight-plan template is deleted in favor of the copied `change.md`. + +Because this repo has **no `architecture/` today**, the migration also seeds it +with three capability files drawn from `CLAUDE.md`'s Architecture section and +the existing `docs/integrations/` pages, so promotion has a real target from day +one. `CLAUDE.md`'s `## Planning artifacts` section is rewritten into a +`## Workflow` section naming `architecture/` as the promotion target. + +This change touches no runtime code, no test code, and no public API. It is the +`lite-bootstrap` instance of the same convention adopted in `faststream-outbox` +(its `portable-planning-convention`, #77). + +## Motivation + +`lite-bootstrap`'s `planning/` directory grew organically and now mixes four +distinct artifact kinds in two flat directories, with no convention document and +no living-truth home: + +- **`planning/specs/` conflates three things.** It holds actual design specs + (`*-design.md`), audit findings reports (`bug-refactor-audit.md`, + `bug-audit-v2.md`), retros (`*-retro.md`), and sequencing docs + (`*-sequencing.md` — plans-of-plans that order many PRs). A reader cannot tell + artifact kind from location. + +- **`planning/plans/` is a flat pile of ~23 files.** Most are per-PR execution + plans (`pr1`…`pr16`) spawned by one audit + one sequencing doc, with no + grouping back to the arc that produced them. The relationship between a + sequencing doc and its PR plans is implicit. + +- **No living-truth home.** The closest thing to "what the system does now" is + `CLAUDE.md`'s Architecture section, which is AI-instruction prose, not a + navigable capability reference. There is no step that forces it to stay + current when behavior changes. + +- **No convention document.** There is no `planning/README.md`; the layout is + undocumented and diverges from the sibling repos (`faststream-outbox`, + `httpware`, `modern-di`). `faststream-outbox` has already converged on a + portable convention (#77) designed to drop into the other three repos; this + change is that adoption for `lite-bootstrap`. + +The portable convention resolves all four: it separates living truth +(`architecture/`) from change history (`planning/changes/`), names the +promotion boundary, gives audits/retros/sequencing dedicated homes, and ships a +single documented convention identical across the ecosystem. + +## Non-goals + +- **Rewriting or trimming archived prose.** Existing shipped specs/plans/audits/ + retros move into the new layout verbatim; only their location and (for bundle + `design.md` files) frontmatter linkage change. + +- **Retrofitting frontmatter onto every historical plan file.** Each migrated + bundle's `design.md` gets full YAML frontmatter; the grouped per-PR plan files + inside an arc bundle keep their original prose headers (see Design §4). Adding + 9+ frontmatter blocks to frozen executor checklists is double-entry bookkeeping + with no payoff for archived material. + +- **Authoring exhaustive `architecture/` capability prose.** The three seed files + capture current truth at the level `CLAUDE.md` + `docs/` already document it. + They are a real starting point, not a from-scratch system manual; future + changes deepen them via promotion. + +- **Formal OpenSpec spec-deltas.** No `ADDED`/`MODIFIED`/`REMOVED` blocks. + Promotion is a hand-edit of the affected `architecture/.md`, + recoverable via `git log -p`. (Same decision as `faststream-outbox` #77.) + +- **An index generator or frontmatter-lint CI job.** The README Index stays + hand-maintained. + +- **Rolling the convention out to `httpware` / `modern-di`.** Out of scope; each + is separate demand-gated work. + +- **mkdocs-serving `planning/` or `architecture/`.** `docs_dir: docs`, so both + are already excluded from the site; this change does not add them. + +## Design + +### 1. The model: two axes, never mixed + +The convention rests on one distinction, identical to `faststream-outbox`: + +> **`architecture/` (repo root) is the present.** One file per capability, +> describing what the system does *now*. Living prose, updated whenever a change +> ships. The truth home and promotion target. +> +> **`planning/changes/` is the past-and-pending.** One folder per change, +> describing how a piece of behavior got (or will get) there. Frozen once +> shipped. + +A reader wanting current truth reads `architecture/`; a reader wanting the +rationale follows a promotion back to the archived change bundle. The two spaces +are two top-level homes (`architecture/` at root, `planning/` for history); +naming the boundary — not co-locating — removes the muddle. + +### 2. Target directory layout + +``` +architecture/ # LIVING TRUTH (new) — promotion target + config-model.md + instruments.md + bootstrappers.md + +planning/ + README.md # Conventions (byte-identical) + Index (fresh) + changes/ + active/ + .gitkeep # empty after migration (all prior work shipped) + archive/ + / # one folder per shipped change + design.md # spec — the thinking (FULL lane) + plan.md # plan — the sequencing (FULL lane) + # OR change.md # single file (LIGHTWEIGHT lane) + audits/ + 2026-05-31-bug-refactor-audit.md + 2026-06-05-bug-audit-v2.md + retros/ + 2026-06-01-audit-implementation-retro.md + 2026-06-05-bug-audit-v2-retro.md + 2026-06-09-docs-and-ci-modern-di-mirror-retro.md + releases/ + 1.1.0.md # unchanged + deferred.md # NEW — standard header, "none today" + _templates/ + design.md plan.md change.md # copied byte-identical from faststream-outbox +``` + +After migration, `planning/specs/`, `planning/plans/`, and +`planning/templates/` no longer exist. + +### 3. `architecture/` seed — three capability files + +This repo has no `architecture/` today, so the migration creates it and seeds +three capability files. Content is drawn from `CLAUDE.md`'s Architecture section +(core pattern, key design decisions, module layout) and the existing +`docs/integrations/` pages — internal capability truth, written as living prose +with **no frontmatter** (dated by git): + +- **`architecture/config-model.md`** — `BaseConfig` (frozen, `kw_only` + dataclasses); the `from_dict` vs `from_object` None-handling asymmetry; + `UnsetType` / `UNSET` sentinel and the `FastAPIConfig.application` + construction; the `__post_init__` cascade invariant (every config + `__post_init__` calls `super().__post_init__()`; `BaseConfig` terminates the + chain). + +- **`architecture/instruments.md`** — `BaseInstrument[ConfigT]` generic, + non-frozen dataclass with slots; lifecycle via `bootstrap()` / `teardown()`; + skip check via `is_configured()`; the instrument catalog (logging, + opentelemetry, sentry, prometheus/metrics, pyroscope, cors, swagger, health); + optional-dependency guard (`import_checker.is_X_installed`); the logging↔Sentry + and OTel↔logging integrations; the OpenTelemetry single-instance-per-process + constraint. + +- **`architecture/bootstrappers.md`** — `BaseBootstrapper` (abc) and the five + framework bootstrappers (FastAPI, Litestar, FastStream, FastMcp, Free); the + instrument registry; `is_configured → check_dependencies → instantiate` + ordering with `skipped_instruments`; reverse-order idempotent teardown; the + `build_summary()` log line; the `_lite_bootstrap_*` app-tagging sentinel + convention. + +Three cohesive files are the starting granularity; finer splits (e.g. one file +per instrument) can come later via normal changes. + +### 4. Migration mapping + +#### 4a. Clean design+plan pairs → one full bundle each + +Each existing `*-design.md` + its matching plan becomes a bundle under +`changes/archive/`: + +| Bundle | design.md ← | plan.md ← | +|--------|-------------|-----------| +| `…-fastmcp-bootstrapper/` | `specs/2026-06-01-fastmcp-bootstrapper-design.md` | `plans/2026-06-01-fastmcp-bootstrapper.md` | +| `…-instrument-skip-rework/` | `specs/2026-06-01-instrument-skip-rework-design.md` | `plans/2026-06-01-instrument-skip-rework.md` | +| `…-stdlib-logging-and-build-summary/` | `specs/2026-06-02-stdlib-logging-and-build-summary-design.md` | `plans/2026-06-02-stdlib-logging-and-build-summary.md` | +| `…-mkdocs-github-pages/` | `specs/2026-06-09-mkdocs-github-actions-design.md` | `plans/2026-06-09-mkdocs-github-actions-plan.md` | + +#### 4b. Audit arcs → per-arc bundle + +An audit arc is `1 audit → 1 sequencing doc → many PR plans`. Per-arc bundling: +the **sequencing doc becomes the bundle's `design.md`** (it is the design-level +"why this set of PRs, in this order"); the wave's per-PR execution plans are +grouped into the same folder as `plan-prN-.md`; the audit findings report +goes to `audits/` and the retro to `retros/`. + +| Bundle | design.md ← (sequencing) | grouped per-PR plans (← `plans/`) | +|--------|--------------------------|-----------------------------------| +| `2026-05-31.NN-audit-implementation/` | `audit-implementation-sequencing.md` | `pr1-crit1-redoc-root-path`, `pr2-crit2-otel-shutdown`, `pr3-crit3-idempotent-teardown`, `pr4-des4-des5-small-cleanups`, `pr5-des3-config-method-semantics`, `pr6-des2-otel-fields-mixin`, `pr7-des1-generic-instruments` | +| `2026-06-01.NN-deferred-refactors/` | `deferred-refactors-sequencing.md` | `pr8-low-1-2-sentry-micro`, `pr9-otel-touch-ups`, `pr10-test-gap-fill`, `pr11-logging-cleanup`, `pr12-base-layer-cleanup`, `pr13-frozen-setattr`, `pr14-faststream-timeout`, `pr15-naming-pass`, `pr16-post-retro-hygiene` | +| `2026-06-05.NN-bug-audit-v2/` | `bug-audit-v2-sequencing.md` | `pr1-lifecycle`, `pr2-config-security`, `pr3-hygiene-ci` | + +Parent audits → `audits/`: +- `specs/2026-05-31-bug-refactor-audit.md` → `audits/2026-05-31-bug-refactor-audit.md` +- `specs/2026-06-05-bug-audit-v2.md` → `audits/2026-06-05-bug-audit-v2.md` + +Retros → `retros/`: +- `specs/2026-06-01-audit-implementation-retro.md` → `retros/2026-06-01-audit-implementation-retro.md` +- `specs/2026-06-05-bug-audit-v2-retro.md` → `retros/2026-06-05-bug-audit-v2-retro.md` +- `specs/2026-06-09-docs-and-ci-modern-di-mirror-retro.md` → `retros/2026-06-09-docs-and-ci-modern-di-mirror-retro.md` + +#### 4c. `.NN` assignment and frontmatter + +- **`.NN`** (zero-padded intra-day counter) is assigned by **merge order** per + date; PR numbers referenced in the existing docs give the order. Where two + bundles share a date (notably `2026-06-01`, which has `instrument-skip-rework`, + `fastmcp-bootstrapper`, and `deferred-refactors`), `.01`/`.02`/`.03` break the + tie. A cosmetic mis-order is harmless — both bundles still exist and sort + adjacently. Exact `.NN` values are assigned in the implementation plan. + +- **Frontmatter (pragmatic retrofit):** each bundle's `design.md` gets full YAML + frontmatter (`status: shipped`, `date`, `slug`, `supersedes`/`superseded_by`, + `pr`, `outcome`). For clean-pair bundles, `plan.md` gets `plan.md` frontmatter. + The **grouped per-PR plan files inside arc bundles keep their original prose + headers** — they are frozen executor checklists, and the bundle's `design.md` + carries the lifecycle metadata for the whole arc. This is a deliberate scope + cut (see Non-goals): we do not author 9+ new frontmatter blocks for archived + checklists. `instrument-skip-rework` is partially superseded by + `stdlib-logging-and-build-summary`; that linkage is preserved via + `supersedes`/`superseded_by` on the two `design.md` files. + +#### 4d. Other moves + +- `planning/templates/lightweight-plan-template.md` → **deleted** (superseded by + the copied `_templates/change.md`). +- `planning/releases/1.1.0.md` → **unchanged**, stays at `planning/releases/`. +- `git mv` is used throughout to preserve blame. + +### 5. The convention doc (`planning/README.md`) + +Two sections: + +1. **Conventions** — copied **byte-identical** from + `faststream-outbox/planning/README.md` (the two-axis model, change-bundle + identity, three lanes, frontmatter schema, audits/retros/releases/deferred/ + templates). This is the portable core, identical across repos. The only edit + is to the prose that names this repo's truth home where the abstract + "truth home" needs a concrete instance — kept consistent with how + `faststream-outbox` references its own `architecture/`. +2. **Index** — authored **fresh** for `lite-bootstrap`. Lists Active (none after + migration) and Archived (all migrated bundles, one line each with PR + date), + plus an "Other" pointer block to `architecture/` (the promotion target), + `audits/`, and `retros/`. + +### 6. Three ceremony lanes (carried in the Conventions section) + +| Lane | Artifact(s) | 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 | +| **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`. + +### 7. `_templates/` + +Copy all three template files byte-identical from +`faststream-outbox/planning/_templates/`: `design.md`, `plan.md`, `change.md`. +Their `changes/active/` path references are already correct for this layout. + +### 8. `CLAUDE.md` update + +This repo's `CLAUDE.md` has a `## Planning artifacts` section (not a +`## Workflow` section). Rewrite it into a `## Workflow` section mirroring +`faststream-outbox`: + +1. Per-feature pipeline: brainstorming → spec in + `planning/changes/active/YYYY-MM-DD.NN-/design.md` → writing-plans → + `plan.md` → executing-plans / subagent-driven-development → + requesting-code-review → finishing-a-development-branch. +2. On merge: bundle moves to `planning/changes/archive/` with `status: shipped`, + `pr:`, `outcome:` filled, **and the change promotes its conclusions into the + affected `architecture/.md`** — name `architecture/` explicitly as + the promotion target. +3. The spec/plan/architecture artifact-boundary paragraph. +4. The three-lane paragraph. +5. Pointers to `planning/README.md` and `planning/_templates/`. + +The `## Architecture` section's existing pointers are untouched. The "Planning +artifacts" content that still applies (the `planning/specs/` vs `planning/plans/` +distinction) is replaced wholesale by the new convention. + +### 9. justfile — add `docs-build` + +The convention's Testing step references `just docs-build`, but this repo's +justfile has only `docs-deploy` (gh-deploy). Add a check-only target: + +``` +docs-build: + uvx --with-requirements docs/requirements.txt mkdocs build --strict +``` + +Small and optional; makes the verification step repeatable and matches +`faststream-outbox`. + +### 10. `deferred.md` + +Create `planning/deferred.md` with the standard header (copied from +`faststream-outbox`, adapted), recording that there are **no deferred items +today**. It is the long-tail register of real-but-unscheduled items with revisit +triggers; items graduate from here into `changes/active/` bundles. + +### 11. Dogfood — this change is its own bundle + +This adoption is itself a change, so it lands as +`planning/changes/active/2026-06-13.01-portable-planning-convention/`: + +- This `design.md` is written there now (during brainstorming) — the first use + of the new layout. +- The implementation plan is written to `plan.md` in the same folder. +- On merge, the bundle moves to `changes/archive/` with `status: shipped`, + `pr:`, `outcome:` filled, and its line moves to Archived in the README Index. + No `architecture/` promotion applies — this change defines the convention + (which lives in `README.md`) and seeds `architecture/`, rather than altering a + library capability. + +## Operations + +None. No DNS, infra, or external-account changes. Pure in-repo file moves, new +files, and doc edits. + +## Testing + +No code touched, so correctness is verified by: + +- `just lint-ci` passes (eof-fixer + ruff format/check in check mode + ty; the + markdown/format gate, since no Python changes). +- `just docs-build` (`mkdocs build --strict`) passes. `architecture/` is outside + `docs_dir: docs`, so it is not part of the site build; the strict build only + re-validates the existing `docs/` tree, which this change leaves untouched. +- Repo-wide grep sweeps return zero stale references: + - `grep -rn "planning/specs"` — none outside this bundle's own prose. + - `grep -rn "planning/plans"` — none. + - `grep -rn "lightweight-plan-template"` — none. +- Every `planning/changes/**/design.md` and `plan.md` (clean-pair) has parseable + YAML frontmatter — spot-checked on review. +- `planning/README.md` Conventions section is byte-identical to + `faststream-outbox` (diff -w shows only the Index and any deliberate truth-home + reference) — verified with `diff`. +- `planning/README.md` Index links resolve — manual click-through. +- The post-migration tree matches §2 exactly; `planning/specs/`, + `planning/plans/`, `planning/templates/` are gone. + +No new pytest, no new CI job. + +## Risk + +- **`.NN` ordering for same-date bundles is a judgment call.** `2026-06-01` has + three bundles. *Mitigation:* PR numbers in the existing docs give merge order; + a wrong tiebreak is cosmetic (bundles sort adjacently regardless). + +- **`architecture/` seed drifts from reality immediately.** Hand-written seed + prose can lag the code the moment it lands. *Mitigation:* the seed is sourced + from `CLAUDE.md` + `docs/`, which are current; and the convention forces + promotion on every future change, which is the mechanism that keeps it true. + An imperfect-but-real seed is strictly better than an empty truth home. + +- **`git mv` blame continuity through folder regrouping.** *Mitigation:* + `git log --follow` and GitHub web both follow renames; low practical impact + for planning artifacts. + +- **Convention drift on the next change.** A contributor could skip the lanes or + forget the `architecture/` promotion. *Mitigation:* `CLAUDE.md` names + `architecture/` as the promotion target so PR review catches a missing + promotion the way it catches a missing test; templates make the shape + copy-pasteable. + +- **Grouped per-PR plan files lack frontmatter.** A future tooling pass that + assumes every plan file has frontmatter would skip them. *Mitigation:* this is + an accepted, documented scope cut (Non-goals); the bundle `design.md` carries + the arc's lifecycle metadata, and no such tooling exists or is planned. diff --git a/planning/changes/archive/2026-06-13.01-portable-planning-convention/plan.md b/planning/changes/archive/2026-06-13.01-portable-planning-convention/plan.md new file mode 100644 index 0000000..1fe69ce --- /dev/null +++ b/planning/changes/archive/2026-06-13.01-portable-planning-convention/plan.md @@ -0,0 +1,779 @@ +--- +status: shipped +date: 2026-06-13 +slug: portable-planning-convention +spec: portable-planning-convention +pr: "120" +--- + +# Portable planning convention — 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:** Migrate `lite-bootstrap`'s `planning/` to the portable two-axis +convention from `faststream-outbox` — `architecture/` truth home + +`planning/changes/` bundles — and seed the truth home. + +**Spec:** [`design.md`](./design.md) + +**Architecture:** Pure file moves + new docs. Create `architecture/` (3 seed +files), restructure `planning/` into `changes/{active,archive}/` bundles + +`audits/` + `retros/`, copy the portable README Conventions + templates +byte-identical from `faststream-outbox`, author a fresh Index, and rewrite +`CLAUDE.md`'s planning section. No runtime/test/API code is touched. + +**Tech Stack:** Markdown, `git mv`, `just lint-ci`, `mkdocs build --strict`. + +**Branch:** `docs/portable-planning-convention` (already created; the spec is +already committed there). + +**Source repo (copy-from):** `/Users/kevinsmith/src/pypi/faststream-outbox`. + +**Commit strategy:** Per-task commits. + +**`.NN` assignment (final, by merge order; cosmetic for archived material):** + +| Bundle id | design.md source | grouped plans / pair plan | +|-----------|------------------|---------------------------| +| `2026-05-31.01-audit-implementation` | `audit-implementation-sequencing.md` | `pr1-crit1`…`pr7-des1` (#89–95) | +| `2026-06-01.01-instrument-skip-rework` | `instrument-skip-rework-design.md` | `instrument-skip-rework.md` | +| `2026-06-01.02-fastmcp-bootstrapper` | `fastmcp-bootstrapper-design.md` | `fastmcp-bootstrapper.md` | +| `2026-06-01.03-deferred-refactors` | `deferred-refactors-sequencing.md` | `pr8-low`…`pr16-post-retro-hygiene` (#96–103) | +| `2026-06-02.01-stdlib-logging-and-build-summary` | `stdlib-logging-and-build-summary-design.md` | `stdlib-logging-and-build-summary.md` (#107) | +| `2026-06-05.01-bug-audit-v2` | `bug-audit-v2-sequencing.md` | `pr1-lifecycle`, `pr2-config-security`, `pr3-hygiene-ci` (#108–110) | +| `2026-06-09.01-mkdocs-github-pages` | `mkdocs-github-actions-design.md` | `mkdocs-github-actions-plan.md` (docs+CI arc #112–115) | + +--- + +### Task 1: Scaffold the new `planning/` skeleton + copy templates + +**Files:** +- Create: `planning/changes/active/.gitkeep`, `planning/changes/archive/` (via bundles later) +- Create: `planning/audits/`, `planning/retros/` +- Create: `planning/_templates/{design,plan,change}.md` (copied byte-identical) +- Create: `planning/deferred.md` + +- [ ] **Step 0: Remove the stray `plan.md` ignore rule** + + `.gitignore` line 22 ignores the literal filename `plan.md` — it collides with + every change bundle's `plan.md` under the new convention (the old flat plans + escaped only because they were named `YYYY-MM-DD-prN-*.md`). The sibling + `faststream-outbox` repo does not ignore it. Delete the line: + + ```bash + cd /Users/kevinsmith/src/pypi/lite-bootstrap + git rm --cached --ignore-unmatch -q -- plan.md 2>/dev/null || true # no-op; nothing tracked + sed -i '' '/^plan\.md$/d' .gitignore + git check-ignore planning/changes/active/2026-06-13.01-portable-planning-convention/plan.md && echo "STILL IGNORED (bad)" || echo "plan.md no longer ignored" + ``` + Expected: `plan.md no longer ignored`. + +- [ ] **Step 1: Create the directory skeleton** + + ```bash + cd /Users/kevinsmith/src/pypi/lite-bootstrap + mkdir -p planning/changes/active planning/changes/archive planning/audits planning/retros planning/_templates + touch planning/changes/active/.gitkeep + ``` + +- [ ] **Step 2: Copy the three templates byte-identical** + + ```bash + cp /Users/kevinsmith/src/pypi/faststream-outbox/planning/_templates/design.md planning/_templates/design.md + cp /Users/kevinsmith/src/pypi/faststream-outbox/planning/_templates/plan.md planning/_templates/plan.md + cp /Users/kevinsmith/src/pypi/faststream-outbox/planning/_templates/change.md planning/_templates/change.md + ``` + +- [ ] **Step 3: Verify templates are byte-identical** + + Run: + ```bash + diff /Users/kevinsmith/src/pypi/faststream-outbox/planning/_templates/design.md planning/_templates/design.md \ + && diff /Users/kevinsmith/src/pypi/faststream-outbox/planning/_templates/plan.md planning/_templates/plan.md \ + && diff /Users/kevinsmith/src/pypi/faststream-outbox/planning/_templates/change.md planning/_templates/change.md \ + && echo "TEMPLATES IDENTICAL" + ``` + Expected: `TEMPLATES IDENTICAL` (no diff output). + +- [ ] **Step 4: Create `planning/deferred.md`** + + Write `planning/deferred.md` with the standard header, adapted to this repo + (no items today): + + ```markdown + # Deferred Work + + Items raised in reviews or audits that are real but not actionable now. + Each is parked here with the reason it's deferred and the concrete trigger + that should bring it back. This is the long-tail register — not a backlog + of planned work. When an item is picked up it graduates to a spec/plan + bundle in [`changes/active/`](changes/active/); see [CLAUDE.md](../CLAUDE.md#workflow). + + ## Open + + _None._ + ``` + +- [ ] **Step 5: Commit** + + ```bash + git add planning/changes planning/audits planning/retros planning/_templates planning/deferred.md + git commit -m "docs(planning): scaffold changes/audits/retros/_templates skeleton + + Co-Authored-By: Claude Opus 4.8 (1M context) " + ``` + +--- + +### Task 2: Seed `architecture/` truth home (3 capability files) + +**Files:** +- Create: `architecture/config-model.md` +- Create: `architecture/instruments.md` +- Create: `architecture/bootstrappers.md` + +**Sources to synthesize from:** `CLAUDE.md` (`## Architecture` → Core pattern, +Key design decisions, Module layout) and `docs/integrations/*.md`. Living prose, +**no frontmatter** (dated by git). Keep each file to the invariants a reader +needs to understand the capability *now* — not change history. + +- [ ] **Step 1: Write `architecture/config-model.md`** + + Sections to cover (synthesize from CLAUDE.md "Frozen configs…", "FastAPIConfig…", + "from_dict vs from_object", "__post_init__ cascade" bullets + `docs/introduction/configuration.md`): + - `BaseConfig` — frozen, `kw_only` dataclass; framework configs compose + instrument configs via multiple inheritance. + - `from_dict` vs `from_object` — the `None`-handling asymmetry (explicit-None + override vs attribute filtering), with the one-line example. + - `UnsetType` / `UNSET` sentinel (`lite_bootstrap/types.py`) and the + `FastAPIConfig.application` construction in `__post_init__` (frozen bypass via + `object.__setattr__`). + - The `__post_init__` cascade invariant (every config `__post_init__` calls + `super().__post_init__()`; `BaseConfig` is the no-op terminator; the + `super(FastAPIConfig, self)` form under `slots=True`). + +- [ ] **Step 2: Write `architecture/instruments.md`** + + Sections (synthesize from CLAUDE.md "Optional dependencies", "Frozen configs/ + non-frozen instruments", "Logging↔Sentry", "OTel↔Logging", "OpenTelemetryInstrument + single-instance", "Module layout"): + - `BaseInstrument[ConfigT]` — generic, non-frozen dataclass with slots; lifecycle + via `bootstrap()` / `teardown()`; skip check via `is_configured()`. + - The instrument catalog (logging, opentelemetry, sentry, prometheus/metrics, + pyroscope, cors, swagger, health) and `logging_factory.py` split-out. + - Optional-dependency guard (`import_checker.is_X_installed`, + `importlib.util.find_spec`); why instruments lose `frozen=True`. + - Cross-instrument integrations: logging↔Sentry (`before_send` chaining, + `skip_sentry`), OTel↔logging (span/trace-id injection). + - OpenTelemetry single-instance-per-process constraint (set-once tracer provider; + teardown flushes but cannot reset the global). + +- [ ] **Step 3: Write `architecture/bootstrappers.md`** + + Sections (synthesize from CLAUDE.md "Core pattern", "Instrument skip ordering", + "Instrument registry", "Idempotent teardown", "_lite_bootstrap_* prefix" + + `docs/integrations/*.md`): + - `BaseBootstrapper` (abc) + the five bootstrappers (FastAPI, Litestar, + FastStream, FastMcp, Free). + - Skip ordering: `is_configured` → `check_dependencies` → instantiate; + `skipped_instruments`; `InstrumentDependencyMissingWarning`. + - Instrument registry: `bootstrap()` in order, `teardown()` in reverse; + idempotent teardown (`is_bootstrapped` guard, `try/finally` state reset). + - `build_summary()` one-line `logging` summary (stdlib logging, `caplog`-friendly). + - `_lite_bootstrap_*` app-tagging sentinel convention (e.g. + `_lite_bootstrap_lifespan_attached`). + +- [ ] **Step 4: Verify the three files exist and are non-empty** + + Run: `wc -l architecture/*.md` + Expected: three files, each with substantive line counts (not empty). + +- [ ] **Step 5: Commit** + + ```bash + git add architecture/ + git commit -m "docs(architecture): seed truth home (config-model, instruments, bootstrappers) + + Co-Authored-By: Claude Opus 4.8 (1M context) " + ``` + +--- + +### Task 3: Migrate the four clean design+plan pairs into bundles + +**Files (per bundle: `git mv` design + plan, then add/adjust frontmatter):** + +- [ ] **Step 1: `git mv` the four pairs into bundle folders** + + ```bash + cd /Users/kevinsmith/src/pypi/lite-bootstrap + + # instrument-skip-rework (2026-06-01.01) + mkdir -p planning/changes/archive/2026-06-01.01-instrument-skip-rework + git mv planning/specs/2026-06-01-instrument-skip-rework-design.md planning/changes/archive/2026-06-01.01-instrument-skip-rework/design.md + git mv planning/plans/2026-06-01-instrument-skip-rework.md planning/changes/archive/2026-06-01.01-instrument-skip-rework/plan.md + + # fastmcp-bootstrapper (2026-06-01.02) + mkdir -p planning/changes/archive/2026-06-01.02-fastmcp-bootstrapper + git mv planning/specs/2026-06-01-fastmcp-bootstrapper-design.md planning/changes/archive/2026-06-01.02-fastmcp-bootstrapper/design.md + git mv planning/plans/2026-06-01-fastmcp-bootstrapper.md planning/changes/archive/2026-06-01.02-fastmcp-bootstrapper/plan.md + + # stdlib-logging-and-build-summary (2026-06-02.01) + mkdir -p planning/changes/archive/2026-06-02.01-stdlib-logging-and-build-summary + git mv planning/specs/2026-06-02-stdlib-logging-and-build-summary-design.md planning/changes/archive/2026-06-02.01-stdlib-logging-and-build-summary/design.md + git mv planning/plans/2026-06-02-stdlib-logging-and-build-summary.md planning/changes/archive/2026-06-02.01-stdlib-logging-and-build-summary/plan.md + + # mkdocs-github-pages (2026-06-09.01) + mkdir -p planning/changes/archive/2026-06-09.01-mkdocs-github-pages + git mv planning/specs/2026-06-09-mkdocs-github-actions-design.md planning/changes/archive/2026-06-09.01-mkdocs-github-pages/design.md + git mv planning/plans/2026-06-09-mkdocs-github-actions-plan.md planning/changes/archive/2026-06-09.01-mkdocs-github-pages/plan.md + ``` + +- [ ] **Step 2: Prepend frontmatter to each `design.md`** + + These docs currently open with a prose `# Title` + `**Date:**`/`**Status:**` + lines. Prepend a YAML frontmatter block at the very top of each `design.md` + (leave the existing prose body intact below it): + + `…/2026-06-01.01-instrument-skip-rework/design.md`: + ```yaml + --- + status: shipped + date: 2026-06-01 + slug: instrument-skip-rework + supersedes: null + superseded_by: stdlib-logging-and-build-summary + pr: null + outcome: shipped (partially superseded; see stdlib-logging-and-build-summary) + --- + ``` + + `…/2026-06-01.02-fastmcp-bootstrapper/design.md`: + ```yaml + --- + status: shipped + date: 2026-06-01 + slug: fastmcp-bootstrapper + supersedes: null + superseded_by: null + pr: null + outcome: shipped + --- + ``` + + `…/2026-06-02.01-stdlib-logging-and-build-summary/design.md`: + ```yaml + --- + status: shipped + date: 2026-06-02 + slug: stdlib-logging-and-build-summary + supersedes: instrument-skip-rework + superseded_by: null + pr: "107" + outcome: "merged as #107" + --- + ``` + + `…/2026-06-09.01-mkdocs-github-pages/design.md`: + ```yaml + --- + status: shipped + date: 2026-06-09 + slug: mkdocs-github-pages + supersedes: null + superseded_by: null + pr: null + outcome: shipped in the docs+CI modern-di mirror arc (#112–#115) + --- + ``` + +- [ ] **Step 3: Prepend frontmatter to each `plan.md`** + + `…/2026-06-01.01-instrument-skip-rework/plan.md`: + ```yaml + --- + status: shipped + date: 2026-06-01 + slug: instrument-skip-rework + spec: instrument-skip-rework + pr: null + --- + ``` + + `…/2026-06-01.02-fastmcp-bootstrapper/plan.md`: + ```yaml + --- + status: shipped + date: 2026-06-01 + slug: fastmcp-bootstrapper + spec: fastmcp-bootstrapper + pr: null + --- + ``` + + `…/2026-06-02.01-stdlib-logging-and-build-summary/plan.md`: + ```yaml + --- + status: shipped + date: 2026-06-02 + slug: stdlib-logging-and-build-summary + spec: stdlib-logging-and-build-summary + pr: "107" + --- + ``` + + `…/2026-06-09.01-mkdocs-github-pages/plan.md`: + ```yaml + --- + status: shipped + date: 2026-06-09 + slug: mkdocs-github-pages + spec: mkdocs-github-pages + pr: null + --- + ``` + +- [ ] **Step 4: Fix any internal cross-links broken by the rename** + + Run: `grep -rn "instrument-skip-rework\|2026-06-01-fastmcp\|2026-06-02-stdlib\|2026-06-09-mkdocs" planning/changes/archive/` + For each hit that points at an old `planning/specs|plans/...` path or a sibling + doc's old filename, update it to the new bundle path + (`./design.md`, `./plan.md`, or `..//design.md`). The + `stdlib-logging` design references `instrument-skip-rework`'s old plan path — + repoint it to `../2026-06-01.01-instrument-skip-rework/plan.md`. + +- [ ] **Step 5: Verify frontmatter parses** + + Run: + ```bash + for f in planning/changes/archive/*/design.md planning/changes/archive/*/plan.md; do + python3 -c "import sys,yaml; t=open('$f').read(); assert t.startswith('---'); yaml.safe_load(t.split('---')[1]); print('OK $f')" + done + ``` + Expected: `OK …` for all eight files (uses `uv run python` if PyYAML is not on + the system interpreter: `uv run python -c …`). + +- [ ] **Step 6: Commit** + + ```bash + git add planning/changes/archive planning/specs planning/plans + git commit -m "docs(planning): migrate clean design+plan pairs into change bundles + + Co-Authored-By: Claude Opus 4.8 (1M context) " + ``` + +--- + +### Task 4: Migrate the three audit arcs + audits/ + retros/ + +**Files:** three per-arc bundles (sequencing → `design.md`, PR plans grouped as +`plan-pr*.md`), two audits → `audits/`, three retros → `retros/`. + +- [ ] **Step 1: `git mv` the audit arc bundles** + + ```bash + cd /Users/kevinsmith/src/pypi/lite-bootstrap + + # Arc 1: audit-implementation (2026-05-31.01) — sequencing → design.md, pr1..7 grouped + mkdir -p planning/changes/archive/2026-05-31.01-audit-implementation + git mv planning/specs/2026-05-31-audit-implementation-sequencing.md planning/changes/archive/2026-05-31.01-audit-implementation/design.md + git mv planning/plans/2026-05-31-pr1-crit1-redoc-root-path.md planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr1-crit1-redoc-root-path.md + git mv planning/plans/2026-05-31-pr2-crit2-otel-shutdown.md planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr2-crit2-otel-shutdown.md + git mv planning/plans/2026-05-31-pr3-crit3-idempotent-teardown.md planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr3-crit3-idempotent-teardown.md + git mv planning/plans/2026-06-01-pr4-des4-des5-small-cleanups.md planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr4-des4-des5-small-cleanups.md + git mv planning/plans/2026-06-01-pr5-des3-config-method-semantics.md planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr5-des3-config-method-semantics.md + git mv planning/plans/2026-06-01-pr6-des2-otel-fields-mixin.md planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr6-des2-otel-fields-mixin.md + git mv planning/plans/2026-06-01-pr7-des1-generic-instruments.md planning/changes/archive/2026-05-31.01-audit-implementation/plan-pr7-des1-generic-instruments.md + + # Arc 2: deferred-refactors (2026-06-01.03) — sequencing → design.md, pr8..16 grouped + mkdir -p planning/changes/archive/2026-06-01.03-deferred-refactors + git mv planning/specs/2026-06-01-deferred-refactors-sequencing.md planning/changes/archive/2026-06-01.03-deferred-refactors/design.md + git mv planning/plans/2026-06-01-pr8-low-1-2-sentry-micro.md planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr8-low-1-2-sentry-micro.md + git mv planning/plans/2026-06-01-pr9-otel-touch-ups.md planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr9-otel-touch-ups.md + git mv planning/plans/2026-06-01-pr10-test-gap-fill.md planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr10-test-gap-fill.md + git mv planning/plans/2026-06-01-pr11-logging-cleanup.md planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr11-logging-cleanup.md + git mv planning/plans/2026-06-01-pr12-base-layer-cleanup.md planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr12-base-layer-cleanup.md + git mv planning/plans/2026-06-01-pr13-frozen-setattr.md planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr13-frozen-setattr.md + git mv planning/plans/2026-06-01-pr14-faststream-timeout.md planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr14-faststream-timeout.md + git mv planning/plans/2026-06-01-pr15-naming-pass.md planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr15-naming-pass.md + git mv planning/plans/2026-06-01-pr16-post-retro-hygiene.md planning/changes/archive/2026-06-01.03-deferred-refactors/plan-pr16-post-retro-hygiene.md + + # Arc 3: bug-audit-v2 (2026-06-05.01) — sequencing → design.md, pr1..3 grouped + mkdir -p planning/changes/archive/2026-06-05.01-bug-audit-v2 + git mv planning/specs/2026-06-05-bug-audit-v2-sequencing.md planning/changes/archive/2026-06-05.01-bug-audit-v2/design.md + git mv planning/plans/2026-06-05-pr1-lifecycle.md planning/changes/archive/2026-06-05.01-bug-audit-v2/plan-pr1-lifecycle.md + git mv planning/plans/2026-06-05-pr2-config-security.md planning/changes/archive/2026-06-05.01-bug-audit-v2/plan-pr2-config-security.md + git mv planning/plans/2026-06-05-pr3-hygiene-ci.md planning/changes/archive/2026-06-05.01-bug-audit-v2/plan-pr3-hygiene-ci.md + ``` + +- [ ] **Step 2: `git mv` audits → `audits/` and retros → `retros/`** + + ```bash + git mv planning/specs/2026-05-31-bug-refactor-audit.md planning/audits/2026-05-31-bug-refactor-audit.md + git mv planning/specs/2026-06-05-bug-audit-v2.md planning/audits/2026-06-05-bug-audit-v2.md + + git mv planning/specs/2026-06-01-audit-implementation-retro.md planning/retros/2026-06-01-audit-implementation-retro.md + git mv planning/specs/2026-06-05-bug-audit-v2-retro.md planning/retros/2026-06-05-bug-audit-v2-retro.md + git mv planning/specs/2026-06-09-docs-and-ci-modern-di-mirror-retro.md planning/retros/2026-06-09-docs-and-ci-modern-di-mirror-retro.md + ``` + +- [ ] **Step 3: Prepend frontmatter to the three arc `design.md` files** + + (Grouped `plan-pr*.md` files keep their original prose headers — deliberate, + per spec Non-goals. Audits and retros keep their original prose headers too.) + + `…/2026-05-31.01-audit-implementation/design.md`: + ```yaml + --- + status: shipped + date: 2026-05-31 + slug: audit-implementation + supersedes: null + superseded_by: null + pr: null + outcome: "shipped as #89–#95" + --- + ``` + + `…/2026-06-01.03-deferred-refactors/design.md`: + ```yaml + --- + status: shipped + date: 2026-06-01 + slug: deferred-refactors + supersedes: null + superseded_by: null + pr: null + outcome: "shipped as #96–#103" + --- + ``` + + `…/2026-06-05.01-bug-audit-v2/design.md`: + ```yaml + --- + status: shipped + date: 2026-06-05 + slug: bug-audit-v2 + supersedes: null + superseded_by: null + pr: null + outcome: "shipped as #108–#110" + --- + ``` + +- [ ] **Step 4: Repoint cross-links in the moved arc/audit/retro docs** + + The sequencing `design.md` files and retros link to their parent audit and + sibling plans by old paths. Update those links: + + Run: `grep -rn "planning/specs\|planning/plans\|2026-05-31-bug-refactor-audit\|-sequencing.md\|../plans/" planning/changes/archive planning/audits planning/retros` + + For each hit, repoint: + - parent-audit links → `../../audits/2026-05-31-bug-refactor-audit.md` (or + `2026-06-05-bug-audit-v2.md`) from within a bundle. + - sibling-plan links → the new `plan-pr*.md` name in the same bundle folder. + - sequencing/retro cross-references → the new bundle `design.md` path or + `retros/` path. + +- [ ] **Step 5: Verify arc frontmatter parses + no plans left behind** + + Run: + ```bash + for f in planning/changes/archive/2026-05-31.01-audit-implementation/design.md \ + planning/changes/archive/2026-06-01.03-deferred-refactors/design.md \ + planning/changes/archive/2026-06-05.01-bug-audit-v2/design.md; do + python3 -c "import yaml; t=open('$f').read(); assert t.startswith('---'); yaml.safe_load(t.split('---')[1]); print('OK $f')" + done + ls planning/specs planning/plans + ``` + Expected: three `OK` lines; `planning/specs` and `planning/plans` now empty + (or "No such file" if git removed them — both acceptable). + +- [ ] **Step 6: Commit** + + ```bash + git add planning/ + git commit -m "docs(planning): migrate audit arcs into per-arc bundles; sort audits/retros + + Co-Authored-By: Claude Opus 4.8 (1M context) " + ``` + +--- + +### Task 5: Remove the old directories + lightweight template + +**Files:** +- Delete: `planning/templates/lightweight-plan-template.md` +- Remove now-empty `planning/specs/`, `planning/plans/`, `planning/templates/` + +- [ ] **Step 1: Remove the lightweight template and empty dirs** + + ```bash + git rm planning/templates/lightweight-plan-template.md + # specs/ and plans/ are emptied by git after the moves; remove any stray files + rmdir planning/specs planning/plans planning/templates 2>/dev/null || true + ``` + +- [ ] **Step 2: Verify the old layout is gone** + + Run: `ls planning/` + Expected: `README.md` (next task) is absent yet; present dirs are + `_templates audits changes deferred.md releases retros`. No `specs`, + `plans`, or `templates`. + +- [ ] **Step 3: Commit** + + ```bash + git add -A planning/ + git commit -m "docs(planning): drop superseded lightweight-plan template and empty dirs + + Co-Authored-By: Claude Opus 4.8 (1M context) " + ``` + +--- + +### Task 6: Author `planning/README.md` (Conventions byte-identical + fresh Index) + +**Files:** +- Create: `planning/README.md` + +- [ ] **Step 1: Build the README from three parts** + + Assemble `planning/README.md` as: + + 1. **Intro** (repo-specific — adapt the faststream-outbox intro, swapping the + repo name): + ```markdown + # Planning + + Specs, plans, and change history for `lite-bootstrap`. The living truth + about *what the system does now* lives in [`architecture/`](../architecture/) + at the repo root; this directory records *how it got there*. + ``` + + 2. **Conventions** — copied **byte-identical** from + `/Users/kevinsmith/src/pypi/faststream-outbox/planning/README.md`: the block + from the `## Conventions` header through the end of the `### Frontmatter` + subsection (i.e. everything before `## Index`). Do not edit a word of it. + + 3. **Index + Other** — authored fresh (Step 2). + + Extraction helper for the Conventions block: + ```bash + awk '/^## Conventions/{f=1} /^## Index/{f=0} f' \ + /Users/kevinsmith/src/pypi/faststream-outbox/planning/README.md + ``` + +- [ ] **Step 2: Author the fresh `## Index` + `## Other` sections** + + Append after the Conventions block: + + ```markdown + ## Index + + ### Active + + - **[portable-planning-convention](changes/active/2026-06-13.01-portable-planning-convention/design.md)** + (2026-06-13) — Adopt the portable two-axis convention: `architecture/` truth + home + `changes/` bundles, per-arc bundling of the audit arcs, fresh Index. + *This change.* + + ### Archived (shipped) + + - **[mkdocs-github-pages](changes/archive/2026-06-09.01-mkdocs-github-pages/design.md)** + (#112–#115, 2026-06-09) — Docs hosting moved from Read the Docs to GitHub + Actions + Pages. + - **[bug-audit-v2](changes/archive/2026-06-05.01-bug-audit-v2/design.md)** + (#108–#110, 2026-06-05) — 26 findings (UX · logic · security · tests) shipped + across three themed PRs. + - **[deferred-refactors](changes/archive/2026-06-01.03-deferred-refactors/design.md)** + (#96–#103, 2026-06-01) — The 20 deferred items from the 2026-05-31 audit + (REF/TEST/LOW) across eight PRs. + - **[fastmcp-bootstrapper](changes/archive/2026-06-01.02-fastmcp-bootstrapper/design.md)** + (2026-06-01) — New `FastMcpBootstrapper` mirroring microbootstrap's fastmcp + support. + - **[instrument-skip-rework](changes/archive/2026-06-01.01-instrument-skip-rework/design.md)** + (2026-06-01) — Replace `InstrumentNotReadyWarning` with a pre-instantiation + config check + summary log. *Partially superseded by + [stdlib-logging-and-build-summary](changes/archive/2026-06-02.01-stdlib-logging-and-build-summary/design.md).* + - **[stdlib-logging-and-build-summary](changes/archive/2026-06-02.01-stdlib-logging-and-build-summary/design.md)** + (#107, 2026-06-02) — Stdlib `logging` in `bootstrappers/base.py` + public + `build_summary()`. + - **[audit-implementation](changes/archive/2026-05-31.01-audit-implementation/design.md)** + (#89–#95, 2026-05-31) — Criticals (CRIT-1..3) + design issues (DES-1..5) + + paired tests across seven sequenced PRs. + + ## Other + + - **[`architecture/`](../architecture/)** at the repo root — the living + capability truth (config model, instruments, bootstrappers). The promotion + target on every ship. + - **[audits/](audits/)** — findings reports (2026-05-31 bug+refactor audit, + 2026-06-05 bug audit v2). + - **[retros/](retros/)** — what we learned after a body of work. + - **[deferred.md](deferred.md)** — the long-tail register of real-but- + unscheduled items with revisit triggers. + ``` + +- [ ] **Step 3: Verify the Conventions block is byte-identical** + + Run: + ```bash + diff <(awk '/^## Conventions/{f=1} /^## Index/{f=0} f' /Users/kevinsmith/src/pypi/faststream-outbox/planning/README.md) \ + <(awk '/^## Conventions/{f=1} /^## Index/{f=0} f' planning/README.md) \ + && echo "CONVENTIONS IDENTICAL" + ``` + Expected: `CONVENTIONS IDENTICAL` (no diff). + +- [ ] **Step 4: Verify Index links resolve** + + Run: + ```bash + grep -oE '\]\(([^)]+\.md)\)' planning/README.md | sed -E 's/\]\(|\)//g' | while read p; do + [ -f "planning/$p" ] || [ -f "$p" ] || echo "BROKEN: $p" + done; echo "done" + ``` + Expected: only `done` (no `BROKEN:` lines). (`../architecture/...` paths + resolve from `planning/`.) + +- [ ] **Step 5: Commit** + + ```bash + git add planning/README.md + git commit -m "docs(planning): add README — portable Conventions + fresh Index + + Co-Authored-By: Claude Opus 4.8 (1M context) " + ``` + +--- + +### Task 7: Update `CLAUDE.md` (`## Workflow`) and add `just docs-build` + +**Files:** +- Modify: `CLAUDE.md` (`## Planning artifacts` → `## Workflow`) +- Modify: `justfile` (add `docs-build`) + +- [ ] **Step 1: Replace the `## Planning artifacts` section in `CLAUDE.md`** + + Delete the existing `## Planning artifacts` section (the `planning/specs/` vs + `planning/plans/` description and the "superpowers default" note) and replace + it with a `## Workflow` section adapted from `faststream-outbox`. Use this text + (adjusting the truth-home reference to this repo's `architecture/`): + + ```markdown + ## Workflow + + Per-feature: brainstorming → spec in `planning/changes/active/YYYY-MM-DD.NN-/design.md` → writing-plans → plan in `planning/changes/active/YYYY-MM-DD.NN-/plan.md` → executing-plans / subagent-driven-development → requesting-code-review → finishing-a-development-branch. Each change is a folder bundle; `` is a kebab-case description, not a story ID; `.NN` is a zero-padded intra-day counter that breaks same-date ties so the timeline sorts stably. On merge, the bundle moves to `planning/changes/archive/` with `status: shipped`, `pr:`, and `outcome:` filled, **and the change promotes its conclusions into the affected `architecture/.md`** — that hand-edit is what keeps `architecture/` true. See [`planning/README.md`](planning/README.md) for the conventions + index and [`planning/_templates/`](planning/_templates/) for copy-and-fill starting points. + + **Spec** (`design.md`) captures the *thinking* — why, what the design is, trade-offs, scope. Written before code; rarely revised after merge. **Plan** (`plan.md`) captures the *sequencing* — the ordered checklist an executor walks; references the spec for the "why". **`architecture/`** captures the *invariants* of shipped systems — the living truth, promoted from a change on merge. A plan paragraph that would still read correctly with all task numbers and checkboxes removed is design content and belongs in the spec. + + **Three lanes.** Scale the artifact to the change. **Full** — a `design.md` + `plan.md` bundle — for real design judgment, a new file/module, a public-API change, cross-cutting/multi-file work, or non-trivial test design. **Lightweight** — a single `change.md` — for small-but-real changes (≲30 LOC net, ≤2 files, no new file, no public-API change, a single straightforward test). **Tiny** — no bundle, just a conventional commit — for a typo, dep bump, linter/formatter/CI tweak, a mechanical rename, or a single-line config change. Heavier lane wins on ambiguity; a `change.md` that outgrows its lane splits into `design.md` + `plan.md`. + + Design docs and implementation plans live under `planning/` (not under `docs/`, so they're excluded from the mkdocs site automatically). When superpowers skills default to `docs/superpowers/specs/` or `docs/superpowers/plans/`, use the change bundle under `planning/changes/active/` here instead. + ``` + +- [ ] **Step 2: Add a `docs-build` target to `justfile`** + + After the `docs-deploy` target, add: + ```make + # Strict local docs build (no deploy). Mirrors CI's link/strict checks. + docs-build: + uvx --with-requirements docs/requirements.txt mkdocs build --strict + ``` + +- [ ] **Step 3: Verify no stale planning references remain in repo prose** + + Run: + ```bash + grep -rn "planning/specs\|planning/plans\|lightweight-plan-template\|planning/templates" \ + --include="*.md" --include="justfile" . \ + | grep -v "planning/changes/" || echo "NO STALE REFERENCES" + ``` + Expected: `NO STALE REFERENCES` (hits inside migrated archive prose that + legitimately describe history are acceptable — judge each; the goal is no + stale *pointers* in `CLAUDE.md`, `justfile`, `README.md`, or `docs/`). + +- [ ] **Step 4: Commit** + + ```bash + git add CLAUDE.md justfile + git commit -m "docs: rewrite CLAUDE.md Planning section as Workflow; add just docs-build + + Co-Authored-By: Claude Opus 4.8 (1M context) " + ``` + +--- + +### Task 8: Full verification sweep + +**Files:** none (verification only). + +- [ ] **Step 1: Lint (markdown/format gate; no code changed)** + + Run: `just lint-ci` + Expected: passes (eof-fixer --check, ruff format --check, ruff check --no-fix, + ty check all green). If eof-fixer flags a new markdown file missing a trailing + newline, run `just lint` to autofix, then re-commit. + +- [ ] **Step 2: Strict docs build** + + Run: `just docs-build` + Expected: `mkdocs build --strict` succeeds with no warnings. `architecture/` + and `planning/` are outside `docs_dir: docs`, so they are not part of the + build; this confirms the untouched `docs/` tree still builds clean. + +- [ ] **Step 3: Stale-reference + tree sweeps** + + Run: + ```bash + echo "--- stale pointers ---" + grep -rn "planning/specs\|planning/plans\|lightweight-plan-template" --include="*.md" --include="justfile" . | grep -vE "planning/changes/archive/" || echo "clean" + echo "--- final planning tree ---" + ls -R planning/ architecture/ + ``` + Expected: `clean` for pointers; the tree matches design §2 (no `specs/`, + `plans/`, `templates/`; `changes/{active,archive}`, `audits`, `retros`, + `releases`, `_templates`, `deferred.md`, `README.md` present; + `architecture/` has three `.md` files). + +- [ ] **Step 4: Frontmatter parse sweep across all bundles** + + Run: + ```bash + for f in $(find planning/changes -name design.md -o -name plan.md); do + python3 -c "import yaml; t=open('$f').read(); assert t.startswith('---'),'$f'; yaml.safe_load(t.split('---')[1])" && echo "OK $f" || echo "BAD $f" + done + ``` + Expected: `OK` for every `design.md` / `plan.md` (the active convention bundle + + all archived bundles). No `BAD` lines. (Grouped `plan-pr*.md` files are + intentionally excluded — they keep prose headers.) + +- [ ] **Step 5: Commit (if any lint autofix touched files)** + + ```bash + git add -A + git commit -m "docs(planning): lint/format fixups from convention migration + + Co-Authored-By: Claude Opus 4.8 (1M context) " || echo "nothing to commit" + ``` + +--- + +### On merge (finishing step — executed in-PR on #120) + +This convention bundle self-migrates from `active/` to `archive/` (it defines +the convention, so **no `architecture/` promotion applies**). Done within the +shipping PR (#120) rather than as a post-merge step, so merged `main` lands in +its final archived state: + +```bash +git mv planning/changes/active/2026-06-13.01-portable-planning-convention \ + planning/changes/archive/2026-06-13.01-portable-planning-convention +``` + +`status: shipped`, `pr: "120"`, and `outcome:` are set in this bundle's +`design.md` frontmatter, and its line moved from **Active** to **Archived** in +`planning/README.md`'s Index. diff --git a/planning/deferred.md b/planning/deferred.md new file mode 100644 index 0000000..f7f1770 --- /dev/null +++ b/planning/deferred.md @@ -0,0 +1,11 @@ +# Deferred Work + +Items raised in reviews or audits that are real but not actionable now. +Each is parked here with the reason it's deferred and the concrete trigger +that should bring it back. This is the long-tail register — not a backlog +of planned work. When an item is picked up it graduates to a spec/plan +bundle in [`changes/active/`](changes/active/); see [CLAUDE.md](../CLAUDE.md#workflow). + +## Open + +_None._ diff --git a/planning/releases/1.1.0.md b/planning/releases/1.1.0.md index 9f3f846..3d6fb0e 100644 --- a/planning/releases/1.1.0.md +++ b/planning/releases/1.1.0.md @@ -2,7 +2,7 @@ **1.1.0 is a minor release. No intentional public-API breakage.** The two behavior changes that could affect existing code are fixes to genuine bugs and are called out in *Behavior changes* below. -This release closes a 26-finding bug-audit cycle (audits + retro live under `planning/specs/2026-06-05-bug-audit-v2*.md`). The changes split across four shipped PRs: +This release closes a 26-finding bug-audit cycle (the audit lives in `planning/audits/`, the implementation arc in `planning/changes/archive/2026-06-05.01-bug-audit-v2/`, and the retro in `planning/retros/`). The changes split across four shipped PRs: - **#108** — Lifecycle & teardown correctness (10 findings) - **#109** — Config UX & security validation (6 findings) @@ -67,13 +67,13 @@ Aside from the two *Behavior changes* called out above, every public API behaves The 26-fix list with full file:line references and rationale is in: -- `planning/specs/2026-06-05-bug-audit-v2.md` — the audit -- `planning/specs/2026-06-05-bug-audit-v2-sequencing.md` — the 3-PR breakdown -- `planning/specs/2026-06-05-bug-audit-v2-retro.md` — what the cycle taught us +- `planning/audits/2026-06-05-bug-audit-v2.md` — the audit +- `planning/changes/archive/2026-06-05.01-bug-audit-v2/design.md` — the 3-PR breakdown +- `planning/retros/2026-06-05-bug-audit-v2-retro.md` — what the cycle taught us ## References -- Audit: [`planning/specs/2026-06-05-bug-audit-v2.md`](../specs/2026-06-05-bug-audit-v2.md) -- Sequencing: [`planning/specs/2026-06-05-bug-audit-v2-sequencing.md`](../specs/2026-06-05-bug-audit-v2-sequencing.md) -- Retro: [`planning/specs/2026-06-05-bug-audit-v2-retro.md`](../specs/2026-06-05-bug-audit-v2-retro.md) +- Audit: [`planning/audits/2026-06-05-bug-audit-v2.md`](../audits/2026-06-05-bug-audit-v2.md) +- Sequencing: [`planning/changes/archive/2026-06-05.01-bug-audit-v2/design.md`](../changes/archive/2026-06-05.01-bug-audit-v2/design.md) +- Retro: [`planning/retros/2026-06-05-bug-audit-v2-retro.md`](../retros/2026-06-05-bug-audit-v2-retro.md) - PRs: [#108](https://github.com/modern-python/lite-bootstrap/pull/108), [#109](https://github.com/modern-python/lite-bootstrap/pull/109), [#110](https://github.com/modern-python/lite-bootstrap/pull/110), [#111](https://github.com/modern-python/lite-bootstrap/pull/111) diff --git a/planning/specs/2026-06-01-audit-implementation-retro.md b/planning/retros/2026-06-01-audit-implementation-retro.md similarity index 98% rename from planning/specs/2026-06-01-audit-implementation-retro.md rename to planning/retros/2026-06-01-audit-implementation-retro.md index 7f8b4e6..cda528e 100644 --- a/planning/specs/2026-06-01-audit-implementation-retro.md +++ b/planning/retros/2026-06-01-audit-implementation-retro.md @@ -3,9 +3,9 @@ **Date:** 2026-06-01 **Scope:** PRs #89–103 across two sequenced waves **Parent docs:** -- Audit: [2026-05-31-bug-refactor-audit.md](2026-05-31-bug-refactor-audit.md) -- Sequencing 1: [2026-05-31-audit-implementation-sequencing.md](2026-05-31-audit-implementation-sequencing.md) -- Sequencing 2: [2026-06-01-deferred-refactors-sequencing.md](2026-06-01-deferred-refactors-sequencing.md) +- Audit: [2026-05-31-bug-refactor-audit.md](../audits/2026-05-31-bug-refactor-audit.md) +- Sequencing 1: [2026-05-31-audit-implementation-sequencing.md](../changes/archive/2026-05-31.01-audit-implementation/design.md) +- Sequencing 2: [2026-06-01-deferred-refactors-sequencing.md](../changes/archive/2026-06-01.03-deferred-refactors/design.md) --- diff --git a/planning/specs/2026-06-05-bug-audit-v2-retro.md b/planning/retros/2026-06-05-bug-audit-v2-retro.md similarity index 96% rename from planning/specs/2026-06-05-bug-audit-v2-retro.md rename to planning/retros/2026-06-05-bug-audit-v2-retro.md index 1a9fb1f..64b1b3a 100644 --- a/planning/specs/2026-06-05-bug-audit-v2-retro.md +++ b/planning/retros/2026-06-05-bug-audit-v2-retro.md @@ -2,7 +2,7 @@ **Date:** 2026-06-05 **Cycle:** Audit → 3-PR sequencing → execute -**Inputs:** [bug-audit-v2.md](2026-06-05-bug-audit-v2.md), [sequencing](2026-06-05-bug-audit-v2-sequencing.md), [PR1 plan](../plans/2026-06-05-pr1-lifecycle.md), [PR2 plan](../plans/2026-06-05-pr2-config-security.md), [PR3 plan](../plans/2026-06-05-pr3-hygiene-ci.md) +**Inputs:** [bug-audit-v2.md](../audits/2026-06-05-bug-audit-v2.md), [sequencing](../changes/archive/2026-06-05.01-bug-audit-v2/design.md), [PR1 plan](../changes/archive/2026-06-05.01-bug-audit-v2/plan-pr1-lifecycle.md), [PR2 plan](../changes/archive/2026-06-05.01-bug-audit-v2/plan-pr2-config-security.md), [PR3 plan](../changes/archive/2026-06-05.01-bug-audit-v2/plan-pr3-hygiene-ci.md) **Outcome:** 26/26 audit findings shipped to main across three PRs (#108, #109, #110). ## Numbers diff --git a/planning/specs/2026-06-09-docs-and-ci-modern-di-mirror-retro.md b/planning/retros/2026-06-09-docs-and-ci-modern-di-mirror-retro.md similarity index 100% rename from planning/specs/2026-06-09-docs-and-ci-modern-di-mirror-retro.md rename to planning/retros/2026-06-09-docs-and-ci-modern-di-mirror-retro.md diff --git a/planning/templates/lightweight-plan-template.md b/planning/templates/lightweight-plan-template.md deleted file mode 100644 index 2a21147..0000000 --- a/planning/templates/lightweight-plan-template.md +++ /dev/null @@ -1,183 +0,0 @@ -# Lightweight Plan Template - -For sub-30-LOC PRs where the diff IS effectively the plan, use this template -instead of the full multi-task plan structure. The full structure is -appropriate when there's real design judgment, cross-cutting changes, or -multiple coordinated edits; for trivial fixes the overhead is inverted (PR8 was -a 222-line plan for a 2-line edit). - -Use the full template when ANY of these apply: -- More than 2 production files modified -- Cross-cutting / cascade-style change (frozen-inheritance, generic typing, file split) -- New module/file introduced -- Public API surface changes (renames, additions, removals) -- Behavior change with non-trivial test design (regression test that hand-crafts mock state, lifecycle replay, etc.) - -Use the lightweight template when ALL of these apply: -- ≤2 production files modified -- ≤30 LOC net change -- No new files -- No public API change (or trivial rename with alias) -- The test (if any) is a single straightforward addition - ---- - -## Template - -```markdown -# PR: - -**Goal:** - -**Files:** -- `path/to/file1.py` — what changes -- `path/to/tests/test_file.py` — test added/updated (if applicable) - -**Parent spec:** `` -**Audit ref:** `` - ---- - -## Diff (or close enough) - -`path/to/file1.py`: - -```python -# Before: - - -# After: - -``` - -`tests/test_file.py`: - -```python -# Append at end of file: -def test_() -> None: - # ... -``` - ---- - -## Verification - -1. `just test -- tests/path/to/test_file.py:: -v` — failing test: - expected error: `` -2. Apply the production change. -3. `just test -- tests/path/to/test_file.py:: -v` — passes. -4. `just test` — full suite (target: ). -5. `just lint` — clean. - -## Pre-flight grep (if change touches public symbols or cross-file references) - -```bash -grep -rn "" lite_bootstrap/ tests/ --include="*.py" -``` - -Confirm the number of matches and their files before editing. After the change, -re-run the grep and verify it matches expectations (all updated, or only -intentional alias / backward-compat sites remain). - -## Commit - -```bash -git add -git commit -m "" -``` - -## PR - -Branch: `fix/-` or `refactor/-`. -Push, open via `gh pr create` with body summarizing the change. -``` - ---- - -## Pre-flight grep checklist - -Add this section to ANY plan (lightweight or full) that touches symbols -referenced across multiple files. PR13 needed this: its surgical 2-class -scope was actually a 24-class cascade because of Python's frozen-inheritance -rule. A pre-flight grep would have surfaced the cascade at plan time. - -Before drafting the diffs, run: - -```bash -# Find all references to the symbol(s) being changed -grep -rln "" lite_bootstrap/ tests/ --include="*.py" - -# Count occurrences per file for scope estimation -grep -rc "" lite_bootstrap/ tests/ --include="*.py" | grep -v ":0$" - -# For inheritance / type-system changes, check what inherits from / references -# the affected type -grep -rn "class.*(\b\b)" lite_bootstrap/ --include="*.py" -grep -rn ": \b" lite_bootstrap/ tests/ --include="*.py" -``` - -Bake the resulting file list into the plan. If the count diverges from -"a couple of files", reconsider whether the lightweight template is still -appropriate — you may have crossed into full-template territory. - -For renames specifically, also check `__init__.py` exports: - -```bash -grep -n "" lite_bootstrap/__init__.py -``` - -If the symbol is publicly exported, the rename must include a backward-compat -alias in `__init__.py` (see PR15 for the established pattern). - ---- - -## Type-checker behavior validation (when the plan claims type narrowing) - -Add this section to ANY plan that claims `ty` will narrow a type — across -asserts, casts, sentinel identity checks, etc. PR16's plan claimed both `ty` -and Pyright would narrow `str | None` → `str` after an assert; one half of -that claim was wrong because the two checkers don't behave identically. -(Pyright has since been dropped from the project — only `ty` is in scope — -but the underlying lesson stands: don't assume narrowing, verify it.) - -Before writing a plan that depends on a type-checker behavior: - -1. Write the minimal failing/working pattern in a scratch file or directly - in the target file. -2. Run `just lint` (which runs `ty check`). Observe the diagnostic state. -3. Bake the OBSERVED behavior into the plan, not the assumed behavior. - -Specific narrowing patterns worth verifying experimentally: - -- **Attribute access across asserts** (`assert self.x.y is not None`). Whether - the checker narrows depends on its purity model — some checkers assume any - intervening method call could mutate the attribute. -- **`typing.cast` with string forward references**. Behavior varies on whether - the cast is treated as a runtime contract or pure documentation. -- **TypedDict optional-key access** after a truthiness guard (`if event.get("k"):`). -- **Conditional imports** under `if X is not None:` runtime guards. - ---- - -## When the lightweight template isn't enough - -The lightweight template fails when: - -- The change cascades in non-obvious ways (Python language rules, framework conventions, build tooling) -- The test setup requires real fixture / mock state work (multiple `patch.object`, fixture composition, conditional skip logic) -- The behavior change crosses module boundaries -- The reviewer would benefit from explicit "Why this approach over alternatives" - -In those cases, fall back to the full plan structure with explicit Task 1-N -sections, locked decisions, cross-cutting concerns, and self-review checklist. -The full template's structure is overhead, but it's overhead that has bought -real correctness wins — see PRs #7, #11, #13 for cases where the detailed -plan caught issues at plan time. - -## Related conventions (PR8–15 emergent rules) - -- **No `# noqa: PLR2004`** — extract magic values to named locals. -- **Backward-compat aliases for renames** — module-level class assignment + `__init__.py` re-export if the old name was public. -- **Frozen-config bypass in `__post_init__`** — `object.__setattr__` is acceptable with a one-line comment documenting the trade-off. -- **Optional-import guard pattern** — top-level `if import_checker.is_X_installed: import X` is the established pattern. Pyright doesn't model the guard; the project enforces `ty` instead. See `pyproject.toml` `[tool.pyright]` suppressions. -- **Real-time spec corrections** — when a plan / spec deviates from reality during execution, update the doc in the same PR as the implementation, not as a future cleanup.