# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.9.1] - 2026-05-22

### Added — MemoryPort wiring (#50 part 2, ADR-025)

The v0.9.0 follow-up that wires the `MemoryPort` Protocol into the `spectra analyze` call site. v0.9.0 shipped the port + `LocalFileMemoryAdapter` foundation; v0.9.1 wires them.

- **Stage-6 deposit hook (`scan_completed`).** After Stage 6 completes (both fresh runs and cache short-circuit), the pipeline appends one `MemoryEvent(kind="scan_completed")` per run. Payload carries `scan_id`, `overall_score`, `overall_grade`, `finding_counts_by_severity`, per-dimension scores, `cost_usd`, `duration_seconds`, `is_degraded`. Idempotent on `ctx.run_id` (event id = `f"scan:{run_id}"`).
- **Stage-2 read hook (prior-context paragraph).** Before MetaPrompter generates the plan, the pipeline queries `MemoryPort` for the latest 10 `scan_completed`, 20 `waiver_added`, and 50 `adr_ingested` events, renders a bounded "Prior context: …" paragraph (hard-capped at 2000 chars to stay well under any reasonable planner prompt budget), and injects it into the MetaPrompter prompt under a `<prior_context>` data-not-instructions guardrail (matching the existing `<repository_file_tree>` boundary).
- **Stage-1 ADR ingest (composition root).** After workspace prep, the composition root walks `docs/architecture/adr/`, `doc/adr/`, and `docs/adrs/` for `*.md` files, parses title (first `# H1`), status (first non-empty line in `## Status`), and date (filename or status body), and deposits one `adr_ingested` event per ADR. Idempotent on path-derived id (`adr:{sha256(adr_path)[:16]}`); re-running on the same workspace is a `INSERT OR IGNORE` no-op.
- **Two new CLI flags:** `--memory-dir DIR` (envvar `SPECTRA_MEMORY_DIR`) and `--no-memory` (envvar `SPECTRA_NO_MEMORY`). Default location: `$XDG_DATA_HOME/spectra/memory/<sha256-of-canonical-repo-url>.db` (canonical URL = lowercase scheme+host, strip trailing `.git` and `/`; bare local paths resolve to absolute). `--no-memory` skips all three hooks for CI-safe runs.
- **Port contract strengthened.** `MemoryPort.query_events` + `MemoryPort.search` docstrings now carry explicit `Raises: AgentError(SPEC-010)` blocks calling out the asymmetric read-side contract (per ADR-025 explore-agent finding). A future remote KV adapter cannot silently return an empty tuple on outage — the integrity guarantee is a port-level promise, not an adapter implementation detail.

### Architecture

- New `PipelineContext.memory_port: MemoryPort | None = None` field. Default `None` preserves prior behaviour — no memory reads, no writes, no ADR ingest.
- `_safe_build_memory_context` (use case, Layer 2) — pure async helper, catches all read failures and degrades to empty paragraph.
- `_safe_deposit_scan_completed` (use case, Layer 2) — catches all write failures, logs one WARN per run, never aborts the pipeline.
- `_provision_memory_safe` (composition root, Layer 4) — adapter factory with same "degrade to None on construction failure" pattern as `_provision_history_store_safe`.
- `_safe_ingest_adrs_at_root` (composition root, Layer 4) — ADR scan + per-event deposit with two-level failure isolation (one bad ADR doesn't fail the rest; whole ingest failure doesn't fail the scan).
- New modules: `src/spectra/use_cases/memory_payloads.py` (3 builders + TypedDict shape contracts), `src/spectra/use_cases/memory_context.py` (prior-context renderer), `src/spectra/infrastructure/memory_paths.py` (XDG resolution + canonical URL hashing), `src/spectra/infrastructure/ingest_adrs.py` (workspace ADR scanner).

### Tests

- **2573 passing (was 2502 at v0.9.0). +71 net.** New suites: `test_memory_paths` (20), `test_memory_payloads` (12), `test_memory_context` (9), `test_ingest_adrs` (15), `test_pipeline_memory_read` (5), `test_pipeline_memory_deposit` (6). Plus failure-mode coverage for write/read/lock/corrupt-schema/missing-dir scenarios.

### Design

- See `docs/superpowers/specs/2026-05-21-v0.9.1-memory-wiring-design.md` for the full architecture overview, sequence diagrams, payload schemas, and TDD build order.

## [0.9.0] - 2026-05-16

