160 lines
5.8 KiB
Python
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())
|