Add MCP server functions for repair commands (bd-7bbc4e6a)

- Add repair_deps(fix=False) for orphaned dependencies
- Add detect_pollution(clean=False) for test issue detection
- Add validate(checks=None, fix_all=False) for health checks
- Implemented in BdCliClient, stubs in BdDaemonClient
- Registered as MCP tools in server.py

Amp-Thread-ID: https://ampcode.com/threads/T-9ce04c75-201b-4019-b9f1-0cf10663557c
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Steve Yegge
2025-11-07 19:40:04 -08:00
parent 73e73aecf9
commit 620d96f142
4 changed files with 241 additions and 0 deletions

View File

@@ -139,6 +139,43 @@ class BdClientBase(ABC):
"""Get current database schema for inspection."""
pass
@abstractmethod
async def repair_deps(self, fix: bool = False) -> dict:
"""Find and optionally fix orphaned dependency references.
Args:
fix: If True, automatically remove orphaned dependencies
Returns:
Dict with orphans_found, orphans list, and fixed count if fix=True
"""
pass
@abstractmethod
async def detect_pollution(self, clean: bool = False) -> dict:
"""Detect test issues that leaked into production database.
Args:
clean: If True, delete detected test issues
Returns:
Dict with detected test issues and deleted count if clean=True
"""
pass
@abstractmethod
async def validate(self, checks: str | None = None, fix_all: bool = False) -> dict:
"""Run database validation checks.
Args:
checks: Comma-separated list of checks (orphans,duplicates,pollution,conflicts)
fix_all: If True, auto-fix all fixable issues
Returns:
Dict with validation results for each check
"""
pass
class BdCliClient(BdClientBase):
"""Client for calling bd CLI commands and parsing JSON output."""
@@ -623,6 +660,63 @@ class BdCliClient(BdClientBase):
raise BdCommandError("Invalid response for get_schema_info")
return data
async def repair_deps(self, fix: bool = False) -> dict:
"""Find and optionally fix orphaned dependency references.
Args:
fix: If True, automatically remove orphaned dependencies
Returns:
Dict with orphans_found, orphans list, and fixed count if fix=True
"""
args = ["repair-deps"]
if fix:
args.append("--fix")
data = await self._run_command(*args)
if not isinstance(data, dict):
raise BdCommandError("Invalid response for repair-deps")
return data
async def detect_pollution(self, clean: bool = False) -> dict:
"""Detect test issues that leaked into production database.
Args:
clean: If True, delete detected test issues
Returns:
Dict with detected test issues and deleted count if clean=True
"""
args = ["detect-pollution"]
if clean:
args.extend(["--clean", "--yes"])
data = await self._run_command(*args)
if not isinstance(data, dict):
raise BdCommandError("Invalid response for detect-pollution")
return data
async def validate(self, checks: str | None = None, fix_all: bool = False) -> dict:
"""Run database validation checks.
Args:
checks: Comma-separated list of checks (orphans,duplicates,pollution,conflicts)
fix_all: If True, auto-fix all fixable issues
Returns:
Dict with validation results for each check
"""
args = ["validate"]
if checks:
args.extend(["--checks", checks])
if fix_all:
args.append("--fix-all")
data = await self._run_command(*args)
if not isinstance(data, dict):
raise BdCommandError("Invalid response for validate")
return data
async def init(self, params: InitParams | None = None) -> str:
"""Initialize bd in current directory.

View File

@@ -452,6 +452,49 @@ class BdDaemonClient(BdClientBase):
"""
raise NotImplementedError("get_schema_info not supported via daemon - use CLI client")
async def repair_deps(self, fix: bool = False) -> dict:
"""Find and optionally fix orphaned dependency references.
Args:
fix: If True, automatically remove orphaned dependencies
Returns:
Dict with orphans_found, orphans list, and fixed count if fix=True
Note:
This falls back to CLI since repair operations are rare
"""
raise NotImplementedError("repair_deps not supported via daemon - use CLI client")
async def detect_pollution(self, clean: bool = False) -> dict:
"""Detect test issues that leaked into production database.
Args:
clean: If True, delete detected test issues
Returns:
Dict with detected test issues and deleted count if clean=True
Note:
This falls back to CLI since pollution detection is a rare operation
"""
raise NotImplementedError("detect_pollution not supported via daemon - use CLI client")
async def validate(self, checks: str | None = None, fix_all: bool = False) -> dict:
"""Run database validation checks.
Args:
checks: Comma-separated list of checks (orphans,duplicates,pollution,conflicts)
fix_all: If True, auto-fix all fixable issues
Returns:
Dict with validation results for each check
Note:
This falls back to CLI since validation is a rare operation
"""
raise NotImplementedError("validate not supported via daemon - use CLI client")
async def add_dependency(self, params: AddDependencyParams) -> None:
"""Add a dependency between issues.

View File

@@ -19,16 +19,19 @@ from beads_mcp.tools import (
beads_blocked,
beads_close_issue,
beads_create_issue,
beads_detect_pollution,
beads_get_schema_info,
beads_init,
beads_inspect_migration,
beads_list_issues,
beads_quickstart,
beads_ready_work,
beads_repair_deps,
beads_reopen_issue,
beads_show_issue,
beads_stats,
beads_update_issue,
beads_validate,
current_workspace, # ContextVar for per-request workspace routing
)
@@ -602,6 +605,53 @@ async def get_schema_info(workspace_root: str | None = None) -> dict:
return await beads_get_schema_info()
@mcp.tool(
name="repair_deps",
description="Find and optionally fix orphaned dependency references.",
)
@with_workspace
async def repair_deps(fix: bool = False, workspace_root: str | None = None) -> dict:
"""Find and optionally fix orphaned dependency references.
Scans all issues for dependencies pointing to non-existent issues.
Returns orphaned dependencies and optionally removes them with fix=True.
"""
return await beads_repair_deps(fix=fix)
@mcp.tool(
name="detect_pollution",
description="Detect test issues that leaked into production database.",
)
@with_workspace
async def detect_pollution(clean: bool = False, workspace_root: str | None = None) -> dict:
"""Detect test issues that leaked into production database.
Detects test issues using pattern matching (titles starting with 'test', etc.).
Returns detected test issues and optionally deletes them with clean=True.
"""
return await beads_detect_pollution(clean=clean)
@mcp.tool(
name="validate",
description="Run comprehensive database health checks.",
)
@with_workspace
async def validate(
checks: str | None = None,
fix_all: bool = False,
workspace_root: str | None = None,
) -> dict:
"""Run comprehensive database health checks.
Available checks: orphans, duplicates, pollution, conflicts.
If checks is None, runs all checks.
Returns validation results for each check.
"""
return await beads_validate(checks=checks, fix_all=fix_all)
async def async_main() -> None:
"""Async entry point for the MCP server."""
await mcp.run_async(transport="stdio")

View File

@@ -484,6 +484,60 @@ async def beads_get_schema_info() -> dict:
return await client.get_schema_info()
async def beads_repair_deps(
fix: Annotated[bool, "If True, automatically remove orphaned dependencies"] = False,
) -> dict:
"""Find and optionally fix orphaned dependency references.
Scans all issues for dependencies pointing to non-existent issues.
Returns orphaned dependencies and optionally removes them with fix=True.
Returns dict with:
- orphans_found: number of orphaned dependencies
- orphans: list of orphaned dependency details
- fixed: number of orphans fixed (if fix=True)
"""
client = await _get_client()
return await client.repair_deps(fix=fix)
async def beads_detect_pollution(
clean: Annotated[bool, "If True, delete detected test issues"] = False,
) -> dict:
"""Detect test issues that leaked into production database.
Detects test issues using pattern matching:
- Titles starting with 'test', 'benchmark', 'sample', 'tmp', 'temp'
- Sequential numbering (test-1, test-2, ...)
- Generic descriptions or no description
- Created in rapid succession
Returns dict with detected test issues and deleted count if clean=True.
"""
client = await _get_client()
return await client.detect_pollution(clean=clean)
async def beads_validate(
checks: Annotated[str | None, "Comma-separated list of checks (orphans,duplicates,pollution,conflicts)"] = None,
fix_all: Annotated[bool, "If True, auto-fix all fixable issues"] = False,
) -> dict:
"""Run comprehensive database health checks.
Available checks:
- orphans: Orphaned dependencies (references to deleted issues)
- duplicates: Duplicate issues (identical content)
- pollution: Test pollution (leaked test issues)
- conflicts: Git merge conflicts in JSONL
If checks is None, runs all checks.
Returns dict with validation results for each check.
"""
client = await _get_client()
return await client.validate(checks=checks, fix_all=fix_all)
async def beads_init(
prefix: Annotated[str | None, "Issue prefix (e.g., 'myproject' for myproject-1, myproject-2)"] = None,
) -> str: