Prepare staged index generation and reconcile implementation paths
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Reject generated-only commits / generated-commit-guard (push) Successful in 9s
Python Tests / pytest (push) Successful in 30s

This commit is contained in:
codex 2026-10-19 11:30:16 +02:00
parent 73801af1c8
commit fd4a3ba00e
9 changed files with 576 additions and 16 deletions

View file

@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Regenerate WORK-RECORDS.md from staged Markdown, preserving unstaged edits.
Uses the existing State Hub renderer. No hub access, commit or push. Intended
for a Make target invoked by pre-commit, before the unchanged generated guard.
"""
import argparse
from pathlib import Path
import re
import subprocess
import sys
import tempfile
INDEX = 'WORK-RECORDS.md'
def git(repo, *args):
return subprocess.check_output(['git', '-C', str(repo), *args])
def render(snapshot, slug, renderer_root):
# Isolate owner imports and their transitive modules for each explicit root.
# Repeated calls cannot accidentally reuse another checkout's cached module.
code = """
import sys
from pathlib import Path
sys.path.insert(0, str(Path(sys.argv[1]).resolve() / 'scripts'))
import consistency_check as owner
if not owner._load_work_record_kind_registry():
raise ValueError('canonical work-record kind registry unavailable')
content = (owner._generate_work_record_index(Path(sys.argv[2]), sys.argv[3])
or owner._work_record_index_document(sys.argv[3], []))
sys.stdout.buffer.write(content.encode())
"""
return subprocess.check_output([sys.executable, '-c', code,
str(renderer_root), str(snapshot), slug])
def generate(repo, renderer_root, *, repo_slug, check=False):
if not re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", repo_slug):
raise ValueError("repo slug must be the canonical lowercase repository identifier")
repo = Path(git(repo, 'rev-parse', '--show-toplevel').decode().strip())
staged_entries = git(repo, 'ls-files', '--stage', '-z')
entries = []
for entry in staged_entries.split(b'\0'):
if not entry:
continue
metadata, path = entry.split(b'\t', 1)
mode, oid, stage = metadata.split()
if stage != b'0':
raise ValueError('unmerged index; resolve conflicts before generating')
path = path.decode()
if path.endswith('.md') and path != INDEX:
if mode not in (b'100644', b'100755'):
raise ValueError('refuse non-regular Markdown source: ' + path)
entries.append((path, oid.decode()))
destination = repo / INDEX
if destination.is_symlink():
raise ValueError('refuse redirected generated index')
indexed = subprocess.run(['git', '-C', str(repo), 'show', ':' + INDEX],
capture_output=True)
previous = indexed.stdout if indexed.returncode == 0 else None
existing = destination.read_bytes() if destination.exists() else None
if existing != previous:
raise ValueError('unstaged WORK-RECORDS.md content; preserve or reconcile it first')
with tempfile.TemporaryDirectory(prefix='staged-work-records-') as directory:
snapshot = Path(directory)
for path, oid in entries:
target = snapshot / path
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(git(repo, 'cat-file', 'blob', oid))
content = render(snapshot, repo_slug, renderer_root)
if content == previous:
return False
if check:
raise ValueError('staged WORK-RECORDS.md is stale')
if staged_entries != git(repo, 'ls-files', '--stage', '-z'):
raise ValueError('Git index changed during rendering; retry')
if (destination.read_bytes() if destination.exists() else None) != existing:
raise ValueError('WORK-RECORDS.md changed during rendering; retry')
destination.write_bytes(content)
subprocess.run(['git', '-C', str(repo), 'add', '--', INDEX], check=True)
return True
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--repo', type=Path, default=Path.cwd())
parser.add_argument('--repo-slug', required=True,
help='canonical repository identity, independent of checkout path')
parser.add_argument('--renderer-root', type=Path,
default=Path(__file__).resolve().parents[2] / 'state-hub')
parser.add_argument('--check', action='store_true')
args = parser.parse_args()
try:
changed = generate(args.repo, args.renderer_root, repo_slug=args.repo_slug, check=args.check)
except (OSError, ValueError, subprocess.CalledProcessError, ImportError) as exc:
parser.exit(1, str(exc) + '\n')
print('staged work-record index regenerated' if changed else 'staged work-record index current')
if __name__ == '__main__':
main()