The "Q4 foundations" minor release. Two ports + their first adapters land — the architectural groundwork for per-repo memory (#50) and supply-chain-aware analysis (#58). Both ship as testable-in-isolation slices: no CLI surface, no composition-root wiring, no Stage-N pipeline integration in this release. Those land in follow-up minors as the surrounding capabilities mature. Plus the post-v0.8.2 hardening (single-source `__version__`, public-mode JSON publication) lands here.

### Added — `MemoryPort` + `LocalFileMemoryAdapter` (#50, ADR-025 part 1)
- **`MemoryEvent` entity (`src/spectra/entities/memory.py`)** — Frozen Pydantic, closed `Literal` `kind` field, UTC-aware `occurred_at` validator that rejects naive datetimes and normalizes non-UTC. Payload is a JSON-safe `dict[str, object]`. Per-event hash so events are usable in sets / as cache keys.
- **`MemoryPort` Protocol (`src/spectra/use_cases/interfaces.py`)** — `append`, `query`, `search` operations. Caller-facing contract for any backing store.
- **`LocalFileMemoryAdapter` (`src/spectra/infrastructure/local_file_memory_adapter.py`)** — SQLite + FTS5 implementation. Per ADR-025: writes degrade to one-shot WARN (the pipeline is never aborted by a memory-log failure); reads raise `AgentError(SPEC-010)` so callers can decide surface-vs-degrade. Idempotent appends via `INSERT OR IGNORE` on event id (post-Stage-6 retries don't dup). Standalone FTS5 (chosen over external-content config — the duplication cost is ~2x storage on what's expected to be ~100-event-per-quarter logs, and the insert path stays trivial). ADR-012 permission discipline: parent dir `0o700`, db file `0o600`, WAL/SHM siblings tightened (POSIX best-effort).

### Added — SBOM emitter + 3 manifest detectors (#58, ADR-026 part 1)
- **`SbomComponent` + `SbomManifest` entities (`src/spectra/entities/sbom.py`)** — Frozen Pydantic, closed `Literal` `ecosystem` field (`pypi` / `npm` / `go` for now; six more land in follow-ups).
- **`detect_all_components` aggregator (`src/spectra/use_cases/sbom_detection.py`)** — Three pure-function detectors, failure-isolated (a malformed manifest in one ecosystem doesn't fail the others). Detector coverage in this release: PyPI `pyproject.toml` (PEP 621, strips PEP 508 markers, captures `==` pins), npm `package.json` (deps + devDeps, scoped packages like `@anthropic-ai/sdk`, drops semver range chars), Go `go.mod` (block + single-line forms).
- **`CycloneDXSbomEmitter` (`src/spectra/infrastructure/cyclonedx_sbom_emitter.py`)** — CycloneDX 1.5 JSON emitter, hand-built (~80 LOC). Chose this over taking the `cyclonedx-python-lib` dep because the spec surface we use is small enough that the dep-management cost (transitive supply-chain surface, version pinning, lockfile churn) is higher than the adapter cost. Emits the load-bearing fields: `bomFormat`, `specVersion`, `serialNumber`, `metadata.tools`, `metadata.component`, every `component.type/name/version/purl`. Six other manifest formats, lockfile transitive parsing, the `--emit-sbom` CLI flag, and Stage-1 pipeline integration land in follow-up PRs.

### Changed — Single-source-of-truth versioning (#87)
- **`src/spectra/__init__.py` derives `__version__` from `importlib.metadata.version("spectra-ai")`** instead of a hardcoded string. Eliminates the two-source-of-truth bug class that bit v0.8.2: the `pyproject.toml` bump alone shipped without the matching `__init__.py` bump (caught by Greptile pre-merge). Source-tree fallback returns `"0.0.0+source"` when the package metadata is not installed (i.e., importing `spectra` directly from a checkout without `pip install -e .`). Documented limitation: if `spectra` is imported from a source checkout while a different `spectra-ai` distribution is installed in the same environment, the installed-distribution version wins — standard Python pattern (`pip`, `anthropic`, etc.). Future version bumps now need to touch one file.

### Added — Public-mode OSS leaderboard JSON (#88)
- **`*-public.json` artifacts published for the v0.7.0 OSS panel** (FastAPI, HTTPX, Aider, Simon Willison's LLM, Spectra self-scan). Generated via the existing `_redact_public_payload` (`src/spectra/infrastructure/main.py`) — preserves overall grade, per-dimension scores, finding counts, scan duration, cost, agents used, and the Ed25519 receipt; strips every individual finding, cross-cutting insight, file path, and description so the artifacts cannot be reverse-engineered into a vulnerability intel feed. The public leaderboard now links real per-repo data instead of telling readers to reproduce.

### Tests
- **2502 passing (was 2436 at v0.8.2). +66 net.** New test suites: `test_memory_event` (19 entity tests), `test_local_file_memory_adapter` (15 adapter tests covering durability, idempotency, search, permissions), `test_sbom_component` (entity validation), `test_cyclonedx_sbom_emitter` (CycloneDX 1.5 schema conformance), `test_sbom_detection` (per-detector + aggregator failure-isolation), `test_version` (regression test enforcing single-source-of-truth invariant).

## [0.8.2] - 2026-05-16

The "documentation refresh" patch. No code changes — three doc-only updates that landed against `main` since v0.8.1 are bundled here so the PyPI long-description and the README on PyPI reflect the new public OSS panel + the Q4 architecture decisions.

### Changed
- **OSS leaderboard refreshed to the v0.7.0 panel** (#85). The `docs/launch/leaderboard.md` table + the README "See It Run on Real Repos" table now cite the v0.7.0 scans (FastAPI A 92, Spectra self A 92, HTTPX B+ 85, Aider B- 79, Simon Willison's LLM B- 77) that have been on disk under `docs/launch/reports/v0.7.0/` since the v0.7.0 release. The Anthropic SDK row (B+ 86) is preserved as a v0.6.0 baseline. Eight retired artifacts (4 HTML reports + 4 raw JSON) removed from the repo. Public-facing docs no longer link directly to `*-confidential.json` artifacts — those are full-finding-detail reports per the dual-mode renderer's classification model (`Classification = "confidential" | "public"` in `src/spectra/entities/models.py`); the public leaderboard now shows summary tables only and points readers at the reproduce script for per-finding detail.

### Added — Q4 architecture decisions (already merged to main)
- **ADR-025 — `MemoryPort` + managed-memory store adapter.** Architectural foundation for the Q4 #50 capability (per-repo memory): defines the port, the local-file adapter, and the managed-memory boundary so the pipeline can recall context across runs without coupling to a specific store.
- **ADR-026 — Multi-cloud LLM gateway.** Captures the design for routing a single `LLMGateway` call to Anthropic, Bedrock, or Vertex (Q4 #14) — same Pydantic models, same telemetry, no caller-visible difference.
- **ADR-027 — Deterministic compliance mapping.** The OWASP/NIST/PCI mapping currently inferred per-finding becomes a deterministic, version-pinned table (Q4 #60) so two runs of the same code produce the same compliance verdict.
- **Q4 plan — "Spectra Learns".** New `docs/strategy/q4-plan.md` documenting the Q4 capability set + sequencing.

### Tests
- No code changes. v0.8.1 test count of 2310 passing carries forward unchanged.

## [0.8.1] - 2026-05-04

The "make it install on macOS" patch. v0.8.0 shipped `pysqlcipher3` as a runtime dependency — it has no macOS wheel and source-builds against `libsqlcipher`, so `pip install spectra-ai==0.8.0` failed for every macOS user without `brew install sqlcipher`. v0.8.1 makes it an opt-in `[encryption]` extra and adds upgrade-path detection so existing users with encrypted caches get an actionable error (not a cryptic SQLite one).

### Fixed
- **`pip install spectra-ai` now works on a clean macOS.** v0.8.0 listed `pysqlcipher3` as a runtime dependency, which has no macOS wheel and source-builds against `libsqlcipher`. Without `brew install sqlcipher` the install failed for every macOS user. Moved to a new `[encryption]` opt-in extra; the cache adapter already degrades to plain SQLite + HMAC when the import fails (one WARN per process). Operators who want at-rest cache encryption now opt in with `pip install "spectra-ai[encryption]"`. CI continues to install the extra (libsqlcipher-dev provided via apt) so the encryption test path is still covered end-to-end.
- **Upgrade-path detection: encrypted cache + plain runtime now surfaces an actionable SPEC-010** instead of `DatabaseError: file is not a database`. When an operator who used `pysqlcipher3` in v0.7.0/v0.8.0 upgrades to v0.8.1 without the `[encryption]` extra, the cache adapter now sniffs the file header before opening — if SQLCipher-encrypted, it raises SPEC-010 with three remediation paths (reinstall with extra, `spectra cache shred`, or `SPECTRA_SHRED_ON_DOWNGRADE=1` for auto-shred on next run). Symmetric counterpart to the existing plaintext→encrypted migration (Roadmap #13).

## [0.8.0] - 2026-05-03

The "fleet observability + portfolio mode" release. v0.7.0 made the grade trustworthy; v0.8.0 turns Spectra from a single-repo CLI into fleet-grade infrastructure. Seven Q3 capabilities ship together — distributed cache, history store, OpenTelemetry tracing, Anthropic Batch + prompt caching, fleet rate limiter, portfolio mode, and drift detection with Slack/Teams notifiers. Five new ADRs (ADR-013, ADR-021 through ADR-024) capture the architectural decisions. All seven capabilities are opt-in — defaults preserve v0.7.0 single-user behaviour with zero new infrastructure dependencies.

### Added — Distributed cache (#21, ADR-021)
- **`RemoteCachePort` Protocol + `RedisCacheAdapter` + `TieredCacheAdapter`.** L1 SQLite (existing) wrapped in a tiered adapter with Redis as the L2 (`TieredCacheAdapter` writes to both, reads L1 first then promotes from L2 on miss). New `--cache-remote redis://...` CLI flag (also reads `SPECTRA_CACHE_REDIS`). Default unset preserves local-only behaviour. Composite-key invalidation honoured end-to-end so a stale L2 row never matches a current-context lookup.

### Added — Postgres history store (#25, ADR-022)
- **`ReportSummary` entity + `ReportStorePort` + `SqliteReportStoreAdapter` + `PostgresReportStoreAdapter`.** Pipeline now persists a per-scan `ReportSummary` after every successful run (failure is non-fatal — same pattern as the audit port). New `spectra history latest|trend|migrate` subcommands surface the trend per repo/dimension. Composition root selects between sqlite (default, single-user) and Postgres (`--history-backend postgres`, portfolio default) lazily so the stdlib-only install still runs without `psycopg`.

### Added — OpenTelemetry tracing + cost attribution (#30, #33, ADR-023)
- **`TracerPort` + `Span` Protocol + `NoopTracerAdapter` + `OtelTracerAdapter` + `InMemoryTracerAdapter`.** Pipeline instrumented end-to-end — root span per analyze, child spans per stage, leaf spans per agent. New `--otel-endpoint` + `--team` CLI flags (also `SPECTRA_TEAM` env). Per-agent cost attribution stamped on every span (`spectra.team`, `cost.usd`, `tokens.input`, `tokens.output`) so a CFO query like `sum by (spectra.team) (cost.usd)` breaks Anthropic spend down by team. Default tracer is the in-process noop — zero overhead until `--otel-endpoint` is supplied. `requirements.lock` regenerated with OTel pins.

### Added — Anthropic Batch API + prompt caching (#23, ADR-024)
- **Prompt-cache breakpoints wired into the LLM gateway.** Stable system prompts and shared agent guidance now carry `cache_control: ephemeral` markers; volatile per-request content lands after the last breakpoint. Cache hit/miss telemetry surfaced in CLI summary and HTML report so operators see the savings live.
- **`AnthropicBatchAdapter` + `BatchSubmitterPort`.** Optional Batch API path for portfolio mode — submits all analyze requests for a portfolio scan as a single batch (50% cost reduction vs streaming). Exported from package roots. Smoke test pins the prompt-cache savings end-to-end.

### Added — Fleet rate limiter (#22, ADR-013)
- **`RateCoordinatorPort` Protocol + `InMemoryRateAdapter` + `RedisRateAdapter`.** New `--rate-limit-rpm` + `--rate-coordinator` CLI flags (also `SPECTRA_RATE_LIMIT_RPM`, `SPECTRA_RATE_COORDINATOR`). In-memory backend (default when `--rate-limit-rpm` is set) caps RPM per process; `redis://...` backend shares one token bucket across every runner pointed at the same Redis (fleet mode, Lua-atomic acquire). Every Anthropic call awaits one token before issuing the request — eliminates 429 stampedes when N runners scale up at once.

### Added — Portfolio mode + scheduler (#26)
- **`RepoRegistryPort` + `RepoRegistryEntry` entity + `SqliteRepoRegistry`.** Persisted set of repositories the operator wants `spectra portfolio scan` to iterate over. Idempotent `add` merges tags so `spectra portfolio add <url> --tag team:payments` can run more than once without duplicating rows.
- **`manage_portfolio` use case + `spectra portfolio` CLI subapp.** `add` / `remove` / `list` / `scan` / `dashboard` subcommands. The scan loop respects `--since` (only re-scan repos whose `last_scan_at` is older than the cutoff) and stamps `mark_scanned` on every success.
- **Composition root wires the registry + portfolio analyzer** behind `set_portfolio_registry_provider` + `set_portfolio_analyzer` injection seams so the CLI never imports infrastructure directly.

### Added — Drift detection + Slack/Teams notifier + weekly digest (#27, #34)
- **`NotifierPort` + `NotifierMessage` entity + `SlackWebhookAdapter` + `TeamsWebhookAdapter`.** Single port shared by both webhook adapters; auto-detected by host substring (`hooks.slack.com` vs `webhook.office.com`). New `notifier_from_url(url)` factory. Webhook outages NEVER abort the analysis pipeline — implementations log and swallow transport errors (same `safe_*` envelope as the audit port).
- **Drift detection use case + post-scan hook.** When the report store finds a prior scan, the pipeline computes per-dimension delta and fires `DriftEvent` to the notifier on any regression. New `spectra trend <repo>` CLI surface for ad-hoc drift queries. New `--notify-webhook` + `--no-drift-alert` CLI flags (also `SPECTRA_NOTIFY_WEBHOOK`).
- **Weekly digest use case + `spectra digest` CLI.** Aggregates the last 7 days of scans per repo and posts a single summary message — fewer alerts in noisy channels, one digest per week.

### Tests
- **2310 passing (was 2069 at v0.7.0). +241 net.** New test suites: `test_redis_cache_adapter`, `test_tiered_cache_adapter`, `test_redis_cache_integration` (live-Redis, opt-in via `SPECTRA_CACHE_REDIS`), `test_report_store_*` (sqlite + Postgres), `test_otel_tracer_adapter`, `test_pipeline_spans`, `test_cost_attribution`, `test_prompt_cache_savings`, `test_anthropic_batch_adapter`, `test_rate_coordinator_*` (both backends + stampede regression), `test_repo_registry`, `test_manage_portfolio`, `test_portfolio_cli`, `test_notifier_message`, `test_notifier_port`, `test_slack_webhook_adapter`, `test_teams_webhook_adapter`, `test_notifier_factory`, `test_pipeline_drift_hook`, `test_notifications`, `test_cli_notifier_flags`.

### Dependencies
- `redis>=5.0,<6.0` — fleet rate limiter L2 cache (`redis.asyncio`, opt-in via `--cache-remote` / `--rate-coordinator`)
- `psycopg[pool]>=3.1,<4.0` — Postgres history store (lazy import; sqlite default still ships)
- `opentelemetry-api>=1.27,<2.0`, `opentelemetry-sdk>=1.27,<2.0`, `opentelemetry-exporter-otlp-proto-http>=1.27,<2.0` — OTel tracing (lazy import via composition root; noop default)
- `fakeredis>=2,<3` (dev-only) — in-process Redis fake for hermetic CI

## [0.7.0] - 2026-04-30

The "make the grade trustworthy" release. v0.6.0 shipped enterprise-readiness; the post-v0.6.0 self-scan exposed grade volatility that masked real fix work (B+ 85 vs A 92 on identical code). v0.7.0 fixes the formula, hardens the codebase against the LLM-as-judge "real signal" findings, ships the OSS leaderboard with the new deterministic scoring, and lays out the Q3 plan + 4 new ADRs.

### Changed (BREAKING for grade values)
- **Scoring is now penalty-only and deterministic** (PR #60). The `_estimate_score()` function previously blended `0.4 * llm_holistic + 0.6 * penalty`. The LLM holistic was responsible for ~95% of the variance observed in the v0.6.0 self-scan series (security swung 99→93→77 across three identical-code runs because the LLM's mood swung; the penalty score was a flat 96-99 across the same runs). The new formula uses the deterministic severity-weighted, confidence-scaled penalty only. The agent's `dimension_score` field is still emitted and captured for telemetry but no longer influences the user-facing grade. **Same finding set produces the same score, every time.** Existing reports will read ~3-7 points higher under the new formula because the LLM blend was historically dragging scores down. **Empirical validation**: 3 OLD-formula vs 3 NEW-formula scans confirmed the prediction — overall spread shrank 8 pts → 2 pts (79% reduction); security dimension shrank 22 pts → 3 pts (88% reduction). See [`docs/launch/reports/v0.6.0/VALIDATION.md`](docs/launch/reports/v0.6.0/VALIDATION.md).

### Added (round-3 self-scan fixes — 17 stable real-signal findings)
- **PR #55: 6 medium-severity quality fixes.** `_PIPELINE_INFO` banner refreshed to v0.6.0 model lineup (was showing stale Sonnet 4.5/Opus 4.6 strings); typed exceptions in heuristic file reader (was bare `except`); `TiktokenAdapter` lru_cache (was instantiated per call); `PolicyGateError` moved to `entities/errors.py` (was in adapter layer); per-agent flag help text now lists allowed values; new `docs/error-codes.md` cross-referenced from CLI ✗ messages. +20 tests.
- **PR #58: 3 architecture-quality fixes.** Single `_handle_pipeline_exceptions()` helper (was duplicate try/except blocks in `analyze()` and never-called `_invoke_analyzer()`); Protocol-typed composition seams (`object` → `LLMGateway` / `AnalysisAgent` / `AuditPort`); typed exception catch in `_attach_receipt` (was swallowing `AttributeError`/`RuntimeError` programmer bugs). +14 tests.
- **PR #62: PR comment renderer hardening — 5 real attack vectors.** Inline markdown links `[text](url)`, reference-style links, bare URLs (GitHub auto-linkify), `@mention`/`#issue`/SHA auto-link triggers, and BiDi + zero-width Unicode now neutered. +15 regression tests pinning each.
- **PR #61: 5 maintainability dep hygiene fixes.** `SECURITY.md` documents `pysqlcipher3` supply-chain risk (last release 2019); `cryptography` bound `<46` → `<45`; `pytest-asyncio` bound `<2.0` → `<1.0`; `anthropic` bound `<2.0` → `<1.0` (the audit asked for `<0.50` but lockfile resolves 0.84 — would've been a downgrade); `pyyaml` removed from `[dev]` extras (duplicated runtime dep); `requirements.lock` regenerated with `--generate-hashes` (1,155 SHA-256 hashes pinned).
- **PR #65: docs glossary + AGENT_MODELS drift fix.** New `docs/glossary.md` indexes capability numbers `#1-#70`, SPEC-001..014, ADR-001..024 with ship status. `progress_reporter.AGENT_MODELS` static dict replaced with live binding to the resolved `AgentRunConfig` map (no more drift when defaults change). 10 ADRs (011-020) consolidated under `docs/architecture/adr/`. +8 tests.
- **PR #63: 3 architecture decomposition fixes.** `_run_analysis()` decomposed into `_DepBundle` + 5 named helpers (drops the v0.6.0 `# noqa: PLR0915` line); source-file selection moved from composition root to new `use_cases/source_file_selection.py` use case; new `SignerPort` + `Ed25519SignerAdapter` so `waiver_cli` no longer imports `cryptography` directly. +27 tests.
- **PR #64: 3 perf fixes (1 validated as not-a-hotspot).** Heuristic source-file loop parallelized via `asyncio.gather`; `shutil.rmtree` cleanup wrapped in `asyncio.to_thread` (was blocking event loop on big repos at end of run); cache key version composition memoized via `lru_cache` (was redoing the work per cache lookup); `_prioritize_source_files` measured at 2-4 ms on the 1000×50 stress case so left untouched with a regression guard. +8 tests.

### Added (Q3 plan + 4 ADRs)
- **PR #66: Q3 plan (4,402 words) + ADR-021 through ADR-024.** `docs/strategy/q3-plan.md` covers all 9 Q3 capabilities: distributed cache (S3/Redis), fleet rate limiter, Anthropic Batch API + prompt caching, Postgres history store, repo registry + scheduler, drift detection + Slack alerts, OpenTelemetry tracing, cost attribution, Slack/Teams digest. New ADRs design the four biggest architectural decisions: distributed cache port + adapter trio, Postgres history store + drift detection, OTel tracing + per-agent spans + cost attribution, Anthropic Batch API + prompt caching. 4-week implementation plan with two-engineer parallel tracks recommended; v0.7.0/v0.8.0 split offered as a one-engineer cadence option. 5 founder questions surfaced.

### Added (OSS leaderboard with new formula)
- **PR #68: v0.7.0 OSS leaderboard.** Five popular Python repos scanned with the new deterministic scoring: FastAPI A (92), Spectra-self A (92), HTTPX B+ (85), Aider B- (79), Simon Willison's LLM B- (77). Spectra ties FastAPI under the new formula — strong validation that round-1-3 fix work compounded into measurable code-quality signal. Total cost $30.68 across 5 scans. See [`docs/launch/reports/v0.7.0/LEADERBOARD.md`](docs/launch/reports/v0.7.0/LEADERBOARD.md).

### Documentation
- **`docs/launch/reports/v0.6.0/SCORING-ANALYSIS.md`** — root-cause investigation of grade volatility. LLM-as-judge clustering across 133 raw findings → 77 distinct issues (1.73x dedup ratio); 17 recur in all 3 scans (real signal); 39 are pure stochastic paraphrasings.
- **`docs/launch/reports/v0.6.0/VALIDATION.md`** — six-scan empirical validation of the scoring fix (3 OLD vs 3 NEW formula).
- **`docs/launch/reports/v0.7.0/LEADERBOARD.md`** — OSS leaderboard.
- **PR #67: CLAUDE.md slimmed 3.5K → 2.5K tokens (-27%)** — duplicated content (full project tree, full pyproject deps, full brand voice block, plugin skill list) moved to canonical homes. Saves ~1K tokens of always-loaded context per agent turn. Forbidden-words list + agent contract + dependency rule kept.
- Analysis scripts checked into `docs/launch/reports/v0.6.0/scripts/` (`compare3.py`, `score_analysis.py`, `llm_judge.py`, `validate6.py`, `leaderboard.py`).

### Tests
- 2069 passing (was 1973 at v0.6.0). +96 net.

### Cost
- ~$66 total Anthropic spend across 6 self-scans + 5 OSS leaderboard scans + multiple round-3 verification runs. v0.7.0 development total tracked in audit log.

## [0.6.0] - 2026-04-30

The Q2 enterprise-readiness release. All ten roadmap items from the Q2 batch land together, taking Spectra from "trustworthy CI gate" (v0.5.0) to "auditable, governable, signable platform that a regulated buyer can integrate." Every capability is additive — no breaking changes to v0.5.0 wire formats. The new `--max-cost-usd` budget gate, signed waivers, signed scan receipts, and the dual-mode confidential/public report are the headline asks from CISO + finance personas.

### Added
- **JSON-Lines audit log + Ed25519 signed scan receipts (ADR-018, roadmap #12 + #57, PR #51).** New `AuditPort` (Layer 2) with three adapters — `JsonLinesAuditAdapter` (file with daily rotation), `OtlpAuditAdapter` (HTTP exporter), `StdoutAuditAdapter` (CI default). All emits go through `safe_emit` so audit failures never abort the pipeline. New `AuditEvent` entity with `FORBIDDEN_PAYLOAD_KEYS` validator (rejects `code`/`content`/`secret`/`key`/`token`/`body`/`raw`/`snippet`/`source`; string values capped at 500 chars). Identity resolution precedence: env `SPECTRA_ACTOR` > git config > OIDC > `getpass@hostname` (hashed to 16-char ID for privacy). Scan receipts use Ed25519 with lazy keypair generation; private key in OS keyring (`spectra-receipt-key`); public PEM at `~/.config/spectra/receipt.pub`. Receipt embedded in JSON output and surfaced in HTML footer. New `spectra verify <report.json>` subcommand exits 0 on signature match + intact score-card hash. New flag `--audit-sink stdout|file:<path>|otlp:<url>`. Adds `cryptography>=43,<46` runtime dep. 90 new tests.
- **SQLCipher-at-rest cache encryption + `cache shred` (roadmap #13, RICE-60, PR #47).** The per-user cache file is now AES-256 encrypted via SQLCipher 4. The encryption key is derived from the same OS-keyring secret that anchors the per-row HMAC, with a different domain-separation step so the two keys cannot collide. `PRAGMA key='x"<hex>"'` is issued immediately after every connection open; an empty `SELECT count(*) FROM sqlite_master` canary surfaces wrong-key errors as SPEC-010 at open time. Backward-compat: any existing v0.5.0 plaintext cache is auto-migrated in place — rows streamed into a fresh encrypted DB, MACs re-computed under the current secret, file atomically swapped, plaintext shredded post-swap. New `spectra cache shred [-y]` subcommand overwrites cache.db (and WAL/SHM siblings) with random bytes (3 passes) then deletes them; also drops the per-user keyring entry. `Encryption` row added to `spectra cache doctor`. CI gains `libsqlcipher-dev` system dep. Adds `pysqlcipher3>=1.2,<2.0` runtime dep. 22 new tests.
- **`--max-cost-usd` per-run + per-hour budget enforcement (ADR-013, roadmap #5, RICE-70, PR #50).** New `CostTrackerPort` (Layer 2) with `InMemoryCostTracker` (default) and `SqliteCostTracker` (rolling 1-hour cap persisted to `cost_log` table in cache.db). Pipeline gate aborts mid-run with new **SPEC-014 `BudgetExceededError`** when the next agent call would cross the threshold. Brand-voice ✗ message names the budget, the spend, and lists per-agent breakdown. New flags `--max-cost-usd FLOAT` (per-run) and `--max-cost-per-hour FLOAT` (rolling). Pre-flight emits a WARN (not abort) when the budget is below the ~$0.04 8-agent input floor. 38 new tests.
- **`.spectra-policy.yml` + signed `.spectra-waivers.yml` + inline pragma (ADR-020, roadmap #17 + #18 + #68 partial, PR #49).** New `PolicyPort` + `WaiverPort` (Layer 2) backed by `YamlPolicyAdapter` + `YamlWaiverAdapter`. Policy enforces severity gates, per-rule forbid lists, custom dimension weights — fires new **SPEC-013 `PolicyViolationError`** on violation; runs even with `--quick`. Waivers carry an Ed25519 signature over canonical JSON of `(repo_signature, finding_signature, reason, waived_by, waived_at, expires_at)` — unsigned/invalid waivers are dropped + logged, never silently accepted. Expired waivers are surfaced on the report so the team knows the gate has gaps. New `spectra waive <id> --reason "..."` and `spectra approver register --name "..." [--key-file <path>]` CLI subcommands. Inline pragma `# spectra: ignore-next-line SEC-AUTH-101` parsed during ingest as ephemeral one-scan waivers. New **SPEC-012 `ConfigInvalidError`** for malformed YAML. Adds `pyyaml>=6,<7` runtime dep. 75 new tests.
- **`--classification confidential|public` dual-mode report render (roadmap #56, RICE-75, PR #48).** New `Classification` literal + `AnalysisReport.classification` field (default `confidential`). Confidential mode: full HTML with diagonal CONFIDENTIAL watermark + DLP-marker meta tag (`<meta name="x-dlp-classification" content="confidential">`) + visible banner. Public mode: strict redaction — drops every individual finding, code snippet, file path, recommendation; keeps overall grade, dimension scores, findings counts, repo name, scan timestamp, version. Output filename suffixed (`-confidential.html` / `-public.html`) so both can coexist on disk. JSON + SARIF parity — public SARIF emits empty `runs[0].results[]` and surfaces score under `runs[0].properties.scoreCard`. Pinned grep test ensures `BEGIN RSA`, `AKIA*`, `password`, `src/secrets.py` cannot leak through public mode. 55 new tests.
- **Severity-gate Action input + non-validated stamp (roadmap #19 + #20, PR #46).** action.yml gains `inputs.fail-on: critical|high|medium|low|none` (default `critical`). New CLI `--fail-on <severity>` exits 1 when a finding is at or above the threshold. Reports stamped with new `validation_status` Literal: `validated` | `non-validated:critique-skipped` | `non-validated:quick-mode`. `--quick` and `--no-critique` runs render a red banner above the ScoreCard plus the same string in JSON top-level + SARIF `runs[0].properties.validation_status`. 60 new tests.
- **DPA + sub-processor declaration + Anthropic data flow diagram (roadmap #11, PR #44).** Three new docs in `docs/legal/`: `DPA.md` (GDPR Art. 28 template, ~2,300 words covering definitions, scope, data-subject rights, retention, sub-processors, transfers, security, audit, termination), `SUBPROCESSORS.md` (single-row table — Anthropic only), `DATA_FLOW.md` (mermaid diagram of every data edge from developer machine → CLI → cache → Anthropic API → audit sink → report file). README "Privacy & Data Processing" subsection links the trio. Each doc carries a clear "this is a template, get your counsel to review" disclaimer.

### Documentation
- **Architecture documentation refresh (PR #52).** New `docs/architecture/` — 10 numbered HLD/LLD documents (`01-system-context` through `10-deployment-and-release`) plus 19 PlantUML source diagrams + rendered SVGs covering C4 levels 1-3, the 6-stage pipeline (happy + cached + compromised paths), the PipelineState transitions, agent orchestration + decorator chain, cache class diagram + state machine + key composition, secret pre-flight + prompt-injection defence in depth, data flow + privacy boundary, Q6-designed plugin architecture, and the publish.yml pipeline. Documents cross-reference the strategy ADRs (011-020) and use status badges (Stable / Q2 designed / Q4 designed / Q6 designed) so promoting a Q2 capability to "shipped" is a single edit per element. Render with `plantuml -tsvg docs/architecture/diagrams/*.puml`.
- **Cross-doc consistency fixes (PR #45).** README hero block now correctly says "8 agents (6 specialists in parallel, plus a planner and a critic)" instead of the misleading "8 in parallel". Mermaid C4 system-context diagrams updated from "5-stage pipeline" to "6-stage pipeline" (matches the canonical claim everywhere else). Master prompt's "8 dimensions" corrected to "6 dimensions". Getting-started promised "under 2 minutes" → corrected to "under 5 minutes" (matches taglines). Replaced placeholder `your-org` clone URL with real `pip install spectra-ai`. Clarified `orchestrate_agents.py` fault-tolerance comment.

### Changed
- `BatchPrompt`, `Finding`, `AnalysisReport`, `PipelineContext` extended with new fields for audit, receipt, classification, validation_status, waivers, cost_tracker, max_cost_usd. All additive — existing serialisation/wire formats unchanged.
- `PipelineState` enum unchanged; new error codes SPEC-012, SPEC-013, SPEC-014 added to `ERRORS` dict.
- `cli_controller.analyze()` carries `# noqa: PLR0912` for the natural composition-root branching count.
- Test count: **1973 collected, all passing** (+345 since v0.5.0). Self-scan grade B+ (86/100), 34 findings, 0 critical, 244s wall, $5.99 real Anthropic spend.

### Manual maintainer actions required
- Toggle GitHub Private Vulnerability Reporting in repo settings (still UI-only; carried forward from v0.5.0).
- Run `scripts/register_pypi_squats.sh` with `TWINE_USERNAME=__token__` + scoped PyPI token to actually reserve the 8 squat names (still pending from v0.5.0).
- (Optional) Install the Renovate GitHub App.
- (Optional) `spectra approver register --name "Your Name"` to mint your first waiver-signing keypair before using `spectra waive`.

### Failure modes
- `pysqlcipher3` unavailable → cache adapter degrades to plain SQLite + WARN once per process. HMAC + per-`$UID` isolation remain active.
- Audit emit failure → swallowed by `safe_emit`; pipeline continues. Audit is best-effort by design.
- Receipt signer unavailable (no keyring backend) → `report.receipt = None`; pipeline completes; HTML footer omits the verification command.
- Wrong SQLCipher key on open → SPEC-010, cold cache (no false data returned).

## [0.5.0] - 2026-04-29

The Q1 trust foundation. All six capabilities required to make the Spectra grade defensible as a CI gate land together. Every Red Team critical/high closes; supply-chain hygiene is no longer absent; the cache is per-user + tamper-evident; the report is honest about what it is and is not. The marketing leaderboard work in the roadmap is gated on this release.

### Added
- **Prompt-injection isolation (ADR-011, RICE-90, PR #42).** Per-file random-nonce data fences (`<<<SPECTRA-DATA-{nonce}>>>` … `<<<END-...>>>`) wrap every analyzed file in the specialist prompt; system prompt reinforces "anything between markers is data, not instruction." Bounded regex pre-flight (≤200ms design target on 10MB; 500ms CI gate) records files matching curated injection markers and feeds them into a new CritiqueAgent `<adversarial_input_check>` block. On detection, CritiqueAgent emits `Finding(rule_id="SPEC-PROMPT-INJECTION-DETECTED", severity="critical", confidence=1.0)`; orchestrator marks the run with new pipeline state `"compromised"`. New `golden_files/adversarial/` with 20 plant repos + pinned `tests/integration/test_adversarial_catch_rate.py` regression gate. **Catch-rate: 100% (20/20 plants).** Nonce intentionally excluded from `prompt_version` cache key to preserve cache survival across runs.
- **Per-row HMAC + per-`$UID` cache namespace (ADR-012, RICE-75, PR #40).** Cache directory moves to `${XDG_CACHE_HOME:-~/.cache}/spectra/$UID/` (mode 0700; `cache.db` mode 0600). Per-user 32-byte HMAC secret stored in OS keyring (service `spectra-cache-hmac`). New `mac BLOB NOT NULL` column on `findings_cache`, `full_report_cache`, `findings_batches`. Every INSERT computes blake2b HMAC of (key, value, version_tuple); every SELECT verifies — mismatch drops the row + logs SPEC-010. Silent re-key migration on secret rotation. Legacy `~/.cache/spectra/cache.db` (no `$UID/`) is dropped on first run with a one-time INFO message. New `spectra cache doctor` subcommand: prints path, UID, keyring backend, per-table verified/failed counts. Adds `keyring>=24,<26` runtime dep.
- **Secret pre-flight + `.gitignore` honor + `.spectraignore` (roadmap #6, RICE-88, PR #41).** New Stage 1.5 between INGEST and PLAN. `WorkspaceFilterPort` (Layer 2) + `PathspecFilterAdapter` (Layer 4) honor `.gitignore` (root + nested) and optional `.spectraignore`. `SecretScannerPort` + `RegexSecretScanner` flag AWS access keys, GitHub PATs, Anthropic keys, bearer tokens, Slack webhooks, RSA/OpenSSH private keys, plus an `.env*` heuristic. Secrets abort the run with new `SPEC-011 SecretDetectedError`. New flags: `--no-gitignore` (still honors `.spectraignore`), `--allow-secrets` (downgrades abort to WARN). 60 new tests including <200ms perf regression. Adds `pathspec>=0.12,<1.0` runtime dep.
- **Markdown-safe PR comment renderer + finding-field allowlist (roadmap #4, RICE-72, PR #39).** New Layer 3 `PRCommentRenderer` (`render_pr_comment(report) -> str`). Field allowlist: `title`, `severity`, `dimension`, `file_path`, `line_start`, `line_end`, `summary` only — `recommendation`, `code_snippet`, `references` dropped. HTML-escape on all text fields; backticks in titles replaced with U+02CB so codeblock fences cannot be broken; image syntax `![](...)` and autolinks `<http...>` stripped from summaries; file paths rendered in inline code with `[`/`]`/`(`/`)` escaped. New `spectra render pr-comment <report.json>` CLI subcommand. `<!-- SPECTRA -->` sentinel preserved as the idempotent-update marker for the GitHub Action. action.yml updated to call the new CLI instead of composing markdown inline. 32 new tests pinning the security contract.
- **"Indicative — not auditor-grade evidence" disclaimer banner (roadmap #61, RICE-80, PR #38).** Full-width amber banner on every HTML report (sticky, ARIA-labelled, dismissible via CSP-safe `data-action="dismiss-disclaimer"` event delegation in the nonce-protected `<script>`; dismissal stored in `sessionStorage`). Top-level `disclaimer: { text, url }` field on every JSON report. SARIF `runs[0].invocations[0].notifications[]` carries the disclaimer text + helpUri for SAST consumers. New Layer 1 `entities/disclaimer.py` as the single source of truth. Copy-scrub guardrail test prevents `compliance evidence`/`audit-grade`/`auditor-ready` from regressing into src/templates/README. 63 new tests.
- **Supply-chain Q1 bundle (roadmap #7+#8+#9+#10, PR #37).**
  - `actions/attest-build-provenance@v2` + Sigstore keyless signing (`sigstore-python`) on every release wheel; `.sigstore` bundles attached to the GitHub Release. New "Verifying releases" README section documents `gh attestation verify` and `python -m sigstore verify identity` flows.
  - `SECURITY.md` with supported-versions table (latest minor only), GitHub Private Vulnerability Reporting as the single intake, 90-day default disclosure (≤7d if exploited), explicit in/out-of-scope lists, GitHub CNA for CVE assignment.
  - `pyproject.toml` gains conservative upper bounds on every runtime + dev dep. `httpx>=0.27,<1.0` added explicitly (was implicit via `anthropic`). `requirements.lock` regenerated via `uv pip compile`. `renovate.json` adds weekly schedule, grouped minor+patch, separate-PR majors, vuln alerts any-time, dashboard autoclose.
  - `scripts/register_pypi_squats.sh` + `scripts/squat-stub/` reserve 8 high-risk PyPI variants (`spectra_ai`, `spectraai`, `spectra-cli`, `spectra-py`, `spectraapi`, `spectra-analyzer`, `spectra-code`, `spectra-review`). Idempotent `--skip-existing`, throwaway venv, shellcheck-clean.

### Changed
- `BatchPrompt` value object gains a `nonce: str` field (default factory `secrets.token_urlsafe(16)`). Excluded from `prompt_version` cache key.
- `Finding` gains a `rule_id` field; `AnalysisReport` gains `is_compromised` derived property.
- `PipelineState` enum gains `"compromised"` literal.

### Manual maintainer actions required
- Toggle GitHub Private Vulnerability Reporting in repo settings (cannot be done via gh CLI).
- Run `scripts/register_pypi_squats.sh` with `TWINE_USERNAME=__token__` + scoped PyPI token to actually reserve the 8 squat names.
- (Optional) Install the Renovate GitHub App on the repo.

## [0.4.0] - 2026-04-29

### Added
- **Per-agent model and effort configuration via CLI flags.** Override the default Claude Opus 4.7 wiring on a per-agent basis. New flags: `--model`, `--effort`, `--<role>-model`, `--<role>-effort` for each of the 8 roles (meta, architecture, security, quality, documentation, dependency, performance, critique), plus `--model-overrides`/`--effort-overrides` JSON for power users. Allowed models: claude-opus-4-7, claude-opus-4-6, claude-sonnet-4-6, claude-haiku-4-5. Allowed effort levels: low, medium, high, xhigh, max. Validation enforces `max`/`xhigh` is Opus-tier only (Sonnet/Haiku reject). New `AgentRunConfig` value object, `resolve_agent_configs` use-case helper, and AgentFactory configs param. Backward-compat: zero flags = existing defaults. 46 new tests.

### Removed
- The leftover `spectra-analysis` job in `.github/workflows/ci.yml` — same token-abuse vector as the workflows deleted in PR #14. Failing on every PR with no actionable signal. The `Test & Lint` required check is unchanged.

## [0.3.3] - 2026-04-29

### Fixed
- **Findings did not expand on click in the HTML report.** The strict CSP shipped in PR #6 (`script-src 'self' 'nonce-...'`) silently blocked the inline `onclick="this.classList.toggle('expanded')"` handler on every finding card. Replaced with CSP-safe event delegation inside the nonce-protected `<script>` block. Click + Space + Enter all expand a focused finding now.
- **ROI section reported the same $700 manual cost on every report regardless of repo size.** `_MANUAL_REVIEW_HOURS` was hardcoded to `4.0`. Now scales with findings count via `_estimate_manual_hours = 2.0 + 0.1 * len(findings)` — a 50-finding repo shows ~7h ($1225) manual cost; a 200-finding repo shows ~22h ($3850). Pinned with regression tests at 0/50/200 findings.
- **Code Complexity widget showed `Max 0 / Avg 0.0 / Unknown Risk` on every report** — the heuristic only text-mines findings for "cyclomatic N" patterns and almost never matches. The widget now hides the numeric stat row when no scores were extracted, replacing it with an honest "X file(s) flagged for complexity by specialists. No numeric complexity scores were extracted from finding text." line plus a one-line disclosure that Spectra does not yet AST-parse code.

## [0.3.2] - 2026-04-29

### Fixed
- **Cost estimation was 3× too high.** `entities/models._OPUS_INPUT_PER_1K` and `_OPUS_OUTPUT_PER_1K` carried `$0.015` and `$0.075` per 1K tokens — Anthropic's actual Opus 4.7 pricing is `$0.005` and `$0.025`. Real scans were reporting $14-20 when the actual API spend was $4-7. Pinned a regression test (`test_opus_cost_matches_anthropic_pricing`) that asserts the blended rate per 1K tokens is exactly $0.011 so future drift surfaces immediately.
- Cost-table comments still referenced "Sonnet 4.5" for the MetaPrompter and "Opus 4.6" for the CritiqueAgent — both now route through the Opus 4.7 row to match the actual model wiring.

## [0.3.1] - 2026-04-29

### Fixed
- `spectra --version` was hardcoded to `v0.1.0` and never bumped — it now reads from `spectra.__version__`. Same fix for the SARIF report's `tool.driver.version` field (also hardcoded). Existing tests were tightened to assert `f"v{__version__}"` so future bumps don't silently regress.

## [0.3.0] - 2026-04-29

### Added
- Phase 3 per-`focus_area` batch caching with hit-log telemetry — splits each ANALYZE batch into cached vs fresh prompts, reuses per-focus_area work across runs, and writes per-lookup outcomes to `hit_log` for per-dimension hit-rate reporting (PR #18).
- Phase 4 cache management CLI: `spectra cache stats`, `spectra cache clear`, and `spectra cache prune`. `stats` surfaces total entries, on-disk size, and per-dimension hit-rate breakdown sourced from the new `hit_log` dimension columns; `prune` does the deferred physical deletion of stale rows (PR #19).
- 7 new architecture diagrams: system context, container view, cache subsystem, sequence with cache decision points, class model with cache entities, decorator chain LLD, and GitHub Action flow. 2 of them ship in Excalidraw form for slide-friendly editing (PR #20).
- ADR-009 (per-`focus_area` batch granularity locked in as the canonical cache unit) and ADR-010 (no self-dogfooding rationale) (PR #20).
- HLD/LLD/CLAUDE.md sync for the cache subsystem and Distribution model (PR #20).
- All 8 agents now run on Claude Opus 4.7, with per-agent `effort` and `task_budget` tuning and adaptive thinking for the CritiqueAgent.
- GitHub Action `spectra-ai/spectra@v1` for running Spectra in PR CI — see `docs/github-action.md`.
- CLI accepts local repository paths, e.g. `spectra analyze .` (no clone needed for the current working tree).
- Incremental analysis: new `CachePort` with a `SqliteCacheAdapter` (Phase 1) and ANALYZE-stage skip on file-tree match (Phase 2) — repeat runs on unchanged code reuse the previous report.
- `--force` flag to bypass the cache and re-run a full analysis.
- `--no-cache` flag to disable cache reads and writes for a single run.

### Changed
- `analyze_repository` collapses its 8 positional dependencies into a single `PipelineContext` for clearer wiring and easier testing.
- HLD/LLD documentation refreshed; 4 new ADRs added in v0.2.0-track work (005 Opus 4.7 migration, 006 CachePort, 007 GitHub Action, 008 adaptive thinking — supersedes ADR-003).
- All 5 architecture diagrams regenerated from their Mermaid sources.
- Error registry extended: SPEC-010 added for cache I/O failures — non-fatal, the pipeline degrades to no-cache for the rest of the run.

### Fixed
- **Security (HIGH):** TOCTOU symlink bypass in path validation closed; SSRF gaps in URL handling tightened; git subprocess environment hardened against `GIT_*` injection.
- **Packaging:** Jinja2 report templates now ship inside the wheel — `pip install spectra-ai` produces a working report renderer (previously broken on PyPI installs).

### Removed
- Unused `extended_thinking` field on `AgentContext` (superseded by per-agent `task_budget` + adaptive thinking).
- Self-analysis CI workflows that ran Spectra against its own repo on every push (avoided API-key abuse on forks).

## [0.2.0] - SKIPPED (never published)

Version bumped in PR #17 but never tagged. Contents folded into v0.3.0.

## [0.1.0] - 2026-04-XX

Initial PyPI release.
