- scripts/_archive_cache.sh: snapshot run_cache/<model>/ to run_cache_archive/<sweep_tag>/ at sweep exit with metadata.json. Sourced by sweep scripts so transcripts survive the next sweep's cache wipe and stay available for audits. - scripts/container_sweep_single.sh: base multi-model sweep. Adds CACHE_SUB entries for claude-opus-4-7 / claude-sonnet-4-7 so their caches are force-cleared at sweep start. Calls archive helper on exit. - scripts/container_sweep_minimal.sh: 1-run-per-task variant for fast fix validation (~20 min) instead of full 3-run sweep (~60 min). - Dockerfile.main: parametrized clawbench-on-openclaw image with ARG BASE for pinning to any openclaw tag. - scripts/git_checkpoint.py + README: documented checkpoint workflow for tagging known-good states during risky work. - .gitignore: un-ignore scripts/, keep targeted ignores for __pycache__, .tmp, .local.py. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
115 lines
3.3 KiB
Python
115 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Create an annotated git checkpoint tag for a clean working tree."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
|
|
def run_git(args: list[str], repo_root: Path, capture_output: bool = True) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(
|
|
["git", *args],
|
|
cwd=repo_root,
|
|
check=True,
|
|
text=True,
|
|
capture_output=capture_output,
|
|
)
|
|
|
|
|
|
def repo_root() -> Path:
|
|
try:
|
|
result = subprocess.run(
|
|
["git", "rev-parse", "--show-toplevel"],
|
|
check=True,
|
|
text=True,
|
|
capture_output=True,
|
|
)
|
|
except subprocess.CalledProcessError as exc:
|
|
raise SystemExit("Not inside a git repository.") from exc
|
|
return Path(result.stdout.strip())
|
|
|
|
|
|
def sanitize_label(label: str) -> str:
|
|
slug = re.sub(r"[^a-z0-9]+", "-", label.strip().lower()).strip("-")
|
|
if not slug:
|
|
raise SystemExit("Checkpoint name must contain at least one letter or number.")
|
|
return slug[:48]
|
|
|
|
|
|
def ensure_clean_worktree(root: Path) -> None:
|
|
status = run_git(["status", "--porcelain"], root).stdout.strip()
|
|
if status:
|
|
raise SystemExit(
|
|
"Working tree is not clean. Commit or stash your changes first, or rerun with --allow-dirty."
|
|
)
|
|
|
|
|
|
def current_branch(root: Path) -> str:
|
|
return run_git(["rev-parse", "--abbrev-ref", "HEAD"], root).stdout.strip()
|
|
|
|
|
|
def tag_exists(root: Path, tag_name: str) -> bool:
|
|
result = run_git(["tag", "--list", tag_name], root)
|
|
return result.stdout.strip() == tag_name
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
parser = argparse.ArgumentParser(
|
|
description="Create an annotated checkpoint tag for the current HEAD commit."
|
|
)
|
|
parser.add_argument("name", help="Human-readable checkpoint name, e.g. 'before benchmark rerun'.")
|
|
parser.add_argument(
|
|
"--allow-dirty",
|
|
action="store_true",
|
|
help="Allow tagging even if the working tree has uncommitted changes.",
|
|
)
|
|
parser.add_argument(
|
|
"--dry-run",
|
|
action="store_true",
|
|
help="Print the tag that would be created without modifying git state.",
|
|
)
|
|
return parser
|
|
|
|
|
|
def main() -> int:
|
|
parser = build_parser()
|
|
args = parser.parse_args()
|
|
|
|
root = repo_root()
|
|
if not args.allow_dirty:
|
|
ensure_clean_worktree(root)
|
|
|
|
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
branch = current_branch(root)
|
|
slug = sanitize_label(args.name)
|
|
tag_name = f"checkpoint/{timestamp}-{slug}"
|
|
|
|
if tag_exists(root, tag_name):
|
|
raise SystemExit(f"Checkpoint tag already exists: {tag_name}")
|
|
|
|
message = f"Checkpoint '{args.name}' from branch '{branch}' at {timestamp}"
|
|
|
|
if args.dry_run:
|
|
print(tag_name)
|
|
print(message)
|
|
return 0
|
|
|
|
run_git(["tag", "-a", tag_name, "-m", message], root, capture_output=False)
|
|
print(f"Created {tag_name}")
|
|
print(f"Push it with: git push origin {tag_name}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
raise SystemExit(main())
|
|
except subprocess.CalledProcessError as exc:
|
|
if exc.stderr:
|
|
sys.stderr.write(exc.stderr)
|
|
raise SystemExit(exc.returncode) from exc
|