Files
cdsl-cad/backend/scripts/remove_generation_spec_artifacts.py
T
2026-08-25 17:41:24 +08:00

160 lines
5.8 KiB
Python

#!/usr/bin/env python3
"""Remove retired semantic-plan artifacts from the local workspace.
Run without arguments to list every file and metadata document that would
change. Run again with --apply to make the irreversible cleanup.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
RETIRED_FILENAMES = {
"generation-spec.json",
"generation-provenance.json",
"acceptance-report.json",
}
RETIRED_DIAGNOSTIC_PREFIX = "generation_spec_"
RETIRED_TOOL_NAMES = {
"create_generation_spec",
"author_cdsl_from_generation_spec",
"compile_generation_spec",
"patch_generation_spec",
"generate_flange_sleeve_model",
}
RETIRED_METADATA_KEYS = {
"generation_spec_path",
"generation_provenance_path",
"acceptance_path",
"approximations",
"current_design_intent_id",
"design_intents",
"design_intent_id",
"design_intent_path",
"design_intent_structures",
"design_intent_assumptions",
"design_intent_capability_gaps",
"capability_translations",
"evidence",
}
def _strip_metadata(value: Any) -> bool:
changed = False
if isinstance(value, list):
for item in value:
changed = _strip_metadata(item) or changed
return changed
if not isinstance(value, dict):
return False
for key in list(value):
if key in RETIRED_METADATA_KEYS or key.startswith("generation_spec_"):
value.pop(key)
changed = True
operation = value.get("operation")
if isinstance(operation, dict) and "spec" in str(operation.get("type") or "").casefold():
value["operation"] = {}
changed = True
for key, child in list(value.items()):
if key == "source" and isinstance(child, str) and "generation_spec" in child.casefold():
value[key] = "legacy_cdsl"
changed = True
elif key in {"message", "text"} and isinstance(child, str) and "generationspec" in child.casefold():
value[key] = "已清理已弃用的规格流程诊断;当前任务统一使用直接 CDSL 生成。"
changed = True
else:
changed = _strip_metadata(child) or changed
return changed
def _retired_diagnostic(path: Path) -> bool:
if path.name.startswith(RETIRED_DIAGNOSTIC_PREFIX):
return True
try:
payload = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return False
if not isinstance(payload, dict):
return False
tool_name = str(payload.get("tool_name") or "")
arguments = str(payload.get("arguments") or "")
return tool_name in RETIRED_TOOL_NAMES or "generation-spec" in arguments or "generation_spec" in arguments
def cleanup_plan(data_root: Path) -> tuple[list[Path], list[Path]]:
files: list[Path] = []
metadata: list[Path] = []
task_root = data_root / "tasks"
conversation_root = data_root / "conversations"
if task_root.is_dir():
for path in task_root.rglob("*"):
if path.is_file() and (
path.name in RETIRED_FILENAMES
or (path.parent.name == "planning" and path.name.startswith("design-intent-"))
):
files.append(path)
for path in task_root.rglob("*.json"):
try:
payload = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
continue
original = json.dumps(payload, sort_keys=True, ensure_ascii=False)
_strip_metadata(payload)
if json.dumps(payload, sort_keys=True, ensure_ascii=False) != original:
metadata.append(path)
if conversation_root.is_dir():
for path in conversation_root.rglob("*.json"):
if _retired_diagnostic(path):
files.append(path)
continue
try:
payload = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
continue
original = json.dumps(payload, sort_keys=True, ensure_ascii=False)
_strip_metadata(payload)
if json.dumps(payload, sort_keys=True, ensure_ascii=False) != original:
metadata.append(path)
file_set = set(files)
return sorted(file_set), sorted(set(metadata) - file_set)
def apply_cleanup(data_root: Path) -> tuple[list[Path], list[Path]]:
files, metadata = cleanup_plan(data_root)
for path in files:
path.unlink()
for path in metadata:
payload = json.loads(path.read_text(encoding="utf-8"))
if _strip_metadata(payload):
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
return files, metadata
def main() -> int:
parser = argparse.ArgumentParser(description="Remove retired GenerationSpec and semantic-plan artifacts")
parser.add_argument("--data-root", type=Path, default=Path(__file__).resolve().parents[1] / "data")
parser.add_argument("--apply", action="store_true", help="Delete the listed artifacts and rewrite metadata")
args = parser.parse_args()
files, metadata = cleanup_plan(args.data_root)
action = "Will remove" if not args.apply else "Removing"
for path in files:
print(f"{action} artifact: {path}")
for path in metadata:
print(f"{action} retired metadata: {path}")
if not files and not metadata:
print("No retired GenerationSpec or semantic-plan artifacts found.")
if not args.apply:
print("Dry run only. Re-run with --apply to make these changes.")
return 0
apply_cleanup(args.data_root)
print(f"Removed {len(files)} artifacts and rewrote {len(metadata)} metadata documents.")
return 0
if __name__ == "__main__":
raise SystemExit(main())