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:
@@ -139,6 +139,43 @@ class BdClientBase(ABC):
|
|||||||
"""Get current database schema for inspection."""
|
"""Get current database schema for inspection."""
|
||||||
pass
|
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):
|
class BdCliClient(BdClientBase):
|
||||||
"""Client for calling bd CLI commands and parsing JSON output."""
|
"""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")
|
raise BdCommandError("Invalid response for get_schema_info")
|
||||||
return data
|
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:
|
async def init(self, params: InitParams | None = None) -> str:
|
||||||
"""Initialize bd in current directory.
|
"""Initialize bd in current directory.
|
||||||
|
|
||||||
|
|||||||
@@ -452,6 +452,49 @@ class BdDaemonClient(BdClientBase):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError("get_schema_info not supported via daemon - use CLI client")
|
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:
|
async def add_dependency(self, params: AddDependencyParams) -> None:
|
||||||
"""Add a dependency between issues.
|
"""Add a dependency between issues.
|
||||||
|
|
||||||
|
|||||||
@@ -19,16 +19,19 @@ from beads_mcp.tools import (
|
|||||||
beads_blocked,
|
beads_blocked,
|
||||||
beads_close_issue,
|
beads_close_issue,
|
||||||
beads_create_issue,
|
beads_create_issue,
|
||||||
|
beads_detect_pollution,
|
||||||
beads_get_schema_info,
|
beads_get_schema_info,
|
||||||
beads_init,
|
beads_init,
|
||||||
beads_inspect_migration,
|
beads_inspect_migration,
|
||||||
beads_list_issues,
|
beads_list_issues,
|
||||||
beads_quickstart,
|
beads_quickstart,
|
||||||
beads_ready_work,
|
beads_ready_work,
|
||||||
|
beads_repair_deps,
|
||||||
beads_reopen_issue,
|
beads_reopen_issue,
|
||||||
beads_show_issue,
|
beads_show_issue,
|
||||||
beads_stats,
|
beads_stats,
|
||||||
beads_update_issue,
|
beads_update_issue,
|
||||||
|
beads_validate,
|
||||||
current_workspace, # ContextVar for per-request workspace routing
|
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()
|
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 def async_main() -> None:
|
||||||
"""Async entry point for the MCP server."""
|
"""Async entry point for the MCP server."""
|
||||||
await mcp.run_async(transport="stdio")
|
await mcp.run_async(transport="stdio")
|
||||||
|
|||||||
@@ -484,6 +484,60 @@ async def beads_get_schema_info() -> dict:
|
|||||||
return await client.get_schema_info()
|
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(
|
async def beads_init(
|
||||||
prefix: Annotated[str | None, "Issue prefix (e.g., 'myproject' for myproject-1, myproject-2)"] = None,
|
prefix: Annotated[str | None, "Issue prefix (e.g., 'myproject' for myproject-1, myproject-2)"] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user