diff --git a/.gitignore b/.gitignore index 8267786..d29b668 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,6 @@ pytest.xml dist/ .python-version .venv -plan.md /site/ /.worktrees/ uv.lock diff --git a/CLAUDE.md b/CLAUDE.md index 0a4adf1..ff4283d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,16 +2,44 @@ ## Workflow -This project uses **Superpowers** (brainstorm → plan → TDD → review). +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. -- Brainstorm specs live in `planning/specs/YYYY-MM-DD--design.md`. -- Implementation plans live in `planning/plans/YYYY-MM-DD-.md`. -- 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`). +Per feature: brainstorming → spec in +`planning/changes/active/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. On merge the bundle moves to +`planning/changes/archive/` with `status: shipped`, `pr:`, and `outcome:` +filled, **and the change promotes its conclusions into the affected +`architecture/.md`** — that hand-edit is what keeps `architecture/` +true. + +**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 +`docs/superpowers/specs/` or `docs/superpowers/plans/`, use the change bundle +under `planning/changes/active/` here instead. ## Commit messages @@ -47,7 +75,7 @@ See `Justfile` for the canonical commands. Quick reference: ## What the codebase ships `semvertag` is a public-OSS auto-tagger for GitLab/GitHub/Bitbucket -repositories. Two strategies (`branch-prefix`, `conventional-commits`), one -provider implemented today (GitLab), distributed as a Python CLI plus a -GitHub Actions wrapper (`action.yml`) and a GitLab CI Catalog component +repositories. Two strategies (`branch-prefix`, `conventional-commits`), two +providers implemented today (GitLab, GitHub), distributed as a Python CLI plus +a GitHub Actions wrapper (`action.yml`) and a GitLab CI Catalog component (`templates/semvertag.yml`). diff --git a/Justfile b/Justfile index 923a405..c5f170c 100644 --- a/Justfile +++ b/Justfile @@ -25,6 +25,10 @@ publish: uv build uv publish --token $PYPI_TOKEN +# Strict local docs build (no deploy). Mirrors CI's link/strict checks. +docs-build: + uvx --with-requirements docs/requirements.txt mkdocs build --strict + # Force-pushes built site to gh-pages; CI runs this on push to main. # Manual invocation from a stale checkout will roll the live site back. docs-deploy: diff --git a/architecture/cli.md b/architecture/cli.md new file mode 100644 index 0000000..392a4c0 --- /dev/null +++ b/architecture/cli.md @@ -0,0 +1,122 @@ +# CLI + +The CLI is the one process everything funnels through: a human at a shell, the +GitHub Action, and the GitLab CI component all invoke the same `semvertag tag` +command. It parses flags + environment into validated `Settings`, wires a +provider and a strategy through a modern-di container, and runs the use-case. + +## Entry point + +`semvertag/__main__.py` builds `MAIN_APP`, a `typer.Typer` app +(`no_args_is_help=True`), with one real command, `tag`, plus a root callback +that gathers global options and an eager `--version`. modern-di is attached via +`modern_di_typer.setup_di(MAIN_APP, ioc.container)`, and `main()` enters the +container as a context manager before running the app. + +`tag` (`_tag_command`) takes `--quiet`, `--json`, and `--dry-run`. It builds the +output (`build_json_output` or `build_rich_output`), resolves the use-case from +DI, and calls it. `--dry-run` is threaded straight into the use-case +(`use_case(output=output, dry_run=dry_run)`): the use-case still fetches the +commit, reads the tag history, and computes the new version, but when +`dry_run` is true it short-circuits *before* `provider.create_tag` — emitting a +`dry_run` status with the planned tag instead of pushing it. Errors are caught +at this boundary: any `SemvertagError` (or `ImportError`) is printed via the +output and re-raised as `typer.Exit(code=err.exit_code)`, mapping the domain +error hierarchy to process exit codes; `BrokenPipeError` exits 0. + +## IoC wiring + +`semvertag/ioc.py` defines a modern-di `Container` over four `Group`s: + +- `SettingsGroup` — a `ContextProvider` for `Settings`; the callback sets the + validated instance into the container context (`set_context(Settings, + settings)`) so everything downstream resolves from one settings object. +- `ProvidersGroup` — `gitlab_client` / `github_client` factories (with + `_close_client` finalizers) and `current_provider`, which dispatches on + `settings.provider`. +- `StrategiesGroup` — the two strategy factories plus `current_strategy`, + dispatching on `settings.strategy`. +- `UseCasesGroup` — `semvertag_use_case`, built from `current_provider` + + `current_strategy`. + +The CLI resolves the use-case through `_resolve_use_case`, a +`@modern_di_typer.inject`'d function with a `FromDI(SemvertagUseCase)` parameter. +Because modern-di's `Factory` eagerly resolves all kwargs, both HTTP clients are +constructed even though only one provider runs; this is safe (lazy httpx2 pools) +and `_build_current_provider` carries `assert` guards on `repo` / `project_id` +that both narrow types for `ty` and document the invariant the settings +validator guarantees — the eager-resolution None-field guard. + +## Settings + +`semvertag/_settings.py` defines `Settings` (and nested `GitLabConfig` / +`GitHubConfig`) as `pydantic-settings` models. Sources are the environment +(prefix `SEMVERTAG_`, nested delimiter `__`) and CLI overrides; `AliasChoices` +lets one field accept several env names — e.g. the GitLab token reads +`SEMVERTAG_GITLAB__TOKEN`, `SEMVERTAG_TOKEN`, `CI_JOB_TOKEN`, or `GITLAB_TOKEN`; +`provider` accepts `SEMVERTAG_PROVIDER` or `PROVIDER`; `project_id` accepts +`CI_PROJECT_ID`; `repo` accepts `GITHUB_REPOSITORY`. A `model_validator` +auto-detects the provider from CI env (`GITHUB_ACTIONS` / `GITLAB_CI`) when +unset and enforces that github needs `repo` and gitlab needs `project_id`. A +field validator clamps `request_timeout` to a 10-second ceiling. + +CLI flags are applied *over* the env-built settings by `apply_cli_overlay`, +which is built on `model_copy(update=...)`: it splits dotted keys +(`gitlab.endpoint`) into nested sub-model copies, copies the top-level fields, +then re-validates the whole model so field/model validators fire again on the +merged result. Precedence is therefore **CLI over env over default** — env (and +defaults) build the base instance, then non-`None` CLI overrides overwrite it. +`--token` is applied in a second overlay pass routed to the *resolved* active +provider (`{provider}.token`), so one flag lands on whichever forge is active. + +## Use-case + +`semvertag/_use_case.py` defines `SemvertagUseCase`, a frozen dataclass holding +a `provider` and a `strategy`; calling it (`__call__(*, output, dry_run=False) +-> RunResult`) is the whole orchestration: + +1. fetch the latest commit on the default branch; +2. list tags and pick the highest semver-parseable one (`_pick_latest_semver_tag` + sorts by `semver.Version`; unparseable names are skipped); +3. early no-bump exits — `no_tags` when there is no prior semver tag (it does + **not** seed an initial tag in v1.0), `already_tagged` when the head commit + already carries the latest tag; +4. ask the strategy for a `Bump`; `Bump.NONE` exits with the strategy's own + status/reason; +5. compute the new version (`_compute_new_version` via `semver`'s + `bump_major/minor/patch`); +6. if `dry_run`, return `dry_run`; else `provider.create_tag` and return + `created`. + +Every exit funnels through `_emit`, which builds a frozen `RunResult` +(`schema_version`, `strategy`, `bump`, `status`, `tag`, `commit`, `reason`), +hands it to the output, and returns it. + +## Output + +`semvertag/_output.py` defines an `Output` protocol (`progress` / `emit` / +`error`) with two implementations. `RichOutput` is the human path: progress +lines and a one-sentence result to stdout via `rich`, errors to stderr. +`JsonOutput` is the machine path: `progress` is a no-op and `emit` writes a +single compact JSON envelope (`dataclasses.asdict(result)`) to stdout. `--quiet` +suppresses progress narrative on both while still emitting the final result. +`RichOutput` redacts all output paths; `JsonOutput` redacts only its `error` path — `emit` writes the result envelope as unredacted JSON (see providers.md). + +## Distribution wrappers + +Two thin wrappers shell out to the same published CLI: + +- `action.yml` — a composite GitHub Action. It sets up `uv`, exports + `GITHUB_TOKEN` and `SEMVERTAG_STRATEGY` from inputs, and runs + `uvx 'semvertag>=0.5.0,<1' tag --json $dry_run_flag`, where `$dry_run_flag` + expands to `--dry-run` when the `dry-run` input is `"true"`. It parses the + JSON envelope with `jq` and normalizes the CLI's internal status to a stable + `created | no-bump` enum for `tag` / `bump` / `status` outputs. +- `templates/semvertag.yml` — a GitLab CI Catalog component. It pip-installs + `uv`, maps the `strategy` input to `SEMVERTAG_STRATEGY`, and runs + `uvx 'semvertag>=0.1,<1' tag`. + +Both shell out to the same CLI, but they are **not** symmetric on dry-run: +`action.yml` passes `--dry-run` through (gated on the `dry-run` input), whereas +`templates/semvertag.yml` exposes only a `strategy` input and runs `tag` with no +`--dry-run` flag — the GitLab component has no dry-run path today. diff --git a/architecture/providers.md b/architecture/providers.md new file mode 100644 index 0000000..6bccdbe --- /dev/null +++ b/architecture/providers.md @@ -0,0 +1,117 @@ +# Providers + +A provider is the API adapter for one forge. It hides REST-vs-REST differences +behind a small, forge-neutral contract so the use-case can read commits and tags +and create a tag without knowing whether it is talking to GitLab or GitHub. +Two providers ship today. + +## The contract + +`semvertag/providers/_base.py` defines `Provider`, a `typing.Protocol` +(structural — providers are matched by shape, registered in the IoC container, +not by subclassing). The abstract operations: + +- `name: str` — the provider id. +- `get_default_branch(self) -> str` — the repo's default branch name. +- `get_latest_commit_on_default_branch(self) -> Commit` — head commit of that + branch as a frozen `Commit` (`sha`, `message`). +- `list_tags(self) -> list[Tag]` — every tag as `Tag` (`name`, `commit_sha`). +- `create_tag(self, name: str, commit_sha: str) -> None` — create a tag + pointing at a commit; side-effecting, returns nothing. + +Both concrete providers are frozen, slotted, kw-only dataclasses holding their +forge config, a repo identifier, and an `httpware.Client`. + +## GitLab + +`semvertag/providers/gitlab.py` targets the GitLab v4 REST API under +`/api/v4/projects/{project_id}`: + +- default branch — `GET /api/v4/projects/{id}`, reads `default_branch`; a null + value raises `ConfigError`. +- latest commit — `GET .../repository/commits?ref_name={branch}&per_page=1`, + takes element `[0]`; an empty list raises `ProviderAPIError`. +- tags — `GET .../repository/tags?per_page=100`, paginated (below). +- create — `POST .../repository/tags` with `{"tag_name": name, "ref": + commit_sha}`. + +Auth is the `PRIVATE-TOKEN` header, set once when the client is built in +`semvertag/ioc.py` (`_build_gitlab_client`) from `settings.gitlab.token`. + +## GitHub + +`semvertag/providers/github.py` targets the GitHub REST API under +`/repos/{owner}/{repo}`: + +- default branch — `GET /repos/{repo}`, reads `default_branch` (null → + `ConfigError`). +- latest commit — `GET /repos/{repo}/commits?sha={branch}&per_page=1`, element + `[0]` (empty → `ProviderAPIError`); the message lives at `commit.message` in + the GitHub payload shape. +- tags — `GET /repos/{repo}/tags?per_page=100`, paginated. +- create — `POST /repos/{repo}/git/refs` with `{"ref": + "refs/tags/{name}", "sha": commit_sha}` (the git-refs endpoint, not a tags + endpoint). + +Auth and the GitHub-required headers are set when the client is built +(`_build_github_client`): `Authorization: Bearer `, `Accept: +application/vnd.github+json`, `X-GitHub-Api-Version: 2022-11-28`. + +## HTTP client + +Every request goes through an `httpware.Client` constructed in +`semvertag/ioc.py`. The factories set `base_url` (from the provider's +`endpoint`), `timeout` (`settings.request_timeout`), the auth headers above, and +a single middleware: `httpware.Retry` over status codes +`{408, 429, 500, 502, 503, 504}`. Both clients are eagerly resolved by +modern-di, which is safe because httpx2 connection pools are lazy — the unused +client opens no sockets. Clients are closed by a modern-di cache finalizer +(`_close_client`). Responses are decoded by httpware against pydantic +`response_model`s (`_ProjectResponse`, `_CommitList`, `_TagList`, …) via the +`get` / `send_with_response` helpers; a decode failure surfaces as +`httpware.DecodeError` and is translated to `ProviderAPIError`. + +## Link-header pagination + +Tag listing walks RFC 8288 `Link` headers. `semvertag/_link_pagination.py` +exposes `next_page_url(response, *, current_url)`, which parses the `Link` +header, finds the `rel="next"` entry, and resolves it against the current URL +(returning `None` when there is no next page). Both providers call it inside +`list_tags`: after each page they request the next URL until `next_page_url` +returns `None`, capped at `_MAX_TAG_PAGES = 100` (exceeding it raises +`ProviderAPIError`). Before following a next URL, `same_origin(next_url, +endpoint)` checks that scheme + netloc match the configured endpoint; a +cross-host `Link` is refused with `ProviderAPIError` so a malicious or +misconfigured server cannot redirect a token-bearing request to another host. + +## Secret redaction + +`semvertag/_redact.py` exposes `redact(text)`, which substitutes `***` for any +substring matching known token shapes — GitLab `glpat-…`, GitHub +`github_pat_…` / `ghp_` / `gho_` / `ghu_` / `ghs_` / `ghr_…`, Bitbucket +`ATBB…`, and any bare 32+-char hex run. It is applied at the output boundary: +`RichOutput` (`semvertag/_output.py`) runs `redact` on all three paths +(`progress`, `emit`, `error`). `JsonOutput` is more selective: `progress` is a +no-op (nothing is printed), `emit` writes the result envelope as raw JSON +without redaction, and only `error` passes its message through `redact` before +printing to stderr. A token that leaks into an error message never reaches the +terminal, but a token embedded in a result field would appear unredacted in JSON +output. + +## Errors + +`semvertag/_errors.py` defines the domain hierarchy, each carrying an +`exit_code` the CLI uses verbatim: `SemvertagError` (1, base), `ConfigError` +(2), `AuthError` (3), `ProviderAPIError` (4). `semvertag/providers/_errors.py` +translates raw `httpware.ClientError`s into these via `translate_gitlab` / +`translate_github`, which split into: + +- status errors — 401/403 → `AuthError` (with forge-specific scope hints), + 404 → `ConfigError` (project/repo not found), 422 → `ConfigError`, + 429/5xx → `ProviderAPIError` (retries exhausted), other → `ProviderAPIError`. +- transport errors (shared `_translate_transport`) — `DecodeError`, + `TimeoutError`, `RetryBudgetExhaustedError`, `NetworkError`, and a fallback + all → `ProviderAPIError`. +- tag-creation specials — a 400 ("already exists", GitLab) or 422 + ("already_exists", GitHub) on create becomes a `ConfigError` naming the tag, + distinguishing a concurrent/duplicate run from a malformed request. diff --git a/architecture/strategies.md b/architecture/strategies.md new file mode 100644 index 0000000..3824fbe --- /dev/null +++ b/architecture/strategies.md @@ -0,0 +1,103 @@ +# Strategies + +A strategy decides the next semantic-version *bump level* from a single repo +signal: the latest commit on the default branch. It does not pick a tag, read +the tag history, or touch the network — it answers one question, "given this +commit, how much should the version move?", and returns a `Bump`. The use-case +(`semvertag/_use_case.py`) owns everything else: it fetches the commit, picks +the latest semver tag, calls the strategy, and only then applies the bump. + +## The contract + +`semvertag/strategies/_base.py` defines `BumpStrategy`, a `typing.Protocol` +(structural, not an ABC — strategies are matched by shape, not inheritance). +Every strategy carries three class-level strings and one method: + +- `name: str` — the strategy id surfaced in output and matched against + `--strategy` / `SEMVERTAG_STRATEGY`. +- `no_bump_status: str` / `no_bump_reason: str` — the machine status and the + human sentence the use-case emits when this strategy declines to bump. Each + strategy owns its own pair so the "why nothing happened" message is specific + to the rule that fired. +- `decide(self, commit: Commit) -> Bump` — receives the latest commit + (`Commit` is a frozen `sha` + `message`, from `semvertag/_types.py`) and + returns one of `Bump.NONE | PATCH | MINOR | MAJOR` (`semvertag/_types.py`). + +The two concrete strategies are frozen, slotted, kw-only dataclasses, each +holding a frozen pydantic config so the prefix/type tables are validated once +at construction and immutable thereafter. They are registered for resolution in `semvertag/ioc.py` inside +`StrategiesGroup`, where `current_strategy` dispatches via +`_build_current_strategy` based on `settings.strategy`. `decide` sees one commit, never a range — +the use-case only fetches the head of the default branch. + +## branch-prefix + +`semvertag/strategies/branch_prefix.py` infers the bump from the *subject line* +of a merge commit. `BranchPrefixStrategy.decide` takes the first non-blank line +of the message (via `subject_line`, below) and applies, in order: + +1. If the subject contains none of `config.merge_mark_texts`, return + `Bump.NONE`. The default marks are `"Merge branch"` and `"Merge pull + request"`, so an ordinary GitHub PR merge-commit subject + (`Merge pull request #N from owner/feature/...`) and a GitLab merge-commit + subject both qualify under the defaults; a plain non-merge commit does not. +2. If any string in `config.minor` appears in the subject, return `Bump.MINOR`. + Default: `("feature/",)`. +3. If any string in `config.patch` appears, return `Bump.PATCH`. Default: + `("bugfix/", "hotfix/")`. +4. Otherwise `Bump.NONE`. + +Matching is substring containment, not a prefix anchor — the merged branch name +appears mid-subject in a merge commit, so the prefix is sought anywhere in the +line. The mapping is therefore prefix → level: `feature/` → minor, +`bugfix/`/`hotfix/` → patch. The tables come from `BranchPrefixConfig` (a frozen +pydantic model in the same file), which is populated from settings under +`SEMVERTAG_BRANCH_PREFIX__*` and defaulted via `Settings.branch_prefix`. Each +tuple is `min_length=1`, so a strategy can never be configured with an empty +match set. + +## conventional-commits + +`semvertag/strategies/conventional_commits.py` derives the bump from a +[Conventional Commits](https://www.conventionalcommits.org/) header on the +commit subject. `ConventionalCommitsStrategy.decide`: + +1. Matches the subject against + `^(?P[a-z]+)(?:\((?P[^)]+)\))?(?P!?):` — a lowercase + type, an optional `(scope)`, an optional `!`, then a colon. No match → + `Bump.NONE`. +2. Scans the commit *body* (via `body_lines`) for a footer line beginning with + `BREAKING CHANGE:` or `BREAKING-CHANGE:`. Found → `Bump.MAJOR`. +3. If the header carried `!` (`match["bang"] == "!"`) → `Bump.MAJOR`. +4. If the type is in `config.minor_types` (default `("feat",)`) → `Bump.MINOR`. +5. If the type is in `config.patch_types` (default `("fix", "perf")`) → + `Bump.PATCH`. Note both `fix` *and* `perf` map to patch by default. +6. Otherwise `Bump.NONE`. + +Breaking always wins over the type-table lookup because steps 2–3 return before +step 4. `ConventionalCommitsConfig` (frozen pydantic, same file) validates each +configured type against `^[a-z]+$` at load time, rejecting malformed entries +before any commit is parsed. + +## The no-bump path + +When a strategy finds nothing to act on it returns `Bump.NONE`. The use-case +detects this with an identity check (`if bump is Bump.NONE:`) and emits a +`RunResult` whose `status`/`reason` are the strategy's own `no_bump_status` / +`no_bump_reason` — `no_merge_commit` for branch-prefix, `no_conforming_commit` +for conventional-commits. `Bump.NONE` is thus the single, explicit "do nothing" +signal; callers never infer a no-op from a missing tag. (The use-case has two +*earlier* no-bump exits of its own — `no_tags` and `already_tagged` — that fire +before the strategy is consulted at all.) + +## _commit_parse.py + +`semvertag/_commit_parse.py` holds two pure message-slicing helpers shared by +both strategies; it does **not** parse Conventional Commits itself (the type / +scope / breaking-marker regex lives in `conventional_commits.py`): + +- `subject_line(message)` — the first non-blank line, right-stripped, or `""`. +- `body_lines(message)` — every line after the subject and its first blank + separator, right-stripped, blanks within the body preserved as skips. This is + what lets the conventional-commits strategy find a `BREAKING CHANGE:` footer + while ignoring the subject and the blank line under it. diff --git a/planning/README.md b/planning/README.md new file mode 100644 index 0000000..dbb4d75 --- /dev/null +++ b/planning/README.md @@ -0,0 +1,109 @@ +# Planning + +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*. + +## Conventions + +> This section is the portable convention — identical across the +> modern-python repos. The Index below is repo-specific. To adopt elsewhere, +> copy this section plus [`_templates/`](_templates/) and point that repo's +> `CLAUDE.md` Workflow + truth home at it. + +### Two axes, never mixed + +- **`architecture/` (repo root) — the present.** One file per capability, + living prose, updated whenever a change ships. The truth home. +- **`planning/changes/` — the past-and-pending.** One folder per change, + frozen once shipped. + +Shipping a change **promotes** its conclusions into the affected +`architecture/.md` by hand, then archives the bundle. That +hand-edit is what keeps `architecture/` true; the archived bundle carries the +*why*. + +### Change bundles + +A change is a folder `changes/active/YYYY-MM-DD.NN-/`: + +- `YYYY-MM-DD` — proposal date; `.NN` — zero-padded intra-day counter + (`.01`, `.02`, …) that breaks same-date ties so the timeline sorts stably. +- `` — kebab-case description, not a story ID. + +On merge the folder moves to `changes/archive/` with `status: shipped`, `pr:`, +and `outcome:` filled, and its line moves from **Active** to **Archived** in +the Index below. + +### Three lanes + +| Lane | Artifacts | Use when | +|------|-----------|----------| +| **Full** | `design.md` + `plan.md` | design judgment; new file/module; public-API change; cross-cutting/multi-file; non-trivial test design | +| **Lightweight** | `change.md` | small-but-real: ≲30 LOC net, ≤2 files, no new file, no public-API change, single straightforward test | +| **Tiny** | none — conventional commit | typo, dep bump, linter/formatter/CI tweak, mechanical rename, single-line config | + +Heavier lane wins on ambiguity. A `change.md` that outgrows its lane splits +into `design.md` + `plan.md`. + +### Artifacts at a glance + +- **`design.md`** — the spec: the *thinking* (why, design, trade-offs, scope). +- **`plan.md`** — the plan: the *sequencing* (the executor's task checklist). +- **`change.md`** — both, condensed, for the lightweight lane. +- **`releases/.md`** — per-release user-facing notes. +- **`audits/-.md`** — findings from a code/docs/bug-hunt sweep; + spawns fix changes. +- **`retros/-.md`** — what we learned after a body of work. +- **`deferred.md`** — real-but-unscheduled items, each with a revisit trigger. + +Templates live in [`_templates/`](_templates/). + +### Frontmatter + +`design.md` / `change.md`: `status` (draft|approved|shipped|superseded), +`date`, `slug`, `supersedes`, `superseded_by`, `pr`, `outcome`. +`plan.md`: `status`, `date`, `slug`, `spec`, `pr`. Files in `architecture/` +carry **no** frontmatter — living prose, dated by git. + +## Index + +### Active + +- **[portable-planning-convention](changes/active/2026-06-13.01-portable-planning-convention/design.md)** + (2026-06-13) — Adopt the portable two-axis convention: `architecture/` truth + home + `changes/` bundles, migrate the 15 spec/plan pairs, fresh Index. + +### Archived (shipped) + +- **[action-yml-dry-run](changes/archive/2026-06-09.03-action-yml-dry-run/design.md)** + (#16, 2026-06-09) — Composite action `dry-run` input wired to the CLI flag. +- **[dry-run-flag](changes/archive/2026-06-09.02-dry-run-flag/design.md)** + (#15, 2026-06-09) — `--dry-run` CLI flag: compute the next tag without + creating it. +- **[mkdocs-github-actions](changes/archive/2026-06-09.01-mkdocs-github-actions/design.md)** + (#14, 2026-06-09) — Docs hosting via MkDocs + GitHub Actions + Pages. +- **[action-yml-composite-wrapper](changes/archive/2026-06-08.03-action-yml-composite-wrapper/design.md)** + (#10, 2026-06-08) — `action.yml` composite GitHub Action wrapping the CLI. +- **[github-provider](changes/archive/2026-06-08.02-github-provider/design.md)** + (#4, 2026-06-08) — GitHub provider alongside GitLab. +- **[httpware-decoder-adoption](changes/archive/2026-06-08.01-httpware-decoder-adoption/design.md)** + (#3, 2026-06-08) — Adopt the httpware response decoder in the providers. +- **[httpware-migration](changes/archive/2026-06-07.01-httpware-migration/design.md)** + (#2, 2026-06-07) — Migrate the HTTP client onto httpware. +- **[v0-1-0-release-prep](changes/archive/2026-05-31.08-v0-1-0-release-prep/design.md)** + (2026-05-31) — Pre-1.0 release preparation. +- **[strategy-no-bump-cleanup](changes/archive/2026-05-31.07-strategy-no-bump-cleanup/design.md)** + (2026-05-31) — Clean up the strategies' no-bump return path. +- **[cli-overlay-simplification](changes/archive/2026-05-31.06-cli-overlay-simplification/design.md)** + (2026-05-31) — Replace the CLI-overlay machinery with `model_copy`. +- **[ioc-idiomatic-modern-di-typer](changes/archive/2026-05-31.05-ioc-idiomatic-modern-di-typer/design.md)** + (2026-05-31) — Idiomatic modern-di + Typer IoC wiring. +- **[usecase-callable](changes/archive/2026-05-31.04-usecase-callable/design.md)** + (2026-05-31) — Make the use-case a callable. +- **[settings-aliaschoices](changes/archive/2026-05-31.03-settings-aliaschoices/design.md)** + (2026-05-31) — pydantic-settings `AliasChoices` for env/CLI names. +- **[drop-doctor](changes/archive/2026-05-31.02-drop-doctor/design.md)** + (2026-05-31) — Remove the `doctor` command. +- **[bmad-to-superpowers-migration-and-httpx2-wrapper](changes/archive/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/design.md)** + (2026-05-31) — Retire BMad for Superpowers; add the HTTP-client wrapper. diff --git a/planning/_templates/change.md b/planning/_templates/change.md new file mode 100644 index 0000000..0fe24c0 --- /dev/null +++ b/planning/_templates/change.md @@ -0,0 +1,38 @@ +--- +status: draft +date: YYYY-MM-DD +slug: my-change +supersedes: null +superseded_by: null +pr: null +outcome: null +--- + +# Change: One-line capitalized title + +**Lane:** lightweight — ≲30 LOC net, ≤2 files, no new file, no public-API +change, a single straightforward test. If it outgrows this, split into +`design.md` + `plan.md`. + +## Goal + +One or two sentences: what changes and why. + +## Approach + +The shape of the change in brief — enough that a reviewer sees the design +without a full spec. Link the truth home (`architecture/.md`) if a +capability contract moves. + +## Files + +- `path/to/file.py` — what changes +- `tests/test_x.py` — test added / updated + +## Verification + +- [ ] Failing test first — command + expected error. +- [ ] Apply the change. +- [ ] Test passes — command. +- [ ] `just test` — full suite green. +- [ ] `just lint` — clean. diff --git a/planning/_templates/design.md b/planning/_templates/design.md new file mode 100644 index 0000000..fb0fe5b --- /dev/null +++ b/planning/_templates/design.md @@ -0,0 +1,55 @@ +--- +status: draft +date: YYYY-MM-DD +slug: my-change +supersedes: null +superseded_by: null +pr: null +outcome: null +--- + +# Design: One-line capitalized title + +## Summary + +One paragraph. What changes, at the level a reader needs to decide if this +spec is worth reading in full. + +## Motivation + +Why now. What is broken or missing. Concrete observations / numbers, not +abstract complaints. Link to memory entries or earlier specs when relevant. + +## Non-goals + +What is deliberately out of scope and (when nontrivial) why. Each item is +a sentence; one line each. + +## Design + +### 1. + +What changes, in enough detail that a reader who has not seen the codebase +can follow. Code samples / diagrams welcome. + +### 2. + +... + +## Operations + +Out-of-repo steps (DNS, infra, external account changes). Omit if none. + +## Out of scope + +Already covered above under Non-goals if appropriate. Repeat-list of +explicitly-excluded follow-ups belongs here when the list is long. + +## Testing + +How we know it landed correctly. New pytest? Smoke check on live URL? +Lint pass? Be specific. + +## Risk + +What could go wrong, ranked by likelihood × impact. Mitigations. diff --git a/planning/_templates/plan.md b/planning/_templates/plan.md new file mode 100644 index 0000000..f2b90e8 --- /dev/null +++ b/planning/_templates/plan.md @@ -0,0 +1,56 @@ +--- +status: draft +date: YYYY-MM-DD +slug: my-change +spec: my-change +pr: null +--- + +# — implementation plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> superpowers:subagent-driven-development (recommended) or +> superpowers:executing-plans to implement this plan task-by-task. Steps +> use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** One sentence — what shipping this plan achieves. No design +rationale; link to the spec for that. + +**Spec:** [`design.md`](./design.md) + +**Branch:** `feat/my-change` (or `fix/`, `chore/`, etc.) + +**Commit strategy:** Per-task commits / single commit / squash on merge. +Whichever fits. + +--- + +### Task 1: + +**Files:** +- Modify: `path/to/file.py` +- Create: `path/to/new.py` + +One sentence on what this task accomplishes. No deeper reasoning — that's +in the spec. + +- [ ] **Step 1: ** + + Run / edit / verify command. Expected output. + +- [ ] **Step 2: ** + + ... + +- [ ] **Step 3: Commit** + + ```bash + git add path/to/file.py + git commit -m ": + + Co-Authored-By: Claude Opus 4.7 (1M context) " + ``` + +--- + +### Task 2: ... diff --git a/planning/plans/.gitkeep b/planning/changes/active/.gitkeep similarity index 100% rename from planning/plans/.gitkeep rename to planning/changes/active/.gitkeep diff --git a/planning/changes/active/2026-06-13.01-portable-planning-convention/design.md b/planning/changes/active/2026-06-13.01-portable-planning-convention/design.md new file mode 100644 index 0000000..f54a12e --- /dev/null +++ b/planning/changes/active/2026-06-13.01-portable-planning-convention/design.md @@ -0,0 +1,190 @@ +--- +status: draft +date: 2026-06-13 +slug: portable-planning-convention +supersedes: null +superseded_by: null +pr: null +outcome: null +--- + +# Design: Adopt the portable two-axis planning convention + +## Summary + +Port the planning convention from `lite-bootstrap` (its #120, itself from +`faststream-outbox` #77) into `semvertag`. It splits planning into two axes: an +`architecture/` truth home at the repo root (one living-prose file per +capability, the promotion target on every ship) and `planning/changes/` +bundles (one frozen folder per change). The existing 15 `planning/specs/` + +`planning/plans/` pairs migrate into dated `changes/archive/` bundles; the +portable `## Conventions` block, `_templates/`, and `deferred.md` are copied +byte-identical. **Docs and file-moves only — no runtime code, tests, or public +API touched.** + +## Motivation + +`semvertag` currently keeps design docs in `planning/specs/` and plans in +`planning/plans/` as two parallel flat directories keyed by +`YYYY-MM-DD-`. This has two gaps the sibling repos already solved: + +- **No truth home.** There is no single place that states *what the system + does now*. That knowledge is scattered across `CLAUDE.md` and the code, and + drifts as changes land. `architecture/` fixes this with a promote-on-merge + discipline. +- **Spec and plan drift apart.** A spec and its plan for the same change live + in two directories with no folder binding them; nothing co-locates the + *thinking* with the *sequencing*. Change bundles co-locate them. + +Adopting the same convention as `lite-bootstrap` and `faststream-outbox` also +makes the three modern-python repos navigable the same way — the `## +Conventions` block and `_templates/` are deliberately byte-identical so the +convention is learned once. + +A latent bug also gets fixed: `.gitignore` carries a bare `plan.md` rule that +would silently swallow every bundle's `plan.md` once the convention lands. + +## Non-goals + +- No runtime code, test, or public-API change. The published `semvertag` + package is untouched. +- No `audits/` or `retros/` directories — this repo has produced neither; + YAGNI. The README still documents them for when they first appear. +- No rewrite of the migrated specs/plans' bodies — they move verbatim (via + `git mv`) and gain only YAML frontmatter. +- No GitLab Catalog or release work; unrelated to this change. + +## Design + +### 1. Two-axis model + +- **`architecture/` (repo root) — the present.** One file per capability, + living prose, **no frontmatter** (dated by git). Updated whenever a change + ships. The truth home. +- **`planning/changes/` — the past-and-pending.** One folder per change, + frozen once shipped. + +Shipping a change **promotes** its conclusions into the affected +`architecture/.md` by hand, then archives the bundle. The hand-edit +is what keeps `architecture/` true; the archived bundle carries the *why*. + +### 2. `architecture/` seeded with three capability files + +Mirroring lite-bootstrap's three-file carving, seeded from `CLAUDE.md` and the +code: + +- **`strategies.md`** — the `Strategy` base (`strategies/_base.py`) and the two + bump strategies, `branch-prefix` and `conventional-commits` + (`strategies/branch_prefix.py`, `strategies/conventional_commits.py`), plus + commit-message parsing (`_commit_parse.py`). +- **`providers.md`** — the provider abstraction (`providers/_base.py`), the + GitLab and GitHub providers (`providers/gitlab.py`, `providers/github.py`), + link-header pagination (`_link_pagination.py`), the httpware HTTP client, and + secret redaction (`_redact.py`). +- **`cli.md`** — the CLI surface (`__main__.py`), IoC wiring (`ioc.py`), + settings + CLI overlay (`_settings.py`), use-case orchestration + (`_use_case.py`), and output formatting (`_output.py`). + +### 3. Migrate the 15 spec/plan pairs into archived bundles + +Each `planning/specs/--design.md` + `planning/plans/-.md` +pair becomes `planning/changes/archive/.NN-/{design.md,plan.md}`. +The `.NN` intra-day counter is taken from git first-commit order so the timeline +sorts stably: + +| Bundle | +|--------| +| `2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper` | +| `2026-05-31.02-drop-doctor` | +| `2026-05-31.03-settings-aliaschoices` | +| `2026-05-31.04-usecase-callable` | +| `2026-05-31.05-ioc-idiomatic-modern-di-typer` | +| `2026-05-31.06-cli-overlay-simplification` | +| `2026-05-31.07-strategy-no-bump-cleanup` | +| `2026-05-31.08-v0-1-0-release-prep` | +| `2026-06-07.01-httpware-migration` | +| `2026-06-08.01-httpware-decoder-adoption` | +| `2026-06-08.02-github-provider` | +| `2026-06-08.03-action-yml-composite-wrapper` | +| `2026-06-09.01-mkdocs-github-actions` | +| `2026-06-09.02-dry-run-flag` | +| `2026-06-09.03-action-yml-dry-run` | + +Migration mechanics per pair: + +- `git mv` both files into the bundle folder, renaming to `design.md` / + `plan.md` (preserves history). +- Prepend YAML frontmatter. `design.md`: `status: shipped`, `date`, `slug`, + `supersedes: null`, `superseded_by: null`, `pr`, `outcome`. `plan.md`: + `status: shipped`, `date`, `slug`, `spec: `, `pr`. The existing + header-style `**Date:**` / `**Status:**` lines stay in the body untouched. +- `planning/specs/` and `planning/plans/` are removed once empty (their + `.gitkeep` files go too). + +**Frontmatter `pr` / `outcome` completeness.** `pr` and a one-line factual +`outcome` are backfilled where git/GitHub merge history maps a bundle cleanly to +a single PR. Where a bundle does not map to one PR (e.g. early pre-release work +landed across several commits), `pr` is left `null` and `outcome` carries a +short factual note instead of a guessed PR number. Accuracy over completeness: +no invented PR links. + +### 4. Supporting files copied byte-identical + +- **`planning/README.md`** — the portable `## Conventions` block byte-identical + to lite-bootstrap, plus a fresh semvertag-specific `## Index` (Active: this + convention change until merge; Archived: the 15 migrated bundles). +- **`planning/_templates/{design,plan,change}.md`** — byte-identical. +- **`planning/deferred.md`** — added, seeded empty (no genuinely-deferred items + on hand). +- `planning/releases/` (0.2.0–0.6.0) is left as-is. + +### 5. `CLAUDE.md`, `Justfile`, `.gitignore` + +- **`CLAUDE.md`** — rewrite the `## Workflow` section to the bundle convention, + naming `architecture/` as the promotion target and pointing at + `planning/changes/active/YYYY-MM-DD.NN-/` (replacing the current + `planning/specs/` + `planning/plans/` references). Keep `## Reference + directories` and `## What the codebase ships` intact. The three-lanes + guidance (Full / Lightweight / Tiny) is added. +- **`Justfile`** — add a `docs-build` recipe + (`uvx --with-requirements docs/requirements.txt mkdocs build --strict`) as a + local strict gate alongside the existing `docs-deploy`. +- **`.gitignore`** — remove the bare `plan.md` line so bundle `plan.md` files + are tracked. + +### 6. Dogfood + +This change is its own bundle: +`planning/changes/active/2026-06-13.01-portable-planning-convention/`. On merge +it moves to `archive/` with `status: shipped`, `pr:`, and `outcome:` filled, and +its Index line shifts from Active to Archived. No `architecture/` promotion — +this change defines the convention, not a library capability. + +## Testing + +- `just lint-ci` — eof-fixer, ruff format/check, ty all clean. +- `just test` — full suite green, coverage unchanged (no runtime code touched). +- `mkdocs build --strict` (via the new `just docs-build`) — exits 0; + `architecture/` and `planning/` are outside `docs_dir`, so the site is + unchanged. +- Frontmatter parses as valid YAML on every migrated `design.md` / `plan.md`; + any `outcome` value containing `#` is quoted (YAML comment trap). +- Stale-pointer sweep: no remaining references to `planning/specs/` or + `planning/plans/`; README Index links and the migrated `Spec:` links all + resolve. +- `## Conventions` block and `_templates/` are byte-identical to lite-bootstrap. + +## Risk + +- **`.gitignore` plan.md trap (high likelihood if missed, high impact).** Until + the rule is removed, `git add` silently skips every bundle's `plan.md`. + Mitigation: remove it in the same change and verify with `git status` / + `git check-ignore` that no bundle `plan.md` is ignored. +- **Lost git history on migrated files (medium / medium).** Using `cp`+`rm` + instead of `git mv` would break `--follow`. Mitigation: `git mv` only; + spot-check `git log --follow` on a migrated file. +- **Inaccurate frontmatter `pr` (low / medium).** Guessing PR numbers would + mislead. Mitigation: backfill only when merge history is unambiguous; + otherwise `null` (see §3). +- **Stale references in `CLAUDE.md` / docs (low / low).** Mitigation: grep sweep + for `planning/specs` and `planning/plans` after migration. diff --git a/planning/changes/active/2026-06-13.01-portable-planning-convention/plan.md b/planning/changes/active/2026-06-13.01-portable-planning-convention/plan.md new file mode 100644 index 0000000..36866be --- /dev/null +++ b/planning/changes/active/2026-06-13.01-portable-planning-convention/plan.md @@ -0,0 +1,879 @@ +--- +status: draft +date: 2026-06-13 +slug: portable-planning-convention +spec: portable-planning-convention +pr: null +--- + +# Portable Planning Convention Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> superpowers:subagent-driven-development (recommended) or +> superpowers:executing-plans to implement this plan task-by-task. Steps use +> checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Adopt the portable two-axis planning convention in `semvertag` — +seed an `architecture/` truth home, migrate the 15 spec/plan pairs into dated +`planning/changes/` bundles, and copy the byte-identical convention scaffolding +(`README.md` `## Conventions`, `_templates/`, `deferred.md`). + +**Architecture:** Pure docs / file-moves. No runtime code, tests, or public API +touched, so there is no TDD loop — each task's "test" is a structural check +(file exists, frontmatter parses, grep sweep clean) plus the standing gates +`just lint-ci`, `just test`, and `mkdocs build --strict`. Existing specs/plans +move via `git mv` (history preserved) and gain only YAML frontmatter. + +**Tech Stack:** Markdown, YAML frontmatter, `git mv`, `just`, `mkdocs`. + +**Spec:** [`design.md`](./design.md) + +**Branch:** `docs/portable-planning-convention` (already created; the spec is +committed there). + +**Commit strategy:** Per-task commits. + +--- + +### Task 1: Scaffold the portable convention files + +**Files:** +- Create: `planning/_templates/design.md` +- Create: `planning/_templates/plan.md` +- Create: `planning/_templates/change.md` +- Create: `planning/deferred.md` +- Create: `planning/changes/active/.gitkeep` +- Create: `planning/changes/archive/.gitkeep` + +These four content files are copied **byte-identical** from `lite-bootstrap` +(the portable convention). Reproduce them exactly. + +- [ ] **Step 1: Write `planning/_templates/design.md`** + +```markdown +--- +status: draft +date: YYYY-MM-DD +slug: my-change +supersedes: null +superseded_by: null +pr: null +outcome: null +--- + +# Design: One-line capitalized title + +## Summary + +One paragraph. What changes, at the level a reader needs to decide if this +spec is worth reading in full. + +## Motivation + +Why now. What is broken or missing. Concrete observations / numbers, not +abstract complaints. Link to memory entries or earlier specs when relevant. + +## Non-goals + +What is deliberately out of scope and (when nontrivial) why. Each item is +a sentence; one line each. + +## Design + +### 1. + +What changes, in enough detail that a reader who has not seen the codebase +can follow. Code samples / diagrams welcome. + +### 2. + +... + +## Operations + +Out-of-repo steps (DNS, infra, external account changes). Omit if none. + +## Out of scope + +Already covered above under Non-goals if appropriate. Repeat-list of +explicitly-excluded follow-ups belongs here when the list is long. + +## Testing + +How we know it landed correctly. New pytest? Smoke check on live URL? +Lint pass? Be specific. + +## Risk + +What could go wrong, ranked by likelihood × impact. Mitigations. +``` + +- [ ] **Step 2: Write `planning/_templates/plan.md`** + +```markdown +--- +status: draft +date: YYYY-MM-DD +slug: my-change +spec: my-change +pr: null +--- + +# — implementation plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> superpowers:subagent-driven-development (recommended) or +> superpowers:executing-plans to implement this plan task-by-task. Steps +> use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** One sentence — what shipping this plan achieves. No design +rationale; link to the spec for that. + +**Spec:** [`design.md`](./design.md) + +**Branch:** `feat/my-change` (or `fix/`, `chore/`, etc.) + +**Commit strategy:** Per-task commits / single commit / squash on merge. +Whichever fits. + +--- + +### Task 1: + +**Files:** +- Modify: `path/to/file.py` +- Create: `path/to/new.py` + +One sentence on what this task accomplishes. No deeper reasoning — that's +in the spec. + +- [ ] **Step 1: ** + + Run / edit / verify command. Expected output. + +- [ ] **Step 2: ** + + ... + +- [ ] **Step 3: Commit** + + ```bash + git add path/to/file.py + git commit -m ": + + Co-Authored-By: Claude Opus 4.7 (1M context) " + ``` + +--- + +### Task 2: ... +``` + +- [ ] **Step 3: Write `planning/_templates/change.md`** + +```markdown +--- +status: draft +date: YYYY-MM-DD +slug: my-change +supersedes: null +superseded_by: null +pr: null +outcome: null +--- + +# Change: One-line capitalized title + +**Lane:** lightweight — ≲30 LOC net, ≤2 files, no new file, no public-API +change, a single straightforward test. If it outgrows this, split into +`design.md` + `plan.md`. + +## Goal + +One or two sentences: what changes and why. + +## Approach + +The shape of the change in brief — enough that a reviewer sees the design +without a full spec. Link the truth home (`architecture/.md`) if a +capability contract moves. + +## Files + +- `path/to/file.py` — what changes +- `tests/test_x.py` — test added / updated + +## Verification + +- [ ] Failing test first — command + expected error. +- [ ] Apply the change. +- [ ] Test passes — command. +- [ ] `just test` — full suite green. +- [ ] `just lint` — clean. +``` + +- [ ] **Step 4: Write `planning/deferred.md`** + +```markdown +# Deferred Work + +Items raised in reviews or audits that are real but not actionable now. +Each is parked here with the reason it's deferred and the concrete trigger +that should bring it back. This is the long-tail register — not a backlog +of planned work. When an item is picked up it graduates to a spec/plan +bundle in [`changes/active/`](changes/active/); see [CLAUDE.md](../CLAUDE.md#workflow). + +## Open + +_None._ +``` + +- [ ] **Step 5: Create the `changes/` directory keepers** + +```bash +mkdir -p planning/changes/active planning/changes/archive +touch planning/changes/active/.gitkeep planning/changes/archive/.gitkeep +``` + +- [ ] **Step 6: Verify the templates are byte-identical to lite-bootstrap** + +```bash +for f in design plan change; do + diff <(gh api "repos/modern-python/lite-bootstrap/contents/planning/_templates/$f.md?ref=main" --jq '.content' | base64 -d) \ + planning/_templates/$f.md && echo "$f.md: identical" +done +diff <(gh api "repos/modern-python/lite-bootstrap/contents/planning/deferred.md?ref=main" --jq '.content' | base64 -d) \ + planning/deferred.md && echo "deferred.md: identical" +``` +Expected: four `identical` lines, no diff output. + +- [ ] **Step 7: Commit** + +```bash +git add planning/_templates planning/deferred.md planning/changes/active/.gitkeep planning/changes/archive/.gitkeep +git commit -m "docs(planning): add portable templates, deferred register, changes/ skeleton + +Co-Authored-By: Claude Opus 4.8 (1M context) " +``` + +--- + +### Task 2: Fix the `.gitignore` trap and add the `docs-build` gate + +**Files:** +- Modify: `.gitignore` +- Modify: `Justfile` + +The bare `plan.md` rule in `.gitignore` would silently exclude every bundle's +`plan.md`. Remove it. Add a strict local docs build recipe mirroring CI. + +- [ ] **Step 1: Remove the `plan.md` line from `.gitignore`** + +Delete the line containing exactly `plan.md` (between `.python-version`/`.venv` +and `/site/`). Leave every other line untouched. + +- [ ] **Step 2: Verify no bundle plan.md is ignored** + +```bash +git check-ignore planning/changes/active/2026-06-13.01-portable-planning-convention/plan.md; echo "exit=$?" +``` +Expected: no output, `exit=1` (not ignored). + +- [ ] **Step 3: Add the `docs-build` recipe to `Justfile`** + +Insert this recipe immediately before the existing `docs-deploy` recipe: + +```makefile +# Strict local docs build (no deploy). Mirrors CI's link/strict checks. +docs-build: + uvx --with-requirements docs/requirements.txt mkdocs build --strict +``` + +- [ ] **Step 4: Verify the recipe runs** + +```bash +just docs-build +``` +Expected: `mkdocs build --strict` exits 0 (site builds, no warnings-as-errors). + +- [ ] **Step 5: Commit** + +```bash +git add .gitignore Justfile +git commit -m "chore: untrack bundle plan.md and add docs-build gate + +Co-Authored-By: Claude Opus 4.8 (1M context) " +``` + +--- + +### Task 3: Migrate the 15 spec/plan pairs into archived bundles + +**Files (per pair):** +- Move: `planning/specs/--design.md` → `planning/changes/archive/.NN-/design.md` +- Move: `planning/plans/-.md` → `planning/changes/archive/.NN-/plan.md` +- Delete: `planning/specs/.gitkeep`, `planning/plans/.gitkeep` (after dirs are empty) + +This is the bundle table. `.NN` is the git first-commit order. `pr` stays +`null`; the PR reference (where one exists) goes in `outcome` per the +lite-bootstrap house style. The eight `2026-05-31` items predate PR numbering +(direct pre-1.0 merges), so they carry a factual bootstrap note. + +| Bundle dir (`.NN-`) | Source slug (`-`) | `outcome` value | +|---|---|---| +| `2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper` | `2026-05-31-bmad-to-superpowers-migration-and-httpx2-wrapper` | shipped in the pre-1.0 bootstrap (retired BMad; added the httpx HTTP-client wrapper) | +| `2026-05-31.02-drop-doctor` | `2026-05-31-drop-doctor` | shipped in the pre-1.0 bootstrap (removed the doctor command) | +| `2026-05-31.03-settings-aliaschoices` | `2026-05-31-settings-aliaschoices` | shipped in the pre-1.0 bootstrap (pydantic-settings AliasChoices) | +| `2026-05-31.04-usecase-callable` | `2026-05-31-usecase-callable` | shipped in the pre-1.0 bootstrap (callable use-case) | +| `2026-05-31.05-ioc-idiomatic-modern-di-typer` | `2026-05-31-ioc-idiomatic-modern-di-typer` | shipped in the pre-1.0 bootstrap (modern-di + Typer IoC) | +| `2026-05-31.06-cli-overlay-simplification` | `2026-05-31-cli-overlay-simplification` | shipped in the pre-1.0 bootstrap (model_copy CLI overlay) | +| `2026-05-31.07-strategy-no-bump-cleanup` | `2026-05-31-strategy-no-bump-cleanup` | shipped in the pre-1.0 bootstrap (no-bump return path) | +| `2026-05-31.08-v0-1-0-release-prep` | `2026-05-31-v0-1-0-release-prep` | shipped in the pre-1.0 bootstrap (0.1.0 release prep) | +| `2026-06-07.01-httpware-migration` | `2026-06-07-httpware-migration` | shipped (#2) | +| `2026-06-08.01-httpware-decoder-adoption` | `2026-06-08-httpware-decoder-adoption` | shipped (#3) | +| `2026-06-08.02-github-provider` | `2026-06-08-github-provider` | shipped (#4) | +| `2026-06-08.03-action-yml-composite-wrapper` | `2026-06-08-action-yml-composite-wrapper` | shipped (#10) | +| `2026-06-09.01-mkdocs-github-actions` | `2026-06-09-mkdocs-github-actions` | shipped (#14) | +| `2026-06-09.02-dry-run-flag` | `2026-06-09-dry-run-flag` | shipped (#15) | +| `2026-06-09.03-action-yml-dry-run` | `2026-06-09-action-yml-dry-run` | shipped (#16) | + +- [ ] **Step 1: `git mv` every pair into its bundle folder** + +For each row, derive `` and `` from the bundle dir, then: + +```bash +mkdir -p "planning/changes/archive/.NN-" +git mv "planning/specs/--design.md" "planning/changes/archive/.NN-/design.md" +git mv "planning/plans/-.md" "planning/changes/archive/.NN-/plan.md" +``` + +Concretely, the 30 moves are (note `2026-05-31.01`'s plan keeps its full +descriptive source name): + +```bash +B=planning/changes/archive +S=planning/specs +P=planning/plans +for row in \ + "2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper|2026-05-31-bmad-to-superpowers-migration-and-httpx2-wrapper" \ + "2026-05-31.02-drop-doctor|2026-05-31-drop-doctor" \ + "2026-05-31.03-settings-aliaschoices|2026-05-31-settings-aliaschoices" \ + "2026-05-31.04-usecase-callable|2026-05-31-usecase-callable" \ + "2026-05-31.05-ioc-idiomatic-modern-di-typer|2026-05-31-ioc-idiomatic-modern-di-typer" \ + "2026-05-31.06-cli-overlay-simplification|2026-05-31-cli-overlay-simplification" \ + "2026-05-31.07-strategy-no-bump-cleanup|2026-05-31-strategy-no-bump-cleanup" \ + "2026-05-31.08-v0-1-0-release-prep|2026-05-31-v0-1-0-release-prep" \ + "2026-06-07.01-httpware-migration|2026-06-07-httpware-migration" \ + "2026-06-08.01-httpware-decoder-adoption|2026-06-08-httpware-decoder-adoption" \ + "2026-06-08.02-github-provider|2026-06-08-github-provider" \ + "2026-06-08.03-action-yml-composite-wrapper|2026-06-08-action-yml-composite-wrapper" \ + "2026-06-09.01-mkdocs-github-actions|2026-06-09-mkdocs-github-actions" \ + "2026-06-09.02-dry-run-flag|2026-06-09-dry-run-flag" \ + "2026-06-09.03-action-yml-dry-run|2026-06-09-action-yml-dry-run" \ +; do + dir="${row%%|*}"; src="${row##*|}" + mkdir -p "$B/$dir" + git mv "$S/$src-design.md" "$B/$dir/design.md" + git mv "$P/$src.md" "$B/$dir/plan.md" +done +``` + +- [ ] **Step 2: Verify all 15 source pairs moved and dirs are empty** + +```bash +ls planning/specs planning/plans # expect only .gitkeep in each +find planning/changes/archive -name design.md | wc -l # expect 15 +find planning/changes/archive -name plan.md | wc -l # expect 15 +``` + +- [ ] **Step 3: Prepend frontmatter to each `design.md`** + +For every bundle, insert this block at the very top of `design.md`, filling +`date`, `slug`, and `outcome` from the table above (slug = the bundle slug +without the `.NN-` prefix): + +```yaml +--- +status: shipped +date: +slug: +supersedes: null +superseded_by: null +pr: null +outcome: +--- + +``` + +The existing `# Title` / `**Date:**` / `**Status:**` body stays unchanged below +the block. None of the `outcome` values has a space-preceded `#`, so none needs +quoting. + +- [ ] **Step 4: Prepend frontmatter to each `plan.md`** + +For every bundle, insert this block at the very top of `plan.md`: + +```yaml +--- +status: shipped +date: +slug: +spec: +pr: null +--- + +``` + +- [ ] **Step 5: Verify frontmatter parses as YAML on all 30 files** + +```bash +python3 - <<'PY' +import pathlib, yaml, sys +bad = 0 +for f in pathlib.Path("planning/changes/archive").rglob("*.md"): + text = f.read_text() + if not text.startswith("---\n"): + print("NO FRONTMATTER:", f); bad += 1; continue + fm = text.split("---\n", 2)[1] + try: + d = yaml.safe_load(fm) + assert d["status"] == "shipped" and d["slug"] and d["date"] + except Exception as e: + print("BAD:", f, e); bad += 1 +print("checked", len(list(pathlib.Path('planning/changes/archive').rglob('*.md'))), "files, bad =", bad) +sys.exit(1 if bad else 0) +PY +``` +Expected: `bad = 0`, exit 0. + +- [ ] **Step 6: Verify `git mv` preserved history on a sample file** + +```bash +git log --follow --oneline -- planning/changes/archive/2026-06-08.02-github-provider/design.md | tail -1 +``` +Expected: a commit predating this branch (history followed through the rename). + +- [ ] **Step 7: Remove the now-empty source dirs** + +```bash +git rm planning/specs/.gitkeep planning/plans/.gitkeep +rmdir planning/specs planning/plans 2>/dev/null || true +``` + +- [ ] **Step 8: Commit** + +```bash +git add -A planning/changes planning/specs planning/plans +git commit -m "docs(planning): migrate 15 spec/plan pairs into archived change bundles + +Co-Authored-By: Claude Opus 4.8 (1M context) " +``` + +--- + +### Task 4: Write `planning/README.md` + +**Files:** +- Create: `planning/README.md` + +The `## Conventions` block is byte-identical to lite-bootstrap; the `## Index` +is semvertag-specific. + +- [ ] **Step 1: Write the file** + +```markdown +# Planning + +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*. + +## Conventions + +> This section is the portable convention — identical across the +> modern-python repos. The Index below is repo-specific. To adopt elsewhere, +> copy this section plus [`_templates/`](_templates/) and point that repo's +> `CLAUDE.md` Workflow + truth home at it. + +### Two axes, never mixed + +- **`architecture/` (repo root) — the present.** One file per capability, + living prose, updated whenever a change ships. The truth home. +- **`planning/changes/` — the past-and-pending.** One folder per change, + frozen once shipped. + +Shipping a change **promotes** its conclusions into the affected +`architecture/.md` by hand, then archives the bundle. That +hand-edit is what keeps `architecture/` true; the archived bundle carries the +*why*. + +### Change bundles + +A change is a folder `changes/active/YYYY-MM-DD.NN-/`: + +- `YYYY-MM-DD` — proposal date; `.NN` — zero-padded intra-day counter + (`.01`, `.02`, …) that breaks same-date ties so the timeline sorts stably. +- `` — kebab-case description, not a story ID. + +On merge the folder moves to `changes/archive/` with `status: shipped`, `pr:`, +and `outcome:` filled, and its line moves from **Active** to **Archived** in +the Index below. + +### Three lanes + +| Lane | Artifacts | Use when | +|------|-----------|----------| +| **Full** | `design.md` + `plan.md` | design judgment; new file/module; public-API change; cross-cutting/multi-file; non-trivial test design | +| **Lightweight** | `change.md` | small-but-real: ≲30 LOC net, ≤2 files, no new file, no public-API change, single straightforward test | +| **Tiny** | none — conventional commit | typo, dep bump, linter/formatter/CI tweak, mechanical rename, single-line config | + +Heavier lane wins on ambiguity. A `change.md` that outgrows its lane splits +into `design.md` + `plan.md`. + +### Artifacts at a glance + +- **`design.md`** — the spec: the *thinking* (why, design, trade-offs, scope). +- **`plan.md`** — the plan: the *sequencing* (the executor's task checklist). +- **`change.md`** — both, condensed, for the lightweight lane. +- **`releases/.md`** — per-release user-facing notes. +- **`audits/-.md`** — findings from a code/docs/bug-hunt sweep; + spawns fix changes. +- **`retros/-.md`** — what we learned after a body of work. +- **`deferred.md`** — real-but-unscheduled items, each with a revisit trigger. + +Templates live in [`_templates/`](_templates/). + +### Frontmatter + +`design.md` / `change.md`: `status` (draft|approved|shipped|superseded), +`date`, `slug`, `supersedes`, `superseded_by`, `pr`, `outcome`. +`plan.md`: `status`, `date`, `slug`, `spec`, `pr`. Files in `architecture/` +carry **no** frontmatter — living prose, dated by git. + +## Index + +### Active + +- **[portable-planning-convention](changes/active/2026-06-13.01-portable-planning-convention/design.md)** + (2026-06-13) — Adopt the portable two-axis convention: `architecture/` truth + home + `changes/` bundles, migrate the 15 spec/plan pairs, fresh Index. + +### Archived (shipped) + +- **[action-yml-dry-run](changes/archive/2026-06-09.03-action-yml-dry-run/design.md)** + (#16, 2026-06-09) — Composite action `dry-run` input wired to the CLI flag. +- **[dry-run-flag](changes/archive/2026-06-09.02-dry-run-flag/design.md)** + (#15, 2026-06-09) — `--dry-run` CLI flag: compute the next tag without + creating it. +- **[mkdocs-github-actions](changes/archive/2026-06-09.01-mkdocs-github-actions/design.md)** + (#14, 2026-06-09) — Docs hosting via MkDocs + GitHub Actions + Pages. +- **[action-yml-composite-wrapper](changes/archive/2026-06-08.03-action-yml-composite-wrapper/design.md)** + (#10, 2026-06-08) — `action.yml` composite GitHub Action wrapping the CLI. +- **[github-provider](changes/archive/2026-06-08.02-github-provider/design.md)** + (#4, 2026-06-08) — GitHub provider alongside GitLab. +- **[httpware-decoder-adoption](changes/archive/2026-06-08.01-httpware-decoder-adoption/design.md)** + (#3, 2026-06-08) — Adopt the httpware response decoder in the providers. +- **[httpware-migration](changes/archive/2026-06-07.01-httpware-migration/design.md)** + (#2, 2026-06-07) — Migrate the HTTP client onto httpware. +- **[v0-1-0-release-prep](changes/archive/2026-05-31.08-v0-1-0-release-prep/design.md)** + (2026-05-31) — Pre-1.0 release preparation. +- **[strategy-no-bump-cleanup](changes/archive/2026-05-31.07-strategy-no-bump-cleanup/design.md)** + (2026-05-31) — Clean up the strategies' no-bump return path. +- **[cli-overlay-simplification](changes/archive/2026-05-31.06-cli-overlay-simplification/design.md)** + (2026-05-31) — Replace the CLI-overlay machinery with `model_copy`. +- **[ioc-idiomatic-modern-di-typer](changes/archive/2026-05-31.05-ioc-idiomatic-modern-di-typer/design.md)** + (2026-05-31) — Idiomatic modern-di + Typer IoC wiring. +- **[usecase-callable](changes/archive/2026-05-31.04-usecase-callable/design.md)** + (2026-05-31) — Make the use-case a callable. +- **[settings-aliaschoices](changes/archive/2026-05-31.03-settings-aliaschoices/design.md)** + (2026-05-31) — pydantic-settings `AliasChoices` for env/CLI names. +- **[drop-doctor](changes/archive/2026-05-31.02-drop-doctor/design.md)** + (2026-05-31) — Remove the `doctor` command. +- **[bmad-to-superpowers-migration-and-httpx2-wrapper](changes/archive/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/design.md)** + (2026-05-31) — Retire BMad for Superpowers; add the HTTP-client wrapper. +``` + +- [ ] **Step 2: Verify the Conventions block is byte-identical to lite-bootstrap** + +```bash +diff <(gh api "repos/modern-python/lite-bootstrap/contents/planning/README.md?ref=main" --jq '.content' | base64 -d | sed -n '/^## Conventions$/,/^## Index$/p' | sed '$d') \ + <(sed -n '/^## Conventions$/,/^## Index$/p' planning/README.md | sed '$d') \ + && echo "Conventions block: identical" +``` +Expected: `Conventions block: identical`, no diff output. + +- [ ] **Step 3: Verify every Index link resolves** + +```bash +grep -oE '\(changes/[^)]+\)' planning/README.md | tr -d '()' | while read p; do + test -f "planning/$p" || echo "BROKEN: $p" +done; echo "link check done" +``` +Expected: only `link check done` (no `BROKEN` lines). + +- [ ] **Step 4: Commit** + +```bash +git add planning/README.md +git commit -m "docs(planning): add README with portable conventions and index + +Co-Authored-By: Claude Opus 4.8 (1M context) " +``` + +--- + +### Task 5: Seed `architecture/` with three capability files + +**Files:** +- Create: `architecture/strategies.md` +- Create: `architecture/providers.md` +- Create: `architecture/cli.md` + +Each file is **living prose, no frontmatter** (dated by git). Write it by +**reading the listed source files first**, then stating the invariants below as +dense, factual prose — match the tone of lite-bootstrap's `architecture/*.md` +(present-tense, names the symbols and files, states the *why* of each +invariant). Do not copy code; describe contracts. Every bullet below is a fact +the file must capture; expand each into prose, correcting any detail the source +contradicts. + +- [ ] **Step 1: Write `architecture/strategies.md`** + +Read first: `semvertag/strategies/_base.py`, `semvertag/strategies/branch_prefix.py`, +`semvertag/strategies/conventional_commits.py`, `semvertag/strategies/__init__.py`, +`semvertag/_commit_parse.py`. + +Capture, with a `# Strategies` H1 and one section per topic: + +- **What a strategy is.** A strategy decides the next semver tag from the + current tags plus repository signal (branch name or commit messages). The + base contract lives in `strategies/_base.py`; name the base type and the + method(s) every strategy implements and what they receive/return. +- **`branch-prefix`** (`branch_prefix.py`) — maps a branch-name prefix to a + bump level; state the default prefix→level mapping and that it recognizes a + GitHub PR merge-commit subject under the defaults (the `#7` fix). Note the + configured-prefix source. +- **`conventional-commits`** (`conventional_commits.py`) — derives the bump + from Conventional Commits parsed by `_commit_parse.py`; state the + type→level mapping (`feat`→minor, `fix`→patch, breaking→major) and how a + commit range is scanned. +- **No-bump path** — when no signal warrants a bump the strategy yields a + no-bump result rather than a forced increment (the `strategy-no-bump-cleanup` + change). State exactly what is returned and how callers detect it. +- **`_commit_parse.py`** — the single place commit subjects/bodies are parsed; + note what it extracts (type, scope, breaking marker). + +- [ ] **Step 2: Write `architecture/providers.md`** + +Read first: `semvertag/providers/_base.py`, `semvertag/providers/gitlab.py`, +`semvertag/providers/github.py`, `semvertag/providers/_errors.py`, +`semvertag/_link_pagination.py`, `semvertag/_redact.py`, `semvertag/_errors.py`. + +Capture, with a `# Providers` H1 and one section per topic: + +- **What a provider is.** A provider is the API adapter for one forge; the base + contract is `providers/_base.py`. Name the abstract operations (list tags, + read commits/branch, create tag) and their signatures. +- **GitLab** (`gitlab.py`) and **GitHub** (`github.py`) — one section each: + the endpoints used, how tags are created, and any auth/header handling. +- **HTTP client (httpware).** Requests go through the httpware-based client + (the `httpware-migration` + `httpware-decoder-adoption` changes); state how + the client is constructed and that responses are decoded via the httpware + decoder. +- **Link-header pagination** (`_link_pagination.py`) — how `Link` headers are + followed to page through tags/commits; name the function and where providers + call it. +- **Secret redaction** (`_redact.py`) — tokens are redacted from errors/log + output; state what is redacted and where it is applied. +- **Errors** (`providers/_errors.py`, `_errors.py`) — the provider error types + and what maps to them (auth failure, not-found, rate-limit, etc.). + +- [ ] **Step 3: Write `architecture/cli.md`** + +Read first: `semvertag/__main__.py`, `semvertag/ioc.py`, `semvertag/_settings.py`, +`semvertag/_use_case.py`, `semvertag/_output.py`, `semvertag/_types.py`, +`action.yml`, `templates/semvertag.yml`. + +Capture, with a `# CLI` H1 and one section per topic: + +- **Entry point** (`__main__.py`) — the Typer app, the command(s) it exposes, + and the `--dry-run` flag (compute the next tag without creating it; the + `dry-run-flag` change). State what `--dry-run` short-circuits. +- **IoC wiring** (`ioc.py`) — the modern-di container; what it provides + (settings, provider, strategy, use-case) and how the CLI resolves the + use-case (the `ioc-idiomatic-modern-di-typer` change). Note the eager-DI + None-field guard if present. +- **Settings** (`_settings.py`) — pydantic-settings model; env + CLI sources, + `AliasChoices` for alternate names (the `settings-aliaschoices` change), and + the `apply_cli_overlay` overlay built on `model_copy(update=...)` (the + `cli-overlay-simplification` change). State precedence (CLI over env over + default). +- **Use-case** (`_use_case.py`) — the callable that wires provider + strategy + to produce/create the tag (the `usecase-callable` change); state its inputs + and return. +- **Output** (`_output.py`) — how results are rendered (human vs machine + output, if both). +- **Distribution wrappers** — `action.yml` is the composite GitHub Action + wrapping the CLI (the `action-yml-composite-wrapper` + `action-yml-dry-run` + changes); `templates/semvertag.yml` is the GitLab CI component. State that + both shell out to the same CLI and pass `--dry-run` through. + +- [ ] **Step 4: Verify the files exist, are non-empty, and carry no frontmatter** + +```bash +for f in strategies providers cli; do + test -s "architecture/$f.md" || echo "MISSING/EMPTY: $f.md" + head -1 "architecture/$f.md" | grep -q '^---$' && echo "HAS FRONTMATTER (remove): $f.md" +done; echo "architecture check done" +``` +Expected: only `architecture check done`. + +- [ ] **Step 5: Verify `mkdocs build --strict` still passes (architecture/ is outside docs_dir)** + +```bash +just docs-build +``` +Expected: exit 0; the published site is unchanged. + +- [ ] **Step 6: Commit** + +```bash +git add architecture +git commit -m "docs(architecture): seed strategies, providers, and cli truth files + +Co-Authored-By: Claude Opus 4.8 (1M context) " +``` + +--- + +### Task 6: Rewrite the `CLAUDE.md` Workflow section + +**Files:** +- Modify: `CLAUDE.md` + +Replace the current `## Workflow` section (the Superpowers/brainstorm→plan→TDD +list that points at `planning/specs/` + `planning/plans/`) with the +bundle-convention text below. Leave `## Commit messages`, `## Reference +directories`, and `## What the codebase ships` untouched. + +- [ ] **Step 1: Replace the `## Workflow` section body** + +Use this text (between the `## Workflow` heading and the next `##` heading): + +```markdown +## 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/active/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. On merge the bundle moves to +`planning/changes/archive/` with `status: shipped`, `pr:`, and `outcome:` +filled, **and the change promotes its conclusions into the affected +`architecture/.md`** — that hand-edit is what keeps `architecture/` +true. + +**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 +`docs/superpowers/specs/` or `docs/superpowers/plans/`, use the change bundle +under `planning/changes/active/` here instead. +``` + +- [ ] **Step 2: Verify no stale references remain** + +```bash +grep -rn 'planning/specs\|planning/plans' CLAUDE.md README.md mkdocs.yml docs/ 2>/dev/null && echo "STALE FOUND" || echo "no stale references" +``` +Expected: `no stale references`. + +- [ ] **Step 3: Commit** + +```bash +git add CLAUDE.md +git commit -m "docs: rewrite CLAUDE.md workflow for the two-axis convention + +Co-Authored-By: Claude Opus 4.8 (1M context) " +``` + +--- + +### Task 7: Final verification sweep + +**Files:** none (verification only). + +- [ ] **Step 1: Repo-wide stale-pointer sweep** + +```bash +grep -rn 'planning/specs\|planning/plans' . --include='*.md' --include='*.yml' --include='*.toml' \ + --exclude-dir=.git --exclude-dir=.venv --exclude-dir=_archive --exclude-dir=_autosemver_reference \ + && echo "STALE FOUND" || echo "no stale references" +``` +Expected: `no stale references`. (The `_archive/` and `_autosemver_reference/` +trees are excluded — do not edit them.) + +- [ ] **Step 2: Lint** + +```bash +just lint-ci +``` +Expected: eof-fixer, ruff format check, ruff check, ty — all clean. + +- [ ] **Step 3: Tests** + +```bash +just test +``` +Expected: full suite passes, coverage unchanged from `main` (no runtime code +touched). + +- [ ] **Step 4: Docs build** + +```bash +just docs-build +``` +Expected: `mkdocs build --strict` exits 0. + +- [ ] **Step 5: Confirm final tree shape** + +```bash +test ! -d planning/specs && test ! -d planning/plans && echo "old dirs gone" +find architecture -name '*.md' | wc -l # expect 3 +find planning/changes/archive -name design.md | wc -l # expect 15 +ls planning/changes/active # expect the convention bundle + .gitkeep +``` +Expected: `old dirs gone`, `3`, `15`, and the active bundle present. + +--- + +## On merge + +Move `planning/changes/active/2026-06-13.01-portable-planning-convention/` → +`planning/changes/archive/`, fill its `design.md` frontmatter (`status: +shipped`, `pr:` = this PR's number, `outcome:`) and `plan.md` (`status: +shipped`, `pr:`), and shift its README Index line from **Active** to +**Archived**. No `architecture/` promotion — this change defines the +convention, not a library capability. diff --git a/planning/changes/archive/.gitkeep b/planning/changes/archive/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/planning/specs/2026-05-31-bmad-to-superpowers-migration-and-httpx2-wrapper-design.md b/planning/changes/archive/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/design.md similarity index 98% rename from planning/specs/2026-05-31-bmad-to-superpowers-migration-and-httpx2-wrapper-design.md rename to planning/changes/archive/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/design.md index c5a20db..ce3c1d3 100644 --- a/planning/specs/2026-05-31-bmad-to-superpowers-migration-and-httpx2-wrapper-design.md +++ b/planning/changes/archive/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: bmad-to-superpowers-migration-and-httpx2-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 **Date:** 2026-05-31 diff --git a/planning/plans/2026-05-31-bmad-to-superpowers-migration-and-httpx2-wrapper.md b/planning/changes/archive/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/plan.md similarity index 99% rename from planning/plans/2026-05-31-bmad-to-superpowers-migration-and-httpx2-wrapper.md rename to planning/changes/archive/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/plan.md index 723fcb7..fb9057a 100644 --- a/planning/plans/2026-05-31-bmad-to-superpowers-migration-and-httpx2-wrapper.md +++ b/planning/changes/archive/2026-05-31.01-bmad-to-superpowers-migration-and-httpx2-wrapper/plan.md @@ -1,3 +1,11 @@ +--- +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/specs/2026-05-31-drop-doctor-design.md b/planning/changes/archive/2026-05-31.02-drop-doctor/design.md similarity index 98% rename from planning/specs/2026-05-31-drop-doctor-design.md rename to planning/changes/archive/2026-05-31.02-drop-doctor/design.md index 289d995..50b20cd 100644 --- a/planning/specs/2026-05-31-drop-doctor-design.md +++ b/planning/changes/archive/2026-05-31.02-drop-doctor/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: drop-doctor +supersedes: null +superseded_by: null +pr: null +outcome: shipped in the pre-1.0 bootstrap (removed the doctor command) +--- + # Drop `semvertag doctor` subcommand **Date:** 2026-05-31 diff --git a/planning/plans/2026-05-31-drop-doctor.md b/planning/changes/archive/2026-05-31.02-drop-doctor/plan.md similarity index 99% rename from planning/plans/2026-05-31-drop-doctor.md rename to planning/changes/archive/2026-05-31.02-drop-doctor/plan.md index 4d5ff29..6553559 100644 --- a/planning/plans/2026-05-31-drop-doctor.md +++ b/planning/changes/archive/2026-05-31.02-drop-doctor/plan.md @@ -1,3 +1,11 @@ +--- +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/specs/2026-05-31-settings-aliaschoices-design.md b/planning/changes/archive/2026-05-31.03-settings-aliaschoices/design.md similarity index 98% rename from planning/specs/2026-05-31-settings-aliaschoices-design.md rename to planning/changes/archive/2026-05-31.03-settings-aliaschoices/design.md index 88cad47..c204333 100644 --- a/planning/specs/2026-05-31-settings-aliaschoices-design.md +++ b/planning/changes/archive/2026-05-31.03-settings-aliaschoices/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: settings-aliaschoices +supersedes: null +superseded_by: null +pr: null +outcome: shipped in the pre-1.0 bootstrap (pydantic-settings AliasChoices) +--- + # Adopt Pydantic `AliasChoices` in `_settings.py` **Date:** 2026-05-31 diff --git a/planning/plans/2026-05-31-settings-aliaschoices.md b/planning/changes/archive/2026-05-31.03-settings-aliaschoices/plan.md similarity index 99% rename from planning/plans/2026-05-31-settings-aliaschoices.md rename to planning/changes/archive/2026-05-31.03-settings-aliaschoices/plan.md index abae15c..592b521 100644 --- a/planning/plans/2026-05-31-settings-aliaschoices.md +++ b/planning/changes/archive/2026-05-31.03-settings-aliaschoices/plan.md @@ -1,3 +1,11 @@ +--- +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/specs/2026-05-31-usecase-callable-design.md b/planning/changes/archive/2026-05-31.04-usecase-callable/design.md similarity index 98% rename from planning/specs/2026-05-31-usecase-callable-design.md rename to planning/changes/archive/2026-05-31.04-usecase-callable/design.md index 236a4c4..25e9692 100644 --- a/planning/specs/2026-05-31-usecase-callable-design.md +++ b/planning/changes/archive/2026-05-31.04-usecase-callable/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: usecase-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 **Date:** 2026-05-31 diff --git a/planning/plans/2026-05-31-usecase-callable.md b/planning/changes/archive/2026-05-31.04-usecase-callable/plan.md similarity index 99% rename from planning/plans/2026-05-31-usecase-callable.md rename to planning/changes/archive/2026-05-31.04-usecase-callable/plan.md index 1b27e9a..64a021d 100644 --- a/planning/plans/2026-05-31-usecase-callable.md +++ b/planning/changes/archive/2026-05-31.04-usecase-callable/plan.md @@ -1,3 +1,11 @@ +--- +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/specs/2026-05-31-ioc-idiomatic-modern-di-typer-design.md b/planning/changes/archive/2026-05-31.05-ioc-idiomatic-modern-di-typer/design.md similarity index 99% rename from planning/specs/2026-05-31-ioc-idiomatic-modern-di-typer-design.md rename to planning/changes/archive/2026-05-31.05-ioc-idiomatic-modern-di-typer/design.md index 4326549..b6880cc 100644 --- a/planning/specs/2026-05-31-ioc-idiomatic-modern-di-typer-design.md +++ b/planning/changes/archive/2026-05-31.05-ioc-idiomatic-modern-di-typer/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: ioc-idiomatic-modern-di-typer +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` **Date:** 2026-05-31 diff --git a/planning/plans/2026-05-31-ioc-idiomatic-modern-di-typer.md b/planning/changes/archive/2026-05-31.05-ioc-idiomatic-modern-di-typer/plan.md similarity index 99% rename from planning/plans/2026-05-31-ioc-idiomatic-modern-di-typer.md rename to planning/changes/archive/2026-05-31.05-ioc-idiomatic-modern-di-typer/plan.md index d779f71..1970fff 100644 --- a/planning/plans/2026-05-31-ioc-idiomatic-modern-di-typer.md +++ b/planning/changes/archive/2026-05-31.05-ioc-idiomatic-modern-di-typer/plan.md @@ -1,3 +1,11 @@ +--- +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/specs/2026-05-31-cli-overlay-simplification-design.md b/planning/changes/archive/2026-05-31.06-cli-overlay-simplification/design.md similarity index 98% rename from planning/specs/2026-05-31-cli-overlay-simplification-design.md rename to planning/changes/archive/2026-05-31.06-cli-overlay-simplification/design.md index 0945bbc..2141ec8 100644 --- a/planning/specs/2026-05-31-cli-overlay-simplification-design.md +++ b/planning/changes/archive/2026-05-31.06-cli-overlay-simplification/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: cli-overlay-simplification +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` **Date:** 2026-05-31 diff --git a/planning/plans/2026-05-31-cli-overlay-simplification.md b/planning/changes/archive/2026-05-31.06-cli-overlay-simplification/plan.md similarity index 99% rename from planning/plans/2026-05-31-cli-overlay-simplification.md rename to planning/changes/archive/2026-05-31.06-cli-overlay-simplification/plan.md index ff51e43..fbca9cc 100644 --- a/planning/plans/2026-05-31-cli-overlay-simplification.md +++ b/planning/changes/archive/2026-05-31.06-cli-overlay-simplification/plan.md @@ -1,3 +1,11 @@ +--- +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/specs/2026-05-31-strategy-no-bump-cleanup-design.md b/planning/changes/archive/2026-05-31.07-strategy-no-bump-cleanup/design.md similarity index 98% rename from planning/specs/2026-05-31-strategy-no-bump-cleanup-design.md rename to planning/changes/archive/2026-05-31.07-strategy-no-bump-cleanup/design.md index 98183bc..0042a63 100644 --- a/planning/specs/2026-05-31-strategy-no-bump-cleanup-design.md +++ b/planning/changes/archive/2026-05-31.07-strategy-no-bump-cleanup/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: strategy-no-bump-cleanup +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 **Date:** 2026-05-31 diff --git a/planning/plans/2026-05-31-strategy-no-bump-cleanup.md b/planning/changes/archive/2026-05-31.07-strategy-no-bump-cleanup/plan.md similarity index 99% rename from planning/plans/2026-05-31-strategy-no-bump-cleanup.md rename to planning/changes/archive/2026-05-31.07-strategy-no-bump-cleanup/plan.md index 96443af..adc8fa7 100644 --- a/planning/plans/2026-05-31-strategy-no-bump-cleanup.md +++ b/planning/changes/archive/2026-05-31.07-strategy-no-bump-cleanup/plan.md @@ -1,3 +1,11 @@ +--- +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/specs/2026-05-31-v0-1-0-release-prep-design.md b/planning/changes/archive/2026-05-31.08-v0-1-0-release-prep/design.md similarity index 98% rename from planning/specs/2026-05-31-v0-1-0-release-prep-design.md rename to planning/changes/archive/2026-05-31.08-v0-1-0-release-prep/design.md index 1822ef0..9941846 100644 --- a/planning/specs/2026-05-31-v0-1-0-release-prep-design.md +++ b/planning/changes/archive/2026-05-31.08-v0-1-0-release-prep/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-05-31 +slug: v0-1-0-release-prep +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 **Date:** 2026-05-31 diff --git a/planning/plans/2026-05-31-v0-1-0-release-prep.md b/planning/changes/archive/2026-05-31.08-v0-1-0-release-prep/plan.md similarity index 99% rename from planning/plans/2026-05-31-v0-1-0-release-prep.md rename to planning/changes/archive/2026-05-31.08-v0-1-0-release-prep/plan.md index 0da9ada..4a2bb3a 100644 --- a/planning/plans/2026-05-31-v0-1-0-release-prep.md +++ b/planning/changes/archive/2026-05-31.08-v0-1-0-release-prep/plan.md @@ -1,3 +1,11 @@ +--- +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/specs/2026-06-07-httpware-migration-design.md b/planning/changes/archive/2026-06-07.01-httpware-migration/design.md similarity index 99% rename from planning/specs/2026-06-07-httpware-migration-design.md rename to planning/changes/archive/2026-06-07.01-httpware-migration/design.md index 7f0eba9..71f4bf0 100644 --- a/planning/specs/2026-06-07-httpware-migration-design.md +++ b/planning/changes/archive/2026-06-07.01-httpware-migration/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-07 +slug: httpware-migration +supersedes: null +superseded_by: null +pr: null +outcome: shipped (#2) +--- + # httpware migration — design spec **Date:** 2026-06-07 diff --git a/planning/plans/2026-06-07-httpware-migration.md b/planning/changes/archive/2026-06-07.01-httpware-migration/plan.md similarity index 99% rename from planning/plans/2026-06-07-httpware-migration.md rename to planning/changes/archive/2026-06-07.01-httpware-migration/plan.md index e034dd0..5b56e08 100644 --- a/planning/plans/2026-06-07-httpware-migration.md +++ b/planning/changes/archive/2026-06-07.01-httpware-migration/plan.md @@ -1,3 +1,11 @@ +--- +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/specs/2026-06-08-httpware-decoder-adoption-design.md b/planning/changes/archive/2026-06-08.01-httpware-decoder-adoption/design.md similarity index 99% rename from planning/specs/2026-06-08-httpware-decoder-adoption-design.md rename to planning/changes/archive/2026-06-08.01-httpware-decoder-adoption/design.md index 189e679..8321c3c 100644 --- a/planning/specs/2026-06-08-httpware-decoder-adoption-design.md +++ b/planning/changes/archive/2026-06-08.01-httpware-decoder-adoption/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-08 +slug: httpware-decoder-adoption +supersedes: null +superseded_by: null +pr: null +outcome: shipped (#3) +--- + # httpware decoder adoption — design spec **Date:** 2026-06-08 diff --git a/planning/plans/2026-06-08-httpware-decoder-adoption.md b/planning/changes/archive/2026-06-08.01-httpware-decoder-adoption/plan.md similarity index 99% rename from planning/plans/2026-06-08-httpware-decoder-adoption.md rename to planning/changes/archive/2026-06-08.01-httpware-decoder-adoption/plan.md index 1df45c0..86ff28f 100644 --- a/planning/plans/2026-06-08-httpware-decoder-adoption.md +++ b/planning/changes/archive/2026-06-08.01-httpware-decoder-adoption/plan.md @@ -1,3 +1,11 @@ +--- +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/specs/2026-06-08-github-provider-design.md b/planning/changes/archive/2026-06-08.02-github-provider/design.md similarity index 99% rename from planning/specs/2026-06-08-github-provider-design.md rename to planning/changes/archive/2026-06-08.02-github-provider/design.md index 5e58a9e..2f453cf 100644 --- a/planning/specs/2026-06-08-github-provider-design.md +++ b/planning/changes/archive/2026-06-08.02-github-provider/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-08 +slug: github-provider +supersedes: null +superseded_by: null +pr: null +outcome: shipped (#4) +--- + # GitHub provider — design spec **Date:** 2026-06-08 diff --git a/planning/plans/2026-06-08-github-provider.md b/planning/changes/archive/2026-06-08.02-github-provider/plan.md similarity index 99% rename from planning/plans/2026-06-08-github-provider.md rename to planning/changes/archive/2026-06-08.02-github-provider/plan.md index 19ba401..c3956c3 100644 --- a/planning/plans/2026-06-08-github-provider.md +++ b/planning/changes/archive/2026-06-08.02-github-provider/plan.md @@ -1,3 +1,11 @@ +--- +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/specs/2026-06-08-action-yml-composite-wrapper-design.md b/planning/changes/archive/2026-06-08.03-action-yml-composite-wrapper/design.md similarity index 99% rename from planning/specs/2026-06-08-action-yml-composite-wrapper-design.md rename to planning/changes/archive/2026-06-08.03-action-yml-composite-wrapper/design.md index 8e471ff..63637ec 100644 --- a/planning/specs/2026-06-08-action-yml-composite-wrapper-design.md +++ b/planning/changes/archive/2026-06-08.03-action-yml-composite-wrapper/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-08 +slug: action-yml-composite-wrapper +supersedes: null +superseded_by: null +pr: null +outcome: shipped (#10) +--- + # action.yml composite wrapper — design spec **Date:** 2026-06-08 diff --git a/planning/plans/2026-06-08-action-yml-composite-wrapper.md b/planning/changes/archive/2026-06-08.03-action-yml-composite-wrapper/plan.md similarity index 99% rename from planning/plans/2026-06-08-action-yml-composite-wrapper.md rename to planning/changes/archive/2026-06-08.03-action-yml-composite-wrapper/plan.md index 7576547..0b9d707 100644 --- a/planning/plans/2026-06-08-action-yml-composite-wrapper.md +++ b/planning/changes/archive/2026-06-08.03-action-yml-composite-wrapper/plan.md @@ -1,3 +1,11 @@ +--- +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/specs/2026-06-09-mkdocs-github-actions-design.md b/planning/changes/archive/2026-06-09.01-mkdocs-github-actions/design.md similarity index 98% rename from planning/specs/2026-06-09-mkdocs-github-actions-design.md rename to planning/changes/archive/2026-06-09.01-mkdocs-github-actions/design.md index 322a7c6..bbba1fa 100644 --- a/planning/specs/2026-06-09-mkdocs-github-actions-design.md +++ b/planning/changes/archive/2026-06-09.01-mkdocs-github-actions/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-09 +slug: mkdocs-github-actions +supersedes: null +superseded_by: null +pr: null +outcome: shipped (#14) +--- + # mkdocs deploy via GitHub Actions — design **Status:** approved diff --git a/planning/plans/2026-06-09-mkdocs-github-actions.md b/planning/changes/archive/2026-06-09.01-mkdocs-github-actions/plan.md similarity index 99% rename from planning/plans/2026-06-09-mkdocs-github-actions.md rename to planning/changes/archive/2026-06-09.01-mkdocs-github-actions/plan.md index 264d97e..274d963 100644 --- a/planning/plans/2026-06-09-mkdocs-github-actions.md +++ b/planning/changes/archive/2026-06-09.01-mkdocs-github-actions/plan.md @@ -1,3 +1,11 @@ +--- +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/specs/2026-06-09-dry-run-flag-design.md b/planning/changes/archive/2026-06-09.02-dry-run-flag/design.md similarity index 98% rename from planning/specs/2026-06-09-dry-run-flag-design.md rename to planning/changes/archive/2026-06-09.02-dry-run-flag/design.md index 6c6a854..6362668 100644 --- a/planning/specs/2026-06-09-dry-run-flag-design.md +++ b/planning/changes/archive/2026-06-09.02-dry-run-flag/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-09 +slug: dry-run-flag +supersedes: null +superseded_by: null +pr: null +outcome: shipped (#15) +--- + # semvertag `--dry-run` flag — design **Status:** approved diff --git a/planning/plans/2026-06-09-dry-run-flag.md b/planning/changes/archive/2026-06-09.02-dry-run-flag/plan.md similarity index 99% rename from planning/plans/2026-06-09-dry-run-flag.md rename to planning/changes/archive/2026-06-09.02-dry-run-flag/plan.md index c30a8d5..914e080 100644 --- a/planning/plans/2026-06-09-dry-run-flag.md +++ b/planning/changes/archive/2026-06-09.02-dry-run-flag/plan.md @@ -1,3 +1,11 @@ +--- +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/specs/2026-06-09-action-yml-dry-run-design.md b/planning/changes/archive/2026-06-09.03-action-yml-dry-run/design.md similarity index 98% rename from planning/specs/2026-06-09-action-yml-dry-run-design.md rename to planning/changes/archive/2026-06-09.03-action-yml-dry-run/design.md index aa0cdb7..8b125c1 100644 --- a/planning/specs/2026-06-09-action-yml-dry-run-design.md +++ b/planning/changes/archive/2026-06-09.03-action-yml-dry-run/design.md @@ -1,3 +1,13 @@ +--- +status: shipped +date: 2026-06-09 +slug: action-yml-dry-run +supersedes: null +superseded_by: null +pr: null +outcome: shipped (#16) +--- + # action.yml `dry-run` input + side-effect-free action-smoke — design **Status:** approved diff --git a/planning/plans/2026-06-09-action-yml-dry-run.md b/planning/changes/archive/2026-06-09.03-action-yml-dry-run/plan.md similarity index 99% rename from planning/plans/2026-06-09-action-yml-dry-run.md rename to planning/changes/archive/2026-06-09.03-action-yml-dry-run/plan.md index e28c500..d1b0529 100644 --- a/planning/plans/2026-06-09-action-yml-dry-run.md +++ b/planning/changes/archive/2026-06-09.03-action-yml-dry-run/plan.md @@ -1,3 +1,11 @@ +--- +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/deferred.md b/planning/deferred.md new file mode 100644 index 0000000..f7f1770 --- /dev/null +++ b/planning/deferred.md @@ -0,0 +1,11 @@ +# Deferred Work + +Items raised in reviews or audits that are real but not actionable now. +Each is parked here with the reason it's deferred and the concrete trigger +that should bring it back. This is the long-tail register — not a backlog +of planned work. When an item is picked up it graduates to a spec/plan +bundle in [`changes/active/`](changes/active/); see [CLAUDE.md](../CLAUDE.md#workflow). + +## Open + +_None._ diff --git a/planning/releases/0.2.0.md b/planning/releases/0.2.0.md index fe5b02e..34fd96f 100644 --- a/planning/releases/0.2.0.md +++ b/planning/releases/0.2.0.md @@ -56,10 +56,10 @@ The `semvertag` CLI surface, exit codes, environment variables (`SEMVERTAG_*`), Two design+implementation cycles fed this release: - **httpware migration** — replace the in-tree HTTP stack with `httpware.Client` + `httpware.Retry`. - - Spec: [`planning/specs/2026-06-07-httpware-migration-design.md`](../specs/2026-06-07-httpware-migration-design.md) - - Plan: [`planning/plans/2026-06-07-httpware-migration.md`](../plans/2026-06-07-httpware-migration.md) + - Spec: [`planning/changes/archive/2026-06-07.01-httpware-migration/design.md`](../changes/archive/2026-06-07.01-httpware-migration/design.md) + - Plan: [`planning/changes/archive/2026-06-07.01-httpware-migration/plan.md`](../changes/archive/2026-06-07.01-httpware-migration/plan.md) - PR [#2](https://github.com/modern-python/semvertag/pull/2) - **Decoder adoption** — switch the three GETs to use `response_model=` / `send_with_response`, delete the in-tree validator helpers. - - Spec: [`planning/specs/2026-06-08-httpware-decoder-adoption-design.md`](../specs/2026-06-08-httpware-decoder-adoption-design.md) - - Plan: [`planning/plans/2026-06-08-httpware-decoder-adoption.md`](../plans/2026-06-08-httpware-decoder-adoption.md) + - Spec: [`planning/changes/archive/2026-06-08.01-httpware-decoder-adoption/design.md`](../changes/archive/2026-06-08.01-httpware-decoder-adoption/design.md) + - Plan: [`planning/changes/archive/2026-06-08.01-httpware-decoder-adoption/plan.md`](../changes/archive/2026-06-08.01-httpware-decoder-adoption/plan.md) - PR [#3](https://github.com/modern-python/semvertag/pull/3) diff --git a/planning/releases/0.3.0.md b/planning/releases/0.3.0.md index 05bc336..dee0ae7 100644 --- a/planning/releases/0.3.0.md +++ b/planning/releases/0.3.0.md @@ -69,8 +69,8 @@ See `docs/providers/github.md` for token scopes, fine-grained vs classic PATs, G ## See also -- Spec: [`planning/specs/2026-06-08-github-provider-design.md`](../specs/2026-06-08-github-provider-design.md) -- Plan: [`planning/plans/2026-06-08-github-provider.md`](../plans/2026-06-08-github-provider.md) +- Spec: [`planning/changes/archive/2026-06-08.02-github-provider/design.md`](../changes/archive/2026-06-08.02-github-provider/design.md) +- Plan: [`planning/changes/archive/2026-06-08.02-github-provider/plan.md`](../changes/archive/2026-06-08.02-github-provider/plan.md) - PR [#4](https://github.com/modern-python/semvertag/pull/4) ## Known follow-ups diff --git a/planning/releases/0.4.0.md b/planning/releases/0.4.0.md index a345c65..5701f85 100644 --- a/planning/releases/0.4.0.md +++ b/planning/releases/0.4.0.md @@ -76,5 +76,5 @@ None. The action is additive; the pure-CLI recipe still works exactly as before ## See also -- Spec: `planning/specs/2026-06-08-action-yml-composite-wrapper-design.md` -- Implementation plan: `planning/plans/2026-06-08-action-yml-composite-wrapper.md` +- Spec: `planning/changes/archive/2026-06-08.03-action-yml-composite-wrapper/design.md` +- Implementation plan: `planning/changes/archive/2026-06-08.03-action-yml-composite-wrapper/plan.md` diff --git a/planning/releases/0.5.0.md b/planning/releases/0.5.0.md index f802d80..1e7619a 100644 --- a/planning/releases/0.5.0.md +++ b/planning/releases/0.5.0.md @@ -43,8 +43,8 @@ The `modern-python/semvertag@v0` action still pins `'semvertag>=0.3.1,<1'` after ## See also -- Spec: `planning/specs/2026-06-09-dry-run-flag-design.md` -- Implementation plan: `planning/plans/2026-06-09-dry-run-flag.md` +- Spec: `planning/changes/archive/2026-06-09.02-dry-run-flag/design.md` +- Implementation plan: `planning/changes/archive/2026-06-09.02-dry-run-flag/plan.md` - Dry-run feature: [PR #15](https://github.com/modern-python/semvertag/pull/15) - Docs site auto-deploy: [PR #14](https://github.com/modern-python/semvertag/pull/14) - Composite action follow-up (the second half of the smell fix): 0.6.0 — see `planning/releases/0.6.0.md` diff --git a/planning/releases/0.6.0.md b/planning/releases/0.6.0.md index 3e60d08..56ce70f 100644 --- a/planning/releases/0.6.0.md +++ b/planning/releases/0.6.0.md @@ -55,8 +55,8 @@ gh release create 0.6.0 \ ## See also -- Spec: `planning/specs/2026-06-09-action-yml-dry-run-design.md` -- Implementation plan: `planning/plans/2026-06-09-action-yml-dry-run.md` +- Spec: `planning/changes/archive/2026-06-09.03-action-yml-dry-run/design.md` +- Implementation plan: `planning/changes/archive/2026-06-09.03-action-yml-dry-run/plan.md` - PR: [#16](https://github.com/modern-python/semvertag/pull/16) - Predecessor (CLI half): [PR #15](https://github.com/modern-python/semvertag/pull/15) — semvertag 0.5.0 - Original smell surfacing: [PR #14](https://github.com/modern-python/semvertag/pull/14) — mkdocs deploy + the action-smoke side-effect discovery