from __future__ import annotations from collections import defaultdict from pathlib import Path from typing import Any import yaml GENERATED_NOTICE = "" def generate_indexes(context: Any) -> dict[str, Any]: assets: list[dict[str, Any]] = [] ownership = concept_ownership(context) import_matrix = relationship_matrix(context) assets.append( _write_yaml( context.infospace_root / "indexes" / "concept-ownership.yaml", ownership, ) ) assets.append( _write_yaml( context.infospace_root / "indexes" / "import-matrix.yaml", import_matrix, ) ) assets.append( _write_yaml( context.infospace_root / "indexes" / "artifact-tree.yaml", artifact_tree(context), ) ) assets.extend(generate_views(context, ownership, import_matrix)["files"]) return _result("index", assets) def generate_views( context: Any, ownership: dict[str, Any] | None = None, import_matrix: dict[str, Any] | None = None, ) -> dict[str, Any]: ownership = ownership or concept_ownership(context) import_matrix = import_matrix or relationship_matrix(context) files = [ _write_text( context.infospace_root / "views" / "by-standard.md", _render_by_standard(context), ), _write_text( context.infospace_root / "views" / "by-concept.md", _render_by_concept(ownership), ), _write_text( context.infospace_root / "views" / "by-profile.md", _render_by_profile(context), ), _write_text( context.infospace_root / "views" / "by-mapping-target.md", _render_by_mapping_target(context), ), _write_text( context.infospace_root / "views" / "kernel-overview.md", _render_kernel_overview(context), ), _write_text( context.infospace_root / "views" / "import-matrix.md", _render_import_matrix(import_matrix), ), ] return _result("views", files) def generate_tree(context: Any) -> dict[str, Any]: tree = artifact_tree(context) files = [ _write_yaml(context.infospace_root / "indexes" / "artifact-tree.yaml", tree), _write_text( context.infospace_root / "views" / "repository-tree.md", _render_repository_tree(tree), ), ] return _result("tree", files) def generate_agent_briefs(context: Any) -> dict[str, Any]: files = [ _write_text( context.infospace_root / "agent" / "global-agent-brief.md", _render_global_agent_brief(context), ) ] return _result("agent-briefs", files) def list_generated_views(context: Any) -> dict[str, Any]: views = [] for path in sorted((context.infospace_root / "views").glob("*.md")): views.append( { "name": path.name, "path": str(path.relative_to(context.infospace_root)), "generated": _is_generated(path), } ) return {"ok": True, "count": len(views), "views": views} def read_generated_view(context: Any, name: str) -> dict[str, Any]: if "/" in name or "\\" in name: raise ValueError("View name must be a single file name.") path = context.infospace_root / "views" / name if not path.is_file(): raise FileNotFoundError(name) return { "ok": True, "name": name, "path": str(path.relative_to(context.infospace_root)), "generated": _is_generated(path), "content": path.read_text(encoding="utf-8"), } def concept_ownership(context: Any) -> dict[str, Any]: concepts: list[dict[str, Any]] = [] for artifact in sorted(context.infospace.artifacts, key=lambda item: item.id): concepts.append( { "concept": artifact.title, "owner": artifact.id, "path": artifact.path, "source": "artifact_title", } ) frontmatter = _frontmatter(context.infospace_root / artifact.path) owned = frontmatter.get("owned_concepts") or [] if isinstance(owned, list): for concept in owned: concepts.append( { "concept": str(concept), "owner": artifact.id, "path": artifact.path, "source": "frontmatter.owned_concepts", } ) by_key: dict[str, list[dict[str, Any]]] = defaultdict(list) for item in concepts: by_key[_normalize_concept(item["concept"])].append(item) duplicates = [ {"normalized": key, "candidates": items} for key, items in sorted(by_key.items()) if len(items) > 1 ] conflicts = [ { "normalized": item["normalized"], "owners": sorted({candidate["owner"] for candidate in item["candidates"]}), "candidates": item["candidates"], } for item in duplicates if len({candidate["owner"] for candidate in item["candidates"]}) > 1 ] return { "concept_count": len(concepts), "concepts": concepts, "duplicate_candidates": duplicates, "ownership_conflicts": conflicts, } def relationship_matrix(context: Any) -> dict[str, Any]: artifact_ids = sorted(artifact.id for artifact in context.infospace.artifacts) rows: list[dict[str, Any]] = [] for artifact in sorted(context.infospace.artifacts, key=lambda item: item.id): targets: dict[str, list[str]] = {target: [] for target in artifact_ids} for relationship in artifact.relationships: target = relationship.get("target") relation_type = str(relationship.get("type") or "related") if isinstance(target, str) and target in targets: targets[target].append(relation_type) rows.append( { "artifact": artifact.id, "targets": { target: sorted(types) for target, types in targets.items() if types }, } ) return {"artifacts": artifact_ids, "rows": rows} def artifact_tree(context: Any) -> dict[str, Any]: files: list[dict[str, Any]] = [] for path in sorted(context.infospace_root.rglob("*")): if path.is_file(): relative = path.relative_to(context.infospace_root) files.append( { "path": str(relative), "directory": str(relative.parent), "name": path.name, } ) return {"root": "infospace", "file_count": len(files), "files": files} def _render_by_standard(context: Any) -> str: lines = _heading("By Standard") standards = [ artifact for artifact in context.infospace.artifacts if artifact.kind in {"kernel", "standard"} ] for artifact in sorted(standards, key=lambda item: item.id): lines.extend( [ f"## {artifact.title}", "", f"- ID: `{artifact.id}`", f"- Kind: `{artifact.kind}`", f"- Path: `{artifact.path}`", f"- Relationships: {len(artifact.relationships)}", "", ] ) return "\n".join(lines).rstrip() + "\n" def _render_by_concept(ownership: dict[str, Any]) -> str: lines = _heading("By Concept") lines.extend( [ f"Concept count: **{ownership['concept_count']}**", "", "| Concept | Owner | Source |", "| --- | --- | --- |", ] ) for concept in ownership["concepts"]: lines.append( f"| {concept['concept']} | `{concept['owner']}` | `{concept['source']}` |" ) lines.extend(["", "## Duplicate Candidates", ""]) duplicates = ownership["duplicate_candidates"] if not duplicates: lines.append("No duplicate concept candidates detected.") else: for duplicate in duplicates: lines.append(f"- `{duplicate['normalized']}`") lines.extend(["", "## Ownership Conflicts", ""]) conflicts = ownership["ownership_conflicts"] if not conflicts: lines.append("No ownership conflicts detected.") else: for conflict in conflicts: owners = ", ".join(f"`{owner}`" for owner in conflict["owners"]) lines.append(f"- `{conflict['normalized']}` owned by {owners}") return "\n".join(lines).rstrip() + "\n" def _render_by_profile(context: Any) -> str: lines = _heading("By Profile") profiles = sorted((context.infospace_root / "profiles").glob("*/profile.yaml")) if not profiles: lines.append("No profiles have been registered yet.") for path in profiles: lines.extend( [ f"## {path.parent.name}", "", f"- Path: `{path.relative_to(context.infospace_root)}`", "", ] ) return "\n".join(lines).rstrip() + "\n" def _render_by_mapping_target(context: Any) -> str: incoming: dict[str, list[tuple[str, str]]] = defaultdict(list) for artifact in context.infospace.artifacts: for relationship in artifact.relationships: target = relationship.get("target") relation_type = str(relationship.get("type") or "related") if isinstance(target, str): incoming[target].append((artifact.id, relation_type)) lines = _heading("By Mapping Target") for target in sorted(incoming): lines.extend([f"## `{target}`", ""]) for source, relation_type in sorted(incoming[target]): lines.append(f"- `{source}` via `{relation_type}`") lines.append("") return "\n".join(lines).rstrip() + "\n" def _render_kernel_overview(context: Any) -> str: kind_counts: dict[str, int] = defaultdict(int) relationship_counts: dict[str, int] = defaultdict(int) for artifact in context.infospace.artifacts: kind_counts[artifact.kind] += 1 for relationship in artifact.relationships: relationship_counts[str(relationship.get("type") or "related")] += 1 lines = _heading("Kernel Overview") lines.extend( [ f"- Infospace: `{context.infospace.config.slug}`", f"- Artifacts: {len(context.infospace.artifacts)}", "", "## Artifact Kinds", "", ] ) for kind, count in sorted(kind_counts.items()): lines.append(f"- `{kind}`: {count}") lines.extend(["", "## Relationship Types", ""]) for relation_type, count in sorted(relationship_counts.items()): lines.append(f"- `{relation_type}`: {count}") return "\n".join(lines).rstrip() + "\n" def _render_import_matrix(matrix: dict[str, Any]) -> str: artifacts = matrix["artifacts"] lines = _heading("Import Matrix") header = "| Artifact | " + " | ".join(f"`{artifact}`" for artifact in artifacts) + " |" divider = "| --- | " + " | ".join("---" for _ in artifacts) + " |" lines.extend([header, divider]) for row in matrix["rows"]: cells = [] targets = row["targets"] for artifact in artifacts: cells.append(", ".join(f"`{item}`" for item in targets.get(artifact, []))) lines.append(f"| `{row['artifact']}` | " + " | ".join(cells) + " |") return "\n".join(lines).rstrip() + "\n" def _render_repository_tree(tree: dict[str, Any]) -> str: lines = _heading("Repository Tree") lines.append(f"File count: **{tree['file_count']}**") lines.append("") for file_info in tree["files"]: lines.append(f"- `{file_info['path']}`") return "\n".join(lines).rstrip() + "\n" def _render_global_agent_brief(context: Any) -> str: lines = _heading("Global Agent Brief") lines.extend( [ "This brief summarizes the current canon service surface for agents.", "", f"- Infospace slug: `{context.infospace.config.slug}`", f"- Artifact count: {len(context.infospace.artifacts)}", "- Primary confidence command: `make validate`", "- Refresh generated indexes and views with: `make index`", "", "## Useful Commands", "", "- `PYTHONPATH=src python3 -m info_tech_canon inspect`", "- `PYTHONPATH=src python3 -m info_tech_canon validate`", "- `PYTHONPATH=src python3 -m info_tech_canon graph`", "- `PYTHONPATH=src python3 -m info_tech_canon index`", "", "## Consumption Notes", "", "- Treat `seeds/` as provenance.", "- Treat `infospace/` as the service-consumable canon root.", "- Generated files are marked and can be refreshed deterministically.", ] ) return "\n".join(lines).rstrip() + "\n" def _heading(title: str) -> list[str]: return [GENERATED_NOTICE, "", f"# {title}", ""] def _write_text(path: Path, content: str) -> dict[str, Any]: path.parent.mkdir(parents=True, exist_ok=True) old = path.read_text(encoding="utf-8") if path.exists() else None changed = old != content if changed: path.write_text(content, encoding="utf-8") return {"path": str(path), "changed": changed} def _write_yaml(path: Path, data: dict[str, Any]) -> dict[str, Any]: path.parent.mkdir(parents=True, exist_ok=True) content = yaml.safe_dump(data, sort_keys=False) return _write_text(path, content) def _result(kind: str, files: list[dict[str, Any]]) -> dict[str, Any]: return { "ok": True, "kind": kind, "count": len(files), "changed": [item for item in files if item["changed"]], "files": files, } def _frontmatter(path: Path) -> dict[str, Any]: text = path.read_text(encoding="utf-8") if not text.startswith("---\n"): return {} end = text.find("\n---\n", 4) if end == -1: return {} data = yaml.safe_load(text[4:end]) or {} return data if isinstance(data, dict) else {} def _normalize_concept(value: str) -> str: return "-".join(value.lower().replace("_", "-").split()) def _is_generated(path: Path) -> bool: try: return path.read_text(encoding="utf-8").startswith(GENERATED_NOTICE) except FileNotFoundError: return False