generated from coulomb/repo-seed
Add retrieval agent briefs and interface cards
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -8,6 +9,50 @@ import yaml
|
||||
|
||||
|
||||
GENERATED_NOTICE = "<!-- GENERATED by info_tech_canon; do not edit by hand. -->"
|
||||
RETRIEVAL_ARTIFACT_KINDS = {"kernel", "model", "standard", "profile"}
|
||||
CONSUMER_BRIEF_IDS = ("user-engine", "railiance-fabric", "repo-scoping")
|
||||
COMMON_DISTINCTIONS = [
|
||||
{
|
||||
"id": "actor-subject-principal",
|
||||
"title": "Actor vs Subject vs Principal",
|
||||
"summary": "Use actor for the acting entity in a context, subject for the entity a policy evaluates, and principal for the authenticated identity bound to access decisions.",
|
||||
"source_artifacts": [
|
||||
"model/organization",
|
||||
"model/access-control",
|
||||
"standard/caring",
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "organization-role-access-role-caring-role",
|
||||
"title": "Organization Role vs AccessRole vs CARING role",
|
||||
"summary": "Organization roles describe responsibility or position; access roles describe permissions; CARING roles classify access-governance needs and analysis.",
|
||||
"source_artifacts": [
|
||||
"model/organization",
|
||||
"model/access-control",
|
||||
"standard/caring",
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "policy-control-evidence",
|
||||
"title": "Policy vs Control vs Evidence",
|
||||
"summary": "Policy states intent or rule, control implements or enforces that rule, and evidence records why the claim should be trusted.",
|
||||
"source_artifacts": [
|
||||
"model/governance",
|
||||
"model/security",
|
||||
"model/observability",
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "intent-scope-purpose",
|
||||
"title": "Intent vs Scope vs Purpose",
|
||||
"summary": "Intent captures why an actor wants something, scope bounds what is included, and purpose captures consumer demand or use case pressure on the repo.",
|
||||
"source_artifacts": [
|
||||
"kernel/itc-core",
|
||||
"model/governance",
|
||||
"profile/small-saas",
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def generate_indexes(context: Any) -> dict[str, Any]:
|
||||
@@ -86,12 +131,48 @@ def generate_tree(context: Any) -> dict[str, Any]:
|
||||
|
||||
|
||||
def generate_agent_briefs(context: Any) -> dict[str, Any]:
|
||||
retrieval = retrieval_index(context)
|
||||
files = [
|
||||
_write_text(
|
||||
context.infospace_root / "agent" / "global-agent-brief.md",
|
||||
_render_global_agent_brief(context),
|
||||
)
|
||||
_render_global_agent_brief(context, retrieval),
|
||||
),
|
||||
_write_text(
|
||||
context.infospace_root / "agent" / "retrieval-index.md",
|
||||
_render_retrieval_index_markdown(retrieval),
|
||||
),
|
||||
_write_yaml(
|
||||
context.infospace_root / "agent" / "retrieval-index.yaml",
|
||||
retrieval,
|
||||
),
|
||||
_write_json(
|
||||
context.infospace_root / "agent" / "retrieval-index.json",
|
||||
retrieval,
|
||||
),
|
||||
_write_yaml(
|
||||
context.infospace_root / "agent" / "templates" / "canon-interface-card.template.yaml",
|
||||
interface_card_template(),
|
||||
),
|
||||
_write_text(
|
||||
context.infospace_root / "agent" / "templates" / "consumer-brief.template.md",
|
||||
_render_consumer_brief_template(),
|
||||
),
|
||||
]
|
||||
for artifact in sorted(context.infospace.artifacts, key=lambda item: item.id):
|
||||
if artifact.kind in RETRIEVAL_ARTIFACT_KINDS:
|
||||
files.append(
|
||||
_write_text(
|
||||
context.infospace_root / "agent" / "briefs" / f"{_safe_id(artifact.id)}.md",
|
||||
_render_artifact_agent_brief(context, artifact, retrieval),
|
||||
)
|
||||
)
|
||||
for consumer_id in CONSUMER_BRIEF_IDS:
|
||||
files.append(
|
||||
_write_text(
|
||||
context.infospace_root / "agent" / "consumer-briefs" / f"{consumer_id}.md",
|
||||
_render_consumer_brief(consumer_id),
|
||||
)
|
||||
)
|
||||
return _result("agent-briefs", files)
|
||||
|
||||
|
||||
@@ -211,6 +292,61 @@ def artifact_tree(context: Any) -> dict[str, Any]:
|
||||
return {"root": "infospace", "file_count": len(files), "files": files}
|
||||
|
||||
|
||||
def retrieval_index(context: Any) -> dict[str, Any]:
|
||||
ownership = concept_ownership(context)
|
||||
concepts_by_owner: dict[str, list[str]] = defaultdict(list)
|
||||
for concept in ownership["concepts"]:
|
||||
concepts_by_owner[str(concept["owner"])].append(str(concept["concept"]))
|
||||
|
||||
items = []
|
||||
for artifact in sorted(context.infospace.artifacts, key=lambda item: item.id):
|
||||
relationships = [
|
||||
{
|
||||
"type": str(relationship.get("type") or "related"),
|
||||
"target": str(relationship.get("target") or ""),
|
||||
}
|
||||
for relationship in artifact.relationships
|
||||
]
|
||||
imports = [
|
||||
item["target"]
|
||||
for item in relationships
|
||||
if item["type"] in {"imports", "requires", "uses", "conforms_to"}
|
||||
]
|
||||
warnings = []
|
||||
source_path = str(artifact.provenance.get("source_path") or artifact.path)
|
||||
if not (context.repo_root / source_path).is_file() and not (
|
||||
context.infospace_root / source_path
|
||||
).is_file():
|
||||
warnings.append(
|
||||
{
|
||||
"code": "source_path_not_file",
|
||||
"source_path": source_path,
|
||||
}
|
||||
)
|
||||
items.append(
|
||||
{
|
||||
"id": artifact.id,
|
||||
"kind": artifact.kind,
|
||||
"title": artifact.title,
|
||||
"canonical_path": artifact.path,
|
||||
"source_path": source_path,
|
||||
"summary": _summary_for_artifact(artifact),
|
||||
"owned_concepts": sorted(set(concepts_by_owner.get(artifact.id, []))),
|
||||
"imports": sorted(set(imports)),
|
||||
"relationships": relationships,
|
||||
"warnings": warnings,
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"schema": "info-tech-canon.retrieval-index.v1",
|
||||
"infospace": context.infospace.config.slug,
|
||||
"item_count": len(items),
|
||||
"items": items,
|
||||
"common_distinctions": COMMON_DISTINCTIONS,
|
||||
}
|
||||
|
||||
|
||||
def _render_by_standard(context: Any) -> str:
|
||||
lines = _heading("By Standard")
|
||||
standards = [
|
||||
@@ -348,7 +484,7 @@ def _render_repository_tree(tree: dict[str, Any]) -> str:
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def _render_global_agent_brief(context: Any) -> str:
|
||||
def _render_global_agent_brief(context: Any, retrieval: dict[str, Any]) -> str:
|
||||
lines = _heading("Global Agent Brief")
|
||||
lines.extend(
|
||||
[
|
||||
@@ -356,8 +492,10 @@ def _render_global_agent_brief(context: Any) -> str:
|
||||
"",
|
||||
f"- Infospace slug: `{context.infospace.config.slug}`",
|
||||
f"- Artifact count: {len(context.infospace.artifacts)}",
|
||||
f"- Retrieval index items: {retrieval['item_count']}",
|
||||
"- Primary confidence command: `make validate`",
|
||||
"- Refresh generated indexes and views with: `make index`",
|
||||
"- Refresh agent briefs and interface templates with: `make agent-briefs`",
|
||||
"",
|
||||
"## Useful Commands",
|
||||
"",
|
||||
@@ -365,6 +503,24 @@ def _render_global_agent_brief(context: Any) -> str:
|
||||
"- `PYTHONPATH=src python3 -m info_tech_canon validate`",
|
||||
"- `PYTHONPATH=src python3 -m info_tech_canon graph`",
|
||||
"- `PYTHONPATH=src python3 -m info_tech_canon index`",
|
||||
"- `PYTHONPATH=src python3 -m info_tech_canon profile validate small-saas`",
|
||||
"",
|
||||
"## Retrieval Entry Points",
|
||||
"",
|
||||
"- `agent/retrieval-index.md`",
|
||||
"- `agent/retrieval-index.yaml`",
|
||||
"- `agent/retrieval-index.json`",
|
||||
"- `agent/briefs/` for per-artifact briefs",
|
||||
"- `agent/templates/canon-interface-card.template.yaml`",
|
||||
"",
|
||||
"## Common Distinctions",
|
||||
"",
|
||||
]
|
||||
)
|
||||
for distinction in retrieval["common_distinctions"]:
|
||||
lines.append(f"- **{distinction['title']}**: {distinction['summary']}")
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"## Consumption Notes",
|
||||
"",
|
||||
@@ -376,6 +532,205 @@ def _render_global_agent_brief(context: Any) -> str:
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def _render_retrieval_index_markdown(retrieval: dict[str, Any]) -> str:
|
||||
lines = _heading("Retrieval Index")
|
||||
lines.extend(
|
||||
[
|
||||
f"Schema: `{retrieval['schema']}`",
|
||||
f"Infospace: `{retrieval['infospace']}`",
|
||||
f"Items: **{retrieval['item_count']}**",
|
||||
"",
|
||||
"## Common Distinctions",
|
||||
"",
|
||||
]
|
||||
)
|
||||
for distinction in retrieval["common_distinctions"]:
|
||||
sources = ", ".join(f"`{item}`" for item in distinction["source_artifacts"])
|
||||
lines.append(f"- **{distinction['title']}**: {distinction['summary']} Sources: {sources}")
|
||||
lines.extend(["", "## Items", ""])
|
||||
for item in retrieval["items"]:
|
||||
imports = ", ".join(f"`{target}`" for target in item["imports"]) or "none"
|
||||
concepts = ", ".join(f"`{concept}`" for concept in item["owned_concepts"]) or "none"
|
||||
lines.extend(
|
||||
[
|
||||
f"### {item['title']}",
|
||||
"",
|
||||
f"- ID: `{item['id']}`",
|
||||
f"- Kind: `{item['kind']}`",
|
||||
f"- Canonical path: `{item['canonical_path']}`",
|
||||
f"- Source path: `{item['source_path']}`",
|
||||
f"- Summary: {item['summary']}",
|
||||
f"- Imports and anchors: {imports}",
|
||||
f"- Owned concepts: {concepts}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def _render_artifact_agent_brief(
|
||||
context: Any,
|
||||
artifact: Any,
|
||||
retrieval: dict[str, Any],
|
||||
) -> str:
|
||||
item = next(entry for entry in retrieval["items"] if entry["id"] == artifact.id)
|
||||
frontmatter = {
|
||||
"id": f"agent-brief/{_safe_id(artifact.id)}",
|
||||
"artifact_id": artifact.id,
|
||||
"source_path": artifact.path,
|
||||
"source_kind": artifact.kind,
|
||||
"generated": True,
|
||||
}
|
||||
lines = [
|
||||
"---",
|
||||
yaml.safe_dump(frontmatter, sort_keys=False).strip(),
|
||||
"---",
|
||||
"",
|
||||
GENERATED_NOTICE,
|
||||
"",
|
||||
f"# Agent Brief: {artifact.title}",
|
||||
"",
|
||||
f"- Artifact ID: `{artifact.id}`",
|
||||
f"- Kind: `{artifact.kind}`",
|
||||
f"- Canonical path: `{artifact.path}`",
|
||||
f"- Full source: `{artifact.path}`",
|
||||
f"- Summary: {item['summary']}",
|
||||
"",
|
||||
"## Retrieval Hints",
|
||||
"",
|
||||
]
|
||||
if item["imports"]:
|
||||
lines.append("Imports and anchors:")
|
||||
lines.extend(f"- `{target}`" for target in item["imports"])
|
||||
else:
|
||||
lines.append("No imports or anchors recorded.")
|
||||
lines.extend(["", "## Owned Concepts", ""])
|
||||
if item["owned_concepts"]:
|
||||
lines.extend(f"- `{concept}`" for concept in item["owned_concepts"])
|
||||
else:
|
||||
lines.append("No owned concepts recorded yet.")
|
||||
lines.extend(["", "## Related Distinctions", ""])
|
||||
related = [
|
||||
distinction
|
||||
for distinction in retrieval["common_distinctions"]
|
||||
if artifact.id in distinction["source_artifacts"]
|
||||
]
|
||||
if related:
|
||||
for distinction in related:
|
||||
lines.append(f"- **{distinction['title']}**: {distinction['summary']}")
|
||||
else:
|
||||
lines.append("No common distinction is anchored directly on this artifact.")
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def interface_card_template() -> dict[str, Any]:
|
||||
return {
|
||||
"schema": "info-tech-canon.interface-card.v1",
|
||||
"id": "consumer-repo/interface-card",
|
||||
"title": "Consumer Repo Canon Interface Card",
|
||||
"consumer": {
|
||||
"repo": "",
|
||||
"domain": "",
|
||||
"owner": "",
|
||||
"intent": "",
|
||||
"scope": "",
|
||||
"purposes": [],
|
||||
},
|
||||
"canon_surfaces": {
|
||||
"implemented_profiles": [],
|
||||
"consumed_artifacts": [],
|
||||
"owned_concepts": [],
|
||||
"produced_concepts": [],
|
||||
"consumed_concepts": [],
|
||||
"mappings": [],
|
||||
},
|
||||
"validation_expectations": {
|
||||
"commands": [],
|
||||
"evidence_required": [],
|
||||
"known_gaps": [],
|
||||
},
|
||||
"consumer_needs": {
|
||||
"current": [],
|
||||
"requested_extensions": [],
|
||||
"feedback": [],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _render_consumer_brief_template() -> str:
|
||||
lines = [
|
||||
"---",
|
||||
"id: consumer-brief/template",
|
||||
"consumer: TBD",
|
||||
"generated: true",
|
||||
"---",
|
||||
"",
|
||||
GENERATED_NOTICE,
|
||||
"",
|
||||
"# Consumer Brief Template",
|
||||
"",
|
||||
"## Consumer Intent",
|
||||
"",
|
||||
"- Intent:",
|
||||
"- Scope:",
|
||||
"- Purposes:",
|
||||
"",
|
||||
"## Canon Surfaces",
|
||||
"",
|
||||
"- Implemented profiles:",
|
||||
"- Consumed standards:",
|
||||
"- Produced concepts:",
|
||||
"- Consumed concepts:",
|
||||
"",
|
||||
"## Validation Expectations",
|
||||
"",
|
||||
"- Commands:",
|
||||
"- Evidence:",
|
||||
"- Known gaps:",
|
||||
]
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def _render_consumer_brief(consumer_id: str) -> str:
|
||||
titles = {
|
||||
"user-engine": "User Engine Canon Consumer Brief",
|
||||
"railiance-fabric": "Railiance Fabric Canon Consumer Brief",
|
||||
"repo-scoping": "Repo Scoping Canon Consumer Brief",
|
||||
}
|
||||
purposes = {
|
||||
"user-engine": "Evaluate user-management concepts, roles, access traces, profile claims, and governance evidence against the canon before integration.",
|
||||
"railiance-fabric": "Use the canon to make captured entities and edges cleaner for conformance and visualization.",
|
||||
"repo-scoping": "Compare repo-scoping concepts with canon INTENT, SCOPE, PURPOSES, and interface-card expectations.",
|
||||
}
|
||||
lines = [
|
||||
"---",
|
||||
f"id: consumer-brief/{consumer_id}",
|
||||
f"consumer: {consumer_id}",
|
||||
"generated: true",
|
||||
"---",
|
||||
"",
|
||||
GENERATED_NOTICE,
|
||||
"",
|
||||
f"# {titles[consumer_id]}",
|
||||
"",
|
||||
"## Purpose",
|
||||
"",
|
||||
purposes[consumer_id],
|
||||
"",
|
||||
"## Starting Points",
|
||||
"",
|
||||
"- `agent/retrieval-index.md`",
|
||||
"- `agent/templates/canon-interface-card.template.yaml`",
|
||||
"- `profiles/small-saas/profile.yaml`",
|
||||
"- `views/by-concept.md`",
|
||||
"",
|
||||
"## Workplan Boundary",
|
||||
"",
|
||||
"Adoption and repo-specific implementation workplans belong in the consumer repository.",
|
||||
]
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def _heading(title: str) -> list[str]:
|
||||
return [GENERATED_NOTICE, "", f"# {title}", ""]
|
||||
|
||||
@@ -395,6 +750,12 @@ def _write_yaml(path: Path, data: dict[str, Any]) -> dict[str, Any]:
|
||||
return _write_text(path, content)
|
||||
|
||||
|
||||
def _write_json(path: Path, data: dict[str, Any]) -> dict[str, Any]:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
content = json.dumps(data, indent=2, sort_keys=True) + "\n"
|
||||
return _write_text(path, content)
|
||||
|
||||
|
||||
def _result(kind: str, files: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
return {
|
||||
"ok": True,
|
||||
@@ -420,6 +781,24 @@ def _normalize_concept(value: str) -> str:
|
||||
return "-".join(value.lower().replace("_", "-").split())
|
||||
|
||||
|
||||
def _safe_id(value: str) -> str:
|
||||
return value.replace("/", "-").replace("_", "-")
|
||||
|
||||
|
||||
def _summary_for_artifact(artifact: Any) -> str:
|
||||
if artifact.kind == "profile-artifact":
|
||||
return f"Example artifact for the {artifact.provenance.get('profile', 'unknown')} profile: {artifact.title}."
|
||||
if artifact.kind == "profile":
|
||||
return f"Profile that constrains canon artifacts for a practical implementation slice: {artifact.title}."
|
||||
if artifact.kind == "kernel":
|
||||
return f"Kernel artifact that defines canon structure or integration: {artifact.title}."
|
||||
if artifact.kind == "model":
|
||||
return f"Domain model used by canon profiles and standards: {artifact.title}."
|
||||
if artifact.kind == "standard":
|
||||
return f"Cross-cutting canon standard: {artifact.title}."
|
||||
return f"Canon artifact: {artifact.title}."
|
||||
|
||||
|
||||
def _is_generated(path: Path) -> bool:
|
||||
try:
|
||||
return path.read_text(encoding="utf-8").startswith(GENERATED_NOTICE)
|
||||
|
||||
Reference in New Issue
Block a user