Skip to content

Commit b6a6a93

Browse files
nickrociclaude
andcommitted
fix(scholar): match full wikilink token in phantom-row removal
The index-row repair matched broken targets with a bare ``[[{t}`` prefix, so a broken target that is a prefix of a valid entry's link (e.g. broken ``global/python/use`` vs valid ``[[global/python/use-uv]]``) would delete a row whose only link was the longer, valid one — an integrity regression that silently destroys real catalog rows. Require the full token boundary (``]]`` or the ``|`` alias separator) after the target. Adds a regression test; the live ``some-fake-project`` phantom (no alias) still removes correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5245864 commit b6a6a93

2 files changed

Lines changed: 30 additions & 1 deletion

File tree

daemon/agent_mem_daemon/scholar_prompt.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1234,7 +1234,15 @@ def _repair_index_rows(text: str, broken_targets: set[str]) -> tuple[str, int]:
12341234
removed = 0
12351235
for line in text.splitlines(keepends=True):
12361236
stripped = line.lstrip()
1237-
if stripped.startswith("|") and any(f"[[{t}" in line for t in broken_targets):
1237+
# Match the FULL wikilink token, not a prefix: a broken target
1238+
# ``global/foo`` must not delete a row whose only link is the
1239+
# valid ``[[global/foobar]]`` (prefix ``[[global/foo`` would match
1240+
# inside it). A table-cell wikilink is terminated by ``]]`` or by
1241+
# ``|`` (the alias separator), so require one of those right after
1242+
# the target.
1243+
if stripped.startswith("|") and any(
1244+
f"[[{t}]]" in line or f"[[{t}|" in line for t in broken_targets
1245+
):
12381246
removed += 1
12391247
continue
12401248
kept.append(line)

daemon/tests/test_scholar_prompt.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,3 +940,24 @@ def test_repair_skips_archive_and_log(tmp_path: Path):
940940
changes = scholar_prompt.repair_broken_wikilinks(k)
941941
assert changes == []
942942
assert "[[global/ghost/dead]]" in (k / "log.md").read_text(encoding="utf-8")
943+
944+
945+
def test_repair_index_row_prefix_does_not_delete_valid_longer_row(tmp_path: Path):
946+
# A broken target that is a PREFIX of a valid entry's link must not
947+
# trigger deletion of a row whose only link is the longer, valid one.
948+
# E.g. broken ``global/python/use`` vs valid ``[[global/python/use-uv]]`` —
949+
# a naive substring match on ``[[global/python/use`` would wrongly nuke
950+
# the valid row. The match must be on the full ``]]``/``|`` token boundary.
951+
k = _seed_repair_tree(tmp_path)
952+
valid_row = "| [[global/python/use-uv]] | global | provisional | 0.7 | uv | x | y | z |\n"
953+
phantom_row = "| [[global/python/use]] | global | provisional | 0.5 | g | a | b | c |\n"
954+
(k / "index.md").write_text(_INDEX_HEADER + valid_row + phantom_row, encoding="utf-8")
955+
956+
changes = scholar_prompt.repair_broken_wikilinks(k)
957+
assert any("phantom row" in c for c in changes)
958+
index_after = (k / "index.md").read_text(encoding="utf-8")
959+
# The valid longer-named entry's row must survive.
960+
assert "[[global/python/use-uv]]" in index_after
961+
# Only the genuinely broken prefix row is removed.
962+
assert "[[global/python/use]]" not in index_after
963+
assert not any("broken wikilink" in v for v in scholar_prompt.check_invariants(k))

0 commit comments

Comments
 (0)