#!/usr/bin/env python3 """Seed a fresh meta repository on a local Gitea instance. Creates `/` if it does not exist, seeds it with the hand-authored files §2 describes (README.md, PHILOSOPHY.md, CONTRIBUTING.md, the workflow file, and an empty rfcs/ directory), and registers the webhook the app needs per §4.1. Run this once after standing up Gitea + the bot account + the .env. Re-running is safe; everything is upsert-shaped. Usage: cd backend && .venv/bin/python ../scripts/seed_meta_repo.py """ from __future__ import annotations import asyncio import base64 import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "backend")) from app.config import load_config # noqa: E402 from app.gitea import Gitea, GiteaError # noqa: E402 PHILOSOPHY_PATH = Path(__file__).resolve().parent.parent / "PHILOSOPHY.md" README_HEADER = """# Wiggleverse RFCs *A standards process for shared meaning between humans and machines.* This repository is the meta repo for the Wiggleverse RFC framework — the authoritative directory of every RFC in the system, super-drafts and graduated entries alike. Every active or in-progress RFC has exactly one markdown file in `rfcs/` describing it; once graduated, the RFC itself lives in its own dedicated repository, linked from its entry here. See [PHILOSOPHY.md](./PHILOSOPHY.md) for the full statement of why this framework exists. *The index below is regenerated by CI on every merge to main.* """ CONTRIBUTING = """# Contributing to the Wiggleverse RFC framework All contribution flows through the RFC app, which talks to this Gitea instance on your behalf via a single bot service account. Raw `git clone` plus `git push` is not a supported contribution path. To propose a new RFC, sign in at the app and use the **"+ Propose New RFC"** affordance at the bottom of the catalog. Your proposal opens a PR against this repository adding one file under `rfcs/`. An owner or admin reviews and either merges (creating the super-draft) or declines (with a comment to you). See the app for the full revision, conversation, and graduation flows. """ ACTIONS_WORKFLOW = """# Regenerates README.md's index section on every merge to main. # See §2 of SPEC.md. Implementation is a follow-up — this workflow # file is present as a marker so the meta repo's shape matches the # spec from day one. name: regenerate-readme-index on: push: branches: [main] jobs: noop: runs-on: ubuntu-latest steps: - run: echo "Index regeneration is a follow-up (see SPEC §2)" """ async def main() -> None: config = load_config() gitea = Gitea(config) try: await ensure_repo(config, gitea) await seed_files(config, gitea) await ensure_webhook(config, gitea) finally: await gitea.close() async def ensure_repo(config, gitea: Gitea) -> None: existing = await gitea.get_repo(config.gitea_org, config.meta_repo) if existing: print(f"meta repo {config.meta_repo_full} already exists") return print(f"creating meta repo {config.meta_repo_full}") # Create unauto-initialized so we control the initial commit. await gitea._request( # noqa: SLF001 — bootstrap-only direct call "POST", f"/orgs/{config.gitea_org}/repos", json={ "name": config.meta_repo, "description": "Wiggleverse RFC framework — meta repository", "private": False, "auto_init": True, "default_branch": "main", }, ) async def seed_files(config, gitea: Gitea) -> None: """Write README.md, PHILOSOPHY.md, CONTRIBUTING.md, and the workflow. Each file is created if missing, left alone if present — re-running the seed will not overwrite manual edits made post-bootstrap. """ files = { "PHILOSOPHY.md": PHILOSOPHY_PATH.read_text() if PHILOSOPHY_PATH.exists() else "(placeholder)\n", "README.md": README_HEADER, "CONTRIBUTING.md": CONTRIBUTING, ".gitea/workflows/regenerate-readme-index.yml": ACTIONS_WORKFLOW, "rfcs/.gitkeep": "", } for path, content in files.items(): existing = await gitea.get_contents(config.gitea_org, config.meta_repo, path, ref="main") if existing: print(f" {path} already present — skipping") continue print(f" seeding {path}") await gitea.create_file( config.gitea_org, config.meta_repo, path, content=content, message=f"Bootstrap: add {path}", branch="main", ) async def ensure_webhook(config, gitea: Gitea) -> None: if not config.webhook_secret: print("skipping webhook setup (GITEA_WEBHOOK_SECRET not set)") return url = f"{config.app_url}/api/webhooks/gitea" print(f"ensuring webhook on {config.meta_repo_full} -> {url}") await gitea.ensure_webhook( config.gitea_org, config.meta_repo, url=url, secret=config.webhook_secret, events=["push", "pull_request", "create", "delete", "repository"], ) if __name__ == "__main__": asyncio.run(main())