from __future__ import annotations import argparse import json import sys from collections.abc import Callable from pathlib import Path from typing import Any from .api import serve from .service import ( CanonServiceError, alignment_template, artifact_graph, generate_agent_briefs, generate_indexes, generate_tree, inspect_canon, list_artifacts, list_models, list_standards, list_views, profile_graph, profile_inspect, profile_validate, read_view, review_capability_record, review_kit, validate_canon, write_validation_report, ) Command = Callable[[argparse.Namespace], dict[str, Any]] def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(prog="info-tech-canon") parser.add_argument( "--root", default="", help="Infospace root. Defaults to ./infospace from the repository root.", ) sub = parser.add_subparsers(dest="command", required=True) inspect = sub.add_parser("inspect", help="Inspect the canon infospace") inspect.set_defaults(handler=_inspect) artifacts = sub.add_parser("artifacts", help="List canon artifacts") artifacts.add_argument("--kind", default="") artifacts.set_defaults(handler=_artifacts) models = sub.add_parser("models", help="List canon model artifacts") models.set_defaults(handler=_models) standards = sub.add_parser("standards", help="List canon standard artifacts") standards.set_defaults(handler=_standards) review_kit_cmd = sub.add_parser( "review-kit", help="Read the consumer repository alignment review kit", ) review_kit_cmd.set_defaults(handler=_review_kit) alignment_template_cmd = sub.add_parser( "alignment-template", help="Read the consumer alignment workplan template", ) alignment_template_cmd.set_defaults(handler=_alignment_template) validate = sub.add_parser("validate", help="Validate the canon infospace") validate.add_argument( "--write", default="", help="Write the JSON validation payload to this path.", ) validate.set_defaults(handler=_validate) index = sub.add_parser("index", help="Refresh generated indexes and views") index.set_defaults(handler=_index) tree = sub.add_parser("tree", help="Refresh the generated infospace tree") tree.set_defaults(handler=_tree) agent_briefs = sub.add_parser("agent-briefs", help="Refresh generated agent briefs") agent_briefs.set_defaults(handler=_agent_briefs) views = sub.add_parser("views", help="List or read generated views") views.add_argument("name", nargs="?", default="") views.set_defaults(handler=_views) graph = sub.add_parser("graph", help="Export the canon artifact graph") graph.add_argument("--format", choices=["json", "mermaid"], default="json") graph.set_defaults(handler=_graph) profile = sub.add_parser("profile", help="Inspect canon profiles") profile_sub = profile.add_subparsers(dest="profile_command", required=True) profile_inspect_cmd = profile_sub.add_parser("inspect", help="Inspect a profile") profile_inspect_cmd.add_argument("profile") profile_inspect_cmd.set_defaults(handler=_profile_inspect) profile_validate_cmd = profile_sub.add_parser("validate", help="Validate a profile") profile_validate_cmd.add_argument("profile") profile_validate_cmd.set_defaults(handler=_profile_validate) profile_graph_cmd = profile_sub.add_parser("graph", help="Export a profile graph") profile_graph_cmd.add_argument("profile") profile_graph_cmd.add_argument("--format", choices=["json", "mermaid"], default="json") profile_graph_cmd.set_defaults(handler=_profile_graph) capability_review = sub.add_parser( "capability-review", help="Review a consumer capability record against the live catalog", ) capability_review.add_argument("record") capability_review.set_defaults(handler=_capability_review) emission = sub.add_parser("emission-review", help="Validate a source-owned YAML/JSON cadence declaration") emission.add_argument("record") emission.set_defaults(handler=_emission_review) coverage = sub.add_parser("validation-coverage", help="List implemented checks and explicit limits") coverage.set_defaults(handler=_coverage) freshness = sub.add_parser("check-generated", help="Check projections without changing source files") freshness.set_defaults(handler=_check_generated) inventory = sub.add_parser("scope-inventory", help="Derive scope counts from the artifact registry") inventory.set_defaults(handler=_scope_inventory) bundle = sub.add_parser("export-emission-contract", help="Export a content-addressed contract tar") bundle.add_argument("destination") bundle.set_defaults(handler=_export_emission) benchmark = sub.add_parser("benchmark-reads", help="Measure ten uncached inspect calls") benchmark.set_defaults(handler=_benchmark_reads) api = sub.add_parser("api", help="Run the read-only local API") api.add_argument("--host", default="127.0.0.1") api.add_argument("--port", type=int, default=8765) api.set_defaults(handler=_api) return parser def main(argv: list[str] | None = None) -> int: parser = build_parser() args = parser.parse_args(argv) handler: Command = args.handler try: result = handler(args) except CanonServiceError as exc: _print_json(exc.to_dict()) return 2 except Exception as exc: _print_json( { "ok": False, "error": { "code": "unhandled_error", "message": str(exc), "details": {}, }, } ) return 1 if result: _print_json(result) return 0 if result.get("ok", False) else 1 def _root(args: argparse.Namespace) -> Path | None: return Path(args.root) if args.root else None def _inspect(args: argparse.Namespace) -> dict[str, Any]: return inspect_canon(_root(args)) def _artifacts(args: argparse.Namespace) -> dict[str, Any]: return list_artifacts(_root(args), kind=args.kind or None) def _models(args: argparse.Namespace) -> dict[str, Any]: return list_models(_root(args)) def _standards(args: argparse.Namespace) -> dict[str, Any]: return list_standards(_root(args)) def _review_kit(args: argparse.Namespace) -> dict[str, Any]: return review_kit(_root(args)) def _alignment_template(args: argparse.Namespace) -> dict[str, Any]: return alignment_template(_root(args)) def _validate(args: argparse.Namespace) -> dict[str, Any]: if args.write: return write_validation_report(args.write, _root(args)) return validate_canon(_root(args)) def _index(args: argparse.Namespace) -> dict[str, Any]: return generate_indexes(_root(args)) def _tree(args: argparse.Namespace) -> dict[str, Any]: return generate_tree(_root(args)) def _agent_briefs(args: argparse.Namespace) -> dict[str, Any]: return generate_agent_briefs(_root(args)) def _views(args: argparse.Namespace) -> dict[str, Any]: if args.name: return read_view(args.name, _root(args)) return list_views(_root(args)) def _graph(args: argparse.Namespace) -> dict[str, Any]: return artifact_graph(_root(args), output_format=args.format) def _capability_review(args: argparse.Namespace) -> dict[str, Any]: return review_capability_record(args.record, _root(args)) def _emission_review(args): from .contracts import review_emission return review_emission(args.record, _root(args)) def _coverage(args): from .contracts import coverage from .service import load_context return coverage(load_context(_root(args))) def _check_generated(args): from .maintenance import check_generated from .service import load_context return check_generated(load_context(_root(args))) def _scope_inventory(args): from .maintenance import scope_inventory from .service import load_context return dict(scope_inventory(load_context(_root(args))), ok=True) def _export_emission(args): from .maintenance import export_emission_bundle from .paths import infospace_root return export_emission_bundle(infospace_root(_root(args)), Path(args.destination)) def _benchmark_reads(args): from .maintenance import benchmark_reads from .paths import infospace_root return benchmark_reads(infospace_root(_root(args))) def _profile_inspect(args: argparse.Namespace) -> dict[str, Any]: return profile_inspect(args.profile, _root(args)) def _profile_validate(args: argparse.Namespace) -> dict[str, Any]: return profile_validate(args.profile, _root(args)) def _profile_graph(args: argparse.Namespace) -> dict[str, Any]: return profile_graph(args.profile, _root(args), output_format=args.format) def _api(args: argparse.Namespace) -> dict[str, Any]: serve(host=args.host, port=args.port, root=_root(args)) return {} def _print_json(data: dict[str, Any]) -> None: json.dump(data, sys.stdout, indent=2, sort_keys=True) sys.stdout.write("\n")