2026-05-14 11:32:25 +02:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
import json
|
|
|
|
|
import sys
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
2026-05-14 15:35:04 +02:00
|
|
|
from .checks import run_collection_checks
|
2026-05-14 11:32:25 +02:00
|
|
|
from .errors import InfospaceError
|
2026-05-14 15:35:04 +02:00
|
|
|
from .history import find_snapshot, get_history, metric_trend, record_check_results
|
2026-05-14 11:32:25 +02:00
|
|
|
from .lifecycle import add_artifact, create_infospace, load_infospace
|
2026-05-14 14:53:16 +02:00
|
|
|
from .markdown_adapter import validate_infospace_artifacts
|
2026-05-14 15:06:17 +02:00
|
|
|
from .semantics import list_entities, list_relations
|
2026-05-14 16:01:32 +02:00
|
|
|
from .workflow import load_workflows, plan_workflow, run_workflow
|
2026-05-14 11:32:25 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
|
|
|
parser = argparse.ArgumentParser(prog="infospace-bench")
|
|
|
|
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
|
|
|
|
|
|
|
|
create = sub.add_parser("create", help="Create an infospace")
|
|
|
|
|
create.add_argument("workspace")
|
|
|
|
|
create.add_argument("slug")
|
|
|
|
|
create.add_argument("--name", required=True)
|
|
|
|
|
create.add_argument("--topic-domain", default="")
|
|
|
|
|
|
|
|
|
|
inspect = sub.add_parser("inspect", help="Inspect an infospace")
|
|
|
|
|
inspect.add_argument("root")
|
|
|
|
|
|
|
|
|
|
add = sub.add_parser("add-artifact", help="Add an artifact to an infospace")
|
|
|
|
|
add.add_argument("root")
|
|
|
|
|
add.add_argument("source")
|
|
|
|
|
add.add_argument("--kind", required=True)
|
|
|
|
|
add.add_argument("--title", default="")
|
|
|
|
|
|
|
|
|
|
export = sub.add_parser("export", help="Print the infospace representation")
|
|
|
|
|
export.add_argument("root")
|
|
|
|
|
|
2026-05-14 14:53:16 +02:00
|
|
|
validate = sub.add_parser("validate", help="Validate infospace artifacts")
|
|
|
|
|
validate.add_argument("root")
|
|
|
|
|
|
2026-05-14 15:06:17 +02:00
|
|
|
entities = sub.add_parser("entities", help="List parsed entity artifacts")
|
|
|
|
|
entities.add_argument("root")
|
|
|
|
|
|
|
|
|
|
relations = sub.add_parser("relations", help="List parsed relation artifacts")
|
|
|
|
|
relations.add_argument("root")
|
|
|
|
|
|
2026-05-14 15:35:04 +02:00
|
|
|
history = sub.add_parser("history", help="List evaluation snapshot history")
|
|
|
|
|
history.add_argument("root")
|
|
|
|
|
history.add_argument("--metric", default="")
|
|
|
|
|
|
|
|
|
|
history_diff = sub.add_parser(
|
|
|
|
|
"history-diff",
|
|
|
|
|
help="Diff two evaluation snapshots by snapshot ID or date",
|
|
|
|
|
)
|
|
|
|
|
history_diff.add_argument("root")
|
|
|
|
|
history_diff.add_argument("before")
|
|
|
|
|
history_diff.add_argument("after")
|
|
|
|
|
|
|
|
|
|
metrics = sub.add_parser(
|
|
|
|
|
"metrics",
|
|
|
|
|
help="Run collection checks and persist metrics/history",
|
|
|
|
|
)
|
|
|
|
|
metrics.add_argument("root")
|
|
|
|
|
|
2026-05-14 16:01:32 +02:00
|
|
|
workflow = sub.add_parser("workflow", help="Inspect, plan, and run workflows")
|
|
|
|
|
workflow_sub = workflow.add_subparsers(dest="workflow_command", required=True)
|
|
|
|
|
|
|
|
|
|
workflow_inspect = workflow_sub.add_parser(
|
|
|
|
|
"inspect",
|
|
|
|
|
help="Inspect workflow declarations",
|
|
|
|
|
)
|
|
|
|
|
workflow_inspect.add_argument("root")
|
|
|
|
|
|
|
|
|
|
workflow_plan = workflow_sub.add_parser(
|
|
|
|
|
"plan",
|
|
|
|
|
help="Plan a workflow without writing outputs",
|
|
|
|
|
)
|
|
|
|
|
workflow_plan.add_argument("root")
|
|
|
|
|
workflow_plan.add_argument("workflow_id")
|
|
|
|
|
|
|
|
|
|
workflow_run = workflow_sub.add_parser(
|
|
|
|
|
"run",
|
|
|
|
|
help="Run a deterministic workflow",
|
|
|
|
|
)
|
|
|
|
|
workflow_run.add_argument("root")
|
|
|
|
|
workflow_run.add_argument("workflow_id")
|
|
|
|
|
|
2026-05-14 11:32:25 +02:00
|
|
|
return parser
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
|
|
|
parser = build_parser()
|
|
|
|
|
args = parser.parse_args(argv)
|
|
|
|
|
try:
|
|
|
|
|
if args.command == "create":
|
|
|
|
|
infospace = create_infospace(
|
|
|
|
|
Path(args.workspace),
|
|
|
|
|
args.slug,
|
|
|
|
|
name=args.name,
|
|
|
|
|
topic_domain=args.topic_domain,
|
|
|
|
|
)
|
|
|
|
|
_write_json({"slug": infospace.config.slug, "root": str(infospace.root)})
|
|
|
|
|
elif args.command == "inspect":
|
|
|
|
|
_write_json(load_infospace(Path(args.root)).to_dict())
|
|
|
|
|
elif args.command == "add-artifact":
|
|
|
|
|
artifact = add_artifact(
|
|
|
|
|
Path(args.root),
|
|
|
|
|
Path(args.source),
|
|
|
|
|
kind=args.kind,
|
|
|
|
|
title=args.title,
|
|
|
|
|
)
|
|
|
|
|
_write_json({"artifact": artifact.to_dict()})
|
|
|
|
|
elif args.command == "export":
|
|
|
|
|
_write_json(load_infospace(Path(args.root)).to_dict())
|
2026-05-14 14:53:16 +02:00
|
|
|
elif args.command == "validate":
|
|
|
|
|
results = validate_infospace_artifacts(Path(args.root))
|
|
|
|
|
valid = all(result.valid for result in results)
|
|
|
|
|
_write_json(
|
|
|
|
|
{
|
|
|
|
|
"valid": valid,
|
|
|
|
|
"results": [result.to_dict() for result in results],
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
return 0 if valid else 1
|
2026-05-14 15:06:17 +02:00
|
|
|
elif args.command == "entities":
|
|
|
|
|
_write_json(
|
|
|
|
|
{
|
|
|
|
|
"entities": [
|
|
|
|
|
entity.to_dict() for entity in list_entities(Path(args.root))
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
elif args.command == "relations":
|
|
|
|
|
_write_json(
|
|
|
|
|
{
|
|
|
|
|
"relations": [
|
|
|
|
|
relation.to_dict()
|
|
|
|
|
for relation in list_relations(Path(args.root))
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
)
|
2026-05-14 15:35:04 +02:00
|
|
|
elif args.command == "history":
|
|
|
|
|
history = get_history(Path(args.root))
|
|
|
|
|
if args.metric:
|
|
|
|
|
_write_json(
|
|
|
|
|
{
|
|
|
|
|
"metric": args.metric,
|
|
|
|
|
"trend": metric_trend(history, args.metric),
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
_write_json({"history": [item.to_dict() for item in history]})
|
|
|
|
|
elif args.command == "history-diff":
|
|
|
|
|
history = get_history(Path(args.root))
|
|
|
|
|
before = find_snapshot(history, args.before)
|
|
|
|
|
after = find_snapshot(history, args.after)
|
|
|
|
|
if before is None or after is None:
|
|
|
|
|
missing = []
|
|
|
|
|
if before is None:
|
|
|
|
|
missing.append(args.before)
|
|
|
|
|
if after is None:
|
|
|
|
|
missing.append(args.after)
|
|
|
|
|
raise InfospaceError(
|
|
|
|
|
"missing_snapshot",
|
|
|
|
|
"Could not resolve requested snapshot reference",
|
|
|
|
|
{"missing_refs": missing},
|
|
|
|
|
)
|
|
|
|
|
_write_json({"diff": before.diff(after).to_dict()})
|
|
|
|
|
elif args.command == "metrics":
|
|
|
|
|
infospace = load_infospace(Path(args.root))
|
|
|
|
|
result = record_check_results(
|
|
|
|
|
infospace.root,
|
|
|
|
|
run_collection_checks(infospace.artifacts),
|
|
|
|
|
)
|
|
|
|
|
_write_json(result.to_dict())
|
2026-05-14 16:01:32 +02:00
|
|
|
elif args.command == "workflow":
|
|
|
|
|
if args.workflow_command == "inspect":
|
|
|
|
|
_write_json(
|
|
|
|
|
{
|
|
|
|
|
"workflows": [
|
|
|
|
|
workflow.to_dict()
|
|
|
|
|
for workflow in load_workflows(Path(args.root))
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
elif args.workflow_command == "plan":
|
|
|
|
|
_write_json(
|
|
|
|
|
plan_workflow(Path(args.root), args.workflow_id).to_dict()
|
|
|
|
|
)
|
|
|
|
|
elif args.workflow_command == "run":
|
|
|
|
|
_write_json(
|
|
|
|
|
run_workflow(Path(args.root), args.workflow_id).to_dict()
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
parser.error(f"Unhandled workflow command: {args.workflow_command}")
|
2026-05-14 11:32:25 +02:00
|
|
|
else:
|
|
|
|
|
parser.error(f"Unhandled command: {args.command}")
|
|
|
|
|
except InfospaceError as exc:
|
|
|
|
|
print(json.dumps(exc.to_dict(), indent=2), file=sys.stderr)
|
|
|
|
|
return 2
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _write_json(payload: dict) -> None:
|
|
|
|
|
print(json.dumps(payload, indent=2))
|