From 85753bc00c48f109c42e2b41db6bc9043a9cedfb Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Tue, 7 Jul 2026 23:40:39 +0300 Subject: [PATCH 1/9] docs(planning): design for convention upgrade 1.1.1 -> 2.0.0 Full-lane change file capturing the APPLY.md flow: flat-file validator and template swap, flatten the 18 change folders, drop 15 plan.md, and rewrite README/CLAUDE prose to the ephemeral-plan model. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...7-07.02-upgrade-planning-convention-2.0.md | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 planning/changes/2026-07-07.02-upgrade-planning-convention-2.0.md diff --git a/planning/changes/2026-07-07.02-upgrade-planning-convention-2.0.md b/planning/changes/2026-07-07.02-upgrade-planning-convention-2.0.md new file mode 100644 index 0000000..301b8f8 --- /dev/null +++ b/planning/changes/2026-07-07.02-upgrade-planning-convention-2.0.md @@ -0,0 +1,91 @@ +--- +summary: Upgrade the planning convention 1.1.1 -> 2.0.0 — swap in the flat-file validator and templates, flatten the 18 existing change folders to files, drop the 15 committed plan.md, and rewrite README/CLAUDE prose to the flat-file / ephemeral-plan model. +--- + +# Design: Upgrade planning convention 1.1.1 -> 2.0.0 + +## Summary + +Re-run the [`lesnik512/planning-convention`](https://github.com/lesnik512/planning-convention) +`APPLY.md` flow to move this repo from the recorded **1.1.1** to the latest +**2.0.0**, absorbing three CHANGELOG deltas at once: 1.1.2 (ruff directive on +`index.py`), 1.2.0 (plans are ephemeral; `plan.md` template dropped), and the +breaking 2.0.0 (change *bundles* become flat *files*). One Full-lane change, +one PR — the new `index.py` rejects folder bundles, so the tooling swap and the +historical migration must land together. + +## Motivation + +`planning/.convention-version` reads `1.1.1`; the canonical repo is at `2.0.0` +(released 2026-07-07). The org convention (`CLAUDE.md`) says to track it via the +canonical `APPLY.md`. Staying behind means our validator, templates, README, and +`CLAUDE.md` describe a folder-and-`plan.md` model the convention has since +replaced. + +## Non-goals + +- No changes to the convention's *content* — we adopt it verbatim, not fork it. +- No rewriting of historical change files' prose — only their location (folder + to file) changes; `summary:` frontmatter is left as-is. +- No new `audits/` or `retros/` dirs — those are authored lazily, when first + needed. + +## Design + +### 1. Tooling swap (verbatim, APPLY.md Step 1) + +- Replace `planning/index.py` with canonical 2.0.0 (validates `changes/*.md` and + `decisions/*.md` as flat files; flags any directory under `changes/`; carries + the `# ruff: noqa: INP001` header). +- Replace the five templates (`change`, `decision`, `design`, `glossary`, + `release`) verbatim and **delete `planning/_templates/plan.md`** (removed in + 1.2.0). Fetch each canonical file with `curl` (byte-exact), not paraphrasing. + +### 2. Historical migration (18 folders -> 18 files, 2.0.0 + 1.2.0) + +- `git rm` all 15 committed `plan.md` (1.2.0: plans are ephemeral; git history + preserves them). +- `git mv planning/changes//{design,change}.md + planning/changes/.md`, then `rmdir` each folder. +- All 18 folders hold exactly one of `design.md` / `change.md` (verified — no + dual-file conflicts), so the move is unambiguous. + +### 3. Prose merge — `planning/README.md` (Step 2) + +Replace the block from `## Quick path` through the end of `## Conventions` with +the canonical `convention.md` body (flat change files, "plans are ephemeral", +"lean specs"). **Preserve** the repo-local `## Index` and `## Other` sections and +the references to the canonical source. + +### 4. `CLAUDE.md` fix (Step 3) + +The Workflow section still says `Full = design.md + plan.md` and "create a bundle +under `planning/changes/YYYY-MM-DD.NN-/`". Rewrite to the flat-file / +single-change-file, ephemeral-plan model. Keep the architecture-promotion +reminder. Verify the `justfile` already carries `index` / `check-planning` / +`lint-ci` (it does — no edit). + +### 5. Scratch git-ignore (1.2.0) + +Git-ignore the executor's ephemeral plan workspace (`.superpowers/`) so plans +stay out of version control. + +### 6. Version + self-record + +Set `planning/.convention-version` to `2.0.0`. This design is itself the first +flat-format change file (`2026-07-07.02-...md`). + +## Testing + +- `just check-planning` -> `planning: OK` (proves no folders remain and all + frontmatter validates under the 2.0.0 rules). +- `find planning/changes -mindepth 1 -type d` returns nothing. +- `just lint-ci` and `just test` green; ruff clean on the new `index.py`. + +## Risk + +- **Stale `refs/pull//merge` CI** (repo's known gotcha) — confirm the failing + check locally at branch HEAD; push a fresh commit to force a recompute. +- **Lossy `plan.md` deletion** — accepted; recoverable from git history. +- **Non-verbatim copies** — mitigated by fetching templates/`index.py` raw via + `curl`, not WebFetch (which paraphrases). From a57e7c45b3ad5508f7f5afc1ef55e18417b7fc8d Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Wed, 8 Jul 2026 08:58:15 +0300 Subject: [PATCH 2/9] chore(planning): swap in 2.0.0 flat-file validator and templates Overwrite index.py and the five templates verbatim from the canonical convention; drop the plan.md template (plans are ephemeral in 1.2.0). Co-Authored-By: Claude Opus 4.8 (1M context) --- planning/_templates/change.md | 4 +- planning/_templates/design.md | 34 ++++------ planning/_templates/plan.md | 46 -------------- planning/index.py | 115 +++++++++++++++------------------- 4 files changed, 65 insertions(+), 134 deletions(-) delete mode 100644 planning/_templates/plan.md diff --git a/planning/_templates/change.md b/planning/_templates/change.md index d4c8962..5aa7e81 100644 --- a/planning/_templates/change.md +++ b/planning/_templates/change.md @@ -5,8 +5,8 @@ summary: One line — shown in the generated index. Written at creation; finaliz # 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`. +change, a single straightforward test. If it outgrows this, rewrite it from +the design template. ## Goal diff --git a/planning/_templates/design.md b/planning/_templates/design.md index d63e22d..17dbee1 100644 --- a/planning/_templates/design.md +++ b/planning/_templates/design.md @@ -4,6 +4,10 @@ summary: One line — shown in the generated index. Written at creation; finaliz # Design: One-line capitalized title + + ## Summary One paragraph. What changes, at the level a reader needs to decide if this @@ -12,37 +16,23 @@ 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. +abstract complaints. ## Design -### 1. - What changes, in enough detail that a reader who has not seen the codebase -can follow. Code samples / diagrams welcome. +can follow. Sketches and interface fragments welcome; never the full +diff-to-be. Reference rejected alternatives in `decisions/` instead of +retelling them. -### 2. - -... - -## Operations - -Out-of-repo steps (DNS, infra, external account changes). Omit if none. - -## Out of scope +## Non-goals -Already covered above under Non-goals if appropriate. Repeat-list of -explicitly-excluded follow-ups belongs here when the list is long. +What is deliberately out of scope and (when nontrivial) why. One line each. ## Testing -How we know it landed correctly. New pytest? Smoke check on live URL? -Lint pass? Be specific. +How we know it landed correctly. Be specific: the command and the expected +signal. ## Risk diff --git a/planning/_templates/plan.md b/planning/_templates/plan.md deleted file mode 100644 index 132d720..0000000 --- a/planning/_templates/plan.md +++ /dev/null @@ -1,46 +0,0 @@ -# — 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/index.py b/planning/index.py index e6e0837..2d70ac3 100644 --- a/planning/index.py +++ b/planning/index.py @@ -1,12 +1,12 @@ +# ruff: noqa: INP001 # planning/ is not a Python package (this file is vendored into consumers' planning/) """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: +Run via ``just index``. Globs ``planning/changes/*.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 +``date`` and ``slug`` are derived from the file name, not frontmatter — the name is the single source of truth for both. """ @@ -17,9 +17,8 @@ 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.+)$") +CHANGE_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") @@ -44,7 +43,7 @@ def parse_frontmatter(text: str) -> dict[str, str]: 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``.""" + """Inject ``date``/``slug`` derived from a file name into ``fields``.""" match = pattern.match(name) if match: fields["date"] = match.group("date") @@ -52,25 +51,20 @@ def _named(fields: dict[str, str], name: str, pattern: re.Pattern[str]) -> dict[ return fields -def load_bundles(root: pathlib.Path) -> list[dict[str, str]]: - """Read each bundle's summary; derive date/slug from the directory name.""" +def load_changes(root: pathlib.Path) -> list[dict[str, str]]: + """Read each change file's summary; derive date/slug from the file name.""" changes_dir = root / "changes" - bundles: list[dict[str, str]] = [] + changes: list[dict[str, str]] = [] if not changes_dir.is_dir(): - return bundles - for bundle in sorted(changes_dir.iterdir()): - if not bundle.is_dir(): + return changes + for path in sorted(changes_dir.glob("*.md")): + if path.name == "README.md" or path.name.startswith(("_", ".")): 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 + fields = _named(parse_frontmatter(path.read_text(encoding="utf-8")), path.stem, CHANGE_RE) + fields["path"] = f"changes/{path.name}" + fields["name"] = path.stem + changes.append(fields) + return changes def load_decisions(root: pathlib.Path) -> list[dict[str, str]]: @@ -89,24 +83,24 @@ def load_decisions(root: pathlib.Path) -> list[dict[str, str]]: 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)" +def format_row(row: dict[str, str]) -> str: + """Render one change or decision as a Markdown list item.""" + slug = row.get("slug", "?") + path = row.get("path", "") + date = row.get("date", "") + summary = row.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']})_" + if row.get("supersedes"): + line += f" _(supersedes {row['supersedes']})_" + if row.get("superseded_by"): + line += f" _(superseded by {row['superseded_by']})_" return line -def render(bundles: list[dict[str, str]], decisions: list[dict[str, str]]) -> str: +def render(changes: 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) + change_rows = sorted(changes, 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) @@ -120,32 +114,15 @@ def _require(fields: dict[str, str], keys: tuple[str, ...], rel: str, violations 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`).""" +def _check_change(path: pathlib.Path, violations: list[str]) -> None: + """Validate one change file (requires `summary`).""" + rel = f"changes/{path.name}" + if CHANGE_RE.match(path.stem) is None: + violations.append(f"{rel}: file name is not 'YYYY-MM-DD.NN-slug.md'") 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}" @@ -159,14 +136,24 @@ def _check_decision(path: pathlib.Path, violations: list[str]) -> None: def check(root: pathlib.Path) -> list[str]: - """Validate every bundle and decision; return the list of violation strings.""" + """Validate every change file 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) + for path in sorted(changes_dir.iterdir()): + if path.is_dir(): + violations.append( + f"changes/{path.name}: directory found — convention 2.0.0 uses flat change files " + f"(changes/YYYY-MM-DD.NN-slug.md; see CHANGELOG 2.0.0 for the migration)" + ) + continue + if path.name == "README.md" or path.name.startswith(("_", ".")): + continue + if path.suffix != ".md": + violations.append(f"changes/{path.name}: unexpected non-md file in changes/") + else: + _check_change(path, violations) if decisions_dir.is_dir(): for path in sorted(decisions_dir.glob("*.md")): if path.name == "README.md" or path.name.startswith("_"): @@ -176,7 +163,7 @@ def check(root: pathlib.Path) -> list[str]: def main(argv: list[str] | None = None, root: pathlib.Path | None = None) -> int: - """Print the listing to stdout, or validate bundles with --check.""" + """Print the listing to stdout, or validate change files and decisions with --check.""" argv = sys.argv[1:] if argv is None else argv root = ROOT if root is None else root if "--check" in argv: @@ -188,7 +175,7 @@ def main(argv: list[str] | None = None, root: pathlib.Path | None = None) -> int return 1 sys.stdout.write("planning: OK\n") return 0 - sys.stdout.write(render(load_bundles(root), load_decisions(root))) + sys.stdout.write(render(load_changes(root), load_decisions(root))) return 0 From ce7d4e883a529e3bdd582bf6a39deffc230d771a Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Wed, 8 Jul 2026 09:04:21 +0300 Subject: [PATCH 3/9] refactor(planning): flatten change bundles to files, drop plans 2.0.0 migration: git mv each changes//{design,change}.md to changes/.md and remove the folders; git rm the 15 committed plan.md (git history preserves them). check-planning: OK. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...md => 2026-06-24.01-promotion-strategy.md} | 0 ...06-24.02-org-community-health-defaults.md} | 0 .../plan.md | 709 ------------- ...md => 2026-06-27.01-community-channels.md} | 0 ... 2026-06-28.01-org-favicon-social-card.md} | 0 .../plan.md | 714 ------------- ...026-06-28.02-adopt-planning-convention.md} | 0 .../plan.md | 693 ------------- ... 2026-06-29.01-per-project-brand-marks.md} | 0 .../plan.md | 981 ------------------ ...=> 2026-06-30.01-per-repo-social-cards.md} | 0 .../plan.md | 428 -------- ... => 2026-06-30.02-docs-ogimage-rollout.md} | 0 .../plan.md | 300 ------ ...n.md => 2026-06-30.03-png-optimization.md} | 0 .../2026-06-30.03-png-optimization/plan.md | 233 ----- ...esign.md => 2026-06-30.04-readme-logos.md} | 0 .../2026-06-30.04-readme-logos/plan.md | 277 ----- ... => 2026-06-30.05-profile-badge-tables.md} | 0 .../plan.md | 284 ----- ...gn.md => 2026-07-02.01-starlette-brand.md} | 0 .../2026-07-02.01-starlette-brand/plan.md | 272 ----- ...sign.md => 2026-07-02.02-aiohttp-brand.md} | 0 ...=> 2026-07-02.03-profile-badge-columns.md} | 0 ...e.md => 2026-07-02.04-aiohttp-surfaces.md} | 0 .../design.md => 2026-07-04.01-org-tshirt.md} | 0 .../changes/2026-07-04.01-org-tshirt/plan.md | 304 ------ ...n.md => 2026-07-05.01-docs-lockup-hero.md} | 0 .../2026-07-05.01-docs-lockup-hero/plan.md | 407 -------- ...esign.md => 2026-07-07.01-boosty-cover.md} | 0 .../2026-07-07.01-boosty-cover/plan.md | 166 --- 31 files changed, 5768 deletions(-) rename planning/changes/{2026-06-24.01-promotion-strategy/design.md => 2026-06-24.01-promotion-strategy.md} (100%) rename planning/changes/{2026-06-24.02-org-community-health-defaults/change.md => 2026-06-24.02-org-community-health-defaults.md} (100%) delete mode 100644 planning/changes/2026-06-24.02-org-community-health-defaults/plan.md rename planning/changes/{2026-06-27.01-community-channels/design.md => 2026-06-27.01-community-channels.md} (100%) rename planning/changes/{2026-06-28.01-org-favicon-social-card/design.md => 2026-06-28.01-org-favicon-social-card.md} (100%) delete mode 100644 planning/changes/2026-06-28.01-org-favicon-social-card/plan.md rename planning/changes/{2026-06-28.02-adopt-planning-convention/design.md => 2026-06-28.02-adopt-planning-convention.md} (100%) delete mode 100644 planning/changes/2026-06-28.02-adopt-planning-convention/plan.md rename planning/changes/{2026-06-29.01-per-project-brand-marks/design.md => 2026-06-29.01-per-project-brand-marks.md} (100%) delete mode 100644 planning/changes/2026-06-29.01-per-project-brand-marks/plan.md rename planning/changes/{2026-06-30.01-per-repo-social-cards/design.md => 2026-06-30.01-per-repo-social-cards.md} (100%) delete mode 100644 planning/changes/2026-06-30.01-per-repo-social-cards/plan.md rename planning/changes/{2026-06-30.02-docs-ogimage-rollout/design.md => 2026-06-30.02-docs-ogimage-rollout.md} (100%) delete mode 100644 planning/changes/2026-06-30.02-docs-ogimage-rollout/plan.md rename planning/changes/{2026-06-30.03-png-optimization/design.md => 2026-06-30.03-png-optimization.md} (100%) delete mode 100644 planning/changes/2026-06-30.03-png-optimization/plan.md rename planning/changes/{2026-06-30.04-readme-logos/design.md => 2026-06-30.04-readme-logos.md} (100%) delete mode 100644 planning/changes/2026-06-30.04-readme-logos/plan.md rename planning/changes/{2026-06-30.05-profile-badge-tables/design.md => 2026-06-30.05-profile-badge-tables.md} (100%) delete mode 100644 planning/changes/2026-06-30.05-profile-badge-tables/plan.md rename planning/changes/{2026-07-02.01-starlette-brand/design.md => 2026-07-02.01-starlette-brand.md} (100%) delete mode 100644 planning/changes/2026-07-02.01-starlette-brand/plan.md rename planning/changes/{2026-07-02.02-aiohttp-brand/design.md => 2026-07-02.02-aiohttp-brand.md} (100%) rename planning/changes/{2026-07-02.03-profile-badge-columns/change.md => 2026-07-02.03-profile-badge-columns.md} (100%) rename planning/changes/{2026-07-02.04-aiohttp-surfaces/change.md => 2026-07-02.04-aiohttp-surfaces.md} (100%) rename planning/changes/{2026-07-04.01-org-tshirt/design.md => 2026-07-04.01-org-tshirt.md} (100%) delete mode 100644 planning/changes/2026-07-04.01-org-tshirt/plan.md rename planning/changes/{2026-07-05.01-docs-lockup-hero/design.md => 2026-07-05.01-docs-lockup-hero.md} (100%) delete mode 100644 planning/changes/2026-07-05.01-docs-lockup-hero/plan.md rename planning/changes/{2026-07-07.01-boosty-cover/design.md => 2026-07-07.01-boosty-cover.md} (100%) delete mode 100644 planning/changes/2026-07-07.01-boosty-cover/plan.md diff --git a/planning/changes/2026-06-24.01-promotion-strategy/design.md b/planning/changes/2026-06-24.01-promotion-strategy.md similarity index 100% rename from planning/changes/2026-06-24.01-promotion-strategy/design.md rename to planning/changes/2026-06-24.01-promotion-strategy.md 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.md similarity index 100% rename from planning/changes/2026-06-24.02-org-community-health-defaults/change.md rename to planning/changes/2026-06-24.02-org-community-health-defaults.md 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 deleted file mode 100644 index e758333..0000000 --- a/planning/changes/2026-06-24.02-org-community-health-defaults/plan.md +++ /dev/null @@ -1,709 +0,0 @@ -# Org Community-Health Defaults 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:** Add org-wide default community-health files to the `modern-python/.github` repo so every repo in the org that lacks its own gets a CONTRIBUTING guide, Code of Conduct, security policy, support routing, issue/PR templates, and funding config — lifting community-health coverage from ~25% to ~90% org-wide in one PR. - -**Architecture:** GitHub serves *default* community-health files from a special `.github` repo in the org. Files placed at the **root** or in **`.github/`** of `modern-python/.github` apply to every repo in the org that does not define its own. This repo also builds the public website from `docs/` via MkDocs, so community-health files must go at the repo root or under `.github/` — **never under `docs/`**, which would publish them to the site. No code, no tests in the traditional sense; each task ends with a concrete verification (YAML parses, MkDocs still builds, file lands at a path GitHub recognizes). - -**Tech Stack:** GitHub community-health defaults, GitHub issue *forms* (YAML schema), Markdown, MkDocs Material (existing site build), `uv` (runs the build). - -## Global Constraints - -- **Repo:** this is `modern-python/.github`. Default community-health files here apply org-wide. Place them at the repo **root** or in **`.github/`** only — never in `docs/` (that directory is the published MkDocs site). -- **Branch:** do this work on a dedicated branch `chore/org-community-health`, cut from `main` (NOT stacked on `docs/promotion-strategy`). One PR. -- **Brand casing (verbatim):** `modern-di`, `that-depends`, `Litestar` (never "LiteStar"), `FastStream`, `FastAPI`, `Typer`, `SQLAlchemy`, `PostgreSQL`. -- **Tooling vocabulary:** projects use `uv` (packaging), `ruff` (lint/format), `ty` (type check), `uv_build` (build backend); most repos expose tasks behind a `justfile`. -- **Security & CoC contact:** use GitHub **private vulnerability reporting** (repo/org → Security → "Report a vulnerability"). Do **not** invent or publish a personal email address. -- **Org Discussions URL:** `https://github.com/orgs/modern-python/discussions` (org-level Discussions must be enabled — see Task 8 manual step). -- **Docs site:** `https://modern-python.org`. -- **Commit footer (every commit):** - ``` - Co-Authored-By: Claude Opus 4.8 (1M context) - ``` -- **No "Generated with Claude Code" lines in commits** (org convention). - ---- - -## File Structure - -Files created (all in `modern-python/.github`): - -``` -.github/ - ISSUE_TEMPLATE/ - bug_report.yml # bug issue form - feature_request.yml # feature issue form - config.yml # disable blank issues; route questions to Discussions/docs - PULL_REQUEST_TEMPLATE.md # default PR template - FUNDING.yml # sponsor button config (optional input) -CONTRIBUTING.md # org-wide contributor guide (root) -CODE_OF_CONDUCT.md # Contributor Covenant 2.1 (root) -SECURITY.md # vulnerability reporting policy (root) -SUPPORT.md # where to get help (root) -``` - -Modified: - -``` -docs/index.md # fix "LiteStar" -> "Litestar" casing (lines ~60, ~66) -``` - ---- - -## Task 1: Issue forms + PR template - -**Files:** -- Create: `.github/ISSUE_TEMPLATE/bug_report.yml` -- Create: `.github/ISSUE_TEMPLATE/feature_request.yml` -- Create: `.github/ISSUE_TEMPLATE/config.yml` -- Create: `.github/PULL_REQUEST_TEMPLATE.md` - -**Interfaces:** -- Produces: org-default issue chooser (Bug / Feature) plus two contact links (Discussions, Docs); a default PR template. Consumed by GitHub's new-issue and new-PR UI on every org repo without its own templates. - -- [ ] **Step 1: Create the bug report form** - -Create `.github/ISSUE_TEMPLATE/bug_report.yml`: - -```yaml -name: Bug report -description: Report a defect in a modern-python project -labels: ["bug"] -body: - - type: markdown - attributes: - value: | - Thanks for filing a bug. Fill in the sections below so we can reproduce it quickly. - - type: input - id: project - attributes: - label: Affected project - description: Which repository / package is this about? - placeholder: e.g. modern-di, that-depends, httpware - validations: - required: true - - type: input - id: version - attributes: - label: Versions - description: Package version, Python version, and OS. - placeholder: e.g. modern-di 1.2.0, Python 3.12, macOS 14 - validations: - required: true - - type: textarea - id: what-happened - attributes: - label: What happened? - description: A clear description of the bug and what you expected instead. - validations: - required: true - - type: textarea - id: repro - attributes: - label: Minimal reproduction - description: The smallest snippet or steps that reproduce the problem. - render: python - validations: - required: true - - type: textarea - id: logs - attributes: - label: Logs / traceback - description: Paste any relevant traceback. Automatically formatted as code. - render: shell - validations: - required: false -``` - -- [ ] **Step 2: Create the feature request form** - -Create `.github/ISSUE_TEMPLATE/feature_request.yml`: - -```yaml -name: Feature request -description: Suggest an idea or improvement for a modern-python project -labels: ["enhancement"] -body: - - type: input - id: project - attributes: - label: Target project - description: Which repository / package would this affect? - placeholder: e.g. modern-di, lite-bootstrap - validations: - required: true - - type: textarea - id: problem - attributes: - label: Problem - description: What problem are you trying to solve? What's the use case? - validations: - required: true - - type: textarea - id: proposal - attributes: - label: Proposed solution - description: What would you like to happen? Include API sketches if relevant. - validations: - required: true - - type: textarea - id: alternatives - attributes: - label: Alternatives considered - description: Other approaches you've weighed, if any. - validations: - required: false -``` - -- [ ] **Step 3: Create the issue chooser config** - -Create `.github/ISSUE_TEMPLATE/config.yml`: - -```yaml -blank_issues_enabled: false -contact_links: - - name: Question / usage help - url: https://github.com/orgs/modern-python/discussions - about: Ask questions and get help in GitHub Discussions. - - name: Documentation - url: https://modern-python.org - about: Browse the docs — your question may already be answered. -``` - -- [ ] **Step 4: Create the PR template** - -Create `.github/PULL_REQUEST_TEMPLATE.md`: - -```markdown -## Summary - - - -## Changes - - -- - -## Checklist - -- [ ] Lint and format pass (`ruff`) -- [ ] Type check passes (`ty`) -- [ ] Tests pass and new behavior is covered -- [ ] Docs updated if behavior or public API changed -- [ ] Repo metadata stays consistent across the three surfaces (GitHub description, pyproject `description`, profile blurb) if this touches packaging -``` - -- [ ] **Step 5: Verify the YAML parses** - -Run: -```bash -python -c "import yaml; [yaml.safe_load(open(f)) for f in ['.github/ISSUE_TEMPLATE/bug_report.yml','.github/ISSUE_TEMPLATE/feature_request.yml','.github/ISSUE_TEMPLATE/config.yml']]; print('YAML OK')" -``` -Expected: `YAML OK` (no traceback). - -- [ ] **Step 6: Commit** - -```bash -git add .github/ISSUE_TEMPLATE .github/PULL_REQUEST_TEMPLATE.md -git commit -m "chore: add org-default issue forms and PR template - -Co-Authored-By: Claude Opus 4.8 (1M context) " -``` - ---- - -## Task 2: CONTRIBUTING.md - -**Files:** -- Create: `CONTRIBUTING.md` (repo root) - -**Interfaces:** -- Produces: the "Contributing" link GitHub surfaces on every org repo's new-issue/new-PR pages and the repo sidebar. - -- [ ] **Step 1: Create CONTRIBUTING.md** - -Create `CONTRIBUTING.md`: - -```markdown -# Contributing to Modern Python - -Thanks for your interest in contributing! These guidelines apply across all -repositories in the [`modern-python`](https://github.com/modern-python) org -unless a repository overrides them with its own `CONTRIBUTING.md`. - -## Ways to contribute - -- **Report bugs** and **request features** via the issue templates. -- **Ask questions** in [GitHub Discussions](https://github.com/orgs/modern-python/discussions). -- **Improve docs** — typo fixes and clarifications are very welcome. -- **Send pull requests** — see the workflow below. - -## Development setup - -Projects use [`uv`](https://github.com/astral-sh/uv) for packaging, -[`ruff`](https://github.com/astral-sh/ruff) for lint/format, -[`ty`](https://github.com/astral-sh/ty) for type checking, and `uv_build` as the -build backend. Most repos expose tasks behind a `justfile`. - -```bash -# Clone your fork, then from the repo root: -uv sync # install dependencies into a local venv -just --list # see available tasks (lint, test, etc.) where a justfile exists -``` - -## Pull request workflow - -1. Fork the repository and create a topic branch from `main`. -2. Make your change. Keep it focused — one logical change per PR. -3. Run lint, type check, and tests locally before pushing: - ```bash - uv run ruff check . && uv run ruff format --check . - uv run ty check - # run the project's test command (often `just test` or `uv run pytest`) - ``` -4. Update docs if you changed behavior or public API. -5. Open the PR using the template. Link the issue it resolves. - -## CI note - -GitHub occasionally type-checks a stale `refs/pull//merge` after a push, so a -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. - -## Code of Conduct - -By participating you agree to abide by our -[Code of Conduct](CODE_OF_CONDUCT.md). -``` - -- [ ] **Step 2: Verify brand casing** - -Run: -```bash -grep -n "LiteStar" CONTRIBUTING.md || echo "casing OK" -``` -Expected: `casing OK`. - -- [ ] **Step 3: Commit** - -```bash -git add CONTRIBUTING.md -git commit -m "docs: add org-wide CONTRIBUTING guide - -Co-Authored-By: Claude Opus 4.8 (1M context) " -``` - ---- - -## Task 3: CODE_OF_CONDUCT.md - -**Files:** -- Create: `CODE_OF_CONDUCT.md` (repo root) - -**Interfaces:** -- Produces: the Code of Conduct GitHub links from every org repo; referenced by `CONTRIBUTING.md` and `SECURITY.md`. - -- [ ] **Step 1: Create CODE_OF_CONDUCT.md (Contributor Covenant 2.1)** - -Create `CODE_OF_CONDUCT.md` with the Contributor Covenant 2.1 text below. The -enforcement-contact paragraph is customized to use GitHub's private reporting -(no email is published). - -```markdown -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or advances of - any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, - without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official email address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement through GitHub's -private reporting (open the relevant repository's **Security** tab and choose -**Report a vulnerability**, which routes confidentially to the maintainers) or -by privately contacting a maintainer. All complaints will be reviewed and -investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at -[https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations -``` - -- [ ] **Step 2: Commit** - -```bash -git add CODE_OF_CONDUCT.md -git commit -m "docs: add Contributor Covenant Code of Conduct - -Co-Authored-By: Claude Opus 4.8 (1M context) " -``` - ---- - -## Task 4: SECURITY.md + SUPPORT.md - -**Files:** -- Create: `SECURITY.md` (repo root) -- Create: `SUPPORT.md` (repo root) - -**Interfaces:** -- Produces: the "Security policy" and "Support" links GitHub surfaces org-wide. SECURITY.md is referenced by the CoC enforcement channel. - -- [ ] **Step 1: Create SECURITY.md** - -Create `SECURITY.md`: - -```markdown -# Security Policy - -## Reporting a vulnerability - -Please **do not** open a public issue for security problems. - -Report vulnerabilities privately using GitHub's built-in private reporting: -open the affected repository's **Security** tab and choose -**"Report a vulnerability"**. This routes confidentially to the maintainers. - -Please include: - -- the affected project and version, -- a description of the vulnerability and its impact, -- steps to reproduce or a proof of concept, if available. - -We aim to acknowledge reports within a few days and will keep you informed as we -investigate and prepare a fix. Once a fix is released, we're happy to credit you -in the release notes unless you prefer to remain anonymous. - -## Supported versions - -Unless a repository states otherwise, security fixes target the **latest -released version** on PyPI. Upgrading to the latest release is the recommended -way to receive fixes. -``` - -- [ ] **Step 2: Create SUPPORT.md** - -Create `SUPPORT.md`: - -```markdown -# Support - -Need help with a `modern-python` project? Here's where to go: - -- **Documentation:** start at [modern-python.org](https://modern-python.org) and - the project's own docs site (linked from each repo). -- **Questions & usage help:** ask in - [GitHub Discussions](https://github.com/orgs/modern-python/discussions). -- **Bugs & feature requests:** open an issue in the relevant repository using the - provided templates. -- **Security issues:** see [SECURITY.md](SECURITY.md) — please report privately, - not in a public issue. - -Please search existing issues and discussions before opening a new one — your -question may already be answered. -``` - -- [ ] **Step 3: Verify casing across both files** - -Run: -```bash -grep -n "LiteStar" SECURITY.md SUPPORT.md || echo "casing OK" -``` -Expected: `casing OK`. - -- [ ] **Step 4: Commit** - -```bash -git add SECURITY.md SUPPORT.md -git commit -m "docs: add org-wide security policy and support routing - -Co-Authored-By: Claude Opus 4.8 (1M context) " -``` - ---- - -## Task 5: FUNDING.yml (optional — needs one input) - -**Files:** -- Create: `.github/FUNDING.yml` - -**Interfaces:** -- Produces: the org-wide "Sponsor" button. **Requires one human input:** the GitHub Sponsors handle (or other funding platform). If the maintainer does not want a sponsor button, **skip this task entirely** and note the skip. - -- [ ] **Step 1: Decide** - -If the maintainer wants a sponsor button, get the GitHub Sponsors username (e.g. `lesnik512`) or other platform handle. If not, skip to "Commit (skip)" below and do not create the file. - -- [ ] **Step 2: Create FUNDING.yml (only if a handle was provided)** - -Create `.github/FUNDING.yml`, replacing `` with the real handle: - -```yaml -# Org-wide funding sources. Uncomment and fill the platforms you use. -github: [] -# ko_fi: -# custom: ["https://modern-python.org"] -``` - -- [ ] **Step 3: Verify YAML parses (only if created)** - -Run: -```bash -python -c "import yaml; yaml.safe_load(open('.github/FUNDING.yml')); print('YAML OK')" -``` -Expected: `YAML OK`. - -- [ ] **Step 4: Commit (or record skip)** - -If created: -```bash -git add .github/FUNDING.yml -git commit -m "chore: add org-wide funding config - -Co-Authored-By: Claude Opus 4.8 (1M context) " -``` -If skipped: no commit; note "FUNDING.yml skipped — no sponsor handle" in the PR description. - ---- - -## Task 6: Fix Litestar casing in docs/index.md - -**Files:** -- Modify: `docs/index.md` (the two `LiteStar` occurrences, ~lines 60 and 66) - -**Interfaces:** -- Produces: brand-consistent docs landing page. Independent of the community-health files; rides along in this PR as Phase 0 hygiene. - -- [ ] **Step 1: Confirm the occurrences** - -Run: -```bash -grep -n "LiteStar" docs/index.md -``` -Expected: two lines (the `litestar-sqlalchemy-template` description and the `modern-di-litestar` line). - -- [ ] **Step 2: Fix the casing** - -Replace both `LiteStar` with `Litestar` in `docs/index.md`. After editing, run: -```bash -grep -n "LiteStar" docs/index.md || echo "casing OK" -``` -Expected: `casing OK`. - -- [ ] **Step 3: Commit** - -```bash -git add docs/index.md -git commit -m "docs: fix Litestar casing on the landing page - -Co-Authored-By: Claude Opus 4.8 (1M context) " -``` - ---- - -## Task 7: Verify the site still builds - -**Files:** none (verification only) - -**Interfaces:** -- Consumes: all files added above. Confirms the root/`.github` community-health files do not leak into or break the published MkDocs site (which builds only from `docs/`). - -- [ ] **Step 1: Build the docs site** - -Run: -```bash -uv run mkdocs build --strict -``` -Expected: build succeeds; output in `site/`. No errors. (Root-level `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, etc. are outside `docs/` and must NOT appear in `site/`.) - -- [ ] **Step 2: Confirm community files did not get published** - -Run: -```bash -ls site/CONTRIBUTING* site/CODE_OF_CONDUCT* 2>/dev/null && echo "LEAK — investigate" || echo "no leak" -``` -Expected: `no leak`. - ---- - -## Task 8: Open the PR (+ manual org settings) - -**Files:** none (integration + PR) - -**Interfaces:** -- Consumes: the full branch. Produces the PR that ships org-wide defaults. - -- [ ] **Step 1: Push the branch** - -```bash -git push -u origin chore/org-community-health -``` - -- [ ] **Step 2: Open the PR** - -```bash -gh pr create --title "chore: add org-wide community-health defaults" --body "$(cat <<'EOF' -Adds default community-health files to the org \`.github\` repo so every repo -without its own inherits them: CONTRIBUTING, Code of Conduct (Contributor -Covenant 2.1), SECURITY (private vulnerability reporting), SUPPORT, issue forms -(bug/feature) + chooser, PR template, and FUNDING. Also fixes \`Litestar\` -casing on the docs landing page. - -Implements Phase 0 (foundation hygiene) of the promotion strategy -(\`../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). - -🤖 Generated with [Claude Code](https://claude.com/claude-code) -EOF -)" -``` - -- [ ] **Step 3: Manual org settings (maintainer action — record in PR)** - -These are GitHub UI actions the maintainer performs; note them in the PR as a checklist: -- Enable **org-level Discussions**: Org → Settings → Discussions (so the issue-chooser "Question" link resolves). Pick a source repo if prompted. -- Confirm the new defaults appear on a repo that has none (e.g. open the new-issue page on `db-retry` — it should show the Bug/Feature chooser). - -- [ ] **Step 4: Verify defaults propagate** - -After merge, run: -```bash -gh api repos/modern-python/db-retry/community/profile --jq '.health_percentage' -``` -Expected: a value well above the pre-change ~25% (the inherited CoC/Contributing/Security/templates count toward the score). - ---- - -## Self-Review - -**Spec coverage (Phase 0 portion):** -- Org-wide `.github` defaults — Tasks 1–5 (CONTRIBUTING, CoC, SECURITY, SUPPORT, ISSUE_TEMPLATE, PR template, FUNDING). ✅ -- `LiteStar`→`Litestar` casing in `docs/index.md` — Task 6. ✅ -- Enable Discussions — Task 8 manual step. ✅ -- **Deferred to later plans (not in this plan, by design):** cross-repo PyPI metadata fixes (`that-depends`, `db-retry`, `eof-fixer`, `faststream-redis-timers`), the `faststream-outbox` readthedocs link, and all Phase 1/2 positioning + README content (those repos aren't in this workspace, and positioning is gated on Phase 1 research + maintainer sign-off). These become **Plan 2 (metadata hygiene sweep)** and **Plan 3 (positioning + READMEs)**. - -**Placeholder scan:** The only intentional human input is the FUNDING.yml handle (Task 5), explicitly gated with a skip path — not a silent placeholder. No "TBD"/"handle edge cases"/"similar to" anywhere; every file's full content is inline. - -**Type/name consistency:** File paths are consistent throughout (`.github/ISSUE_TEMPLATE/`, root-level docs). Cross-references resolve: `CONTRIBUTING.md`→`CODE_OF_CONDUCT.md`; `SUPPORT.md`→`SECURITY.md`; CoC enforcement→private reporting (matches `SECURITY.md`). Org Discussions URL identical in config.yml, CONTRIBUTING, SUPPORT. diff --git a/planning/changes/2026-06-27.01-community-channels/design.md b/planning/changes/2026-06-27.01-community-channels.md similarity index 100% rename from planning/changes/2026-06-27.01-community-channels/design.md rename to planning/changes/2026-06-27.01-community-channels.md diff --git a/planning/changes/2026-06-28.01-org-favicon-social-card/design.md b/planning/changes/2026-06-28.01-org-favicon-social-card.md similarity index 100% rename from planning/changes/2026-06-28.01-org-favicon-social-card/design.md rename to planning/changes/2026-06-28.01-org-favicon-social-card.md diff --git a/planning/changes/2026-06-28.01-org-favicon-social-card/plan.md b/planning/changes/2026-06-28.01-org-favicon-social-card/plan.md deleted file mode 100644 index 01a922d..0000000 --- a/planning/changes/2026-06-28.01-org-favicon-social-card/plan.md +++ /dev/null @@ -1,714 +0,0 @@ -# Org Favicon + Social Card 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:** Replace the org's snake-based brand mark with the approved pinwheel icon (favicon/avatar) and Jost wordmark social card, generated by the `brand/build/` Python toolchain. - -**Architecture:** A small deterministic generator. `tokens.py` holds the palette; `text.py` outlines Jost glyphs to SVG paths (so assets render without the font); `geometry.py` emits the icon, favicon, wordmark lockup and card compositions as concrete-color SVG strings; `render.py` writes every SVG to `brand/org/` and rasterizes PNGs via `rsvg-convert`. The old snake system (geometry/render/tokens, `brand/projects/**`, stale `brand/org/*`) is removed. - -**Tech Stack:** Python 3.11+, `fonttools` (glyph outlining + variable-font instancing), `rsvg-convert` (librsvg) for PNGs, `pytest`, `ty`, `uv`. Font: **Jost** (SIL OFL). - -## Global Constraints - -- **Palette (exact hex):** Green-ink `#356852`, Green-surface `#2f5e4a`, Gold-light `#c98a00`, Gold-dark `#f0b528`, Cream `#f4f1e8`. -- **Two-color pairing:** top-left snake + `MODERN` use the structure color (green `#356852` on cream / cream `#f4f1e8` on green); bottom-right snake + `PYTHON` use gold (`#c98a00` on cream / `#f0b528` on green). -- **Fixed copy:** wordmark is always `MODERN` / `PYTHON` (two lines); card URL is always `modern-python.org`; **no tagline**. -- **Typeface:** Jost weight 400, **outlined to SVG paths** at build time (assets must render with no font installed). Jost + its OFL license vendored under `brand/build/fonts/`. -- **Lockup geometry is authoritative** (540×250 space): snakes `M138 122 L138 50 L210 50` (struct) and `M402 128 L402 200 L330 200` (gold), stroke 8, `butt`/`miter`; heads `rect 202.5,42.5,15,15 rx3` and `322.5,192.5,15,15 rx3`; `MODERN` baseline 126, `PYTHON` baseline 166 (gap 40), both size 50, centered x=270, **`fit_width=210`**. -- **Card composition (1280×640):** `` bg, then the lockup embedded `translate(235,108) scale(1.5)`, then URL outlined at x=640 baseline 575 size 34. **Square (640×640):** lockup `translate(50,195)`, no URL. -- **Icon (100×100):** standard — snakes `M30 60 L30 30 L60 30` / `M70 40 L70 70 L40 70` stroke 8, heads `54.5,24.5,11,11 rx2` / `34.5,64.5,11,11 rx2`, core `circle 50,50 r6`, `rx20`. Favicon (bold) — snakes `M26 66 L26 26 L66 26` / `M74 34 L74 74 L34 74` stroke 11, heads `58,20,14,14 rx2` / `28,66,14,14 rx2`, core `r7`, `rx18`. All on green surface: bg `#2f5e4a`, struct `#f4f1e8`, gold/core `#f0b528`. -- **No CSS variables** — every emitted SVG uses concrete colors (no `var()`, no `