feat(capability-registry): CUST-WP-0031 domain capability registry

- Migration p3k4l5m6n7o8: nullable repo_id FK on capability_catalog
- PATCH /capability-catalog/{id} endpoint for back-filling repo attribution
- register_capability MCP tool accepts optional repo_slug
- get_domain_summary now includes compact capabilities list (type+title+repo_slug)
- New get_capability_profile MCP tool: domain → repos → capabilities tree
- 6 repo descriptions populated; 25 catalog entries attributed to repos
- 9 new capabilities registered for personhood, foerster_capabilities, coulomb_social
- TOOLS.md: Capability Catalog & Requests section with full tool reference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-31 17:23:45 +02:00
parent 907e99e057
commit 09bbf62430
6 changed files with 220 additions and 1 deletions

View File

@@ -258,6 +258,18 @@ def get_domain_summary(domain_slug: str) -> str:
}
if goal_guidance:
result["goal_guidance"] = goal_guidance
# Compact capabilities list (type + title + repo_slug only, capped at 20)
caps_raw = _get("/capability-catalog/", {"domain": domain_slug, "status": "active"})
if isinstance(caps_raw, list):
compact_caps = [
{"type": c["capability_type"], "title": c["title"], "repo_slug": c.get("repo_slug")}
for c in caps_raw[:20]
]
result["capabilities"] = compact_caps
if len(caps_raw) > 20:
result["capabilities_truncated"] = True
return json.dumps(result, indent=2)
@@ -1818,6 +1830,7 @@ def register_capability(
title: str,
description: str | None = None,
keywords: list[str] | None = None,
repo_slug: str | None = None,
) -> str:
"""Register a capability that a domain can provide. Used for routing requests.
@@ -1827,6 +1840,7 @@ def register_capability(
title: Short title for this capability
description: Longer description (optional)
keywords: List of keywords for routing (e.g. ['cluster', 'k8s', 'privacy'])
repo_slug: Optional repo slug to attribute this capability to a specific repo
"""
entry = _post("/capability-catalog", {
"domain": domain,
@@ -1834,6 +1848,7 @@ def register_capability(
"title": title,
"description": description,
"keywords": keywords or [],
"repo_slug": repo_slug,
})
return json.dumps(entry, indent=2)
@@ -1855,6 +1870,87 @@ def list_capabilities(
}), indent=2)
@mcp.tool()
def get_capability_profile(domain_slug: str | None = None) -> str:
"""Full capability registry: domain → repos (with description) → capabilities.
Designed for deep-dive or cross-domain architectural discussion.
Args:
domain_slug: If provided, return profile for that one domain only.
If omitted, return profiles for all active domains.
Returns a structured dict with repos nested under each domain, and
capabilities nested under each repo. Domain-level capabilities
(no repo assigned) appear under a synthetic entry with slug=null.
"""
if domain_slug:
domain_slugs = [domain_slug]
else:
domains_raw = _get("/domains/")
if isinstance(domains_raw, dict) and "error" in domains_raw:
return json.dumps(domains_raw, indent=2)
domain_slugs = [d["slug"] for d in domains_raw if d.get("status") == "active"]
# Fetch topics once for title lookup
topics_raw = _get("/topics/")
profiles = []
for slug in domain_slugs:
repos_raw = _get("/repos/", {"domain": slug})
caps_raw = _get("/capability-catalog/", {"domain": slug, "status": "active"})
if isinstance(caps_raw, dict) and "error" in caps_raw:
caps_raw = []
# Index capabilities by repo_slug (None → domain-level)
caps_by_repo_slug: dict[str | None, list] = {}
for cap in caps_raw:
r_slug = cap.get("repo_slug")
caps_by_repo_slug.setdefault(r_slug, []).append({
"type": cap["capability_type"],
"title": cap["title"],
"description": cap.get("description"),
"keywords": cap.get("keywords", []),
})
repo_entries = []
if isinstance(repos_raw, list):
for repo in repos_raw:
repo_entries.append({
"slug": repo["slug"],
"name": repo["name"],
"description": repo.get("description"),
"capabilities": caps_by_repo_slug.get(repo["slug"], []),
})
# Domain-level caps (no repo assigned)
domain_level = caps_by_repo_slug.get(None, [])
if domain_level:
repo_entries.append({
"slug": None,
"name": "(domain-level)",
"description": None,
"capabilities": domain_level,
})
# Get topic title
topic_title = ""
if isinstance(topics_raw, list):
topic = next((t for t in topics_raw if t.get("domain_slug") == slug), None)
if topic:
topic_title = topic.get("title", "")
profiles.append({
"slug": slug,
"title": topic_title,
"repos": repo_entries,
})
if domain_slug and profiles:
return json.dumps(profiles[0], indent=2)
return json.dumps(profiles, indent=2)
@mcp.tool()
def request_capability(
title: str,