feat(CUST-WP-0016): kaizen-agentic integration — MCP tools, templates, direct install

- Fix /domains/{slug}/ 500: EP/TD queries now use domain_id FK (not string column)
- Remove dead cascade-slug code in rename_domain (FK handles it)
- MCP: list_kaizen_agents(category?) + get_kaizen_agent(name) via resolve_repo_path()
- TOOLS.md: Kaizen Agents section with discovery/load pattern
- agents.template: new project rule for consumer repos
- claude-md.template + register_project.sh: include agents.md in new-project scaffolding
- agents/: direct install of 6 curated agents for hub sessions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-17 22:24:30 +01:00
parent 196e6c5aed
commit 8619cd2218
6 changed files with 132 additions and 17 deletions

View File

@@ -73,16 +73,16 @@ async def get_domain(
)
ws_count = ws_count_row.scalar_one()
# Count EPs and TDs (domain is a string column there)
# Count EPs and TDs
ep_count_row = await session.execute(
select(func.count()).select_from(ExtensionPoint)
.where(ExtensionPoint.domain == slug)
.where(ExtensionPoint.domain_id == domain.id)
)
ep_count = ep_count_row.scalar_one()
td_count_row = await session.execute(
select(func.count()).select_from(TechnicalDebt)
.where(TechnicalDebt.domain == slug)
.where(TechnicalDebt.domain_id == domain.id)
)
td_count = td_count_row.scalar_one()
@@ -127,19 +127,6 @@ async def rename_domain(
domain.slug = body.new_slug
domain.name = body.new_name
# Cascade slug rename to EP/TD string columns
if old_slug != body.new_slug:
await session.execute(
ExtensionPoint.__table__.update()
.where(ExtensionPoint.domain == old_slug)
.values(domain=body.new_slug)
)
await session.execute(
TechnicalDebt.__table__.update()
.where(TechnicalDebt.domain == old_slug)
.values(domain=body.new_slug)
)
await session.commit()
await session.refresh(domain)
return domain

View File

@@ -127,6 +127,30 @@ Dashboard: `http://localhost:3000/inbox`
---
## Kaizen Agents
Specialized agent personas from `kaizen-agentic/agents/`. Each agent is a markdown
instruction set — load it and follow the instructions it contains.
| Tool | Key Args | When to use |
|------|----------|-------------|
| `list_kaizen_agents(category?)` | `category`: optional filter (testing/quality/process/infrastructure) | Discover all 17 available agent personas with name, description, category. |
| `get_kaizen_agent(name)` | `name`: e.g. `"tdd-workflow"`, `"code-refactoring"` | Load full agent instructions. Read and follow them. |
**Common agents:**
| Agent | Category | When to use |
|-------|----------|-------------|
| `tdd-workflow` | testing | Step-by-step TDD8 workflow for any feature |
| `code-refactoring` | quality | Code quality analysis and safe refactoring |
| `test-maintenance` | testing | Diagnose and fix failing tests |
| `requirements-engineering` | process | Prevent interface/mock mismatches upfront |
| `keepaTodofile` | process | Maintain TODO.md during work |
| `project-management` | process | Track status, determine next steps |
| `datamodel-optimization` | quality | Optimize dataclasses and data structures |
---
## Domain Slugs
Run `list_domains()` to get the live list. Default 6: `custodian` · `railiance` · `markitect` · `coulomb_social` · `personhood` · `foerster_capabilities`

View File

@@ -992,6 +992,89 @@ def update_repo_path(repo_slug: str, path: str, host: str | None = None) -> str:
return json.dumps(repo, indent=2)
# ---------------------------------------------------------------------------
# Kaizen Agents
# ---------------------------------------------------------------------------
def _kaizen_agents_dir() -> Path:
"""Resolve the kaizen-agentic agents/ directory via host_paths → local_path fallback."""
import socket as _socket
repo = _get("/repos/kaizen-agentic")
hostname = _socket.gethostname()
host_paths = repo.get("host_paths") or {}
base = host_paths.get(hostname) or repo.get("local_path") or ""
if not base:
raise FileNotFoundError("kaizen-agentic path not found for this host. Register it with update_repo_path().")
agents_dir = Path(base) / "agents"
if not agents_dir.is_dir():
raise FileNotFoundError(f"agents/ directory not found at {agents_dir}")
return agents_dir
@mcp.tool()
def list_kaizen_agents(category: str | None = None) -> str:
"""List all available kaizen agent personas.
Reads agent metadata from kaizen-agentic/agents/agent-*.md frontmatter.
Each agent is a specialized instruction set Claude can load and follow.
Args:
category: Optional filter (e.g. 'testing', 'quality', 'process', 'infrastructure').
Returns all agents when omitted.
Returns:
JSON list of {name, description, category, file} objects.
"""
import re as _re
agents_dir = _kaizen_agents_dir()
result = []
for f in sorted(agents_dir.glob("agent-*.md")):
name = f.stem.removeprefix("agent-")
text = f.read_text(encoding="utf-8")
# Extract optional YAML frontmatter fields
fm_match = _re.match(r"^---\n(.*?\n)---\n", text, _re.DOTALL)
meta: dict = {}
if fm_match:
for line in fm_match.group(1).splitlines():
if ":" in line:
k, _, v = line.partition(":")
meta[k.strip()] = v.strip()
agent_category = meta.get("category", "")
if category and agent_category.lower() != category.lower():
continue
# Fall back to first non-empty line after frontmatter as description
desc = meta.get("description", "")
if not desc:
for line in text.split("\n"):
line = line.strip()
if line and not line.startswith("#") and not line.startswith("---"):
desc = line[:120]
break
result.append({"name": name, "description": desc, "category": agent_category, "file": f.name})
return json.dumps(result, indent=2)
@mcp.tool()
def get_kaizen_agent(name: str) -> str:
"""Load the full instructions for a kaizen agent persona.
Read the returned markdown and follow the instructions it contains.
Use list_kaizen_agents() to discover available agent names.
Args:
name: Agent name without 'agent-' prefix (e.g. 'tdd-workflow', 'code-refactoring').
Returns:
Full markdown content of the agent definition file.
"""
agents_dir = _kaizen_agents_dir()
agent_file = agents_dir / f"agent-{name}.md"
if not agent_file.exists():
available = [f.stem.removeprefix("agent-") for f in sorted(agents_dir.glob("agent-*.md"))]
return json.dumps({"error": f"Agent '{name}' not found.", "available": available})
return agent_file.read_text(encoding="utf-8")
# ---------------------------------------------------------------------------
# ADR-001 compliance validation
# ---------------------------------------------------------------------------

View File

@@ -0,0 +1,20 @@
## Kaizen Agents
Specialized agent personas available on demand via the state-hub MCP.
**Discover:** `list_kaizen_agents()` — returns all agents with name, description, category
**Load:** `get_kaizen_agent("tdd-workflow")` — returns full instructions; read and follow them
Common agents:
| Agent | Category | When to use |
|-------|----------|-------------|
| `tdd-workflow` | testing | Step-by-step TDD8 workflow for any feature |
| `code-refactoring` | quality | Code quality analysis and safe refactoring |
| `test-maintenance` | testing | Diagnose and fix failing tests |
| `requirements-engineering` | process | Prevent interface/mock mismatches upfront |
| `keepaTodofile` | process | Maintain TODO.md during work |
| `project-management` | process | Track status, determine next steps |
| `datamodel-optimization` | quality | Optimize dataclasses and data structures |
All 17 agents: call `list_kaizen_agents()` for the full list.

View File

@@ -7,3 +7,4 @@
@.claude/rules/stack-and-commands.md
@.claude/rules/architecture.md
@.claude/rules/repo-boundary.md
@.claude/rules/agents.md

View File

@@ -131,7 +131,7 @@ if [[ "$ADDITIONAL" != "--additional" ]]; then
mkdir -p "$RULES_DIR"
for rule in repo-identity session-protocol first-session workplan-convention \
stack-and-commands architecture repo-boundary; do
stack-and-commands architecture repo-boundary agents; do
tmpl="$RULES_TEMPLATES_DIR/${rule}.template"
out="$RULES_DIR/${rule}.md"
if [[ -f "$tmpl" ]]; then