ver 1.5.1 — duplicate-gate precision (common tokens, cross-kind)
Live 1.5.0 use caught the gate blocking a decision note over the single vault-common token "echo" (min-normalized overlap gives any one-token entity a 1.0 score). New echo_index.gate_candidates() + token_df(): - gate blocks same-kind candidates only; cross-kind name collisions (a decision titled after its project) warn instead of blocking - a lone shared token blocks only when unique to that entity (df == 1); multi-token overlaps still block fuzzy_candidates (advisory warnings) and exit-76/--merge-into/--force semantics unchanged. +3 offline unit tests, gate e2e cases restructured +2 new; verified live both directions. All suites green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -228,12 +228,14 @@ def capture(kind: str | None, title: str, file_arg: str | None, status_v: str =
|
||||
# Pre-write duplicate gate: a strong fuzzy candidate means this title is very likely
|
||||
# an existing entity under a different name. STOP before creating the duplicate —
|
||||
# after the fact, the warning arrives too late (the parallel note already exists).
|
||||
# gate_candidates applies the 1.5.1 precision rules (same-kind only; a lone shared
|
||||
# token blocks only when unique to that entity) so vault-common words can't gate.
|
||||
gate_hits = []
|
||||
if not existing_reachable and not force and not dry_run:
|
||||
gate_hits = [{"slug": s, "path": c.get("path"), "title": c.get("title"),
|
||||
"kind": c.get("kind"), "score": sc}
|
||||
for s, c, sc in idx_mod.fuzzy_candidates(index, title)
|
||||
if sc >= DUP_GATE]
|
||||
for s, c, sc in idx_mod.gate_candidates(index, title, kind=kind,
|
||||
threshold=DUP_GATE)]
|
||||
if gate_hits:
|
||||
if as_json:
|
||||
env = echo_output.envelope("duplicate-gate", {
|
||||
@@ -255,14 +257,14 @@ def capture(kind: str | None, title: str, file_arg: str | None, status_v: str =
|
||||
if existing_reachable:
|
||||
return done("update", existing["path"], dry=True)
|
||||
s2 = echo_quality.safe_slug(set(index.get("entities", {}).keys()), slug)
|
||||
cands = idx_mod.fuzzy_candidates(index, title)
|
||||
near = [c.get("path") for _, c, _ in cands][:3]
|
||||
near = [c.get("path") for _, c, _ in idx_mod.fuzzy_candidates(index, title)][:3]
|
||||
plan = done("create", idx_mod.derive_path(kind, s2, date=date, domain=domain),
|
||||
dry=True, near=near)
|
||||
if not as_json and not force and any(sc >= DUP_GATE for _, _, sc in cands):
|
||||
if (not as_json and not force
|
||||
and idx_mod.gate_candidates(index, title, kind=kind, threshold=DUP_GATE)):
|
||||
# (in --json mode the near_duplicates field carries this; keep stdout clean)
|
||||
print("note: a real run would STOP at the duplicate gate (candidate score "
|
||||
f">= {DUP_GATE}) — use --merge-into or --force.", file=real_stdout)
|
||||
print("note: a real run would STOP at the duplicate gate — use --merge-into "
|
||||
"or --force.", file=real_stdout)
|
||||
return plan
|
||||
|
||||
near_dupes: list[str] = []
|
||||
|
||||
Reference in New Issue
Block a user