diff --git a/.claude/skills/mechanical-refactor-verify/SKILL.md b/.claude/skills/mechanical-refactor-verify/SKILL.md new file mode 100644 index 00000000000..98f9720d8ec --- /dev/null +++ b/.claude/skills/mechanical-refactor-verify/SKILL.md @@ -0,0 +1,134 @@ +--- +name: mechanical-refactor-verify +description: Verify mechanical refactoring commits by requiring a reproducible transform script (gist) in the PR description. Use when doing or reviewing file splits, function moves, or module extractions. +user_invocable: true +argument: "[verify ] — verify an existing PR, or omit to see the workflow guide" +--- + +# Mechanical Refactor — Reproducible Verification + +## Core Principle + +The deliverable of a mechanical move (file split, function move, module extraction) is NOT the diff — it is **the script that produces the diff**. +A script is auditable; a diff is not. + +## Workflow + +Regardless of who did the move (human or agent) and when (before or after committing), the workflow is the same: + +### Step 1: Write the transform script to /tmp/ + +Write the script to `/tmp/transform_.py`. **Never write it inside the repo.** + +The scaffold (worktree creation, diff check, ruff format, result reporting) lives in `mechanical_refactor_verify_utils.py` next to this skill. + +**MANDATORY**: The transform script MUST use `verify_mechanical_refactor()` from the utils module. Do NOT reimplement the verification scaffold — no hand-written worktree management, no hand-written diff checking. The script only defines `transform()` and calls `verify_mechanical_refactor`. + +Script template (follow this structure exactly): + +```python +#!/usr/bin/env python3 +"""Reproducible transform for: + +Run from the repo root: python3 /tmp/transform_.py +""" +import sys +from pathlib import Path + +sys.path.append(".claude/skills/mechanical-refactor-verify") +from mechanical_refactor_verify_utils import verify_mechanical_refactor, exec_command, git_add_and_commit, dedent + +BASE_COMMIT = "" +TARGET_COMMIT = "" + + +def transform(dir_root: Path) -> None: + """Perform the mechanical transformation and commit each step. + + Args: + dir_root: Path to the worktree (checked out at BASE_COMMIT). + """ + # --- Step 1: Split source file --- + source = dir_root / "path/to/source.py" + content = source.read_text() + lines = content.splitlines(keepends=True) + + splits = [ + ("path/to/pkg/target_a.py", 1, 50), + ("path/to/pkg/target_b.py", 51, 120), + ] + for target_path, start, end in splits: + target = dir_root / target_path + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text("".join(lines[start - 1 : end])) + + source.unlink() + (dir_root / "path/to/pkg/__init__.py").touch() + + git_add_and_commit("mechanical: split source.py", cwd=str(dir_root)) + + # --- Step 2: Fix imports --- + # + # git_add_and_commit("fix imports", cwd=str(dir_root)) + + # Note: pre-commit run --all-files is run automatically after transform() returns + + +if __name__ == "__main__": + verify_mechanical_refactor( + base_commit=BASE_COMMIT, + target_commit=TARGET_COMMIT, + transform=transform, + ) +``` + +### Step 2: Run the script from the repo root + +```bash +cd +python3 /tmp/transform_.py +# Expected: "PASS: transform reproduces the commit exactly." +``` + +If FAIL, fix the script and re-run until PASS. + +### Step 3: Upload gist, delete local file, update PR description + +One gist per PR. Do all three: + +```bash +# 1. Create gist (or update existing) +gh gist create --public -d "Mechanical refactor transform: " /tmp/transform_.py +# Or update: gh gist edit -a /tmp/transform_.py + +# 2. Delete local file +rm /tmp/transform_.py + +# 3. Update PR description (paste the block below) +``` + +PR description must include: + +````markdown +## Mechanical Move + +Transform script: + +### One-click verification + +```bash +python3 <(curl -sL ) +``` +```` + +### Step 4: PR scope + +A mechanical refactor PR must contain **only** mechanical changes (moves, splits, renames, import fixes, formatting). All of these must be reproducible by the transform script. + +Semantic changes (new logic, API restructuring, behavior changes) belong in a **separate PR**. + +## Verifying an existing PR (`/mechanical-refactor-verify verify`) + +1. Find the gist URL and one-click command in the PR description +2. Run the one-click command from the repo root +3. Report: PASS or show the diff diff --git a/.claude/skills/mechanical-refactor-verify/mechanical_refactor_verify_utils.py b/.claude/skills/mechanical-refactor-verify/mechanical_refactor_verify_utils.py new file mode 100644 index 00000000000..5896bcb8700 --- /dev/null +++ b/.claude/skills/mechanical-refactor-verify/mechanical_refactor_verify_utils.py @@ -0,0 +1,80 @@ +"""Utilities for mechanical refactor verification scripts. + +See SKILL.md for usage and transform script template. +""" + +import shlex +import subprocess +import sys +import tempfile +from collections.abc import Callable +from pathlib import Path + + +def exec_command(cmd: str, cwd: str | None = None, check: bool = True) -> str: + print(f" $ {cmd}", flush=True) + result = subprocess.run( + cmd, + shell=True, + cwd=cwd, + capture_output=True, + text=True, + ) + if check and result.returncode != 0: + print(f"FAILED: {result.stderr}", file=sys.stderr) + sys.exit(1) + return result.stdout.strip() + + +def git_add_and_commit(message: str, cwd: str) -> None: + exec_command(f"git add -A && git commit -m {shlex.quote(message)}", cwd=cwd) + + +def dedent(text: str, n: int) -> str: + """Remove exactly n leading spaces from each line.""" + lines = text.splitlines(keepends=True) + return "".join(line[n:] if line[:n] == " " * n else line for line in lines) + + +def verify_mechanical_refactor( + base_commit: str, + target_commit: str, + transform: "Callable[[Path], None]", +) -> None: + repo_root = exec_command("git rev-parse --show-toplevel") + worktree_dir = tempfile.mkdtemp(prefix="verify-mechanical-") + branch_name = f"verify-mechanical-{base_commit[:8]}" + + try: + print(f"[1/4] Creating worktree at {base_commit[:8]}...") + exec_command( + f"git worktree add -b {branch_name} {worktree_dir} {base_commit}", + cwd=repo_root, + ) + + print("[2/4] Running transformation...") + transform(Path(worktree_dir)) + + print("[3/4] Running pre-commit...") + exec_command("pre-commit run --all-files", cwd=worktree_dir, check=False) + if exec_command("git status --porcelain", cwd=worktree_dir): + git_add_and_commit("pre-commit fixes", cwd=worktree_dir) + + print(f"[4/4] Diffing against {target_commit[:8]}...") + diff = exec_command( + f"git diff {target_commit} -- .", + cwd=worktree_dir, + check=False, + ) + + if diff: + print(f"\nFAIL: diff is non-empty:\n{diff}") + sys.exit(1) + else: + print("\nPASS: transform reproduces the commit exactly.") + + finally: + print(f"\nWorktree left at: {worktree_dir}") + print(f"Branch: {branch_name}") + print("To clean up manually:") + print(f" git worktree remove {worktree_dir} && git branch -D {branch_name}") diff --git a/.gitignore b/.gitignore index da2e15c936c..d937b99965c 100644 --- a/.gitignore +++ b/.gitignore @@ -192,4 +192,3 @@ glm/ _examples_synced/ .env .DS_Store -.claude/