This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
just --list is the source of truth. Non-obvious "which to use when":
just lintauto-fixes;just lint-ciis check-only (CI) and also runs the planning validator (planning/index.py --check).just check-planningruns just that validator.just test -- -k "test_name"runs a single test;just test-branchadds branch coverage.
All commands use uv run — do not invoke tools directly (e.g., use uv run pytest, not pytest).
lite-bootstrap bootstraps Python microservices with pre-configured
observability instruments: a frozen BaseConfig hierarchy describes what the
user wants, BaseInstrument[ConfigT] subclasses each own one observability
concern, and a BaseBootstrapper per framework decides which instruments apply
and drives their lifecycle.
The authoritative, code-current account of each capability lives in
architecture/. When a change alters a capability's
behavior, update the matching architecture/<capability>.md in the same PR —
that promotion is what keeps architecture/ true.
Invariants (what must not break) — see the capability page for the full account:
- Frozen configs, non-frozen instruments. All
*Configare frozen for user-facing immutability;*Instrumentare non-frozen becauseLoggingInstrument/OpenTelemetryInstrumentcache runtime state, forcing the whole hierarchy non-frozen. →architecture/config-model.md,architecture/instruments.md __post_init__cascade. Every config__post_init__must callsuper().__post_init__()(BaseConfigis the no-op terminator);FastAPIConfiguses the explicitsuper(FastAPIConfig, self)form underslots=True. →architecture/config-model.mdfrom_dictvsfrom_objectdiffer onNone.from_dictpasses explicitNonethrough (overrides default);from_objectfiltersNone/missing (default wins). →architecture/config-model.md- Optional-dependency guard. Optional imports sit behind
if import_checker.is_X_installed:; code referencing the symbol runs only aftercheck_dependencies()returned True.tymodels this; other checkers may false-flag "possibly unbound". →architecture/instruments.md - Construction skip ordering.
is_configured(silent skip) →check_dependencies(warns on configured-but-missing) → instantiate; one INFO summary line viabuild_summary(). →architecture/bootstrappers.md - Idempotent teardown.
teardown()no-ops when not bootstrapped, runs instruments in reverse, collects per-instrument errors into oneTeardownError, resets cached state intry/finally. →architecture/bootstrappers.md - OTel is single-instance per process.
set_tracer_provideris set-once; a second instance is ignored. Construct exactly oneOpenTelemetryInstrumentper process. →architecture/instruments.md - Teardown attaches once.
_attach_teardown_onceguards against double-attach via the_lite_bootstrap_teardown_attachedmarker;_lite_bootstrap_*-prefixed attributes are the sanctioned way to tag user-supplied apps. →architecture/bootstrappers.md
Capability index (all of architecture/):
| Capability | File |
|---|---|
Config model — BaseConfig, multiple-inheritance composition, from_dict/from_object, UNSET, __post_init__ cascade |
architecture/config-model.md |
Instruments — BaseInstrument lifecycle, catalog, optional-dep guard, non-frozen rationale, cross-instrument integrations (Logging↔Sentry, OTel↔Logging, Pyroscope↔OTel), OTel single-instance |
architecture/instruments.md |
| Bootstrappers — hierarchy, skip ordering, registry + idempotent teardown, summary logging, teardown-attach seam, app-tagging sentinels | architecture/bootstrappers.md |
Recent design context, bugs, and rationale: bug-audit findings in
planning/audits/, post-work reflections in planning/retros/, and the audit
arcs recorded as change files under planning/changes/. The full extras
matrix is [project.optional-dependencies] in pyproject.toml.
Planning uses the portable two-axis convention: architecture/ (repo root) is
the living truth home and promotion target; planning/changes/ holds the
per-change files. Start at the
Quick path in
planning/README.md to choose a lane (Full / Lightweight /
Tiny), create a change file, and ship — that file is the authoritative spec, with
copy-and-fill starters in planning/_templates/. Run
just check-planning to validate changes and just index to print the listing.
Repo-local notes:
- Design docs and plans live under
planning/, not underdocs/, so the mkdocs site excludes them automatically. When superpowers skills default todocs/superpowers/specs/ordocs/superpowers/plans/, use a change file underplanning/changes/instead. summaryis finalized at ship to state the realized result, in the implementing PR alongside the code and thearchitecture/promotion — no post-merge bookkeeping, no file move.
- Line length: 120 characters (ruff enforced)
- Ruff ALL rules enabled; notable ignores: D1 (missing docstrings), S101 (assert), TCH (type-checking imports), FBT (boolean args)
- Type annotations required; checked with
ty
These are coding rules. The capability invariants they touch are detailed in
architecture/ (pointers below).
- No
# noqa: PLR2004: extract magic values to named locals. Example:expected_max_age = 600; assert config.cors_max_age == expected_max_age(notassert config.cors_max_age == 600 # noqa: PLR2004). - Backward-compat aliases for renames: when renaming a public class, add a silent module-level alias (
OldName = NewName) at the end of the file. Re-export both names from__init__.pyif the old name was publicly exported. Aliases are class assignments, not subclasses — same class object, soisinstancebehavior is preserved. - Frozen-config bypass in
__post_init__:object.__setattr__(self, "field", value)inside a frozen config's__post_init__is acceptable to set a field that requires other config values to construct; document with a one-line comment naming the trade-off (user-facing immutability vs. construction-time mutation). →architecture/config-model.md - Optional-import guard pattern: top-level conditional imports (
if import_checker.is_X_installed: import X) keep optional dependencies actually optional; code referencingXruns only aftercheck_dependencies()returned True (theis_configured → check_dependencies → instantiateflow inBaseBootstrapper.__init__). See "Type checking" below. →architecture/instruments.md from_dictvsfrom_objectdiffer onNone:from_dictoverrides the default with explicitNone;from_objectfiltersNone/missing so the default wins. Documented in both docstrings (instruments/base.py) and pinned bytests/test_config.py. Pickfrom_dictif explicit-None override is the load-bearing semantic. →architecture/config-model.md__post_init__cascade: every config-class__post_init__must callsuper().__post_init__();BaseConfig's no-op terminates the chain;FastAPIConfigneeds the explicitsuper(FastAPIConfig, self).__post_init__()form underslots=True. →architecture/config-model.md_lite_bootstrap_*prefix for sentinels on user-supplied apps: tag a user-supplied framework app with a direct_lite_bootstrap_-prefixed attribute (read viagetattr(..., default)— no SLF violation; write with# noqa: SLF001); don't squat in framework namespaces like Starlette'sapplication.state. The canonical example is the_lite_bootstrap_teardown_attacheddouble-attach marker set by_attach_teardown_once. →architecture/bootstrappers.md
The project uses ty (Astral's type checker), enforced via just lint. No other type checker is supported; the codebase patterns (conditional imports for optional dependencies, covariant bootstrap_config narrowing in framework instrument subclasses, TypedDict optional-key access guarded by .get() truthiness checks) require a checker that models them correctly. Pyright is not used.