fix(dedup): the two new report kinds say what to DO about a duplicate (#4164)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 10s
CI & Build / integration (push) Successful in 44s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / Python tests (push) Successful in 1m41s
CI & Build / Build & push image (push) Successful in 27s

`_REPORT_KINDS` gained `lesson` and `process` without `_KIND_SUGGESTION`
gaining either, so the report would have listed pairs with no advice —
and the suggestion is the report's point. CI's per-kind guard caught it.

Lesson: read both triggers first (a lesson is retrieved by the situation
it names, so alike insights under different triggers are two lessons);
same trigger means one lesson learned twice, so fold the `taught_by`
union into the survivor and delete the other. Process: a process arrives
as a skill, so a duplicate runs the wrong procedure rather than merely
cluttering a list — keep the one in use, fold the missing steps, delete
the loser.

The guard now asserts the property per kind rather than spot-checking
two: no non-snippet suggestion may propose merge_snippets, so a sixth
kind inherits the bar without anyone editing the test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
This commit is contained in:
2026-09-18 18:39:25 -04:00
co-authored by Claude Opus 5
parent 6a2476addb
commit 31b478b7ac
2 changed files with 23 additions and 0 deletions
+19
View File
@@ -523,6 +523,25 @@ _KIND_SUGGESTION = {
"a work-log, and cancel the other with a pointer. If one CORRECTS the " "a work-log, and cancel the other with a pointer. If one CORRECTS the "
"other's conclusions, supersession also works for tasks." "other's conclusions, supersession also works for tasks."
), ),
"lesson": (
"Read both TRIGGERS before anything else — a lesson is retrieved by "
"the situation it names, so two alike insights under different "
"triggers are two lessons and belong apart. Same trigger, same "
"teaching means one lesson learned twice: keep the clearer one, "
"update_lesson it with the union of both `taught_by` lists and "
"anything the other said that it doesn't, then delete_lesson the "
"other. The sources are the point — a lesson that loses an incident "
"loses its evidence."
),
"process": (
"A process arrives at the agent as a skill, so two alike processes "
"compete for the same moment and whichever wins retrieval is the one "
"that runs — a duplicate here executes the wrong procedure rather "
"than merely cluttering a list. Keep the one actually in use, "
"update_process it with any step the other has, then delete_process "
"the loser. If they are genuinely different procedures that share "
"vocabulary, sharpen the titles instead so the right one wins."
),
} }
# kind → the Note-model predicate for BOTH sides of the self-join. Tasks are # kind → the Note-model predicate for BOTH sides of the self-join. Tasks are
+4
View File
@@ -344,6 +344,10 @@ def test_every_kind_has_a_suggestion_and_none_proposes_merging_notes():
for kind in _REPORT_KINDS: for kind in _REPORT_KINDS:
assert _KIND_SUGGESTION.get(kind), f"no suggestion for {kind}" assert _KIND_SUGGESTION.get(kind), f"no suggestion for {kind}"
if kind != "snippet":
# The property, not a spot-check: a sixth kind added to the
# report inherits the bar without anyone editing this test.
assert "merge_snippets" not in _KIND_SUGGESTION[kind]
assert "merge" in _KIND_SUGGESTION["snippet"] assert "merge" in _KIND_SUGGESTION["snippet"]
assert "NOT merge" in _KIND_SUGGESTION["note"] assert "NOT merge" in _KIND_SUGGESTION["note"]
assert "supersedes" in _KIND_SUGGESTION["note"] assert "supersedes" in _KIND_SUGGESTION["note"]