WP-0016 finished: interactive registry maintain with llm-connect automation
Some checks failed
ci / validate-registry (push) Has been cancelled

Closes the registry maintenance loop from inside each domain repo:
interactive prompting for judgment calls, full automation for safe and
high-confidence changes, both backed by the llm-connect HTTP bridge.

- New modules: maintain.py, maintain_llm.py, patches.py, interactive.py
- Schema: schemas/registry-patch.schema.json
- CLI: reuse-surface maintain; establish --scaffold --hook
- Sibling templates: Makefile fragment, pre-commit hook
- Deterministic signal collectors extended; validate cwd auto-detect
- Docs, gap priority 28, SCOPE update
- Tests: test_maintain.py, test_interactive.py (59 pytest total)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 04:00:39 +02:00
parent 1afa7e5ee5
commit b24ec507aa
22 changed files with 3604 additions and 39 deletions

View File

@@ -33,6 +33,7 @@ from reuse_surface.reports import (
from reuse_surface.establish import (
discover_capabilities,
format_publish_check_markdown,
install_registry_hook,
publish_check,
scaffold_next_steps,
scaffold_registry,
@@ -52,6 +53,11 @@ from reuse_surface.stats import (
format_stats_json,
format_stats_markdown,
)
from reuse_surface.maintain import (
format_maintain_json,
format_maintain_markdown,
run_maintain,
)
from reuse_surface.registry import (
ROOT,
capability_paths,
@@ -62,13 +68,26 @@ from reuse_surface.registry import (
parse_front_matter,
parse_vector,
registry_paths,
resolve_repo_root,
)
def _registry_root(args: argparse.Namespace) -> Path:
if getattr(args, "root", None):
return Path(args.root).resolve()
return ROOT
return resolve_repo_root(getattr(args, "root", None))
def _make_validate_fn(repo_root: Path) -> Any:
def _validate() -> tuple[int, list[str], list[str]]:
errors, warnings, _paths = _run_validate(repo_root, target=None, relations=False)
for warning in warnings:
print(f"warning: {warning}", file=sys.stderr)
for error in errors:
print(f"error: {error}", file=sys.stderr)
if errors:
return 1, errors, warnings
return 0, errors, warnings
return _validate
def _check_index_drift(
@@ -427,6 +446,9 @@ def cmd_establish(args: argparse.Namespace) -> int:
)
for path in created:
print(f"ok: wrote {path.relative_to(repo_root)}")
if args.hook:
hook = install_registry_hook(repo_root, force=args.force)
print(f"ok: wrote {hook.relative_to(repo_root)}")
print(scaffold_next_steps(repo_root))
return 0
if args.publish_check:
@@ -461,6 +483,38 @@ def cmd_establish(args: argparse.Namespace) -> int:
return 1
def cmd_maintain(args: argparse.Namespace) -> int:
repo_root = Path(args.path or ".").resolve()
try:
if not args.capability and not args.all:
print("error: specify --capability or --all", file=sys.stderr)
return 1
result = run_maintain(
repo_root,
capability_id=args.capability,
all_capabilities=args.all,
git_since=args.from_git_since,
llm_url=args.llm_url,
no_llm=args.no_llm,
auto=args.auto,
yes=args.yes,
auto_confidence=args.auto_confidence,
auto_max_delta=args.auto_max_delta,
publish=args.publish,
raw_url=args.raw_url,
output_format=args.format,
validate_fn=_make_validate_fn(repo_root),
)
if args.format == "json":
print(format_maintain_json(result))
else:
print(format_maintain_markdown(result), end="")
return result.exit_code
except ValueError as exc:
print(f"error: {exc}", file=sys.stderr)
return 1
def cmd_update(args: argparse.Namespace) -> int:
repo_root = Path(args.path or ".").resolve()
try:
@@ -782,6 +836,11 @@ def main(argv: list[str] | None = None) -> int:
establish.add_argument("--raw-url", help="raw Gitea index URL for publish-check")
establish.add_argument("--llm-url", help="llm-connect base URL (or LLM_CONNECT_URL)")
establish.add_argument("--context-max-files", type=int, default=12)
establish.add_argument(
"--hook",
action="store_true",
help="install registry pre-commit hook (with --scaffold)",
)
establish.set_defaults(func=cmd_establish)
update = subparsers.add_parser("update", help="refresh registry metadata from repo signals")
@@ -795,6 +854,28 @@ def main(argv: list[str] | None = None) -> int:
update.add_argument("--format", choices=["markdown", "json"], default="markdown")
update.set_defaults(func=cmd_update)
maintain = subparsers.add_parser(
"maintain", help="interactive or automated registry maintenance"
)
maintain.add_argument("--path", help="repo root (default: cwd)")
maintain.add_argument("--capability", help="single capability id")
maintain.add_argument("--all", action="store_true")
maintain.add_argument("--from-git-since", help="git ref for change detection")
maintain.add_argument("--llm-url", help="llm-connect base URL (or LLM_CONNECT_URL)")
maintain.add_argument("--no-llm", action="store_true")
maintain.add_argument("--auto", action="store_true", help="apply safe + gated LLM patches")
maintain.add_argument("--yes", action="store_true", help="non-TTY auto-apply equivalent")
maintain.add_argument(
"--auto-confidence",
choices=["low", "medium", "high"],
default="high",
)
maintain.add_argument("--auto-max-delta", type=int, default=1)
maintain.add_argument("--publish", action="store_true")
maintain.add_argument("--raw-url", help="raw Gitea index URL for --publish")
maintain.add_argument("--format", choices=["markdown", "json"], default="markdown")
maintain.set_defaults(func=cmd_maintain)
args = parser.parse_args(argv)
return args.func(args)