Add discovery reconciliation engine

This commit is contained in:
2026-05-19 04:49:08 +02:00
parent 73f7cdbdb5
commit 17356a41d6
6 changed files with 675 additions and 1 deletions

View File

@@ -14,6 +14,7 @@ from .loader import declaration_files, load_yaml
from .graph import FabricGraph, build_graph
from .graph_explorer import fabric_graph_explorer_payload
from .llm_extraction import LLMExtractionConfig
from .reconciliation import reconcile_discovery_snapshots
from .scanner import ScanOptions, scan_repo
from .validation import validate_roots
@@ -73,6 +74,7 @@ def build_parser() -> argparse.ArgumentParser:
scan.add_argument("--profile", default="deterministic")
scan.add_argument("--dry-run", action="store_true", help="Do not write anywhere except an explicit --output file.")
scan.add_argument("--output", type=Path, default=None, help="Write the discovery snapshot JSON to a file.")
scan.add_argument("--previous-snapshot", type=Path, default=None, help="Reconcile against a previous discovery snapshot JSON.")
scan.add_argument("--json", action="store_true", help="Print the discovery snapshot JSON to stdout.")
scan.add_argument("--llm", action="store_true", help="Enable llm-connect assisted extraction.")
scan.add_argument("--llm-provider", default="mock", help="llm-connect provider name.")
@@ -410,6 +412,16 @@ def _scan_repo(args: argparse.Namespace) -> int:
),
)
)
if args.previous_snapshot:
try:
previous = json.loads(args.previous_snapshot.read_text(encoding="utf-8"))
except Exception as exc:
print(f"ERROR {args.previous_snapshot}: cannot read previous snapshot: {exc}", file=sys.stderr)
return 1
if not isinstance(previous, dict):
print(f"ERROR {args.previous_snapshot}: previous snapshot must be a JSON object", file=sys.stderr)
return 1
snapshot = reconcile_discovery_snapshots(previous, snapshot)
payload = json.dumps(snapshot, indent=2, sort_keys=True)
if args.output:
args.output.parent.mkdir(parents=True, exist_ok=True)
@@ -421,6 +433,15 @@ def _scan_repo(args: argparse.Namespace) -> int:
candidates = snapshot["candidates"]
review_count = len(snapshot.get("review_artifacts", []))
review_summary = f", {review_count} review artifact(s)" if review_count else ""
diff = snapshot.get("reconciliation", {}).get("diff", {})
diff_summary = ""
if isinstance(diff, dict):
diff_summary = (
f", diff +{len(diff.get('added', []))}"
f"/~{len(diff.get('changed', []))}"
f"/-{len(diff.get('retired', []))}"
f"/!{len(diff.get('conflicted', []))}"
)
mode = "dry-run " if args.dry_run else ""
print(
f"{mode}scan {snapshot['source']['repo_slug']} "
@@ -430,6 +451,7 @@ def _scan_repo(args: argparse.Namespace) -> int:
f"{len(candidates['attributes'])} attribute(s), "
f"{len(snapshot['replacement_scopes'])} replacement scope(s)"
f"{review_summary}"
f"{diff_summary}"
)
if args.output:
print(f"wrote {args.output}")