#!/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())