feat(dedup): the duplicate report reaches notes and tasks, with per-kind cures
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 17s
CI & Build / Python tests (push) Failing after 30s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 17s
CI & Build / Python tests (push) Failing after 30s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Build & push image (push) Skipped
Step 5 of #278, folding in #2534. The operator's no-gate decision for the web UI (#2482 — "an llm attached to this surface is the corrections system") has a precondition nobody had built: the corrector has to be able to SEE what needs correcting. find_duplicate_snippets had no equivalent for notes or tasks, so a duplicate note was only ever noticed by accident. find_duplicate_records(kind="snippet"|"note"|"task") — the same indexed self-join, parameterised. Tasks are notes with a status, not a note_type, so the kind split is a status predicate; mixing them would propose folding a to-do into a write-up. find_duplicate_snippets stays as a wrapper because both surfaces and SnippetListView consume it by name. What differs by kind is the CURE, and the report says so in a `suggestion` field rather than leaving the caller to guess: snippet merge — lossless, the survivor keeps every call site note NEVER merge. A correction pair → supersedes on the newer; state smeared across dated records → extract to the System's reference note; genuinely parallel → leave alone. Choosing needs the records READ, which is the agent's job — so non-snippet groups carry `members` with dates and any `existing_supersessions` already declared inside the group. A pair someone ruled on is not an open question. task usually the same work opened twice — keep the one with the history, cancel the other with a pointer. The snippet sibling filter stays snippet-only: it keys on symbol/code_sha, which other kinds don't carry — and for them a look-alike is a finding. Surfaces: MCP find_duplicate_records (classified into _READ_ONLY_TOOLS — the completeness test would have caught the omission), REST /api/notes/duplicates, and a KnowledgeView panel mirroring SnippetListView's — links only, no merge button, because for notes the report proposes and the correction is a read- and-decide act. The panel follows the type filter and clears when it changes, so a note report can't linger under a task view. Correcting the task's own premise: it claimed the snippet report had "no view consuming it" — stale; SnippetListView has consumed it since it shipped. The UI gap was only ever notes/tasks. Answers the question carried from #2482: yes, the update routes on BOTH surfaces can turn a record into a duplicate — the gate is create-time by design. This report is the mechanism that catches it after the fact, which is the model the operator chose. Refs #278, #2547
This commit is contained in:
@@ -332,9 +332,9 @@ _READ_ONLY_TOOLS = frozenset({
|
||||
"list_rules", "list_tags", "list_tasks", "list_topics", "list_trash",
|
||||
"list_always_on_rules", "search",
|
||||
"get_system", "list_systems", "list_system_records",
|
||||
# Reports on the snippet corpus. Reads only — the merge it suggests is a
|
||||
# separate, explicitly-called write.
|
||||
"find_duplicate_snippets",
|
||||
# Reports on the corpus. Reads only — the merge or supersession each
|
||||
# suggests is a separate, explicitly-called write.
|
||||
"find_duplicate_snippets", "find_duplicate_records",
|
||||
# Snippets and processes are notes with a kind. A key that may read a note
|
||||
# but not a snippet inverts the sensitivity ordering: it exposes the
|
||||
# free-text records and withholds the structured ones (#2496).
|
||||
|
||||
Reference in New Issue
Block a user