60 lines
2.0 KiB
Python
60 lines
2.0 KiB
Python
"""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
|