From 2fa940dd5a78e9ebf0ee7a2f8a64a85038e12414 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sun, 28 Jun 2026 21:24:10 +0300 Subject: [PATCH 1/9] docs: spec for adopting the planning convention Brainstorming output: adopt lesnik512/planning-convention v1.1.1 at the repo root and migrate docs/superpowers/ into planning/ change bundles plus a lazy architecture/ truth home. Spec is bootstrapped into the new tree it describes. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../design.md | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 planning/changes/2026-06-28.02-adopt-planning-convention/design.md diff --git a/planning/changes/2026-06-28.02-adopt-planning-convention/design.md b/planning/changes/2026-06-28.02-adopt-planning-convention/design.md new file mode 100644 index 0000000..e5e23d8 --- /dev/null +++ b/planning/changes/2026-06-28.02-adopt-planning-convention/design.md @@ -0,0 +1,156 @@ +--- +summary: Adopt lesnik512/planning-convention v1.1.1 at the repo root and migrate docs/superpowers/ into planning/ change bundles plus a lazy architecture/ truth home. +--- + +# Design: Adopt the planning convention and migrate docs/superpowers/ + +## Summary + +Apply the portable two-axis planning convention from +[`lesnik512/planning-convention`](https://github.com/lesnik512/planning-convention) +(v1.1.1) to this repo via its `APPLY.md` flow, as a **fresh adoption**, and +migrate the existing planning material under `docs/superpowers/` into the +convention's `planning/changes/` bundles. The convention's `architecture/` and +`planning/` trees live at the **repo root**, where the convention's prose, +`index.py` path resolution, and `APPLY.md` §5 all assume they live. + +This spec is itself written into the new tree it describes +(`planning/changes/2026-06-28.02-adopt-planning-convention/`), dogfooding the +convention from the first artifact. + +## Motivation + +Planning material currently lives ad hoc under `docs/superpowers/{specs,plans}/` +with a `**Date:**/**Status:**/**Owner:**/**Scope:**` heading block per file and +no validator, index, or shared lane discipline. Specs and plans are loosely +paired by date/slug and are not machine-checkable. Adopting the canonical +convention gives the org a single, portable, agent-friendly planning model +(shared across every `modern-python` repo): lean frontmatter, a `check-planning` +validator, a generated index, a Quick-path lane decision, and a living +`architecture/` truth home. + +## Decisions (ruled by the maintainer during brainstorming) + +1. **Location: repo root.** Create `/planning/` and `/architecture/`; migrate and + delete `docs/superpowers/`. Matches the convention verbatim with no prose + rewrites. Nothing lands under `docs/`, so MkDocs never publishes it and the + `exclude_docs: superpowers/` rule is retired. +2. **community-health bundle: `change.md`.** The `org-community-health-defaults` + plan (06-24) has no design doc; the bundle is given a condensed `change.md` as + its spec. The substantial shipped `plan.md` is **kept alongside** it (the + validator allows `change.md` + `plan.md` in one bundle) so the detailed + shipped record is preserved in-repo rather than only in git history. + *(Open for confirmation at the review gate: keep both, or fold the plan into + change.md and drop plan.md.)* +3. **Tooling: Justfile + CI check.** Add a `Justfile` (the repo has none) with + `index` / `check-planning` recipes and run the check in CI on pull requests. + The new task runner is the `APPLY.md` §3 deviation noted in the PR. +4. **architecture/: lazy.** Create only `architecture/README.md` (the promotion + rule) per `APPLY.md` §5. Capability files and `glossary.md` are authored + lazily, when the first capability or term is worth pinning down. + +## Design + +### 1. Target tree (repo root) + +``` +planning/ + README.md # repo intro + merged convention prose (Quick path + Conventions) + Index + Other + .convention-version # 1.1.1 + index.py # verbatim from canonical + _templates/{change,design,plan,decision,release,glossary}.md # verbatim + deferred.md # header only (lazy) + launch-playbook.md # moved from docs/superpowers/launch-playbook.md + changes/ + 2026-06-24.01-promotion-strategy/ design.md + 2026-06-24.02-org-community-health-defaults/ change.md + plan.md + 2026-06-27.01-community-channels/ design.md + 2026-06-28.01-org-favicon-social-card/ design.md + plan.md + 2026-06-28.02-adopt-planning-convention/ design.md (this spec) + plan.md + decisions/ (.gitkeep — lazy) + releases/ (.gitkeep — lazy) +architecture/ + README.md # promotion rule only +``` + +`index.py` resolves `changes/` and `decisions/` relative to its own parent, so +`planning/index.py` validates the `planning/` tree correctly. + +### 2. Content migration + +Each migrated artifact is moved (`git mv` where 1:1) and reshaped to convention +frontmatter. Migration is **structural only**: each document's existing body and +headings are preserved (the templates are starting points, not mandatory section +names; the validator enforces only `summary:` on specs). + +| Bundle | Lane | Source | Frontmatter change | +|---|---|---|---| +| `2026-06-24.01-promotion-strategy` | design-only (pending) | `specs/2026-06-24-promotion-strategy-design.md` → `design.md` | add `summary:`; drop the `**Date/Status/Owner/Scope**` block (date derived from dir; `status`/`owner` are not convention fields); keep Scope as a prose line | +| `2026-06-24.02-org-community-health-defaults` | `change.md` + `plan.md` | `plans/2026-06-24-org-community-health-defaults.md` → `plan.md`; new `change.md` from the plan intro | plan.md: none; change.md: `summary:` | +| `2026-06-27.01-community-channels` | design-only (pending) | `specs/2026-06-27-community-channels-design.md` → `design.md` | same as promotion-strategy | +| `2026-06-28.01-org-favicon-social-card` | Full | `specs/2026-06-28-org-favicon-social-card-design.md` → `design.md`; `plans/2026-06-28-org-favicon-social-card.md` → `plan.md` | design.md: `summary:`, drop Status; plan.md: none | + +The `.NN` counter on 06-24 places the strategic umbrella (`promotion-strategy`) +at `.01` and its implementation (`community-health-defaults`) at `.02`. + +`launch-playbook.md` moves to `planning/launch-playbook.md` as a loose +operational asset — it cannot live inside a bundle (the validator rejects any +file other than `design.md`/`plan.md`/`change.md` there). Its "excluded via +`exclude_docs`" note is updated, since it now sits at the repo root and is never +published. After migration, `docs/superpowers/` is deleted. + +### 3. Tooling, CI, and instructions + +- **`Justfile`** (new): `index` (`uv run python planning/index.py`), + `check-planning` (`uv run python planning/index.py --check`), a `lint-ci` that + runs the check, and `test` (`uv run pytest`) since the repo already has tests. +- **CI** (new `.github/workflows/planning.yml`): on `pull_request` and `push`, + run `uv run python planning/index.py --check` (expect `planning: OK`). + `deploy.yml` is untouched. +- **`CLAUDE.md`**: add a `## Workflow` pointer naming `planning/README.md`'s + Quick path as the authoritative convention, and the promotion reminder ("When a + change alters a capability's behavior, update the matching + `architecture/.md` in the same PR"). All existing org-conventions + content is preserved. +- **`planning/README.md`**: repo-specific title/intro, then the body of the + canonical `convention.md` (Quick path + Conventions) below its title, plus an + `## Index` note (`just index`) and `## Other` pointers (launch-playbook). +- **`mkdocs.yml`**: remove the now-stale `superpowers/` entry from `exclude_docs` + (and the block if it empties). +- **`.convention-version`**: `1.1.1`. + +### 4. Verbatim-copied artifacts + +Per `APPLY.md` §1, copied exactly from the canonical repo into `planning/`: +`index.py` and `_templates/{change,design,plan,decision,release,glossary}.md`. +Local edits to these are intentionally not made — they are owned upstream. + +## Non-goals + +- Authoring `architecture/` capability files or `glossary.md` now — deferred to + lazy authoring per the chosen decision. +- Rewriting the bodies of the migrated specs/plans — migration is structural. +- Extracting `decisions/` entries from the existing specs — authored lazily. +- Changing what the published MkDocs site contains (other than dropping the + retired `superpowers/` exclude line). + +## Testing + +- `just check-planning` → must print `planning: OK`. +- `uv run mkdocs build --strict` → still succeeds (no `docs/superpowers/` + references remain). +- `git grep -n "docs/superpowers"` → returns nothing stale. +- New `planning.yml` CI run is green on the PR. + +## Risk + +- **Plan-without-design bundle fails validation** (community-health). Mitigated by + decision 2 (write a `change.md`). +- **Stale references** to `docs/superpowers/` elsewhere in the repo (e.g. + `mkdocs.yml`, CLAUDE.md, launch-playbook prose). Mitigated by the `git grep` + verification step. +- **CI gotcha** (per repo CLAUDE.md): GitHub may type-check a stale + `refs/pull//merge`. If the new check fails spuriously, confirm clean locally + at branch HEAD and push a fresh commit. +- **Convention drift**: future `APPLY.md` updates re-copy `index.py`/templates; + any local edits to them would be discarded. Mitigated by not editing them. From e7ef2273bd76c93ca6babf65dc950c3ec381b20a Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sun, 28 Jun 2026 21:29:34 +0300 Subject: [PATCH 2/9] docs: implementation plan for adopting the planning convention Eight bite-sized tasks: scaffold the convention skeleton, merge the README prose, migrate the four docs/superpowers bundles, move the launch playbook and retire docs/superpowers, wire just + CI, and verify + PR. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../plan.md | 693 ++++++++++++++++++ 1 file changed, 693 insertions(+) create mode 100644 planning/changes/2026-06-28.02-adopt-planning-convention/plan.md diff --git a/planning/changes/2026-06-28.02-adopt-planning-convention/plan.md b/planning/changes/2026-06-28.02-adopt-planning-convention/plan.md new file mode 100644 index 0000000..bd155dd --- /dev/null +++ b/planning/changes/2026-06-28.02-adopt-planning-convention/plan.md @@ -0,0 +1,693 @@ +# Adopt the 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 `lesnik512/planning-convention` v1.1.1 at this repo's root and migrate the existing `docs/superpowers/` planning material into convention-shaped `planning/changes/` bundles plus a lazy `architecture/` truth home. + +**Architecture:** Fresh adoption per the canonical repo's `APPLY.md`. The convention's two axes — `planning/` (bundles, validator, templates) and `architecture/` (living truth) — land at the repo root. Upstream-owned files (`index.py`, `_templates/*`) are copied verbatim from a clone; the existing specs/plans are `git mv`d into dated bundles and reshaped to lean `summary:` frontmatter; tooling (`Justfile`, CI) gates `check-planning`. + +**Tech Stack:** Python 3.11+ stdlib (the `index.py` validator has no third-party deps), `uv` (task/env runner), `just` (new task runner), GitHub Actions, MkDocs Material (existing site build). + +**Spec:** [`design.md`](./design.md) + +**Branch:** `chore/adopt-planning-convention` (already created; the spec is committed on it). + +**Commit strategy:** One commit per task. + +## Global Constraints + +- **Convention version:** v1.1.1 (latest CHANGELOG entry in the canonical repo). Record it verbatim in `planning/.convention-version`. +- **Canonical source:** `https://github.com/lesnik512/planning-convention`. `index.py` and `_templates/*` are **owned upstream — copy verbatim, never hand-edit**. +- **Lean frontmatter (validator-enforced):** `date`/`slug` are derived from the directory/file name, never written. `design.md`/`change.md` carry `summary:` only; `plan.md` carries **none**; `decisions/*.md` carry `status` + `summary` (+ optional `supersedes`/`superseded_by`); `architecture/` files carry none. +- **Bundle dir name:** `YYYY-MM-DD.NN-slug` (zero-padded intra-day counter). Bundle may contain only `design.md`, `plan.md`, `change.md`. +- **Repo layout:** `planning/` and `architecture/` live at the **repo root**, never under `docs/` (MkDocs publishes only `docs/`). +- **Verification baseline:** `uv run python planning/index.py --check` must print `planning: OK`; `uv run mkdocs build --strict` must succeed. +- **Commit trailer:** end every commit message body with `Co-Authored-By: Claude Opus 4.8 (1M context) `. Do not mention Claude Code in the body. + +--- + +### Task 1: Scaffold the convention skeleton (verbatim copies + dirs + version) + +Copy the upstream-owned files and create the fresh-adopt scaffolding. After this task, `changes/` already contains the committed `2026-06-28.02-adopt-planning-convention/` bundle (design.md + plan.md), so the validator has a real bundle to check. + +**Files:** +- Create: `planning/index.py` (verbatim copy) +- Create: `planning/_templates/{change,design,plan,decision,release,glossary}.md` (verbatim copies) +- Create: `planning/.convention-version` +- Create: `planning/deferred.md` +- Create: `planning/decisions/.gitkeep` +- Create: `planning/releases/.gitkeep` +- Create: `architecture/README.md` + +**Interfaces:** +- Produces: `planning/index.py` runnable as `uv run python planning/index.py [--check]`; the `planning/_templates/` set later tasks copy from; the `changes/` directory the migration tasks populate. + +- [ ] **Step 1: Clone the canonical repo to a temp dir** + +```bash +CONV=$(mktemp -d)/planning-convention +git clone --depth 1 https://github.com/lesnik512/planning-convention "$CONV" +echo "$CONV" +``` + +Expected: clone succeeds. Keep `$CONV` for the next steps (re-export if your shell resets between steps: `CONV=`). + +- [ ] **Step 2: Copy the verbatim-owned files** + +```bash +mkdir -p planning/_templates planning/decisions planning/releases architecture +cp "$CONV/index.py" planning/index.py +cp "$CONV/_templates/"*.md planning/_templates/ +``` + +- [ ] **Step 3: Verify the copies are byte-identical to upstream** + +```bash +diff "$CONV/index.py" planning/index.py && \ +for f in change design plan decision release glossary; do \ + diff "$CONV/_templates/$f.md" "planning/_templates/$f.md" || echo "MISMATCH: $f"; \ +done && echo "all verbatim copies OK" +``` + +Expected: no diff output, ends with `all verbatim copies OK`. + +- [ ] **Step 4: Write the version file, deferred stub, gitkeeps, and architecture README** + +```bash +printf '1.1.1\n' > planning/.convention-version +printf '# Deferred\n\nReal-but-unscheduled items, each with a revisit trigger. Add entries lazily.\n' > planning/deferred.md +: > planning/decisions/.gitkeep +: > planning/releases/.gitkeep +``` + +Then create `architecture/README.md` with exactly this content: + +```markdown +# Architecture — the truth home + +This directory holds the **living truth** about what `modern-python/.github` does +*now*: one file per capability, plus a single `glossary.md` (the ubiquitous +language). These files carry **no frontmatter** and are dated by git. + +**Promotion rule:** when a change alters a capability's behavior, hand-edit the +matching `architecture/.md` in the **same PR** as the code — the edit +rides in the same diff and is reviewed with it. The change bundle in +`planning/changes/` stays as the *why*; this directory stays *true*. + +Capability files and `glossary.md` are authored **lazily** — each appears when the +first capability or term is worth pinning down. +``` + +- [ ] **Step 5: Run the validator to confirm a clean baseline** + +```bash +uv run python planning/index.py --check +``` + +Expected: `planning: OK` (the committed `2026-06-28.02-adopt-planning-convention/` bundle has a valid `design.md`; `decisions/`/`releases/` hold only `.gitkeep`, which the validator ignores). + +- [ ] **Step 6: Confirm the index generator runs** + +```bash +uv run python planning/index.py +``` + +Expected: a `# Planning index` listing showing the `adopt-planning-convention` change under `## Changes` and `_None._` under `## Decisions`. + +- [ ] **Step 7: Commit** + +```bash +git add planning/index.py planning/_templates planning/.convention-version \ + planning/deferred.md planning/decisions/.gitkeep planning/releases/.gitkeep \ + architecture/README.md +git commit -m "chore: scaffold planning convention skeleton (v1.1.1)" +``` + +--- + +### Task 2: Write `planning/README.md` (merged convention prose) + +Create the consumer README: a repo-specific intro, then the canonical `convention.md` body (Quick path + Conventions) pasted verbatim, then repo-local `## Index` and `## Other` sections. + +**Files:** +- Create: `planning/README.md` + +**Interfaces:** +- Consumes: the cloned `$CONV/convention.md` from Task 1 (re-clone if the temp dir is gone: `CONV=$(mktemp -d)/pc && git clone --depth 1 https://github.com/lesnik512/planning-convention "$CONV"`). +- Produces: `planning/README.md` — the authoritative convention prose `CLAUDE.md` will point at in Task 7. + +- [ ] **Step 1: Write the repo intro + section scaffold** + +Create `planning/README.md` with exactly this content: + +```markdown +# Planning — modern-python/.github + +This repo's planning home, following the portable two-axis convention from +[`lesnik512/planning-convention`](https://github.com/lesnik512/planning-convention) +(applied version in [`.convention-version`](.convention-version)). `architecture/` +(repo root) holds the living truth about what the system does now; the bundles in +[`changes/`](changes/) record how it got there. To update the convention itself, +re-run that repo's `APPLY.md` flow. + + + +## Index + +Run `just index` to print the change/decision listing — a query over the files, +never a committed artifact. + +## Other + +- [`launch-playbook.md`](launch-playbook.md) — internal Phase 4 launch asset (not published). +- [`deferred.md`](deferred.md) — real-but-unscheduled items, each with a revisit trigger. +``` + +- [ ] **Step 2: Splice in the canonical convention prose** + +Replace the `` marker line with everything in `$CONV/convention.md` from the `## Quick path (start here)` heading through the end of the file (the Quick path + Conventions sections — i.e. the canonical body below its `# Planning convention` title and intro): + +```bash +# Extract the Quick path → EOF block from the canonical convention.md +awk '/^## Quick path/{p=1} p' "$CONV/convention.md" > /tmp/conv-body.md +# Replace the marker line with that block +python3 - <<'PY' +import pathlib +readme = pathlib.Path("planning/README.md") +body = pathlib.Path("/tmp/conv-body.md").read_text(encoding="utf-8").rstrip() + "\n" +text = readme.read_text(encoding="utf-8") +marker = "\n" +assert marker in text, "marker not found" +readme.write_text(text.replace(marker, body), encoding="utf-8") +print("spliced") +PY +``` + +Expected: prints `spliced`. + +- [ ] **Step 3: Verify the prose landed** + +```bash +grep -q "^## Quick path" planning/README.md && \ +grep -q "^### Frontmatter" planning/README.md && \ +grep -q "^## Index" planning/README.md && \ +grep -q "^## Other" planning/README.md && echo "README OK" +``` + +Expected: `README OK` (Quick path + Conventions body present, repo-local sections preserved). + +- [ ] **Step 4: Commit** + +```bash +git add planning/README.md +git commit -m "docs: add planning/README with merged convention prose" +``` + +--- + +### Task 3: Migrate the design-only bundles (promotion-strategy, community-channels) + +`git mv` two specs into dated bundles and reshape their headers to `summary:` frontmatter. Both are approved-but-unimplemented designs, so they are valid design-only bundles (no `plan.md`). + +**Files:** +- Create: `planning/changes/2026-06-24.01-promotion-strategy/design.md` (moved from `docs/superpowers/specs/2026-06-24-promotion-strategy-design.md`) +- Create: `planning/changes/2026-06-27.01-community-channels/design.md` (moved from `docs/superpowers/specs/2026-06-27-community-channels-design.md`) + +- [ ] **Step 1: Move the two specs into bundles** + +```bash +mkdir -p planning/changes/2026-06-24.01-promotion-strategy \ + planning/changes/2026-06-27.01-community-channels +git mv docs/superpowers/specs/2026-06-24-promotion-strategy-design.md \ + planning/changes/2026-06-24.01-promotion-strategy/design.md +git mv docs/superpowers/specs/2026-06-27-community-channels-design.md \ + planning/changes/2026-06-27.01-community-channels/design.md +``` + +- [ ] **Step 2: Reshape the promotion-strategy header** + +In `planning/changes/2026-06-24.01-promotion-strategy/design.md`, replace the top block. The current top is: + +```markdown +# Modern Python — Promotion Strategy + +**Date:** 2026-06-24 +**Status:** Approved design, ready for planning +**Owner:** modern-python maintainers +**Scope:** Org-wide growth strategy for the `modern-python` GitHub org +``` + +Replace it with (add frontmatter; drop `**Date:**`/`**Status:**`/`**Owner:**`; keep `**Scope:**` as prose): + +```markdown +--- +summary: Org-wide, users-first staged growth strategy for the modern-python GitHub org along three axes (reach, retain, contribute). +--- + +# Modern Python — Promotion Strategy + +**Scope:** Org-wide growth strategy for the `modern-python` GitHub org +``` + +Leave the rest of the document (from `## Goal` onward) unchanged. + +- [ ] **Step 3: Reshape the community-channels header** + +In `planning/changes/2026-06-27.01-community-channels/design.md`, the current top is: + +```markdown +# Modern Python — Community & Channels + +**Date:** 2026-06-27 +**Status:** Approved design, ready for planning +**Owner:** modern-python maintainers +**Scope:** Which channels the `modern-python` org uses to reach, support, and +retain users — and how they fit together. Extends the +``` + +Replace **only** the title-and-metadata lines (the `# Modern Python — Community & Channels` heading and the four `**Date/Status/Owner/Scope**` lines, keeping `**Scope:**` as prose) with: + +```markdown +--- +summary: Which channels the modern-python org uses to reach, support, and retain users, and how they fit together. +--- + +# Modern Python — Community & Channels + +**Scope:** Which channels the `modern-python` org uses to reach, support, and +retain users — and how they fit together. Extends the +``` + +Keep everything after the `**Scope:**` sentence unchanged (the Scope sentence continues into the existing body — do not truncate it). + +- [ ] **Step 4: Validate** + +```bash +uv run python planning/index.py --check +``` + +Expected: `planning: OK`. + +- [ ] **Step 5: Commit** + +```bash +git add planning/changes/2026-06-24.01-promotion-strategy \ + planning/changes/2026-06-27.01-community-channels +git commit -m "docs: migrate promotion-strategy and community-channels into bundles" +``` + +--- + +### Task 4: Migrate the org-favicon-social-card Full bundle + +`git mv` the paired spec + plan into one Full bundle; reshape the spec header, leave the plan frontmatter-free. + +**Files:** +- Create: `planning/changes/2026-06-28.01-org-favicon-social-card/design.md` (moved from `docs/superpowers/specs/2026-06-28-org-favicon-social-card-design.md`) +- Create: `planning/changes/2026-06-28.01-org-favicon-social-card/plan.md` (moved from `docs/superpowers/plans/2026-06-28-org-favicon-social-card.md`) + +- [ ] **Step 1: Move the spec and plan into one bundle** + +```bash +mkdir -p planning/changes/2026-06-28.01-org-favicon-social-card +git mv docs/superpowers/specs/2026-06-28-org-favicon-social-card-design.md \ + planning/changes/2026-06-28.01-org-favicon-social-card/design.md +git mv docs/superpowers/plans/2026-06-28-org-favicon-social-card.md \ + planning/changes/2026-06-28.01-org-favicon-social-card/plan.md +``` + +- [ ] **Step 2: Reshape the design header** + +In `planning/changes/2026-06-28.01-org-favicon-social-card/design.md`, the current top is: + +```markdown +# modern-python org favicon + social card — design spec + +**Status:** approved design (brainstorming output) +**Scope:** the org-level **favicon** and **social card** only. Everything else +``` + +Replace the title and the `**Status:**` line (drop `**Status:**`; keep `**Scope:**` as prose) with: + +```markdown +--- +summary: Replaced the snake brand mark with the pinwheel favicon/avatar and Jost wordmark social card, generated by the brand/build/ toolchain. +--- + +# modern-python org favicon + social card — design spec + +**Scope:** the org-level **favicon** and **social card** only. Everything else +``` + +Leave the `**Scope:**` sentence and everything after it unchanged. + +- [ ] **Step 3: Confirm the plan carries no frontmatter** + +```bash +head -1 planning/changes/2026-06-28.01-org-favicon-social-card/plan.md +``` + +Expected: the plan's first line is its `#` title (the "For agentic workers" plan), **not** `---`. Plans carry no frontmatter — make no change. + +- [ ] **Step 4: Validate** + +```bash +uv run python planning/index.py --check +``` + +Expected: `planning: OK`. + +- [ ] **Step 5: Commit** + +```bash +git add planning/changes/2026-06-28.01-org-favicon-social-card +git commit -m "docs: migrate org-favicon-social-card into a Full bundle" +``` + +--- + +### Task 5: Migrate the community-health bundle (plan.md + new change.md) + +`git mv` the existing detailed plan, then author a condensed `change.md` as the bundle's required spec. Per the spec's decision, **both** files are kept: `change.md` is the condensed why; `plan.md` is the shipped detail. + +**Files:** +- Create: `planning/changes/2026-06-24.02-org-community-health-defaults/plan.md` (moved from `docs/superpowers/plans/2026-06-24-org-community-health-defaults.md`) +- Create: `planning/changes/2026-06-24.02-org-community-health-defaults/change.md` (new) + +- [ ] **Step 1: Move the plan into the bundle** + +```bash +mkdir -p planning/changes/2026-06-24.02-org-community-health-defaults +git mv docs/superpowers/plans/2026-06-24-org-community-health-defaults.md \ + planning/changes/2026-06-24.02-org-community-health-defaults/plan.md +``` + +- [ ] **Step 2: Write the condensed `change.md`** + +Create `planning/changes/2026-06-24.02-org-community-health-defaults/change.md` with exactly this content: + +```markdown +--- +summary: Added org-wide default community-health files so every modern-python repo lacking its own inherits a CONTRIBUTING guide, Code of Conduct, security policy, support routing, issue/PR templates, and funding config — lifting coverage from ~25% to ~90% in one PR. +--- + +# Change: Org-wide community-health defaults + +**Lane:** condensed spec for an already-shipped, multi-file org-wide change. +The full task-by-task record lives in [`plan.md`](./plan.md); this page is the +why. + +## Goal + +Every repo in the `modern-python` org that does not define its own +community-health files should inherit sensible org defaults, so contributors get +a consistent CONTRIBUTING guide, Code of Conduct, security policy, support +routing, issue/PR templates, and funding config without per-repo upkeep. + +## Approach + +GitHub serves *default* community-health files from the special `modern-python/.github` +repo: files at the repo **root** or under **`.github/`** apply to every org repo +that lacks its own. This repo also builds the public site from `docs/`, so the +community-health files go at the root or under `.github/` — **never under +`docs/`**, which would publish them. See [`plan.md`](./plan.md) for the per-file +task breakdown and verifications. + +## Files + +- Root community-health files (`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, + `SECURITY.md`, `SUPPORT.md`) and `.github/` issue/PR templates + funding config. +- [`plan.md`](./plan.md) — the detailed implementation plan (shipped). + +## Verification + +- [x] Each YAML form/config parses. +- [x] `mkdocs build --strict` still succeeds (no community-health file leaks into the site). +- [x] Each file lands at a path GitHub recognizes as an org default. +``` + +- [ ] **Step 3: Validate (the bundle now has change.md + plan.md)** + +```bash +uv run python planning/index.py --check +``` + +Expected: `planning: OK` (the bundle has `change.md` for its spec; `plan.md` carries no frontmatter; both are allowed bundle files). + +- [ ] **Step 4: Confirm the bundle appears in the index with its summary** + +```bash +uv run python planning/index.py | grep "org-community-health-defaults" +``` + +Expected: a `- **[org-community-health-defaults](...)** (2026-06-24) — Added org-wide default community-health files…` line. + +- [ ] **Step 5: Commit** + +```bash +git add planning/changes/2026-06-24.02-org-community-health-defaults +git commit -m "docs: migrate community-health defaults into a bundle" +``` + +--- + +### Task 6: Move the launch playbook and delete `docs/superpowers/` + +Relocate the loose internal asset to `planning/`, refresh its now-stale publishing note, and remove the emptied directory. + +**Files:** +- Create: `planning/launch-playbook.md` (moved from `docs/superpowers/launch-playbook.md`) +- Delete: `docs/superpowers/` (now empty of tracked files) + +- [ ] **Step 1: Move the launch playbook** + +```bash +git mv docs/superpowers/launch-playbook.md planning/launch-playbook.md +``` + +- [ ] **Step 2: Refresh its publishing note** + +In `planning/launch-playbook.md`, the second line currently reads: + +```markdown +Internal asset — **not published** (excluded from the site via `exclude_docs`). +``` + +Replace it with (it now lives at the repo root, outside `docs/`, so it is never seen by MkDocs): + +```markdown +Internal asset — **not published** (lives at the repo root, outside the MkDocs `docs/` tree). +``` + +- [ ] **Step 3: Confirm `docs/superpowers/` has no tracked files left, then remove it** + +```bash +git ls-files docs/superpowers +``` + +Expected: **no output** (every tracked file was moved). Then: + +```bash +rm -rf docs/superpowers +``` + +- [ ] **Step 4: Verify no stale references remain** + +```bash +git grep -n "docs/superpowers" -- . ':!planning/changes/2026-06-28.02-adopt-planning-convention/*' || echo "no stale references" +``` + +Expected: `no stale references`. (The adopt-convention spec/plan describe the migration and legitimately name the old paths; they are excluded from this check.) + +- [ ] **Step 5: Commit** + +```bash +git add planning/launch-playbook.md +git add -A docs/superpowers 2>/dev/null || true +git commit -m "docs: move launch playbook to planning/, retire docs/superpowers" +``` + +--- + +### Task 7: Add the `Justfile`, CI check, and `CLAUDE.md` wiring + +Introduce the task runner (the repo has none), gate `check-planning` in CI, and point the repo's `CLAUDE.md` at the convention. This is the `APPLY.md` §3 deviation (new `just`) to note in the PR. + +**Files:** +- Create: `Justfile` +- Create: `.github/workflows/planning.yml` +- Modify: `CLAUDE.md` +- Modify: `mkdocs.yml` (remove the `superpowers/` exclude line) + +- [ ] **Step 1: Create the `Justfile`** + +Create `Justfile` at the repo root with exactly this content: + +```make +default: check-planning test + +index: + uv run python planning/index.py + +check-planning: + uv run python planning/index.py --check + +lint-ci: check-planning + +test: + uv run pytest +``` + +- [ ] **Step 2: Verify the recipes run** + +```bash +just check-planning +``` + +Expected: `planning: OK`. (If `just` is not installed: `uv run python planning/index.py --check` directly, but the recipe is what CI and contributors use.) + +- [ ] **Step 3: Create the CI workflow** + +Create `.github/workflows/planning.yml` with exactly this content: + +```yaml +name: Planning + +on: + pull_request: + push: + branches: [main] + +jobs: + check-planning: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + python-version: "3.11" + - name: Validate planning bundles + run: uv run python planning/index.py --check +``` + +- [ ] **Step 4: Remove the stale `superpowers/` exclude from `mkdocs.yml`** + +In `mkdocs.yml`, the `exclude_docs` block currently reads: + +```yaml +exclude_docs: | + DEPLOYMENT.md + superpowers/ +``` + +Remove only the `superpowers/` line (keep `DEPLOYMENT.md`): + +```yaml +exclude_docs: | + DEPLOYMENT.md +``` + +- [ ] **Step 5: Add the `## Workflow` and `## Architecture` sections to `CLAUDE.md`** + +Append the following two sections to the end of `CLAUDE.md` (preserve all existing content above): + +```markdown +## Workflow + +Planning follows the convention in [`planning/README.md`](planning/README.md) — +its **Quick path** is authoritative. Pick a lane (Full = `design.md` + `plan.md`, +Lightweight = `change.md`, Tiny = conventional commit), create a bundle under +`planning/changes/YYYY-MM-DD.NN-/` from `planning/_templates/`, and run +`just check-planning` before pushing. The applied convention version is in +`planning/.convention-version`; update it via the canonical repo's `APPLY.md`. + +## Architecture + +`architecture/` (repo root) is the living truth about what this repo does now — +one file per capability plus `glossary.md`, no frontmatter, authored lazily. +**When a change alters a capability's behavior, update the matching +`architecture/.md` in the same PR.** The change bundle in +`planning/changes/` stays as the *why*. +``` + +- [ ] **Step 6: Verify the site still builds and planning still validates** + +```bash +uv run mkdocs build --strict && just check-planning +``` + +Expected: MkDocs build succeeds; `planning: OK`. + +- [ ] **Step 7: Commit** + +```bash +git add Justfile .github/workflows/planning.yml CLAUDE.md mkdocs.yml +git commit -m "chore: wire just + CI check-planning, point CLAUDE.md at the convention" +``` + +--- + +### Task 8: Final full verification and open the PR + +Confirm the end state matches the spec, then push and open the PR (per the maintainer's PR-only workflow). + +**Files:** none (verification + PR). + +- [ ] **Step 1: Run the full verification suite** + +```bash +just check-planning && \ +uv run mkdocs build --strict && \ +git grep -n "docs/superpowers" -- . ':!planning/changes/2026-06-28.02-adopt-planning-convention/*' || echo "no stale docs/superpowers references" +``` + +Expected: `planning: OK`, MkDocs build succeeds, and `no stale docs/superpowers references`. + +- [ ] **Step 2: Confirm the end-state tree** + +```bash +test -f planning/.convention-version && \ +test -f planning/index.py && \ +test -d architecture && \ +ls planning/changes +``` + +Expected: the four migrated bundles plus `2026-06-28.02-adopt-planning-convention/` are listed; `.convention-version` and `index.py` exist; `architecture/` exists. + +- [ ] **Step 3: Push the branch** + +```bash +git push -u origin chore/adopt-planning-convention +``` + +- [ ] **Step 4: Open the PR** + +```bash +gh pr create --title "Adopt planning-convention v1.1.1 and migrate docs/superpowers" --body "$(cat <<'EOF' +Fresh adoption of `lesnik512/planning-convention` **v1.1.1** at the repo root, and migration of the old `docs/superpowers/` planning material into convention bundles. + +## What changed +- `planning/` (root): verbatim `index.py` + `_templates/`, merged `README.md`, `.convention-version` (1.1.1), `deferred.md`, lazy `decisions/`/`releases/`. +- `architecture/README.md`: promotion rule only (capability files + glossary authored lazily). +- Migrated bundles: `2026-06-24.01-promotion-strategy` (design-only), `2026-06-24.02-org-community-health-defaults` (change.md + plan.md), `2026-06-27.01-community-channels` (design-only), `2026-06-28.01-org-favicon-social-card` (Full). +- `launch-playbook.md` → `planning/`; `docs/superpowers/` removed; stale `superpowers/` exclude dropped from `mkdocs.yml`. +- Tooling: new `Justfile` (`index`/`check-planning`/`lint-ci`/`test`) and `.github/workflows/planning.yml`; `CLAUDE.md` points at the convention. + +## APPLY.md §3 deviation +The repo had **no task runner**; this PR introduces `just` for the convention recipes. + +## Verification +- `just check-planning` → `planning: OK` +- `uv run mkdocs build --strict` → succeeds +EOF +)" +``` + +- [ ] **Step 5: Watch CI** + +```bash +gh pr checks --watch +``` + +Expected: the `Planning` and `Deploy site` workflows pass. (Per the repo's CLAUDE.md CI gotcha: if a check fails on a stale `refs/pull//merge`, confirm clean locally at HEAD and push a fresh commit to force a recompute.) From b89b6ca0e0835e25c7040f48324600087aebe025 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sun, 28 Jun 2026 21:32:04 +0300 Subject: [PATCH 3/9] chore: scaffold planning convention skeleton (v1.1.1) --- architecture/README.md | 13 +++ planning/.convention-version | 1 + planning/_templates/change.md | 32 ++++++ planning/_templates/decision.md | 23 ++++ planning/_templates/design.md | 49 ++++++++ planning/_templates/glossary.md | 15 +++ planning/_templates/plan.md | 46 ++++++++ planning/_templates/release.md | 38 +++++++ planning/decisions/.gitkeep | 0 planning/deferred.md | 3 + planning/index.py | 196 ++++++++++++++++++++++++++++++++ planning/releases/.gitkeep | 0 12 files changed, 416 insertions(+) create mode 100644 architecture/README.md create mode 100644 planning/.convention-version create mode 100644 planning/_templates/change.md create mode 100644 planning/_templates/decision.md create mode 100644 planning/_templates/design.md create mode 100644 planning/_templates/glossary.md create mode 100644 planning/_templates/plan.md create mode 100644 planning/_templates/release.md create mode 100644 planning/decisions/.gitkeep create mode 100644 planning/deferred.md create mode 100644 planning/index.py create mode 100644 planning/releases/.gitkeep diff --git a/architecture/README.md b/architecture/README.md new file mode 100644 index 0000000..5485b7c --- /dev/null +++ b/architecture/README.md @@ -0,0 +1,13 @@ +# Architecture — the truth home + +This directory holds the **living truth** about what `modern-python/.github` does +*now*: one file per capability, plus a single `glossary.md` (the ubiquitous +language). These files carry **no frontmatter** and are dated by git. + +**Promotion rule:** when a change alters a capability's behavior, hand-edit the +matching `architecture/.md` in the **same PR** as the code — the edit +rides in the same diff and is reviewed with it. The change bundle in +`planning/changes/` stays as the *why*; this directory stays *true*. + +Capability files and `glossary.md` are authored **lazily** — each appears when the +first capability or term is worth pinning down. diff --git a/planning/.convention-version b/planning/.convention-version new file mode 100644 index 0000000..524cb55 --- /dev/null +++ b/planning/.convention-version @@ -0,0 +1 @@ +1.1.1 diff --git a/planning/_templates/change.md b/planning/_templates/change.md new file mode 100644 index 0000000..d4c8962 --- /dev/null +++ b/planning/_templates/change.md @@ -0,0 +1,32 @@ +--- +summary: One line — shown in the generated index. Written at creation; finalize at ship to state the realized result. +--- + +# 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/decision.md b/planning/_templates/decision.md new file mode 100644 index 0000000..45ccaf0 --- /dev/null +++ b/planning/_templates/decision.md @@ -0,0 +1,23 @@ +--- +status: accepted # accepted | superseded +summary: One line — shown in `just index`. +supersedes: null +superseded_by: null +--- + +# One-line capitalized title + +**Decision:** What was decided, in a sentence. + +## Context + +Why this came up; the options that were on the table. + +## Decision & rationale + +The call and why — including why the alternatives were rejected. Enough that a +future explorer doesn't re-litigate it. + +## Revisit trigger + +The concrete signal that should reopen this decision. diff --git a/planning/_templates/design.md b/planning/_templates/design.md new file mode 100644 index 0000000..d63e22d --- /dev/null +++ b/planning/_templates/design.md @@ -0,0 +1,49 @@ +--- +summary: One line — shown in the generated index. Written at creation; finalize at ship to state the realized result. +--- + +# 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/glossary.md b/planning/_templates/glossary.md new file mode 100644 index 0000000..82385c3 --- /dev/null +++ b/planning/_templates/glossary.md @@ -0,0 +1,15 @@ +# Glossary + +The project's ubiquitous language — the domain terms that code, specs, and +capability pages share. Living prose, no frontmatter, dated by git. Each entry is +a term, what it *is* (not what it does), and the synonyms to avoid. No +implementation detail; this is a glossary, not a spec. + +**Term**: +A one-or-two-sentence definition of what it is. +_Avoid_: rejected-synonym, another-one + +**Another term**: +Define what it is, tightly. Group related terms under `##` subheadings when +natural clusters emerge; a flat list is fine when they don't. +_Avoid_: … diff --git a/planning/_templates/plan.md b/planning/_templates/plan.md new file mode 100644 index 0000000..132d720 --- /dev/null +++ b/planning/_templates/plan.md @@ -0,0 +1,46 @@ +# — 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 ": " + ``` + +--- + +### Task 2: ... diff --git a/planning/_templates/release.md b/planning/_templates/release.md new file mode 100644 index 0000000..5081187 --- /dev/null +++ b/planning/_templates/release.md @@ -0,0 +1,38 @@ +# + + + + + +## Feature + +- **.** What it adds and how to use it. + +## Fix + +- **.** What was broken, now fixed (reference the issue/regression). + +## Internal refactors + +- **.** What changed under the hood, stated as no behavior change. + +## Packaging + +- Metadata / build / dependency changes visible to installers. + +## Why + +Context a reader needs for the headline change. Omit for small releases. + +## Downstream + +What dependents must do — e.g. bump their version floor — or "No action +needed" when there is no API change. Omit if the project has no downstreams. + +## Internals + +- Coverage / tooling notes. diff --git a/planning/decisions/.gitkeep b/planning/decisions/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/planning/deferred.md b/planning/deferred.md new file mode 100644 index 0000000..c58c705 --- /dev/null +++ b/planning/deferred.md @@ -0,0 +1,3 @@ +# Deferred + +Real-but-unscheduled items, each with a revisit trigger. Add entries lazily. diff --git a/planning/index.py b/planning/index.py new file mode 100644 index 0000000..e6e0837 --- /dev/null +++ b/planning/index.py @@ -0,0 +1,196 @@ +"""Generate the planning index from frontmatter. + +Run via ``just index``. Globs ``planning/changes/*/`` (each bundle's +``design.md``, falling back to ``change.md``) and ``planning/decisions/*.md``, +reads their frontmatter, and prints a Markdown listing to stdout — changes +then decisions, newest-first. Never writes a file: +the listing is a query over the files, not a committed artifact. + +``date`` and ``slug`` are derived from the directory / file name, not +frontmatter — the name is the single source of truth for both. +""" + +import pathlib +import re +import sys + + +ROOT = pathlib.Path(__file__).parent +VALID_DECISION_STATUS = {"accepted", "superseded"} +BUNDLE_RE = re.compile(r"^(?P\d{4}-\d{2}-\d{2})\.\d{2}-(?P.+)$") +DECISION_RE = re.compile(r"^(?P\d{4}-\d{2}-\d{2})-(?P.+)$") +ALLOWED_BUNDLE_FILES = {"design.md", "plan.md", "change.md"} +SPEC_REQUIRED = ("summary",) +DECISION_REQUIRED = ("status", "summary") + + +def parse_frontmatter(text: str) -> dict[str, str]: + """Parse a single-line-scalar YAML frontmatter block into a dict.""" + lines = text.splitlines() + if not lines or lines[0].strip() != "---": + return {} + fields: dict[str, str] = {} + for line in lines[1:]: + if line.strip() == "---": + break + if line[:1] in (" ", "\t"): + continue + key, sep, value = line.partition(": ") + if not sep: + continue + cleaned = value.strip().strip('"').strip("'") + fields[key.strip()] = "" if cleaned == "null" else cleaned + return fields + + +def _named(fields: dict[str, str], name: str, pattern: re.Pattern[str]) -> dict[str, str]: + """Inject ``date``/``slug`` derived from a dir/file name into ``fields``.""" + match = pattern.match(name) + if match: + fields["date"] = match.group("date") + fields["slug"] = match.group("slug") + return fields + + +def load_bundles(root: pathlib.Path) -> list[dict[str, str]]: + """Read each bundle's summary; derive date/slug from the directory name.""" + changes_dir = root / "changes" + bundles: list[dict[str, str]] = [] + if not changes_dir.is_dir(): + return bundles + for bundle in sorted(changes_dir.iterdir()): + if not bundle.is_dir(): + continue + spec = bundle / "design.md" + if not spec.exists(): + spec = bundle / "change.md" + if not spec.exists(): + continue + fields = _named(parse_frontmatter(spec.read_text(encoding="utf-8")), bundle.name, BUNDLE_RE) + fields["path"] = f"changes/{bundle.name}/{spec.name}" + fields["name"] = bundle.name + bundles.append(fields) + return bundles + + +def load_decisions(root: pathlib.Path) -> list[dict[str, str]]: + """Read each decision's frontmatter; derive date/slug from the file name.""" + decisions_dir = root / "decisions" + decisions: list[dict[str, str]] = [] + if not decisions_dir.is_dir(): + return decisions + for path in sorted(decisions_dir.glob("*.md")): + if path.name == "README.md" or path.name.startswith("_"): + continue + fields = _named(parse_frontmatter(path.read_text(encoding="utf-8")), path.stem, DECISION_RE) + fields["path"] = f"decisions/{path.name}" + fields["name"] = path.stem + decisions.append(fields) + return decisions + + +def format_row(bundle: dict[str, str]) -> str: + """Render one bundle as a Markdown list item.""" + slug = bundle.get("slug", "?") + path = bundle.get("path", "") + date = bundle.get("date", "") + summary = bundle.get("summary") or "(no summary)" + line = f"- **[{slug}]({path})** ({date}) — {summary}" + if bundle.get("supersedes"): + line += f" _(supersedes {bundle['supersedes']})_" + if bundle.get("superseded_by"): + line += f" _(superseded by {bundle['superseded_by']})_" + return line + + +def render(bundles: list[dict[str, str]], decisions: list[dict[str, str]]) -> str: + """Render the full Markdown listing: changes then decisions, newest-first.""" + out = ["# Planning index", "", "_Generated by `just index` — do not edit._", "", "## Changes", ""] + change_rows = sorted(bundles, key=lambda b: b.get("name", ""), reverse=True) + out += [format_row(b) for b in change_rows] if change_rows else ["_None._"] + out += ["", "## Decisions", ""] + decision_rows = sorted(decisions, key=lambda d: d.get("name", ""), reverse=True) + out += [format_row(d) for d in decision_rows] if decision_rows else ["_None._"] + out.append("") + return "\n".join(out).rstrip() + "\n" + + +def _require(fields: dict[str, str], keys: tuple[str, ...], rel: str, violations: list[str]) -> None: + """Append a violation for each required key that is absent or empty.""" + violations.extend(f"{rel}: missing or empty frontmatter key '{key}'" for key in keys if not fields.get(key)) + + +def _check_spec_file(path: pathlib.Path, rel: str, violations: list[str]) -> None: + """Validate a design.md / change.md spec file (requires `summary`).""" + fields = parse_frontmatter(path.read_text(encoding="utf-8")) + _require(fields, SPEC_REQUIRED, rel, violations) + + +def _check_bundle(bundle: pathlib.Path, violations: list[str]) -> None: + """Validate one change bundle directory.""" + rel = f"changes/{bundle.name}" + if BUNDLE_RE.match(bundle.name) is None: + violations.append(f"{rel}: directory name is not 'YYYY-MM-DD.NN-slug'") + violations.extend( + f"{rel}/{child.name}: unexpected file in bundle (allowed: {', '.join(sorted(ALLOWED_BUNDLE_FILES))})" + for child in sorted(bundle.iterdir()) + if child.name not in ALLOWED_BUNDLE_FILES + ) + design = bundle / "design.md" + change = bundle / "change.md" + if not design.exists() and not change.exists(): + violations.append(f"{rel}: bundle has neither design.md nor change.md") + for spec_file in (design, change): + if spec_file.exists(): + _check_spec_file(spec_file, f"{rel}/{spec_file.name}", violations) + # plan.md carries no frontmatter — its identity comes from the bundle dir. + + +def _check_decision(path: pathlib.Path, violations: list[str]) -> None: + """Validate one decision file (requires `status` + `summary`).""" + rel = f"decisions/{path.name}" + if DECISION_RE.match(path.stem) is None: + violations.append(f"{rel}: file name is not 'YYYY-MM-DD-slug.md'") + fields = parse_frontmatter(path.read_text(encoding="utf-8")) + _require(fields, DECISION_REQUIRED, rel, violations) + status = fields.get("status", "") + if status and status not in VALID_DECISION_STATUS: + violations.append(f"{rel}: invalid status '{status}' (allowed: {', '.join(sorted(VALID_DECISION_STATUS))})") + + +def check(root: pathlib.Path) -> list[str]: + """Validate every bundle and decision; return the list of violation strings.""" + violations: list[str] = [] + changes_dir = root / "changes" + decisions_dir = root / "decisions" + if changes_dir.is_dir(): + for bundle in sorted(changes_dir.iterdir()): + if bundle.is_dir(): + _check_bundle(bundle, violations) + if decisions_dir.is_dir(): + for path in sorted(decisions_dir.glob("*.md")): + if path.name == "README.md" or path.name.startswith("_"): + continue + _check_decision(path, violations) + return violations + + +def main(argv: list[str] | None = None, root: pathlib.Path | None = None) -> int: + """Print the listing to stdout, or validate bundles with --check.""" + argv = sys.argv[1:] if argv is None else argv + root = ROOT if root is None else root + if "--check" in argv: + violations = check(root) + if violations: + sys.stderr.write(f"planning: {len(violations)} violation(s)\n") + for violation in violations: + sys.stderr.write(f" - {violation}\n") + return 1 + sys.stdout.write("planning: OK\n") + return 0 + sys.stdout.write(render(load_bundles(root), load_decisions(root))) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/planning/releases/.gitkeep b/planning/releases/.gitkeep new file mode 100644 index 0000000..e69de29 From 01bae2e5067333158208bd5cda67758dcdc3abef Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sun, 28 Jun 2026 21:36:05 +0300 Subject: [PATCH 4/9] docs: add planning/README with merged convention prose --- planning/README.md | 145 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 planning/README.md diff --git a/planning/README.md b/planning/README.md new file mode 100644 index 0000000..4797e51 --- /dev/null +++ b/planning/README.md @@ -0,0 +1,145 @@ +# Planning — modern-python/.github + +This repo's planning home, following the portable two-axis convention from +[`lesnik512/planning-convention`](https://github.com/lesnik512/planning-convention) +(applied version in [`.convention-version`](.convention-version)). `architecture/` +(repo root) holds the living truth about what the system does now; the bundles in +[`changes/`](changes/) record how it got there. To update the convention itself, +re-run that repo's `APPLY.md` flow. + +## Quick path (start here) + +> The fast lane for making a change. The full reference is in +> [Conventions](#conventions) below — read it only when this isn't enough. + +**1. Choose a lane — first matching rule wins:** + +1. Any of: needs design judgment · new file/module · public-API change · + cross-cutting or multi-file · non-trivial test design → **Full** + (`design.md` + `plan.md`) +2. Purely mechanical: typo · dep bump · linter/formatter/CI tweak · + mechanical rename · single-line config → **Tiny** (no bundle, conventional + commit) +3. Small-but-real, none of the above: ≲30 LOC net · ≤2 files · no new file · + no public-API change · one straightforward test → **Lightweight** + (`change.md`) + +Ambiguous between two? Take the heavier. A `change.md` that outgrows its lane +splits into `design.md` + `plan.md`. + +**2. Create the bundle** (Full / Lightweight only): +`planning/changes/YYYY-MM-DD.NN-/`, where `.NN` is a zero-padded +intra-day counter. Copy the matching template from +[`_templates/`](_templates/). + +**3. Ship in the implementing PR:** hand-edit the affected +`architecture/.md`, finalize the bundle's `summary:` to the +realized result, and run `just check-planning` before pushing. + +## Conventions + +> This is the portable convention, sourced from the canonical repo +> [`lesnik512/planning-convention`](https://github.com/lesnik512/planning-convention) +> (applied version in [`.convention-version`](.convention-version)). To update +> it, run that repo's `APPLY.md` flow. The generated change index (`just index`) +> and the `## Other` pointers below are repo-local. + +### Two axes, never mixed + +- **`architecture/` (repo root) — the present.** One file per capability, plus + a single `glossary.md` (the ubiquitous language); living prose, updated in the + same PR that ships the change. The truth home. +- **`planning/changes/` — the past-and-pending.** One folder per change, + kept in place after ship. + +A change **promotes** its conclusions into the affected +`architecture/.md` by hand **in the implementing PR, alongside the +code** — the edit rides in the same diff and is reviewed with it, never applied +as a separate post-merge step. That hand-edit is what keeps `architecture/` +true; the bundle stays in `changes/` as the *why*. + +### Glossary + +`architecture/glossary.md` is the project's **ubiquitous language** — one page +defining the domain terms that code, specs, and capability pages all share. Like +the capability files beside it, it is living prose with **no frontmatter**, dated +by git, and authored lazily: it appears when the first term is worth pinning down. + +Each entry is a term, a one-or-two-sentence definition of what it *is* (not what +it does), and an optional `_Avoid_:` line naming the synonyms to reject: + +```md +**Timer**: +A scheduled future delivery, identified by a timer id. +_Avoid_: job, task, alarm +``` + +Keep it a glossary, not a spec — no implementation detail. A change that +introduces or sharpens a term updates `glossary.md` in the same PR, the same way +a behavior change promotes into a capability file. + +### Change bundles + +A change is a folder `changes/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. + +`summary` is written when the change is created (the intent one-liner) and +**finalized at ship** to state the realized result — set in the implementing +PR, alongside the code and the `architecture/` promotion. No post-merge +bookkeeping, no folder move. `date` and `slug` are never written — they are +read from the bundle's directory name. + +### Three lanes + +| 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. +- **`decisions/-.md`** — one file per design decision taken + (especially options *rejected*), each with a revisit trigger; listed by + `just index`. + +Templates live in [`_templates/`](_templates/). + +### Frontmatter + +`date` and `slug` are **derived from the directory / file name** — never +repeated in frontmatter. So: + +- `design.md` / `change.md`: `summary` (single line) only. +- `plan.md`: **no frontmatter** — its identity is the bundle directory. +- `decisions/*.md`: `status` (accepted|superseded), `summary`, and optional + `supersedes` / `superseded_by`. +- Files in `architecture/` carry **no** frontmatter — living prose, dated by git. + +**`summary`** is one line: written at creation as the intent, then **finalized +at ship** to state the realized result — what shipped and its effect. It is the +only field the index renders. + +## Index + +Run `just index` to print the change/decision listing — a query over the files, +never a committed artifact. + +## Other + +- [`launch-playbook.md`](launch-playbook.md) — internal Phase 4 launch asset (not published). +- [`deferred.md`](deferred.md) — real-but-unscheduled items, each with a revisit trigger. From e08c9db8fcfb7c4a4c3d73c5805c1776690bf811 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sun, 28 Jun 2026 21:39:06 +0300 Subject: [PATCH 5/9] docs: migrate promotion-strategy and community-channels into bundles --- .../changes/2026-06-24.01-promotion-strategy/design.md | 7 ++++--- .../changes/2026-06-27.01-community-channels/design.md | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) rename docs/superpowers/specs/2026-06-24-promotion-strategy-design.md => planning/changes/2026-06-24.01-promotion-strategy/design.md (98%) rename docs/superpowers/specs/2026-06-27-community-channels-design.md => planning/changes/2026-06-27.01-community-channels/design.md (98%) diff --git a/docs/superpowers/specs/2026-06-24-promotion-strategy-design.md b/planning/changes/2026-06-24.01-promotion-strategy/design.md similarity index 98% rename from docs/superpowers/specs/2026-06-24-promotion-strategy-design.md rename to planning/changes/2026-06-24.01-promotion-strategy/design.md index 472b753..115d94b 100644 --- a/docs/superpowers/specs/2026-06-24-promotion-strategy-design.md +++ b/planning/changes/2026-06-24.01-promotion-strategy/design.md @@ -1,8 +1,9 @@ +--- +summary: Org-wide, users-first staged growth strategy for the modern-python GitHub org along three axes (reach, retain, contribute). +--- + # Modern Python — Promotion Strategy -**Date:** 2026-06-24 -**Status:** Approved design, ready for planning -**Owner:** modern-python maintainers **Scope:** Org-wide growth strategy for the `modern-python` GitHub org ## Goal diff --git a/docs/superpowers/specs/2026-06-27-community-channels-design.md b/planning/changes/2026-06-27.01-community-channels/design.md similarity index 98% rename from docs/superpowers/specs/2026-06-27-community-channels-design.md rename to planning/changes/2026-06-27.01-community-channels/design.md index ce34c4f..2db2589 100644 --- a/docs/superpowers/specs/2026-06-27-community-channels-design.md +++ b/planning/changes/2026-06-27.01-community-channels/design.md @@ -1,8 +1,9 @@ +--- +summary: Which channels the modern-python org uses to reach, support, and retain users, and how they fit together. +--- + # Modern Python — Community & Channels -**Date:** 2026-06-27 -**Status:** Approved design, ready for planning -**Owner:** modern-python maintainers **Scope:** Which channels the `modern-python` org uses to reach, support, and retain users — and how they fit together. Extends the [promotion strategy](2026-06-24-promotion-strategy-design.md); **amends its Phase 5 From 7b923bd3462b1832d2e8e9cc5371ae0eba2947eb Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sun, 28 Jun 2026 21:41:53 +0300 Subject: [PATCH 6/9] docs: migrate org-favicon-social-card into a Full bundle --- .../changes/2026-06-28.01-org-favicon-social-card/design.md | 5 ++++- .../changes/2026-06-28.01-org-favicon-social-card/plan.md | 0 2 files changed, 4 insertions(+), 1 deletion(-) rename docs/superpowers/specs/2026-06-28-org-favicon-social-card-design.md => planning/changes/2026-06-28.01-org-favicon-social-card/design.md (97%) rename docs/superpowers/plans/2026-06-28-org-favicon-social-card.md => planning/changes/2026-06-28.01-org-favicon-social-card/plan.md (100%) diff --git a/docs/superpowers/specs/2026-06-28-org-favicon-social-card-design.md b/planning/changes/2026-06-28.01-org-favicon-social-card/design.md similarity index 97% rename from docs/superpowers/specs/2026-06-28-org-favicon-social-card-design.md rename to planning/changes/2026-06-28.01-org-favicon-social-card/design.md index e502102..80a866c 100644 --- a/docs/superpowers/specs/2026-06-28-org-favicon-social-card-design.md +++ b/planning/changes/2026-06-28.01-org-favicon-social-card/design.md @@ -1,6 +1,9 @@ +--- +summary: Replaced the snake brand mark with the pinwheel favicon/avatar and Jost wordmark social card, generated by the brand/build/ toolchain. +--- + # modern-python org favicon + social card — design spec -**Status:** approved design (brainstorming output) **Scope:** the org-level **favicon** and **social card** only. Everything else (per-project marks, the subfamily/family system, inner glyphs, framework integrations) is explicitly **deferred** to a later task. diff --git a/docs/superpowers/plans/2026-06-28-org-favicon-social-card.md b/planning/changes/2026-06-28.01-org-favicon-social-card/plan.md similarity index 100% rename from docs/superpowers/plans/2026-06-28-org-favicon-social-card.md rename to planning/changes/2026-06-28.01-org-favicon-social-card/plan.md From 822f3aa43543a4a98f1b9df62ff222a4c3b3969d Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sun, 28 Jun 2026 21:44:51 +0300 Subject: [PATCH 7/9] docs: migrate community-health defaults into a bundle --- .../change.md | 37 +++++++++++++++++++ .../plan.md | 0 2 files changed, 37 insertions(+) create mode 100644 planning/changes/2026-06-24.02-org-community-health-defaults/change.md rename docs/superpowers/plans/2026-06-24-org-community-health-defaults.md => planning/changes/2026-06-24.02-org-community-health-defaults/plan.md (100%) diff --git a/planning/changes/2026-06-24.02-org-community-health-defaults/change.md b/planning/changes/2026-06-24.02-org-community-health-defaults/change.md new file mode 100644 index 0000000..5d01a09 --- /dev/null +++ b/planning/changes/2026-06-24.02-org-community-health-defaults/change.md @@ -0,0 +1,37 @@ +--- +summary: Added org-wide default community-health files so every modern-python repo lacking its own inherits a CONTRIBUTING guide, Code of Conduct, security policy, support routing, issue/PR templates, and funding config — lifting coverage from ~25% to ~90% in one PR. +--- + +# Change: Org-wide community-health defaults + +**Lane:** condensed spec for an already-shipped, multi-file org-wide change. +The full task-by-task record lives in [`plan.md`](./plan.md); this page is the +why. + +## Goal + +Every repo in the `modern-python` org that does not define its own +community-health files should inherit sensible org defaults, so contributors get +a consistent CONTRIBUTING guide, Code of Conduct, security policy, support +routing, issue/PR templates, and funding config without per-repo upkeep. + +## Approach + +GitHub serves *default* community-health files from the special `modern-python/.github` +repo: files at the repo **root** or under **`.github/`** apply to every org repo +that lacks its own. This repo also builds the public site from `docs/`, so the +community-health files go at the root or under `.github/` — **never under +`docs/`**, which would publish them. See [`plan.md`](./plan.md) for the per-file +task breakdown and verifications. + +## Files + +- Root community-health files (`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, + `SECURITY.md`, `SUPPORT.md`) and `.github/` issue/PR templates + funding config. +- [`plan.md`](./plan.md) — the detailed implementation plan (shipped). + +## Verification + +- [x] Each YAML form/config parses. +- [x] `mkdocs build --strict` still succeeds (no community-health file leaks into the site). +- [x] Each file lands at a path GitHub recognizes as an org default. diff --git a/docs/superpowers/plans/2026-06-24-org-community-health-defaults.md b/planning/changes/2026-06-24.02-org-community-health-defaults/plan.md similarity index 100% rename from docs/superpowers/plans/2026-06-24-org-community-health-defaults.md rename to planning/changes/2026-06-24.02-org-community-health-defaults/plan.md From e6e18ed450d834bb45d8d119951527fff3dafa93 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sun, 28 Jun 2026 21:50:15 +0300 Subject: [PATCH 8/9] docs: move launch playbook to planning/, retire docs/superpowers Repair stale cross-links to the migrated promotion-strategy spec. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../changes/2026-06-24.02-org-community-health-defaults/plan.md | 2 +- planning/changes/2026-06-27.01-community-channels/design.md | 2 +- {docs/superpowers => planning}/launch-playbook.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename {docs/superpowers => planning}/launch-playbook.md (99%) diff --git a/planning/changes/2026-06-24.02-org-community-health-defaults/plan.md b/planning/changes/2026-06-24.02-org-community-health-defaults/plan.md index 3a22965..e758333 100644 --- a/planning/changes/2026-06-24.02-org-community-health-defaults/plan.md +++ b/planning/changes/2026-06-24.02-org-community-health-defaults/plan.md @@ -670,7 +670,7 @@ Covenant 2.1), SECURITY (private vulnerability reporting), SUPPORT, issue forms casing on the docs landing page. Implements Phase 0 (foundation hygiene) of the promotion strategy -(\`docs/superpowers/specs/2026-06-24-promotion-strategy-design.md\`). +(\`../2026-06-24.01-promotion-strategy/design.md\`). Note: org-level Discussions must be enabled for the issue-chooser "Question" contact link to resolve (see manual step). diff --git a/planning/changes/2026-06-27.01-community-channels/design.md b/planning/changes/2026-06-27.01-community-channels/design.md index 2db2589..5ad493d 100644 --- a/planning/changes/2026-06-27.01-community-channels/design.md +++ b/planning/changes/2026-06-27.01-community-channels/design.md @@ -6,7 +6,7 @@ summary: Which channels the modern-python org uses to reach, support, and retain **Scope:** Which channels the `modern-python` org uses to reach, support, and retain users — and how they fit together. Extends the -[promotion strategy](2026-06-24-promotion-strategy-design.md); **amends its Phase 5 +[promotion strategy](../2026-06-24.01-promotion-strategy/design.md); **amends its Phase 5 deferral of Discord/Telegram.** ## Why this doc exists diff --git a/docs/superpowers/launch-playbook.md b/planning/launch-playbook.md similarity index 99% rename from docs/superpowers/launch-playbook.md rename to planning/launch-playbook.md index b4949e2..369cf86 100644 --- a/docs/superpowers/launch-playbook.md +++ b/planning/launch-playbook.md @@ -1,6 +1,6 @@ # Launch playbook (Phase 4) -Internal asset — **not published** (excluded from the site via `exclude_docs`). +Internal asset — **not published** (lives at the repo root, outside the MkDocs `docs/` tree). Ready-to-post copy for the coordinated launch, plus timing and etiquette. **Design principles** From 30e0caa586f962265eb13246de5d26c50c05ed84 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sun, 28 Jun 2026 21:53:33 +0300 Subject: [PATCH 9/9] chore: wire just + CI check-planning, point CLAUDE.md at the convention --- .github/workflows/planning.yml | 18 ++++++++++++++++++ CLAUDE.md | 17 +++++++++++++++++ Justfile | 12 ++++++++++++ mkdocs.yml | 1 - 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/planning.yml create mode 100644 Justfile diff --git a/.github/workflows/planning.yml b/.github/workflows/planning.yml new file mode 100644 index 0000000..ccf4116 --- /dev/null +++ b/.github/workflows/planning.yml @@ -0,0 +1,18 @@ +name: Planning + +on: + pull_request: + push: + branches: [main] + +jobs: + check-planning: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + python-version: "3.11" + - name: Validate planning bundles + run: uv run python planning/index.py --check diff --git a/CLAUDE.md b/CLAUDE.md index 078caeb..de15a38 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -53,3 +53,20 @@ GitHub occasionally type-checks a **stale `refs/pull//merge`** after a push, PR can show an old lint/test failure that no longer matches the branch. Confirm by running the failing check locally at the branch HEAD (with the pinned tool version); if it's clean, push a fresh commit to force GitHub to recompute the merge ref. + +## Workflow + +Planning follows the convention in [`planning/README.md`](planning/README.md) — +its **Quick path** is authoritative. Pick a lane (Full = `design.md` + `plan.md`, +Lightweight = `change.md`, Tiny = conventional commit), create a bundle under +`planning/changes/YYYY-MM-DD.NN-/` from `planning/_templates/`, and run +`just check-planning` before pushing. The applied convention version is in +`planning/.convention-version`; update it via the canonical repo's `APPLY.md`. + +## Architecture + +`architecture/` (repo root) is the living truth about what this repo does now — +one file per capability plus `glossary.md`, no frontmatter, authored lazily. +**When a change alters a capability's behavior, update the matching +`architecture/.md` in the same PR.** The change bundle in +`planning/changes/` stays as the *why*. diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..c92ba01 --- /dev/null +++ b/Justfile @@ -0,0 +1,12 @@ +default: check-planning test + +index: + uv run python planning/index.py + +check-planning: + uv run python planning/index.py --check + +lint-ci: check-planning + +test: + uv run pytest diff --git a/mkdocs.yml b/mkdocs.yml index 60f5a1a..517ff15 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,7 +7,6 @@ repo_name: modern-python # Repo-internal docs that must not be published to the public site. exclude_docs: | DEPLOYMENT.md - superpowers/ theme: name: material