Implement scope-derived candidate review infrastructure

This commit is contained in:
2026-05-16 00:26:29 +02:00
parent f4d782c997
commit ba2228e889
14 changed files with 1740 additions and 39 deletions

View File

@@ -186,6 +186,19 @@ def build_parser() -> argparse.ArgumentParser:
default="markdown",
help="Inventory output format.",
)
dataset = subparsers.add_parser(
"assess-dataset",
help="Summarize repository generation coverage across the local dataset.",
)
dataset.add_argument("--database-path", help="Override REPO_SCOPING_DATABASE_PATH.")
dataset.add_argument("--checkout-root", help="Override REPO_SCOPING_CHECKOUT_ROOT.")
dataset.add_argument("--output", help="Write dataset assessment to this path instead of stdout.")
dataset.add_argument(
"--format",
choices=["json", "markdown"],
default="markdown",
help="Dataset assessment output format.",
)
return parser
@@ -204,6 +217,8 @@ def main(argv: Sequence[str] | None = None) -> int:
return list_quality_criteria_command(args)
if args.command == "list-legacy-auto-approvals":
return list_legacy_auto_approvals_command(args)
if args.command == "assess-dataset":
return assess_dataset_command(args)
parser.error(f"unknown command: {args.command}")
return 2
@@ -285,6 +300,235 @@ def list_legacy_auto_approvals_command(args: argparse.Namespace) -> int:
return 0
def assess_dataset_command(args: argparse.Namespace) -> int:
service = service_from_args(args)
report = dataset_assessment(service)
content = (
json.dumps(report, indent=2) + "\n"
if args.format == "json"
else dataset_assessment_markdown(report)
)
if args.output:
write_text(args.output, content)
else:
print(content, end="" if content.endswith("\n") else "\n")
return 0
def dataset_assessment(service: RegistryService) -> dict[str, object]:
repositories = []
totals = {
"repositories": 0,
"facts": 0,
"content_chunks": 0,
"candidate_abilities": 0,
"candidate_capabilities": 0,
"candidate_features": 0,
"candidate_evidence": 0,
"approved_abilities": 0,
"approved_capabilities": 0,
"approved_features": 0,
"approved_evidence": 0,
"dependency_graph_nodes": 0,
"dependency_graph_edges": 0,
}
for repository in service.list_repositories():
runs = service.list_analysis_runs(repository.id)
latest_run = next((run for run in reversed(runs) if run.status == "completed"), None)
facts = service.list_observed_facts(repository.id, latest_run.id) if latest_run else []
chunks = service.list_content_chunks(repository.id, latest_run.id) if latest_run else []
candidate_counts = {
"abilities": 0,
"capabilities": 0,
"features": 0,
"evidence": 0,
}
candidate_names: list[str] = []
if latest_run is not None:
try:
graph = service.candidate_graph(repository.id, latest_run.id)
except NotFoundError:
graph = None
if graph is not None:
candidate_counts = candidate_graph_counts(graph)
candidate_names = [
ability.name
for ability in graph.abilities
][:5]
ability_map = service.ability_map(repository.id)
approved_counts = approved_graph_counts(ability_map)
graph_metrics = {"node_count": 0, "edge_count": 0}
try:
dependency_graph = service.dependency_graph_elements(repository.id)
graph_metrics = {
"node_count": int(dependency_graph["metrics"]["node_count"]),
"edge_count": int(dependency_graph["metrics"]["edge_count"]),
}
except (NotFoundError, ValueError):
pass
snapshot = (
service.store.get_snapshot(latest_run.snapshot_id)
if latest_run is not None and latest_run.snapshot_id is not None
else None
)
doc_presence = document_presence(snapshot.source_path if snapshot else "")
issues = dataset_assessment_issues(
fact_count=len(facts),
chunk_count=len(chunks),
candidate_counts=candidate_counts,
approved_counts=approved_counts,
graph_metrics=graph_metrics,
doc_presence=doc_presence,
candidate_names=candidate_names,
)
repositories.append(
{
"repository_id": repository.id,
"name": repository.name,
"status": repository.status,
"latest_analysis_run_id": latest_run.id if latest_run else None,
"latest_analysis_run_status": latest_run.status if latest_run else None,
"facts": len(facts),
"content_chunks": len(chunks),
"candidate_counts": candidate_counts,
"approved_counts": approved_counts,
"dependency_graph": graph_metrics,
"documents": doc_presence,
"candidate_ability_names": candidate_names,
"issues": issues,
}
)
totals["repositories"] += 1
totals["facts"] += len(facts)
totals["content_chunks"] += len(chunks)
totals["candidate_abilities"] += candidate_counts["abilities"]
totals["candidate_capabilities"] += candidate_counts["capabilities"]
totals["candidate_features"] += candidate_counts["features"]
totals["candidate_evidence"] += candidate_counts["evidence"]
totals["approved_abilities"] += approved_counts["abilities"]
totals["approved_capabilities"] += approved_counts["capabilities"]
totals["approved_features"] += approved_counts["features"]
totals["approved_evidence"] += approved_counts["evidence"]
totals["dependency_graph_nodes"] += graph_metrics["node_count"]
totals["dependency_graph_edges"] += graph_metrics["edge_count"]
return {
"schema_version": "repo-scoping-dataset-assessment/v1",
"summary": totals,
"repositories": repositories,
}
def candidate_graph_counts(graph) -> dict[str, int]:
capabilities = [
capability
for ability in graph.abilities
for capability in ability.capabilities
]
return {
"abilities": len(graph.abilities),
"capabilities": len(capabilities),
"features": sum(len(capability.features) for capability in capabilities),
"evidence": sum(len(capability.evidence) for capability in capabilities),
}
def approved_graph_counts(ability_map) -> dict[str, int]:
capabilities = [
capability
for ability in ability_map.abilities
for capability in ability.capabilities
]
return {
"scope": 1 if ability_map.scope else 0,
"abilities": len(ability_map.abilities),
"capabilities": len(capabilities),
"features": sum(len(capability.features) for capability in capabilities),
"evidence": sum(len(capability.evidence) for capability in capabilities),
}
def document_presence(source_path: str) -> dict[str, bool]:
if not source_path:
return {
"INTENT.md": False,
"SCOPE.md": False,
"README": False,
"CLAUDE.md": False,
"AGENTS.md": False,
}
root = Path(source_path)
return {
"INTENT.md": (root / "INTENT.md").is_file(),
"SCOPE.md": (root / "SCOPE.md").is_file(),
"README": any(root.glob("README*")),
"CLAUDE.md": (root / "CLAUDE.md").is_file(),
"AGENTS.md": (root / "AGENTS.md").is_file(),
}
def dataset_assessment_issues(
*,
fact_count: int,
chunk_count: int,
candidate_counts: dict[str, int],
approved_counts: dict[str, int],
graph_metrics: dict[str, int],
doc_presence: dict[str, bool],
candidate_names: list[str],
) -> list[str]:
issues: list[str] = []
if fact_count and not candidate_counts["capabilities"]:
issues.append("facts-without-candidate-capabilities")
if chunk_count and doc_presence.get("SCOPE.md") and not candidate_counts["capabilities"]:
issues.append("scope-text-unused-for-lower-hierarchy")
if fact_count and not graph_metrics["node_count"]:
issues.append("facts-with-empty-dependency-graph")
if approved_counts["abilities"] == 0 and graph_metrics["node_count"] == 0:
issues.append("approved-hierarchy-missing-and-no-graph-fallback")
if any("repo-seed" in name.lower() for name in candidate_names):
issues.append("template-readme-contamination")
return issues
def dataset_assessment_markdown(report: dict[str, object]) -> str:
lines = ["# Repo-Scoping Dataset Assessment", ""]
summary = report["summary"]
lines.extend(
[
f"- Repositories: {summary['repositories']}",
f"- Facts: {summary['facts']}",
f"- Candidate hierarchy: {summary['candidate_abilities']} abilities / "
f"{summary['candidate_capabilities']} capabilities / "
f"{summary['candidate_features']} features / "
f"{summary['candidate_evidence']} evidence",
f"- Approved hierarchy: {summary['approved_abilities']} abilities / "
f"{summary['approved_capabilities']} capabilities / "
f"{summary['approved_features']} features / "
f"{summary['approved_evidence']} evidence",
f"- Dependency graph: {summary['dependency_graph_nodes']} nodes / "
f"{summary['dependency_graph_edges']} edges",
"",
"| Repo | Run | Facts | Chunks | Candidate | Approved | Graph | Issues |",
"| --- | ---: | ---: | ---: | --- | --- | --- | --- |",
]
)
for item in report["repositories"]:
candidate = item["candidate_counts"]
approved = item["approved_counts"]
graph = item["dependency_graph"]
lines.append(
f"| {item['name']} | {item['latest_analysis_run_id'] or '-'} | "
f"{item['facts']} | {item['content_chunks']} | "
f"{candidate['abilities']}/{candidate['capabilities']}/"
f"{candidate['features']}/{candidate['evidence']} | "
f"{approved['abilities']}/{approved['capabilities']}/"
f"{approved['features']}/{approved['evidence']} | "
f"{graph['node_count']}/{graph['edge_count']} | "
f"{', '.join(item['issues']) or '-'} |"
)
return "\n".join(lines) + "\n"
def legacy_auto_approval_records_markdown(records) -> str:
if not records:
return "No legacy trusted auto-approval records found.\n"