"""Duplicate detection & merge tools (#166). * ``find_duplicates`` — READ, vault-scoped: candidate pairs with scores. * ``merge_duplicate_notes`` — DANGEROUS: confirmed fusion with automatic backup (service layer), never without an explicit approval. """ from __future__ import annotations from typing import Any from backend.services import duplicates as _duplicates from backend.services.errors import ServiceError from backend.tools.context import ToolContext, ToolError, ToolRisk from backend.tools.registry import tool from backend.tools.schemas import FindDuplicatesInput, MergeDuplicatesInput @tool( name="find_duplicates", description="Find candidate duplicate markdown notes in a vault (similarity scores).", input_model=FindDuplicatesInput, risk=ToolRisk.READ, requires_vault=True, ) def find_duplicates(ctx: ToolContext, params: FindDuplicatesInput) -> dict[str, Any]: """List duplicate candidates ordered by descending score.""" try: return _duplicates.find_duplicate_pairs( params.vault, threshold=params.threshold, limit=params.limit, subdir=params.subdir, ) except ServiceError as e: raise ToolError(e.message, code=e.code, details=e.details) from e @tool( name="merge_duplicate_notes", description=( "Merge one note into another and delete the source (backup first). " "Destructive: requires confirmation." ), input_model=MergeDuplicatesInput, risk=ToolRisk.DANGEROUS, requires_vault=True, ) def merge_duplicate_notes(ctx: ToolContext, params: MergeDuplicatesInput) -> dict[str, Any]: """Fuse *source_path* into *target_path* using the chosen strategy.""" try: return _duplicates.merge_duplicates( params.vault, params.source_path, params.target_path, strategy=params.strategy, ) except ServiceError as e: raise ToolError(e.message, code=e.code, details=e.details) from e