|
| 1 | +# Regen Oldest Spec |
| 2 | + |
| 3 | +> Picks the oldest non-recently-updated spec and regenerates all its library implementations locally — same flow as |
| 4 | +> `/update`, but unattended and without dispatching the Cloud AI review. PRs are auto-approved (or marked |
| 5 | +> `quality-poor`) based on the local quality score, so `impl-merge.yml` handles the rest. Works identically with the |
| 6 | +> default Claude Code config (Sonnet) and with a locally-configured model — the skill makes no model assumptions. |
| 7 | +
|
| 8 | +## Context |
| 9 | + |
| 10 | +@CLAUDE.md |
| 11 | +@pyproject.toml |
| 12 | + |
| 13 | +## Instructions |
| 14 | + |
| 15 | +You are the **regen-lead**. Your job is to: |
| 16 | + |
| 17 | +1. Pick the single oldest spec by latest implementation `updated` timestamp. |
| 18 | +2. Coordinate per-library updater agents (same mechanics as `/update`). |
| 19 | +3. Ship per-library PRs **without** triggering the Cloud AI review — instead label PRs directly so |
| 20 | + `impl-merge.yml` auto-merges (score ≥ 50) or leaves them open for manual review (score < 50). |
| 21 | + |
| 22 | +**Prerequisite:** This command uses agent teams (experimental). Ensure `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` is set |
| 23 | +in your environment or Claude Code settings. |
| 24 | + |
| 25 | +**Model-agnostic by design:** The skill never inspects or sets the LLM endpoint. Whatever Claude Code is currently |
| 26 | +configured for (Anthropic Sonnet by default, or any local OpenAI/Anthropic-compatible endpoint via `ANTHROPIC_BASE_URL` |
| 27 | ++ `ANTHROPIC_AUTH_TOKEN`) is what spawned sub-agents will use. |
| 28 | + |
| 29 | +--- |
| 30 | + |
| 31 | +### Phase 1: Pick the oldest spec |
| 32 | + |
| 33 | +Run this Python snippet to find the spec whose newest per-library `updated` timestamp is the oldest. Specs without any |
| 34 | +metadata are treated as "ancient" and picked first. |
| 35 | + |
| 36 | +```bash |
| 37 | +SPEC_INFO=$(uv run python -c " |
| 38 | +from datetime import datetime, timezone |
| 39 | +from pathlib import Path |
| 40 | +import yaml |
| 41 | +
|
| 42 | +candidates = [] |
| 43 | +for spec_dir in sorted(Path('plots').iterdir()): |
| 44 | + if not spec_dir.is_dir() or spec_dir.name.startswith('.'): |
| 45 | + continue |
| 46 | + meta_dir = spec_dir / 'metadata' / 'python' |
| 47 | + if not meta_dir.is_dir(): |
| 48 | + continue |
| 49 | + latest = None |
| 50 | + for yf in meta_dir.glob('*.yaml'): |
| 51 | + try: |
| 52 | + d = yaml.safe_load(yf.read_text(encoding='utf-8')) or {} |
| 53 | + except Exception: |
| 54 | + continue |
| 55 | + u = d.get('updated') or d.get('created') |
| 56 | + if u and (latest is None or str(u) > str(latest)): |
| 57 | + latest = u |
| 58 | + if latest is None: |
| 59 | + candidates.append((datetime.min.replace(tzinfo=timezone.utc), spec_dir.name)) |
| 60 | + continue |
| 61 | + try: |
| 62 | + dt = datetime.fromisoformat(str(latest).replace('Z', '+00:00')) |
| 63 | + if dt.tzinfo is None: |
| 64 | + dt = dt.replace(tzinfo=timezone.utc) |
| 65 | + candidates.append((dt, spec_dir.name)) |
| 66 | + except Exception: |
| 67 | + continue |
| 68 | +candidates.sort() |
| 69 | +if candidates: |
| 70 | + dt, name = candidates[0] |
| 71 | + print(f'{name}\t{dt.isoformat()}') |
| 72 | +") |
| 73 | + |
| 74 | +SPEC_ID=$(echo "$SPEC_INFO" | cut -f1) |
| 75 | +SPEC_LATEST=$(echo "$SPEC_INFO" | cut -f2) |
| 76 | +``` |
| 77 | + |
| 78 | +If `SPEC_ID` is empty, abort with a clear message — the repo has no eligible specs. |
| 79 | + |
| 80 | +**Discover existing libraries:** Scan `plots/$SPEC_ID/implementations/python/` for `*.py` files (excluding |
| 81 | +`__init__.py`) — these are the libraries to regenerate. If the directory is empty, abort. |
| 82 | + |
| 83 | +**Confirm with user:** |
| 84 | + |
| 85 | +``` |
| 86 | +Oldest spec: {SPEC_ID} |
| 87 | +Latest implementation update: {SPEC_LATEST} |
| 88 | +Libraries to regenerate: {comma-separated list} |
| 89 | +
|
| 90 | +Proceed? (y/n) |
| 91 | +``` |
| 92 | + |
| 93 | +Wait for explicit `y` / `yes` / `ja` / `passt`. Anything else aborts. |
| 94 | + |
| 95 | +--- |
| 96 | + |
| 97 | +### Phase 2: Spawn per-library agents |
| 98 | + |
| 99 | +Mirror Phase 3 of `/update` (`agentic/commands/update.md`). Read that file once and follow the same agent-spawning |
| 100 | +mechanics: |
| 101 | + |
| 102 | +1. **Read the spec:** `plots/{SPEC_ID}/specification.md` and `plots/{SPEC_ID}/specification.yaml` to extract |
| 103 | + `{SPEC_TITLE}` and the primary `{PLOT_TYPE}` tag. |
| 104 | +2. **Create team:** `TeamCreate` with name `regen-{SPEC_ID}`. |
| 105 | +3. **For each library**, create a task via `TaskCreate`: |
| 106 | + - Subject: `Regen {library} implementation for {SPEC_ID}` |
| 107 | + - Description: `Unattended regeneration — no specific user request. Perform a comprehensive review and improve.` |
| 108 | +4. **Spawn one `general-purpose` agent per library** via the `Agent` tool with: |
| 109 | + - `team_name`: `regen-{SPEC_ID}` |
| 110 | + - `name`: `{library}` |
| 111 | + - `subagent_type`: `general-purpose` |
| 112 | + - `model`: `opus` |
| 113 | + - **Prompt:** Use the exact `Library Agent Prompt` from `agentic/commands/update.md` (the section starting at |
| 114 | + `## Library Agent Prompt`). Fill `{SPEC_ID}`, `{LIBRARY}`, `{CONTEXT7_LIBRARY}`, `{PLOT_TYPE}`, `{SPEC_TITLE}` as |
| 115 | + in `/update`. For `{DESCRIPTION}`, pass: |
| 116 | + `No specific user request — perform a comprehensive review across all dimensions (code quality, data choice, |
| 117 | + visual design, spec compliance, library feature usage, transferability) and improve where genuinely needed. If you |
| 118 | + find nothing meaningful to improve, report "no improvements needed" and leave the code unchanged.` |
| 119 | + |
| 120 | + The library agent's Phase 7 (local quality scoring + up-to-2-iteration repair) runs unchanged — that's the local |
| 121 | + substitute for the Cloud `impl-review.yml`/`impl-repair.yml` chain. |
| 122 | +5. **Assign each task** to its agent immediately via `TaskUpdate` with `owner: "{library}"`. |
| 123 | + |
| 124 | +All agents run in parallel. |
| 125 | + |
| 126 | +**Skip these phases from `/update`:** |
| 127 | +- Phase 2 (Spec Optimization) — `/regen` is unattended. |
| 128 | +- Phase 4 / 5 (Collect-Present-Iterate with user feedback) — go directly to shipping once all agents report |
| 129 | + `STATUS: done` or `STATUS: conflict`. Conflicts are logged but do not block: if an agent reports a conflict, **skip |
| 130 | + that library** (don't ship it) and continue with the others. |
| 131 | + |
| 132 | +--- |
| 133 | + |
| 134 | +### Phase 3: Ship (modified `/update` Phase 6) |
| 135 | + |
| 136 | +For shipping, follow `/update`'s Phase 6 in `agentic/commands/update.md` — sub-phases **6a, 6b, 6c, 6d, 6e, 6f, 6g |
| 137 | +Step 1**. |
| 138 | + |
| 139 | +**6g Step 1 override:** in the per-library worktree block, when creating the PR replace `/update`'s review-trigger: |
| 140 | + |
| 141 | +```bash |
| 142 | +# /update.md does this (Cloud AI review): |
| 143 | +gh api repos/{owner}/{repo}/dispatches \ |
| 144 | + -f event_type=review-pr \ |
| 145 | + -f 'client_payload[pr_number]='"$PR_NUMBER" |
| 146 | +``` |
| 147 | + |
| 148 | +**…with this label-based dispatch (no Cloud AI call):** |
| 149 | + |
| 150 | +```bash |
| 151 | +PR_NUMBER=$(gh pr view --json number -q '.number') |
| 152 | +SCORE={score reported by the library agent in its STATUS: done message} |
| 153 | + |
| 154 | +# Always tag the PR with its locally-computed quality score. |
| 155 | +gh pr edit "$PR_NUMBER" --add-label "quality:${SCORE}" |
| 156 | + |
| 157 | +if [ "$SCORE" -ge 50 ]; then |
| 158 | + # Auto-approve → impl-merge.yml triggers → squash-merge + GCS staging→prod + impl:{lib}:done. |
| 159 | + gh pr edit "$PR_NUMBER" --add-label "ai-approved" |
| 160 | + echo "::notice::PR #$PR_NUMBER ai-approved (score=$SCORE)" |
| 161 | +else |
| 162 | + # Score too low for auto-merge — flag for manual review. |
| 163 | + gh pr edit "$PR_NUMBER" --add-label "quality-poor" |
| 164 | + echo "::warning::PR #$PR_NUMBER flagged quality-poor (score=$SCORE) — left open for manual review" |
| 165 | +fi |
| 166 | +``` |
| 167 | + |
| 168 | +The PR body itself already includes the score and category breakdown (set by `/update` Phase 6g Step 1's `gh pr create` |
| 169 | +template). |
| 170 | + |
| 171 | +**6g Step 2** (cleanup worktrees) and **Phase 7a** (shut down team, `TeamDelete`, remove `.update-preview/`) run |
| 172 | +unchanged. |
| 173 | + |
| 174 | +--- |
| 175 | + |
| 176 | +### Phase 4: Brief monitor |
| 177 | + |
| 178 | +Print a one-shot status table — no polling loop. The CI does the rest. |
| 179 | + |
| 180 | +Wait 60 seconds (so `impl-merge.yml` has time to start), then for each PR: |
| 181 | + |
| 182 | +```bash |
| 183 | +gh pr view "$PR_NUMBER" --json number,state,mergedAt,labels \ |
| 184 | + --jq '[.number, .state, (.mergedAt // "—"), ([.labels[].name] | join(","))] | @tsv' |
| 185 | +``` |
| 186 | + |
| 187 | +Render a table: |
| 188 | + |
| 189 | +``` |
| 190 | +| Library | PR | State | Merged | Labels | |
| 191 | +|--------------|-------|-------|--------|-------------------------------------| |
| 192 | +| matplotlib | #1234 | MERGED| 14:02Z | quality:88, ai-approved | |
| 193 | +| seaborn | #1235 | OPEN | — | quality:42, quality-poor | |
| 194 | +| ... | |
| 195 | +``` |
| 196 | + |
| 197 | +Tell the user: |
| 198 | +- Which PRs auto-merged (success). |
| 199 | +- Which are `quality-poor` (need manual review). |
| 200 | +- That **no Cloud AI review/repair was triggered** — verifiable via: |
| 201 | + ```bash |
| 202 | + gh run list --workflow=impl-review.yml --limit 5 |
| 203 | + gh run list --workflow=impl-repair.yml --limit 5 |
| 204 | + ``` |
| 205 | + (no new runs for this `SPEC_ID` should appear). |
| 206 | + |
| 207 | +Then exit. The user can re-run `/regen` later to pick the next-oldest spec. |
| 208 | + |
| 209 | +--- |
| 210 | + |
| 211 | +## Notes |
| 212 | + |
| 213 | +- **Token cost:** All Claude Code calls (lead + agents) hit whatever endpoint Claude Code is configured for. With the |
| 214 | + default config that's Anthropic Sonnet (useful for end-to-end testing). With a local model configured via |
| 215 | + `ANTHROPIC_BASE_URL` + `ANTHROPIC_AUTH_TOKEN`, cost is effectively zero. **No code path differs between modes.** |
| 216 | +- **Cloud workflows skipped:** `impl-review.yml` and `impl-repair.yml` only trigger on explicit dispatch (see |
| 217 | + `.github/workflows/impl-review.yml` triggers — `workflow_dispatch` + `repository_dispatch types: [review-pr]`). |
| 218 | + Skipping the dispatch in Phase 3 is sufficient to bypass them entirely. |
| 219 | +- **Cloud workflows still active:** `impl-merge.yml` triggers on the `ai-approved` label, so it still squash-merges, |
| 220 | + promotes GCS staging → production, sets `impl:{library}:done` on the parent issue, and triggers `sync-postgres.yml`. |
| 221 | + This is intentional — we want those side effects. |
| 222 | +- **Failure modes:** |
| 223 | + - Agent reports `STATUS: conflict` → skip that library, continue with the others. |
| 224 | + - Library agent crashes (script error, lint failure after 3 retries) → the `/update` agent prompt already handles |
| 225 | + this; library is reported as a failure and skipped. |
| 226 | + - `gh` not authenticated or GCS creds missing → Phase 3 fails fast in the first worktree's push step. |
| 227 | +- **Re-run safe:** Running `/regen` twice in a row picks a different spec the second time (the freshly merged one is |
| 228 | + no longer oldest). |
| 229 | + |
| 230 | +## Usage |
| 231 | + |
| 232 | +``` |
| 233 | +/regen |
| 234 | +``` |
| 235 | + |
| 236 | +No arguments. Always picks the single oldest spec. |
0 commit comments