feat: generate repository rename adoption plans
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a049a4-ee9f-78e1-9d66-2cb0f9bea3e3
This commit is contained in:
parent
bdf3a1fdf8
commit
ef0b6df3b8
7 changed files with 1294 additions and 5 deletions
|
|
@ -16,6 +16,15 @@ import uuid
|
|||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
|
||||
from repository_rename_workplan import (
|
||||
WorkplanGenerationError,
|
||||
build_generation_snapshot,
|
||||
content_checksum,
|
||||
default_output_path,
|
||||
render_workplan,
|
||||
write_workplan_exclusive,
|
||||
)
|
||||
|
||||
|
||||
CLI_SCHEMA_VERSION = "state-hub.repository-rename-cli.v1"
|
||||
DEFAULT_API_BASE = os.environ.get("API_BASE", "http://127.0.0.1:8000")
|
||||
|
|
@ -120,7 +129,9 @@ def _api_request(
|
|||
method: str,
|
||||
path: str,
|
||||
body: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
*,
|
||||
expected: type | tuple[type, ...] = dict,
|
||||
) -> Any:
|
||||
data = json.dumps(body).encode("utf-8") if body is not None else None
|
||||
headers = dict(_HEADERS)
|
||||
if data is not None:
|
||||
|
|
@ -165,9 +176,14 @@ def _api_request(
|
|||
"State Hub returned an invalid JSON response",
|
||||
code="invalid_state_hub_response",
|
||||
) from exc
|
||||
if not isinstance(payload, dict):
|
||||
if not isinstance(payload, expected):
|
||||
expected_name = (
|
||||
", ".join(item.__name__ for item in expected)
|
||||
if isinstance(expected, tuple)
|
||||
else expected.__name__
|
||||
)
|
||||
raise RenameCLIError(
|
||||
"State Hub returned a non-object response",
|
||||
f"State Hub returned JSON with the wrong shape; expected {expected_name}",
|
||||
code="invalid_state_hub_response",
|
||||
)
|
||||
return payload
|
||||
|
|
@ -361,6 +377,9 @@ def _emit(payload: dict[str, Any], *, as_json: bool) -> None:
|
|||
print(f" blockers: {len(result.get('blockers') or [])}", file=stream)
|
||||
if result.get("preflight_file"):
|
||||
print(f" private preflight: {result['preflight_file']}", file=stream)
|
||||
elif public["command"] == "generate-workplan" and result.get("output"):
|
||||
print(f" workplan: {result['output']}", file=stream)
|
||||
print(f" workplan id: {result.get('workplan_id')}", file=stream)
|
||||
error = public.get("error")
|
||||
if error:
|
||||
print(f" error: {error.get('message')}", file=stream)
|
||||
|
|
@ -490,6 +509,88 @@ def cmd_preflight(args: argparse.Namespace) -> None:
|
|||
_run(args, action)
|
||||
|
||||
|
||||
def cmd_generate_workplan(args: argparse.Namespace) -> None:
|
||||
def action() -> dict[str, Any]:
|
||||
if args.output and Path(args.output).expanduser().exists():
|
||||
raise RenameCLIError(
|
||||
f"Refusing to overwrite existing workplan {args.output}",
|
||||
code="output_exists",
|
||||
)
|
||||
repo = _resolve_repo(args, args.old_slug)
|
||||
workplans = _api_request(
|
||||
args.api_base,
|
||||
"GET",
|
||||
f"/workplans/?repo_id={urllib.parse.quote(str(repo['id']), safe='')}",
|
||||
expected=list,
|
||||
)
|
||||
if args.preflight_file:
|
||||
wrapper = _load_json(args.preflight_file, expected=dict)
|
||||
if (
|
||||
wrapper.get("schema_version") != CLI_SCHEMA_VERSION
|
||||
or wrapper.get("command") != "preflight"
|
||||
or str(wrapper.get("repo_id")) != str(repo["id"])
|
||||
):
|
||||
raise RenameCLIError(
|
||||
"Preflight file is not for this registered repository",
|
||||
code="invalid_preflight_file",
|
||||
)
|
||||
report = wrapper.get("result") or {}
|
||||
else:
|
||||
report = _api_request(
|
||||
args.api_base,
|
||||
"POST",
|
||||
f"/repos/{repo['id']}/rename/preflight",
|
||||
{"new_slug": args.new_slug, "queued_edge_writes": []},
|
||||
)
|
||||
if (
|
||||
report.get("old_slug") != args.old_slug
|
||||
or report.get("new_slug") != args.new_slug
|
||||
):
|
||||
raise RenameCLIError(
|
||||
"Preflight snapshot does not address the requested rename",
|
||||
code="invalid_preflight_file",
|
||||
)
|
||||
try:
|
||||
snapshot = build_generation_snapshot(
|
||||
repo=repo,
|
||||
preflight=report,
|
||||
workplans=workplans,
|
||||
repo_path_override=args.repo_path,
|
||||
)
|
||||
content = render_workplan(snapshot, owner=args.owner)
|
||||
output = (
|
||||
Path(args.output).expanduser().resolve()
|
||||
if args.output
|
||||
else default_output_path(snapshot)
|
||||
)
|
||||
write_workplan_exclusive(output, content)
|
||||
except WorkplanGenerationError as exc:
|
||||
raise RenameCLIError(str(exc), code=exc.code) from exc
|
||||
return _envelope(
|
||||
"generate-workplan",
|
||||
state="achieved",
|
||||
ok=True,
|
||||
result={
|
||||
"output": str(output),
|
||||
"workplan_id": snapshot.workplan_id,
|
||||
"workplan_prefix": snapshot.prefix,
|
||||
"content_checksum": content_checksum(content),
|
||||
"preflight_report_checksum": report.get("report_checksum"),
|
||||
"safe_to_apply_at_capture": bool(report.get("safe_to_apply")),
|
||||
"path_available": snapshot.path_available,
|
||||
"state_hub_uuid_fields_written": [],
|
||||
},
|
||||
repo_id=str(repo["id"]),
|
||||
next_safe_action={
|
||||
"action": "review-generated-workplan",
|
||||
"requires_confirmation": False,
|
||||
"command": None,
|
||||
},
|
||||
)
|
||||
|
||||
_run(args, action)
|
||||
|
||||
|
||||
def cmd_start(args: argparse.Namespace) -> None:
|
||||
def action() -> dict[str, Any]:
|
||||
operation_id = _validated_uuid(args.operation_id)
|
||||
|
|
@ -763,6 +864,31 @@ def configure_repo_commands(subparsers: argparse._SubParsersAction) -> None:
|
|||
_common(preflight)
|
||||
preflight.set_defaults(func=cmd_preflight)
|
||||
|
||||
generate = rename_sub.add_parser(
|
||||
"generate-workplan",
|
||||
help="Generate a repository-native rename adoption workplan",
|
||||
)
|
||||
generate.add_argument("old_slug")
|
||||
generate.add_argument("new_slug")
|
||||
generate.add_argument(
|
||||
"--preflight-file",
|
||||
default=None,
|
||||
help="Use a private mode-0600 preflight envelope instead of refreshing",
|
||||
)
|
||||
generate.add_argument(
|
||||
"--repo-path",
|
||||
default=None,
|
||||
help="Visible target checkout used for workplan convention discovery",
|
||||
)
|
||||
generate.add_argument(
|
||||
"--output",
|
||||
default=None,
|
||||
help="Exclusive output path; defaults to the target workplans directory",
|
||||
)
|
||||
generate.add_argument("--owner", default="codex")
|
||||
_common(generate)
|
||||
generate.set_defaults(func=cmd_generate_workplan)
|
||||
|
||||
start = rename_sub.add_parser(
|
||||
"start",
|
||||
help="Create an idempotent operation journal from a private preflight file",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue