generated from coulomb/repo-seed
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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user