Files
rfc-app/scripts/seed_meta_repo.py
Ben Stull 779ba6db59 Slice 1: scaffolding + propose-to-super-draft vertical
Brings the §1 bot wrapper, the §4 cache (webhook + reconciler), the
§5 schema (six numbered migrations), Gitea OAuth + §6 user
provisioning, the §7 catalog left pane, and the propose-to-merge
vertical: propose modal opens an idea PR against the meta repo, an
owner merges from the pending-idea view, the cache picks it up via
webhook or reconciler sweep, and the catalog renders the new
super-draft.

Per §1 the bot is the only Git writer; every commit, branch
creation, and PR merge carries the §6.5 On-behalf-of: trailer and
an `actions` audit row. Per §4 the cache is never written from a
user action — it's webhook+reconciler only.

Covered by `backend/tests/test_propose_vertical.py` against an
in-process Gitea simulator.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 04:31:11 -07:00

158 lines
5.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Seed a fresh meta repository on a local Gitea instance.
Creates `<org>/<meta_repo>` 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.
<!-- INDEX:START -->
*The index below is regenerated by CI on every merge to main.*
<!-- INDEX:END -->
"""
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())