diff --git a/CLAUDE.md b/CLAUDE.md index c95244f..955688b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,47 +1,47 @@ # semvertag — Claude project guide +## Architecture + +> Quick orientation. The authoritative, code-current account of each capability +> lives in [`architecture/`](architecture/). **When a change alters a +> capability's behavior, update the matching `architecture/.md` in +> the same PR** — that promotion is what keeps `architecture/` true. + +`semvertag` funnels everything through one process: a human at a shell, the +GitHub Action, and the GitLab CI component all invoke `semvertag tag`, which +parses flags + environment into validated `Settings`, wires a **provider** and a +**strategy** through a modern-di container, and runs the use-case. Invariants +that must not break: the CLI is the single entry point all wrappers share; +providers expose a forge-neutral contract (read commits/tags, create a tag) +independent of GitLab-vs-GitHub REST differences; strategies answer only "given +this repo signal, what bump level" with no network and no tag-history reads. + +| Capability | File | +|---|---| +| CLI entry point, `Settings`, DI wiring, Action / CI wrappers | [`architecture/cli.md`](architecture/cli.md) | +| Forge adapters (GitLab, GitHub) and their neutral contract | [`architecture/providers.md`](architecture/providers.md) | +| Bump-level strategies (`branch-prefix`, `conventional-commits`) | [`architecture/strategies.md`](architecture/strategies.md) | + ## Workflow This project uses **Superpowers** (brainstorm → plan → TDD → review) with the -portable two-axis planning convention. The living truth about *what the system -does now* lives in [`architecture/`](architecture/) at the repo root (one file -per capability: `strategies.md`, `providers.md`, `cli.md`); `planning/` records -*how it got there*. See [`planning/README.md`](planning/README.md) for the full -conventions and the change Index, and [`planning/_templates/`](planning/_templates/) -for copy-and-fill starters. - -Per feature: brainstorming → spec in -`planning/changes/YYYY-MM-DD.NN-/design.md` → writing-plans → plan -in the same bundle's `plan.md` → executing-plans / subagent-driven-development → -requesting-code-review → finishing-a-development-branch. `` is a -kebab-case description, not a story ID; `.NN` is a zero-padded intra-day counter -that breaks same-date ties. The implementing PR sets `status: shipped` and fills -`pr` / `outcome` in the branch, alongside the code and the -`architecture/.md` promotion — that hand-edit is the only ship-time -step; there is no folder move. The change listing is generated — run `just index` -(no committed Index). A design decision taken **without** a code change — -especially a candidate **rejected** with a load-bearing reason — is recorded as -`planning/decisions/YYYY-MM-DD-.md` (the `decision.md` template, frontmatter -`status: accepted|superseded`), each with a **Revisit trigger** so future reviews -don't re-litigate it; listed by `just index`. - -**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. - -Use TDD by default: red, green, refactor. Tests before implementation. Use git -worktrees for feature isolation (`superpowers:using-git-worktrees`). Use the -verification gate before claiming work complete -(`superpowers:verification-before-completion`). Request code review via a -subagent before landing (`superpowers:requesting-code-review`). - -Planning artifacts live under `planning/` (not under `docs/`, so they're -excluded from the mkdocs site automatically). When superpowers skills default to +portable two-axis planning convention. `architecture/` (repo root) is the living +truth home; `planning/` records *how it got there*. **Start at the +[Quick path](planning/README.md#quick-path-start-here)** in +[`planning/README.md`](planning/README.md) to choose a lane (Full / Lightweight / +Tiny), create a bundle, and ship — that file is the authoritative spec. Run +`just check-planning` to validate bundles and `just index` to print the change +listing. + +Per feature: brainstorming → `design.md` → writing-plans → `plan.md` → +executing-plans / subagent-driven-development → requesting-code-review → +finishing-a-development-branch. Use TDD by default (red, green, refactor), git +worktrees for isolation (`superpowers:using-git-worktrees`), the verification +gate before claiming completion (`superpowers:verification-before-completion`), +and a subagent code review before landing (`superpowers:requesting-code-review`). + +Planning artifacts live under `planning/` (not `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/` here instead. @@ -80,15 +80,15 @@ belong to the retired BMad workflow. package. Behavioral reference only — port logic and test shapes from it but never `git mv` files in or take it as a starter. -## Test stack and lint +## Commands -See `Justfile` for the canonical commands. Quick reference: +`just --list` is the source of truth. Non-obvious "which to use when": -- `just lint-ci` — eof-fixer, ruff format check, ruff check, ty check - (check-only; `just lint` is the autofixing variant) -- `just test` — pytest. The `addopts` in `pyproject.toml` add `--cov-branch` - with a project-wide `fail_under = 100` gate, so every branch (strategy - modules included) must be covered. Pass args through, e.g. +- `just lint` autofixes; `just lint-ci` is check-only and also runs the + planning validator (`planning/index.py --check`, also `just check-planning`). +- `just test` runs pytest with `--cov-branch` and a project-wide + `fail_under = 100` gate (set in `pyproject.toml` addopts), so every branch + must be covered. Pass args through: `just test tests/unit/test_branch_prefix_strategy.py -q`. - `just docs-build` — strict mkdocs build (`mkdocs build --strict`), the docs gate. diff --git a/Justfile b/Justfile index fadd480..56ccbdd 100644 --- a/Justfile +++ b/Justfile @@ -15,6 +15,7 @@ lint-ci: uv run ruff format --check uv run ruff check --no-fix uv run ty check + uv run python planning/index.py --check test *args: uv run --no-sync pytest {{ args }} @@ -29,6 +30,10 @@ publish: docs-build: uvx --with-requirements docs/requirements.txt mkdocs build --strict -# Print the planning change index (grouped by status) to stdout. +# Print the planning change index (flat, newest-first) to stdout. index: uv run python planning/index.py + +# Validate planning bundles + decisions; CI runs this. +check-planning: + uv run python planning/index.py --check diff --git a/architecture/README.md b/architecture/README.md new file mode 100644 index 0000000..21d52b0 --- /dev/null +++ b/architecture/README.md @@ -0,0 +1,25 @@ +# Architecture + +The living, code-current truth about *what `semvertag` does now* — one file per +capability, plain prose, dated by git. This is the truth home: the +authoritative account each capability funnels through. `planning/` records *how +it got here*; this directory records *what it is*. + +## Capabilities + +- **[cli.md](cli.md)** — the `semvertag tag` entry point: flags + environment → + validated `Settings`, modern-di wiring of provider + strategy, the use-case + run, and the GitHub Action / GitLab CI component wrappers. +- **[providers.md](providers.md)** — the forge adapters (GitLab, GitHub): the + forge-neutral contract for reading commits and tags and creating a tag, + hiding REST-vs-REST differences. +- **[strategies.md](strategies.md)** — bump-level strategies (`branch-prefix`, + `conventional-commits`): deciding the next semver bump level from a single + repo signal, no network, no tag history. + +## Promotion rule + +When a change alters a capability's behavior, hand-edit the matching +`architecture/.md` in the **same PR** as the code, reviewed in the +same diff — never as a separate post-merge step. That hand-edit is what keeps +this directory true; the change bundle in `planning/changes/` stays as the *why*. diff --git a/planning/.convention-version b/planning/.convention-version new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/planning/.convention-version @@ -0,0 +1 @@ +1.0.0 diff --git a/planning/README.md b/planning/README.md index 3ef03e7..cc48ed6 100644 --- a/planning/README.md +++ b/planning/README.md @@ -4,12 +4,42 @@ Specs, plans, and change history for `semvertag`. The living truth about *what the system does now* lives in [`architecture/`](../architecture/) at the repo root; this directory records *how it got there*. +## Quick path (start here) + +> The fast lane for making a change. The full reference is in +> [Conventions](#conventions) below — read it only when this isn't enough. + +**1. Choose a lane — first matching rule wins:** + +1. Any of: needs design judgment · new file/module · public-API change · + cross-cutting or multi-file · non-trivial test design → **Full** + (`design.md` + `plan.md`) +2. Purely mechanical: typo · dep bump · linter/formatter/CI tweak · + mechanical rename · single-line config → **Tiny** (no bundle, conventional + commit) +3. Small-but-real, none of the above: ≲30 LOC net · ≤2 files · no new file · + no public-API change · one straightforward test → **Lightweight** + (`change.md`) + +Ambiguous between two? Take the heavier. A `change.md` that outgrows its lane +splits into `design.md` + `plan.md`. + +**2. Create the bundle** (Full / Lightweight only): +`planning/changes/YYYY-MM-DD.NN-/`, where `.NN` is a zero-padded +intra-day counter. Copy the matching template from +[`_templates/`](_templates/). + +**3. Ship in the implementing PR:** hand-edit the affected +`architecture/.md`, finalize the bundle's `summary:` to the +realized result, and run `just check-planning` before pushing. + ## Conventions -> This section is the portable convention — identical across the -> modern-python repos. The generated change listing (`just index`) and the `## Other` pointers below are repo-local. To adopt elsewhere, -> copy this section plus [`_templates/`](_templates/) and point that repo's -> `CLAUDE.md` Workflow + truth home at it. +> This is the portable convention, sourced from the canonical repo +> [`lesnik512/planning-convention`](https://github.com/lesnik512/planning-convention) +> (applied version in [`.convention-version`](.convention-version)). To update +> it, run that repo's `APPLY.md` flow. The generated change index (`just index`) +> and the `## Other` pointers below are repo-local. ### Two axes, never mixed @@ -32,10 +62,11 @@ A change is a folder `changes/YYYY-MM-DD.NN-/`: (`.01`, `.02`, …) that breaks same-date ties so the timeline sorts stably. - `` — kebab-case description, not a story ID. -`summary` is written when the change is created (it is the change's -one-liner). The implementing PR then sets `status: shipped` and fills `pr` -and `outcome` **in the branch**, alongside the code and the `architecture/` -promotion — no post-merge bookkeeping, no folder move. +`summary` is written when the change is created (the intent one-liner) and +**finalized at ship** to state the realized result — set in the implementing +PR, alongside the code and the `architecture/` promotion. No post-merge +bookkeeping, no folder move. `date` and `slug` are never written — they are +read from the bundle's directory name. ### Three lanes @@ -53,32 +84,38 @@ into `design.md` + `plan.md`. - **`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. -- **`decisions/-.md`** — one file per design decision taken - (especially options *rejected*), each with a revisit trigger, so reviews don't - re-litigate them; listed by `just index`. - **`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. +- **`decisions/-.md`** — one file per design decision taken + (especially options *rejected*), each with a revisit trigger; listed by + `just index`. Templates live in [`_templates/`](_templates/). ### Frontmatter -`design.md` / `change.md`: `status` (draft|approved|shipped|superseded), -`date`, `slug`, `summary` (single line), `supersedes`, `superseded_by`, `pr`, -`outcome`. `plan.md`: `status`, `date`, `slug`, `spec`, `pr`. -`decisions/*.md`: `status` (accepted|superseded), `date`, `slug`, `summary`, -`supersedes`, `superseded_by`, `pr`. Files in -`architecture/` carry **no** frontmatter — living prose, dated by git. +`date` and `slug` are **derived from the directory / file name** — never +repeated in frontmatter. So: + +- `design.md` / `change.md`: `summary` (single line) only. +- `plan.md`: **no frontmatter** — its identity is the bundle directory. +- `decisions/*.md`: `status` (accepted|superseded), `summary`, and optional + `supersedes` / `superseded_by`. +- Files in `architecture/` carry **no** frontmatter — living prose, dated by git. + +**`summary`** is one line: written at creation as the intent, then **finalized +at ship** to state the realized result — what shipped and its effect. It is the +only field the index renders. ## Index The listing is **generated**, not maintained — run `just index` to print it: -changes grouped by `status` (In progress / Shipped / Superseded), then -decisions newest-first. The frontmatter in each bundle / decision file is the -single source of truth; there is no committed copy to drift. +changes then decisions, newest-first. The frontmatter in each bundle / decision +file is the single source of truth; there is no committed copy to drift. +Run `just check-planning` to validate every bundle and decision. ## Other diff --git a/planning/_templates/change.md b/planning/_templates/change.md index 7ffec26..d4c8962 100644 --- a/planning/_templates/change.md +++ b/planning/_templates/change.md @@ -1,12 +1,5 @@ --- -status: draft -date: YYYY-MM-DD -slug: my-change -summary: One line — shown in the generated index. Fill at ship time. -supersedes: null -superseded_by: null -pr: null -outcome: null +summary: One line — shown in the generated index. Written at creation; finalize at ship to state the realized result. --- # Change: One-line capitalized title diff --git a/planning/_templates/decision.md b/planning/_templates/decision.md index 940fb37..45ccaf0 100644 --- a/planning/_templates/decision.md +++ b/planning/_templates/decision.md @@ -1,11 +1,8 @@ --- status: accepted # accepted | superseded -date: YYYY-MM-DD -slug: my-decision summary: One line — shown in `just index`. supersedes: null superseded_by: null -pr: null # PR/commit where the decision was made or recorded --- # One-line capitalized title diff --git a/planning/_templates/design.md b/planning/_templates/design.md index b9e11c9..d63e22d 100644 --- a/planning/_templates/design.md +++ b/planning/_templates/design.md @@ -1,12 +1,5 @@ --- -status: draft -date: YYYY-MM-DD -slug: my-change -summary: One line — shown in the generated index. Fill at ship time. -supersedes: null -superseded_by: null -pr: null -outcome: null +summary: One line — shown in the generated index. Written at creation; finalize at ship to state the realized result. --- # Design: One-line capitalized title diff --git a/planning/_templates/plan.md b/planning/_templates/plan.md index f2b90e8..132d720 100644 --- a/planning/_templates/plan.md +++ b/planning/_templates/plan.md @@ -1,11 +1,3 @@ ---- -status: draft -date: YYYY-MM-DD -slug: my-change -spec: my-change -pr: null ---- - # — implementation plan > **For agentic workers:** REQUIRED SUB-SKILL: Use @@ -46,9 +38,7 @@ in the spec. ```bash git add path/to/file.py - git commit -m ": - - Co-Authored-By: Claude Opus 4.7 (1M context) " + git commit -m ": " ``` --- diff --git a/planning/_templates/release.md b/planning/_templates/release.md new file mode 100644 index 0000000..5081187 --- /dev/null +++ b/planning/_templates/release.md @@ -0,0 +1,38 @@ +# + + + + + +## Feature + +- **.** What it adds and how to use it. + +## Fix + +- **.** What was broken, now fixed (reference the issue/regression). + +## Internal refactors + +- **.** What changed under the hood, stated as no behavior change. + +## Packaging + +- Metadata / build / dependency changes visible to installers. + +## Why + +Context a reader needs for the headline change. Omit for small releases. + +## Downstream + +What dependents must do — e.g. bump their version floor — or "No action +needed" when there is no API change. Omit if the project has no downstreams. + +## Internals + +- Coverage / tooling notes. diff --git a/planning/changes/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/design.md b/planning/changes/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/design.md index 6470446..8b51e72 100644 --- a/planning/changes/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/design.md +++ b/planning/changes/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-05-31 -slug: bmad-to-superpowers-migration-and-httpx2-wrapper summary: "Retire BMad for Superpowers; add the HTTP-client wrapper." -supersedes: null -superseded_by: null -pr: null -outcome: shipped in the pre-1.0 bootstrap (retired BMad; added the httpx HTTP-client wrapper) --- # BMad → Superpowers migration + httpx2 wrapper pilot diff --git a/planning/changes/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/plan.md b/planning/changes/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/plan.md index fb9057a..723fcb7 100644 --- a/planning/changes/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/plan.md +++ b/planning/changes/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-05-31 -slug: bmad-to-superpowers-migration-and-httpx2-wrapper -spec: bmad-to-superpowers-migration-and-httpx2-wrapper -pr: null ---- - # BMad → Superpowers Migration + httpx2 Wrapper 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. diff --git a/planning/changes/2026-05-31.02-drop-doctor/design.md b/planning/changes/2026-05-31.02-drop-doctor/design.md index a0dc088..b9dd930 100644 --- a/planning/changes/2026-05-31.02-drop-doctor/design.md +++ b/planning/changes/2026-05-31.02-drop-doctor/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-05-31 -slug: drop-doctor summary: "Remove the `doctor` command." -supersedes: null -superseded_by: null -pr: null -outcome: shipped in the pre-1.0 bootstrap (removed the doctor command) --- # Drop `semvertag doctor` subcommand diff --git a/planning/changes/2026-05-31.02-drop-doctor/plan.md b/planning/changes/2026-05-31.02-drop-doctor/plan.md index 6553559..4d5ff29 100644 --- a/planning/changes/2026-05-31.02-drop-doctor/plan.md +++ b/planning/changes/2026-05-31.02-drop-doctor/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-05-31 -slug: drop-doctor -spec: drop-doctor -pr: null ---- - # Drop `semvertag doctor` 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. diff --git a/planning/changes/2026-05-31.03-settings-aliaschoices/design.md b/planning/changes/2026-05-31.03-settings-aliaschoices/design.md index cf37683..b256c8f 100644 --- a/planning/changes/2026-05-31.03-settings-aliaschoices/design.md +++ b/planning/changes/2026-05-31.03-settings-aliaschoices/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-05-31 -slug: settings-aliaschoices summary: "pydantic-settings `AliasChoices` for env/CLI names." -supersedes: null -superseded_by: null -pr: null -outcome: shipped in the pre-1.0 bootstrap (pydantic-settings AliasChoices) --- # Adopt Pydantic `AliasChoices` in `_settings.py` diff --git a/planning/changes/2026-05-31.03-settings-aliaschoices/plan.md b/planning/changes/2026-05-31.03-settings-aliaschoices/plan.md index 592b521..abae15c 100644 --- a/planning/changes/2026-05-31.03-settings-aliaschoices/plan.md +++ b/planning/changes/2026-05-31.03-settings-aliaschoices/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-05-31 -slug: settings-aliaschoices -spec: settings-aliaschoices -pr: null ---- - # Settings `AliasChoices` Adoption 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. diff --git a/planning/changes/2026-05-31.04-usecase-callable/design.md b/planning/changes/2026-05-31.04-usecase-callable/design.md index 813b91e..4f8d02f 100644 --- a/planning/changes/2026-05-31.04-usecase-callable/design.md +++ b/planning/changes/2026-05-31.04-usecase-callable/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-05-31 -slug: usecase-callable summary: "Make the use-case a callable." -supersedes: null -superseded_by: null -pr: null -outcome: shipped in the pre-1.0 bootstrap (callable use-case) --- # `SemvertagUseCase` as a callable: separate init from invocation diff --git a/planning/changes/2026-05-31.04-usecase-callable/plan.md b/planning/changes/2026-05-31.04-usecase-callable/plan.md index 64a021d..1b27e9a 100644 --- a/planning/changes/2026-05-31.04-usecase-callable/plan.md +++ b/planning/changes/2026-05-31.04-usecase-callable/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-05-31 -slug: usecase-callable -spec: usecase-callable -pr: null ---- - # `SemvertagUseCase` Callable Refactor 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. diff --git a/planning/changes/2026-05-31.05-ioc-idiomatic-modern-di-typer/design.md b/planning/changes/2026-05-31.05-ioc-idiomatic-modern-di-typer/design.md index 21e2330..61d7f46 100644 --- a/planning/changes/2026-05-31.05-ioc-idiomatic-modern-di-typer/design.md +++ b/planning/changes/2026-05-31.05-ioc-idiomatic-modern-di-typer/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-05-31 -slug: ioc-idiomatic-modern-di-typer summary: "Idiomatic modern-di + Typer IoC wiring." -supersedes: null -superseded_by: null -pr: null -outcome: shipped in the pre-1.0 bootstrap (modern-di + Typer IoC) --- # Idiomatic `modern-di-typer` wiring in `ioc.py` + `__main__.py` diff --git a/planning/changes/2026-05-31.05-ioc-idiomatic-modern-di-typer/plan.md b/planning/changes/2026-05-31.05-ioc-idiomatic-modern-di-typer/plan.md index 1970fff..d779f71 100644 --- a/planning/changes/2026-05-31.05-ioc-idiomatic-modern-di-typer/plan.md +++ b/planning/changes/2026-05-31.05-ioc-idiomatic-modern-di-typer/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-05-31 -slug: ioc-idiomatic-modern-di-typer -spec: ioc-idiomatic-modern-di-typer -pr: null ---- - # Idiomatic `modern-di-typer` Wiring 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. diff --git a/planning/changes/2026-05-31.06-cli-overlay-simplification/design.md b/planning/changes/2026-05-31.06-cli-overlay-simplification/design.md index 7244edc..9950873 100644 --- a/planning/changes/2026-05-31.06-cli-overlay-simplification/design.md +++ b/planning/changes/2026-05-31.06-cli-overlay-simplification/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-05-31 -slug: cli-overlay-simplification summary: "Replace the CLI-overlay machinery with `model_copy`." -supersedes: null -superseded_by: null -pr: null -outcome: shipped in the pre-1.0 bootstrap (model_copy CLI overlay) --- # Simplify `apply_cli_overlay` in `_settings.py` diff --git a/planning/changes/2026-05-31.06-cli-overlay-simplification/plan.md b/planning/changes/2026-05-31.06-cli-overlay-simplification/plan.md index fbca9cc..ff51e43 100644 --- a/planning/changes/2026-05-31.06-cli-overlay-simplification/plan.md +++ b/planning/changes/2026-05-31.06-cli-overlay-simplification/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-05-31 -slug: cli-overlay-simplification -spec: cli-overlay-simplification -pr: null ---- - # `apply_cli_overlay` Simplification 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. diff --git a/planning/changes/2026-05-31.07-strategy-no-bump-cleanup/design.md b/planning/changes/2026-05-31.07-strategy-no-bump-cleanup/design.md index adbb1d1..ed6b72b 100644 --- a/planning/changes/2026-05-31.07-strategy-no-bump-cleanup/design.md +++ b/planning/changes/2026-05-31.07-strategy-no-bump-cleanup/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-05-31 -slug: strategy-no-bump-cleanup summary: "Clean up the strategies' no-bump return path." -supersedes: null -superseded_by: null -pr: null -outcome: shipped in the pre-1.0 bootstrap (no-bump return path) --- # Move strategy-specific no-bump explanation onto the strategy classes diff --git a/planning/changes/2026-05-31.07-strategy-no-bump-cleanup/plan.md b/planning/changes/2026-05-31.07-strategy-no-bump-cleanup/plan.md index adc8fa7..96443af 100644 --- a/planning/changes/2026-05-31.07-strategy-no-bump-cleanup/plan.md +++ b/planning/changes/2026-05-31.07-strategy-no-bump-cleanup/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-05-31 -slug: strategy-no-bump-cleanup -spec: strategy-no-bump-cleanup -pr: null ---- - # Strategy No-Bump Cleanup 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. diff --git a/planning/changes/2026-05-31.08-v0-1-0-release-prep/design.md b/planning/changes/2026-05-31.08-v0-1-0-release-prep/design.md index e3bb43a..68acc13 100644 --- a/planning/changes/2026-05-31.08-v0-1-0-release-prep/design.md +++ b/planning/changes/2026-05-31.08-v0-1-0-release-prep/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-05-31 -slug: v0-1-0-release-prep summary: "Pre-1.0 release preparation." -supersedes: null -superseded_by: null -pr: null -outcome: shipped in the pre-1.0 bootstrap (0.1.0 release prep) --- # v0.1.0 release prep diff --git a/planning/changes/2026-05-31.08-v0-1-0-release-prep/plan.md b/planning/changes/2026-05-31.08-v0-1-0-release-prep/plan.md index 4a2bb3a..0da9ada 100644 --- a/planning/changes/2026-05-31.08-v0-1-0-release-prep/plan.md +++ b/planning/changes/2026-05-31.08-v0-1-0-release-prep/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-05-31 -slug: v0-1-0-release-prep -spec: v0-1-0-release-prep -pr: null ---- - # v0.1.0 Release Prep 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. diff --git a/planning/changes/2026-06-07.01-httpware-migration/design.md b/planning/changes/2026-06-07.01-httpware-migration/design.md index f8f028e..d0dca36 100644 --- a/planning/changes/2026-06-07.01-httpware-migration/design.md +++ b/planning/changes/2026-06-07.01-httpware-migration/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-06-07 -slug: httpware-migration summary: "Migrate the HTTP client onto httpware." -supersedes: null -superseded_by: null -pr: null -outcome: shipped (#2) --- # httpware migration — design spec diff --git a/planning/changes/2026-06-07.01-httpware-migration/plan.md b/planning/changes/2026-06-07.01-httpware-migration/plan.md index 5b56e08..e034dd0 100644 --- a/planning/changes/2026-06-07.01-httpware-migration/plan.md +++ b/planning/changes/2026-06-07.01-httpware-migration/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-06-07 -slug: httpware-migration -spec: httpware-migration -pr: null ---- - # httpware 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. diff --git a/planning/changes/2026-06-08.01-httpware-decoder-adoption/design.md b/planning/changes/2026-06-08.01-httpware-decoder-adoption/design.md index bd8199f..b219d64 100644 --- a/planning/changes/2026-06-08.01-httpware-decoder-adoption/design.md +++ b/planning/changes/2026-06-08.01-httpware-decoder-adoption/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-06-08 -slug: httpware-decoder-adoption summary: "Adopt the httpware response decoder in the providers." -supersedes: null -superseded_by: null -pr: null -outcome: shipped (#3) --- # httpware decoder adoption — design spec diff --git a/planning/changes/2026-06-08.01-httpware-decoder-adoption/plan.md b/planning/changes/2026-06-08.01-httpware-decoder-adoption/plan.md index 86ff28f..1df45c0 100644 --- a/planning/changes/2026-06-08.01-httpware-decoder-adoption/plan.md +++ b/planning/changes/2026-06-08.01-httpware-decoder-adoption/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-06-08 -slug: httpware-decoder-adoption -spec: httpware-decoder-adoption -pr: null ---- - # httpware decoder adoption — 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. diff --git a/planning/changes/2026-06-08.02-github-provider/design.md b/planning/changes/2026-06-08.02-github-provider/design.md index 3bed565..aebe391 100644 --- a/planning/changes/2026-06-08.02-github-provider/design.md +++ b/planning/changes/2026-06-08.02-github-provider/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-06-08 -slug: github-provider summary: "GitHub provider alongside GitLab." -supersedes: null -superseded_by: null -pr: null -outcome: shipped (#4) --- # GitHub provider — design spec diff --git a/planning/changes/2026-06-08.02-github-provider/plan.md b/planning/changes/2026-06-08.02-github-provider/plan.md index c3956c3..19ba401 100644 --- a/planning/changes/2026-06-08.02-github-provider/plan.md +++ b/planning/changes/2026-06-08.02-github-provider/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-06-08 -slug: github-provider -spec: github-provider -pr: null ---- - # GitHub provider 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. diff --git a/planning/changes/2026-06-08.03-action-yml-composite-wrapper/design.md b/planning/changes/2026-06-08.03-action-yml-composite-wrapper/design.md index a1a05b6..4ba2415 100644 --- a/planning/changes/2026-06-08.03-action-yml-composite-wrapper/design.md +++ b/planning/changes/2026-06-08.03-action-yml-composite-wrapper/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-06-08 -slug: action-yml-composite-wrapper summary: "`action.yml` composite GitHub Action wrapping the CLI." -supersedes: null -superseded_by: null -pr: null -outcome: shipped (#10) --- # action.yml composite wrapper — design spec diff --git a/planning/changes/2026-06-08.03-action-yml-composite-wrapper/plan.md b/planning/changes/2026-06-08.03-action-yml-composite-wrapper/plan.md index 0b9d707..7576547 100644 --- a/planning/changes/2026-06-08.03-action-yml-composite-wrapper/plan.md +++ b/planning/changes/2026-06-08.03-action-yml-composite-wrapper/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-06-08 -slug: action-yml-composite-wrapper -spec: action-yml-composite-wrapper -pr: null ---- - # action.yml composite wrapper — 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. diff --git a/planning/changes/2026-06-09.01-mkdocs-github-actions/design.md b/planning/changes/2026-06-09.01-mkdocs-github-actions/design.md index 868781d..3c7f515 100644 --- a/planning/changes/2026-06-09.01-mkdocs-github-actions/design.md +++ b/planning/changes/2026-06-09.01-mkdocs-github-actions/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-06-09 -slug: mkdocs-github-actions summary: "Docs hosting via MkDocs + GitHub Actions + Pages." -supersedes: null -superseded_by: null -pr: null -outcome: shipped (#14) --- # mkdocs deploy via GitHub Actions — design diff --git a/planning/changes/2026-06-09.01-mkdocs-github-actions/plan.md b/planning/changes/2026-06-09.01-mkdocs-github-actions/plan.md index 274d963..264d97e 100644 --- a/planning/changes/2026-06-09.01-mkdocs-github-actions/plan.md +++ b/planning/changes/2026-06-09.01-mkdocs-github-actions/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-06-09 -slug: mkdocs-github-actions -spec: mkdocs-github-actions -pr: null ---- - # mkdocs deploy via GitHub Actions 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. diff --git a/planning/changes/2026-06-09.02-dry-run-flag/design.md b/planning/changes/2026-06-09.02-dry-run-flag/design.md index 0f87d54..2ac06a4 100644 --- a/planning/changes/2026-06-09.02-dry-run-flag/design.md +++ b/planning/changes/2026-06-09.02-dry-run-flag/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-06-09 -slug: dry-run-flag summary: "`--dry-run` CLI flag: compute the next tag without creating it." -supersedes: null -superseded_by: null -pr: null -outcome: shipped (#15) --- # semvertag `--dry-run` flag — design diff --git a/planning/changes/2026-06-09.02-dry-run-flag/plan.md b/planning/changes/2026-06-09.02-dry-run-flag/plan.md index 914e080..c30a8d5 100644 --- a/planning/changes/2026-06-09.02-dry-run-flag/plan.md +++ b/planning/changes/2026-06-09.02-dry-run-flag/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-06-09 -slug: dry-run-flag -spec: dry-run-flag -pr: null ---- - # semvertag `--dry-run` Flag 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. diff --git a/planning/changes/2026-06-09.03-action-yml-dry-run/design.md b/planning/changes/2026-06-09.03-action-yml-dry-run/design.md index 6eb93eb..0172020 100644 --- a/planning/changes/2026-06-09.03-action-yml-dry-run/design.md +++ b/planning/changes/2026-06-09.03-action-yml-dry-run/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-06-09 -slug: action-yml-dry-run summary: "Composite action `dry-run` input wired to the CLI flag." -supersedes: null -superseded_by: null -pr: null -outcome: shipped (#16) --- # action.yml `dry-run` input + side-effect-free action-smoke — design diff --git a/planning/changes/2026-06-09.03-action-yml-dry-run/plan.md b/planning/changes/2026-06-09.03-action-yml-dry-run/plan.md index d1b0529..e28c500 100644 --- a/planning/changes/2026-06-09.03-action-yml-dry-run/plan.md +++ b/planning/changes/2026-06-09.03-action-yml-dry-run/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-06-09 -slug: action-yml-dry-run -spec: action-yml-dry-run -pr: null ---- - # action.yml `dry-run` + side-effect-free action-smoke 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. diff --git a/planning/changes/2026-06-13.01-portable-planning-convention/design.md b/planning/changes/2026-06-13.01-portable-planning-convention/design.md index 0a2e60b..f432f8c 100644 --- a/planning/changes/2026-06-13.01-portable-planning-convention/design.md +++ b/planning/changes/2026-06-13.01-portable-planning-convention/design.md @@ -1,12 +1,5 @@ --- -status: shipped -date: 2026-06-13 -slug: portable-planning-convention summary: "Adopt the portable two-axis convention: `architecture/` truth home + `changes/` bundles, migrate the 15 spec/plan pairs, fresh Index." -supersedes: null -superseded_by: null -pr: 21 -outcome: shipped (#21) — architecture/ truth home + changes/ bundles; 15 spec/plan pairs migrated; fresh Index --- # Design: Adopt the portable two-axis planning convention diff --git a/planning/changes/2026-06-13.01-portable-planning-convention/plan.md b/planning/changes/2026-06-13.01-portable-planning-convention/plan.md index f1b1975..9cd4d3b 100644 --- a/planning/changes/2026-06-13.01-portable-planning-convention/plan.md +++ b/planning/changes/2026-06-13.01-portable-planning-convention/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-06-13 -slug: portable-planning-convention -spec: portable-planning-convention -pr: 21 ---- - # Portable Planning Convention Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use diff --git a/planning/changes/2026-06-16.01-httpware-0.12-get-with-response/change.md b/planning/changes/2026-06-16.01-httpware-0.12-get-with-response/change.md index c4f9f0a..f79a527 100644 --- a/planning/changes/2026-06-16.01-httpware-0.12-get-with-response/change.md +++ b/planning/changes/2026-06-16.01-httpware-0.12-get-with-response/change.md @@ -1,14 +1,5 @@ --- -status: shipped -date: 2026-06-16 -slug: httpware-0.12-get-with-response summary: "Bump httpware to 0.12.0; adopt `get_with_response` at the pagination call sites." -supersedes: null -superseded_by: null -pr: 24 -outcome: Shipped. httpware floor raised to >=0.12.0; both Link-header pagination - call sites now use get_with_response. No behavior change; no architecture - contract moved. --- # Change: Bump httpware to 0.12.0 and adopt get_with_response in pagination diff --git a/planning/changes/2026-06-16.02-branch-prefix-patch-on-non-merge/design.md b/planning/changes/2026-06-16.02-branch-prefix-patch-on-non-merge/design.md index 09daad4..3847d86 100644 --- a/planning/changes/2026-06-16.02-branch-prefix-patch-on-non-merge/design.md +++ b/planning/changes/2026-06-16.02-branch-prefix-patch-on-non-merge/design.md @@ -1,14 +1,5 @@ --- -status: shipped -date: 2026-06-16 -slug: branch-prefix-patch-on-non-merge summary: "Opt-in `patch_on_non_merge_commit` flag: a non-merge HEAD commit bumps patch instead of nothing." -supersedes: null -superseded_by: null -pr: 24 -outcome: Shipped. Opt-in patch_on_non_merge_commit flag (default False) added to - branch-prefix; a non-merge HEAD commit bumps patch when enabled. Conclusions - promoted into architecture/strategies.md. --- # Design: Opt-in patch bump for non-merge commits (branch-prefix) diff --git a/planning/changes/2026-06-16.02-branch-prefix-patch-on-non-merge/plan.md b/planning/changes/2026-06-16.02-branch-prefix-patch-on-non-merge/plan.md index 1c7f51a..a0b529d 100644 --- a/planning/changes/2026-06-16.02-branch-prefix-patch-on-non-merge/plan.md +++ b/planning/changes/2026-06-16.02-branch-prefix-patch-on-non-merge/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-06-16 -slug: branch-prefix-patch-on-non-merge -spec: branch-prefix-patch-on-non-merge -pr: 24 ---- - # branch-prefix-patch-on-non-merge — implementation plan > **For agentic workers:** REQUIRED SUB-SKILL: Use diff --git a/planning/changes/2026-06-16.03-httpware-max-error-body-bytes/design.md b/planning/changes/2026-06-16.03-httpware-max-error-body-bytes/design.md index 3e90ab3..a035ea3 100644 --- a/planning/changes/2026-06-16.03-httpware-max-error-body-bytes/design.md +++ b/planning/changes/2026-06-16.03-httpware-max-error-body-bytes/design.md @@ -1,14 +1,5 @@ --- -status: shipped -date: 2026-06-16 -slug: httpware-max-error-body-bytes summary: "Cap provider error-body reads at 1 MiB; translate `ResponseTooLargeError` to `ProviderAPIError`." -supersedes: null -superseded_by: null -pr: 26 -outcome: Shipped. Both provider clients built with a 1 MiB max_error_body_bytes - cap; ResponseTooLargeError translates to ProviderAPIError (None-length - guarded). Conclusions promoted into architecture/providers.md. --- # Design: Bound provider error-body reads (httpware max_error_body_bytes) diff --git a/planning/changes/2026-06-16.03-httpware-max-error-body-bytes/plan.md b/planning/changes/2026-06-16.03-httpware-max-error-body-bytes/plan.md index c767af1..6cf2e27 100644 --- a/planning/changes/2026-06-16.03-httpware-max-error-body-bytes/plan.md +++ b/planning/changes/2026-06-16.03-httpware-max-error-body-bytes/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-06-16 -slug: httpware-max-error-body-bytes -spec: httpware-max-error-body-bytes -pr: 26 ---- - # httpware-max-error-body-bytes — implementation plan > **For agentic workers:** REQUIRED SUB-SKILL: Use diff --git a/planning/changes/2026-06-24.01-default-branch-override/design.md b/planning/changes/2026-06-24.01-default-branch-override/design.md index 548bff3..f17f873 100644 --- a/planning/changes/2026-06-24.01-default-branch-override/design.md +++ b/planning/changes/2026-06-24.01-default-branch-override/design.md @@ -1,18 +1,5 @@ --- -status: shipped -date: 2026-06-24 -slug: default-branch-override summary: Make the advertised --default-branch / SEMVERTAG_DEFAULT_BRANCH override actually take effect. -supersedes: null -superseded_by: null -pr: 33 -outcome: > - Shipped in #33. Override honored via an optional `default_branch` field on each - provider plus a `get_default_branch()` short-circuit; wired from settings in - ioc. Review caught a regression in the first cut (`min_length=1` turned a - declared-but-empty `SEMVERTAG_DEFAULT_BRANCH=` into a hard ValidationError); - fixed by normalizing blank to unset (see decision - 2026-06-24-blank-settings-values-normalize-to-unset). --- # Design: Honor the default-branch override diff --git a/planning/changes/2026-06-24.01-default-branch-override/plan.md b/planning/changes/2026-06-24.01-default-branch-override/plan.md index 2c7bebc..0500489 100644 --- a/planning/changes/2026-06-24.01-default-branch-override/plan.md +++ b/planning/changes/2026-06-24.01-default-branch-override/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-06-24 -slug: default-branch-override -spec: default-branch-override -pr: 33 ---- - # default-branch-override — implementation plan **Goal:** Make `--default-branch` / `SEMVERTAG_DEFAULT_BRANCH` actually override diff --git a/planning/changes/2026-06-24.02-closed-outcome-type/design.md b/planning/changes/2026-06-24.02-closed-outcome-type/design.md index 934b7ed..3f50526 100644 --- a/planning/changes/2026-06-24.02-closed-outcome-type/design.md +++ b/planning/changes/2026-06-24.02-closed-outcome-type/design.md @@ -1,18 +1,5 @@ --- -status: shipped -date: 2026-06-24 -slug: closed-outcome-type summary: Replace the free-form run-status strings with a closed Outcome sum type the renderers dispatch over exhaustively. -supersedes: null -superseded_by: null -pr: 34 -outcome: > - Shipped in #34. Closed Outcome sum (semvertag/_outcome.py) + to_run_result wire - mapping; Output.emit(outcome, *, strategy); use-case returns Outcome. - Exhaustiveness proven — a 6th variant makes ty fail at both assert_never sites. - JSON envelope byte-identical (schema_version 1.0); human no-bump output reworded - to a reason sentence. Review came back clean; added a comment marking the - intentional human/wire reason wording split for NoTags/AlreadyTagged. --- # Design: Closed Outcome type diff --git a/planning/changes/2026-06-24.02-closed-outcome-type/plan.md b/planning/changes/2026-06-24.02-closed-outcome-type/plan.md index 3d6a882..a41fdb3 100644 --- a/planning/changes/2026-06-24.02-closed-outcome-type/plan.md +++ b/planning/changes/2026-06-24.02-closed-outcome-type/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-06-24 -slug: closed-outcome-type -spec: closed-outcome-type -pr: 34 ---- - # closed-outcome-type — implementation plan **Goal:** Replace the free-form run-status strings with a closed `Outcome` sum diff --git a/planning/changes/2026-06-25.01-tag-driven-release/design.md b/planning/changes/2026-06-25.01-tag-driven-release/design.md index c039a1b..c160dd6 100644 --- a/planning/changes/2026-06-25.01-tag-driven-release/design.md +++ b/planning/changes/2026-06-25.01-tag-driven-release/design.md @@ -1,16 +1,5 @@ --- -status: shipped -date: 2026-06-25 -slug: tag-driven-release summary: Tag-driven release.yml (PyPI + Release + v0); dogfood goes dry-run; publish.yml/tag-major.yml deleted. -supersedes: null -superseded_by: null -pr: 35 -outcome: | - Shipped a tag-driven release.yml (PyPI -> GitHub Release -> v0 float) replacing - publish.yml + tag-major.yml; semvertag.yml dogfood runs dry-run so a hand-pushed - bare semver tag is the sole release entry point. Maintainer runbook moved to - CLAUDE.md; stale docs/contributing/release.md removed. --- # Design: Tag-driven release for semvertag diff --git a/planning/changes/2026-06-25.01-tag-driven-release/plan.md b/planning/changes/2026-06-25.01-tag-driven-release/plan.md index aa0f5eb..3e0223c 100644 --- a/planning/changes/2026-06-25.01-tag-driven-release/plan.md +++ b/planning/changes/2026-06-25.01-tag-driven-release/plan.md @@ -1,11 +1,3 @@ ---- -status: shipped -date: 2026-06-25 -slug: tag-driven-release -spec: tag-driven-release -pr: 35 ---- - # tag-driven-release — implementation plan > **For agentic workers:** REQUIRED SUB-SKILL: Use diff --git a/planning/decisions/2026-06-23-forge-providers-not-unified.md b/planning/decisions/2026-06-23-forge-providers-not-unified.md index 7fefe61..54326fd 100644 --- a/planning/decisions/2026-06-23-forge-providers-not-unified.md +++ b/planning/decisions/2026-06-23-forge-providers-not-unified.md @@ -1,11 +1,8 @@ --- -status: accepted # accepted | superseded -date: 2026-06-23 -slug: forge-providers-not-unified +status: accepted summary: Keep one provider class per forge; share only the Link-pagination loop, not a unified engine. supersedes: null superseded_by: null -pr: 32 --- # Forge providers stay separate; share only what's shared by standard diff --git a/planning/decisions/2026-06-24-blank-settings-values-normalize-to-unset.md b/planning/decisions/2026-06-24-blank-settings-values-normalize-to-unset.md index 1b7a378..8348814 100644 --- a/planning/decisions/2026-06-24-blank-settings-values-normalize-to-unset.md +++ b/planning/decisions/2026-06-24-blank-settings-values-normalize-to-unset.md @@ -1,11 +1,8 @@ --- -status: accepted # accepted | superseded -date: 2026-06-24 -slug: blank-settings-values-normalize-to-unset +status: accepted summary: A blank (empty/whitespace) optional settings value normalizes to None (unset), it is not rejected. supersedes: null superseded_by: null -pr: 33 --- # Blank optional settings values normalize to unset, not reject diff --git a/planning/decisions/2026-06-24-error-translators-not-tabled.md b/planning/decisions/2026-06-24-error-translators-not-tabled.md index ecf09dc..6b022af 100644 --- a/planning/decisions/2026-06-24-error-translators-not-tabled.md +++ b/planning/decisions/2026-06-24-error-translators-not-tabled.md @@ -1,11 +1,8 @@ --- -status: accepted # accepted | superseded -date: 2026-06-24 -slug: error-translators-not-tabled +status: accepted summary: Keep the two per-forge HTTP status→error ladders as explicit copies; do not table-drive them. supersedes: null superseded_by: null -pr: null --- # Error translators stay duplicated, not table-driven diff --git a/planning/index.py b/planning/index.py index d38dffd..8916661 100644 --- a/planning/index.py +++ b/planning/index.py @@ -1,25 +1,30 @@ -# ruff: noqa: INP001 # planning/ is not a Python package; this is a standalone script +# ruff: noqa: INP001, D212 # planning/ is not a Python package; D212/D213 conflict differs from faststream-outbox """ Generate the planning index from frontmatter. -Run via ``just index``. Globs ``planning/changes/*/`` (each bundle's ``design.md``, -falling back to ``change.md``) and ``planning/decisions/*.md``, reads their -frontmatter, and prints a Markdown listing to stdout — changes grouped by lifecycle -status, then decisions newest-first. Never writes a file: the listing is a query over -the files, not a committed artifact. +Run via ``just index``. Globs ``planning/changes/*/`` (each bundle's +``design.md``, falling back to ``change.md``) and ``planning/decisions/*.md``, +reads their frontmatter, and prints a Markdown listing to stdout — changes +then decisions, newest-first. Never writes a file: +the listing is a query over the files, not a committed artifact. + +``date`` and ``slug`` are derived from the directory / file name, not +frontmatter — the name is the single source of truth for both. """ import pathlib +import re import sys CHANGES_DIR = pathlib.Path(__file__).parent / "changes" DECISIONS_DIR = pathlib.Path(__file__).parent / "decisions" -GROUPS: tuple[tuple[str, tuple[str, ...]], ...] = ( - ("In progress", ("draft", "approved")), - ("Shipped", ("shipped",)), - ("Superseded", ("superseded",)), -) +VALID_DECISION_STATUS = {"accepted", "superseded"} +BUNDLE_RE = re.compile(r"^(?P\d{4}-\d{2}-\d{2})\.\d{2}-(?P.+)$") +DECISION_RE = re.compile(r"^(?P\d{4}-\d{2}-\d{2})-(?P.+)$") +ALLOWED_BUNDLE_FILES = {"design.md", "plan.md", "change.md"} +SPEC_REQUIRED = ("summary",) +DECISION_REQUIRED = ("status", "summary") def parse_frontmatter(text: str) -> dict[str, str]: @@ -31,6 +36,8 @@ def parse_frontmatter(text: str) -> dict[str, str]: for line in lines[1:]: if line.strip() == "---": break + if line[:1] in (" ", "\t"): + continue key, sep, value = line.partition(": ") if not sep: continue @@ -39,8 +46,17 @@ def parse_frontmatter(text: str) -> dict[str, str]: return fields +def _named(fields: dict[str, str], name: str, pattern: re.Pattern[str]) -> dict[str, str]: + """Inject ``date``/``slug`` derived from a dir/file name into ``fields``.""" + match = pattern.match(name) + if match: + fields["date"] = match.group("date") + fields["slug"] = match.group("slug") + return fields + + def load_bundles() -> list[dict[str, str]]: - """Read every bundle's spec frontmatter under ``CHANGES_DIR``.""" + """Read each bundle's summary; derive date/slug from the directory name.""" bundles: list[dict[str, str]] = [] for bundle in sorted(CHANGES_DIR.iterdir()): if not bundle.is_dir(): @@ -50,7 +66,7 @@ def load_bundles() -> list[dict[str, str]]: spec = bundle / "change.md" if not spec.exists(): continue - fields = parse_frontmatter(spec.read_text(encoding="utf-8")) + fields = _named(parse_frontmatter(spec.read_text(encoding="utf-8")), bundle.name, BUNDLE_RE) fields["path"] = f"changes/{bundle.name}/{spec.name}" fields["name"] = bundle.name bundles.append(fields) @@ -58,14 +74,14 @@ def load_bundles() -> list[dict[str, str]]: def load_decisions() -> list[dict[str, str]]: - """Read frontmatter from every decision file under ``DECISIONS_DIR``.""" + """Read each decision's frontmatter; derive date/slug from the file name.""" decisions: list[dict[str, str]] = [] if not DECISIONS_DIR.is_dir(): return decisions for path in sorted(DECISIONS_DIR.glob("*.md")): if path.name == "README.md" or path.name.startswith("_"): continue - fields = parse_frontmatter(path.read_text(encoding="utf-8")) + fields = _named(parse_frontmatter(path.read_text(encoding="utf-8")), path.stem, DECISION_RE) fields["path"] = f"decisions/{path.name}" fields["name"] = path.stem decisions.append(fields) @@ -73,13 +89,12 @@ def load_decisions() -> list[dict[str, str]]: def format_row(bundle: dict[str, str]) -> str: - """Render one bundle or decision as a Markdown list item.""" + """Render one bundle as a Markdown list item.""" slug = bundle.get("slug", "?") path = bundle.get("path", "") - pr = bundle.get("pr") or "—" date = bundle.get("date", "") summary = bundle.get("summary") or "(no summary)" - line = f"- **[{slug}]({path})** (#{pr}, {date}) — {summary}" + line = f"- **[{slug}]({path})** ({date}) — {summary}" if bundle.get("supersedes"): line += f" _(supersedes {bundle['supersedes']})_" if bundle.get("superseded_by"): @@ -88,26 +103,85 @@ def format_row(bundle: dict[str, str]) -> str: def render(bundles: list[dict[str, str]], decisions: list[dict[str, str]]) -> str: - """Render the full Markdown listing: changes by status, then decisions.""" + """Render the full Markdown listing: changes then decisions, newest-first.""" out = ["# Planning index", "", "_Generated by `just index` — do not edit._", "", "## Changes", ""] - for title, statuses in GROUPS: - out += [f"### {title}", ""] - rows = sorted( - (b for b in bundles if b.get("status") in statuses), - key=lambda b: b.get("name", ""), - reverse=True, - ) - out += [format_row(b) for b in rows] if rows else ["_None._"] - out.append("") - out += ["## Decisions", ""] + change_rows = sorted(bundles, key=lambda b: b.get("name", ""), reverse=True) + out += [format_row(b) for b in change_rows] if change_rows else ["_None._"] + out += ["", "## Decisions", ""] decision_rows = sorted(decisions, key=lambda d: d.get("name", ""), reverse=True) out += [format_row(d) for d in decision_rows] if decision_rows else ["_None._"] out.append("") return "\n".join(out).rstrip() + "\n" +def _require(fields: dict[str, str], keys: tuple[str, ...], rel: str, violations: list[str]) -> None: + """Append a violation for each required key that is absent or empty.""" + violations.extend(f"{rel}: missing or empty frontmatter key '{key}'" for key in keys if not fields.get(key)) + + +def _check_spec_file(path: pathlib.Path, rel: str, violations: list[str]) -> None: + """Validate a design.md / change.md spec file (requires `summary`).""" + fields = parse_frontmatter(path.read_text(encoding="utf-8")) + _require(fields, SPEC_REQUIRED, rel, violations) + + +def _check_bundle(bundle: pathlib.Path, violations: list[str]) -> None: + """Validate one change bundle directory.""" + rel = f"changes/{bundle.name}" + if BUNDLE_RE.match(bundle.name) is None: + violations.append(f"{rel}: directory name is not 'YYYY-MM-DD.NN-slug'") + violations.extend( + f"{rel}/{child.name}: unexpected file in bundle (allowed: {', '.join(sorted(ALLOWED_BUNDLE_FILES))})" + for child in sorted(bundle.iterdir()) + if child.name not in ALLOWED_BUNDLE_FILES + ) + design = bundle / "design.md" + change = bundle / "change.md" + if not design.exists() and not change.exists(): + violations.append(f"{rel}: bundle has neither design.md nor change.md") + for spec_file in (design, change): + if spec_file.exists(): + _check_spec_file(spec_file, f"{rel}/{spec_file.name}", violations) + # plan.md carries no frontmatter — its identity comes from the bundle dir. + + +def _check_decision(path: pathlib.Path, violations: list[str]) -> None: + """Validate one decision file (requires `status` + `summary`).""" + rel = f"decisions/{path.name}" + if DECISION_RE.match(path.stem) is None: + violations.append(f"{rel}: file name is not 'YYYY-MM-DD-slug.md'") + fields = parse_frontmatter(path.read_text(encoding="utf-8")) + _require(fields, DECISION_REQUIRED, rel, violations) + status = fields.get("status", "") + if status and status not in VALID_DECISION_STATUS: + violations.append(f"{rel}: invalid status '{status}' (allowed: {', '.join(sorted(VALID_DECISION_STATUS))})") + + +def check() -> list[str]: + """Validate every bundle and decision; return the list of violation strings.""" + violations: list[str] = [] + for bundle in sorted(CHANGES_DIR.iterdir()): + if bundle.is_dir(): + _check_bundle(bundle, violations) + if DECISIONS_DIR.is_dir(): + for path in sorted(DECISIONS_DIR.glob("*.md")): + if path.name == "README.md" or path.name.startswith("_"): + continue + _check_decision(path, violations) + return violations + + def main() -> int: - """Print the listing to stdout.""" + """Print the listing to stdout, or validate bundles with --check.""" + if "--check" in sys.argv[1:]: + violations = check() + if violations: + sys.stderr.write(f"planning: {len(violations)} violation(s)\n") + for violation in violations: + sys.stderr.write(f" - {violation}\n") + return 1 + sys.stdout.write("planning: OK\n") + return 0 sys.stdout.write(render(load_bundles(), load_decisions())) return 0 diff --git a/pyproject.toml b/pyproject.toml index 72465da..14b722f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ ignore = [ "TRY003", "EM102", "D203", - "D212", + "D213", "COM812", "ISC001", "S105", diff --git a/semvertag/_outcome.py b/semvertag/_outcome.py index 600bec0..ed988e3 100644 --- a/semvertag/_outcome.py +++ b/semvertag/_outcome.py @@ -57,8 +57,7 @@ class NoBump: def to_run_result(outcome: Outcome, *, strategy: str) -> RunResult: - """ - Project a closed Outcome onto the JSON wire DTO. + """Project a closed Outcome onto the JSON wire DTO. The single place the four fixed wire status tokens and the fixed reasons live; NoBump passes the strategy's own status/reason through. The diff --git a/semvertag/ioc.py b/semvertag/ioc.py index 6016a55..00aa747 100644 --- a/semvertag/ioc.py +++ b/semvertag/ioc.py @@ -50,8 +50,7 @@ def _build_current_provider( gitlab_client: httpware.Client, github_client: httpware.Client, ) -> Provider: - """ - Construct the active provider. + """Construct the active provider. Both clients are eagerly resolved (modern-di Factory eagerly resolves all provider_kwargs in resolve()). That's acceptable — httpx2 connection pools diff --git a/semvertag/providers/_errors.py b/semvertag/providers/_errors.py index fb05912..8fc3af1 100644 --- a/semvertag/providers/_errors.py +++ b/semvertag/providers/_errors.py @@ -55,8 +55,7 @@ def _translate_transport(exc: httpware.ClientError, *, provider_label: str) -> E def translate_gitlab(exc: httpware.ClientError, *, project_id: int) -> Exception: - """ - Translate an httpware ClientError into the semvertag domain error for GitLab. + """Translate an httpware ClientError into the semvertag domain error for GitLab. Handles both status errors (4xx/5xx) and transport-layer failures (network, timeout, retry budget exhaustion). @@ -107,8 +106,7 @@ def _translate_github_status(exc: httpware.StatusError, *, repo: str) -> Excepti def translate_github(exc: httpware.ClientError, *, repo: str) -> Exception: - """ - Translate an httpware ClientError into the semvertag domain error for GitHub. + """Translate an httpware ClientError into the semvertag domain error for GitHub. Mirrors translate_gitlab's dispatch order; status branches carry GitHub-specific actionable hints. Transport branches (DecodeError, TimeoutError, RetryBudget, diff --git a/semvertag/providers/_rest.py b/semvertag/providers/_rest.py index ba9cfbf..82d556d 100644 --- a/semvertag/providers/_rest.py +++ b/semvertag/providers/_rest.py @@ -23,8 +23,7 @@ def collect_link_pages( # noqa: PLR0913 cross_origin_message: str, pagination_cap_message: str, ) -> list[_ItemT]: - """ - Walk RFC 8288 Link-header pages, accumulating extracted items. + """Walk RFC 8288 Link-header pages, accumulating extracted items. Shared by every forge whose list endpoints paginate via Link headers (GitHub, GitLab both do, because it is a spec — not a coincidence). The diff --git a/tests/_descriptor_gate.py b/tests/_descriptor_gate.py index 3ca31f9..93144ee 100644 --- a/tests/_descriptor_gate.py +++ b/tests/_descriptor_gate.py @@ -1,5 +1,4 @@ -""" -Structural validator for templates/semvertag.yml. +"""Structural validator for templates/semvertag.yml. Shared between the `.github/workflows/ci.yml` lint step and the regression test fixtures in test_ci_descriptor_gate.py so the gate diff --git a/tests/conftest.py b/tests/conftest.py index 8033ea6..589a182 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -50,8 +50,7 @@ @pytest.fixture(autouse=True) def _isolate_ci_env(monkeypatch: pytest.MonkeyPatch) -> None: - """ - Strip every env var that Settings reads, so tests aren't accidentally driven by the host runner. + """Strip every env var that Settings reads, so tests aren't accidentally driven by the host runner. Without this, GitHub Actions runners (which auto-export GITHUB_ACTIONS=true, GITHUB_REPOSITORY, GITHUB_TOKEN) and GitLab CI runners (GITLAB_CI=true, diff --git a/tests/test_ci_descriptor_gate.py b/tests/test_ci_descriptor_gate.py index e83d37a..ecca4f6 100644 --- a/tests/test_ci_descriptor_gate.py +++ b/tests/test_ci_descriptor_gate.py @@ -1,5 +1,4 @@ -""" -Regression tests for the shared descriptor-shape gate in tests/_descriptor_gate.py. +"""Regression tests for the shared descriptor-shape gate in tests/_descriptor_gate.py. Authored as part of Story 4.3b code review (Constraint 7 exception granted at code-review time on 2026-05-30) so the CI gate gains a