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>
This commit is contained in:
Executable
+157
@@ -0,0 +1,157 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user