Skip to content

Commit 88a7a74

Browse files
committed
Fix concept index updates by section
1 parent 65e1493 commit 88a7a74

2 files changed

Lines changed: 107 additions & 14 deletions

File tree

openkb/agent/compiler.py

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,55 @@ def _read_concept_briefs(wiki_dir: Path) -> str:
284284
return "\n".join(lines) or "(none yet)"
285285

286286

287+
def _get_section_bounds(lines: list[str], heading: str) -> tuple[int, int] | None:
288+
"""Return the [start, end) bounds for a Markdown H2 section."""
289+
for i, line in enumerate(lines):
290+
if line == heading:
291+
start = i + 1
292+
end = len(lines)
293+
for j in range(start, len(lines)):
294+
if lines[j].startswith("## "):
295+
end = j
296+
break
297+
return start, end
298+
return None
299+
300+
301+
def _section_contains_link(lines: list[str], heading: str, link: str) -> bool:
302+
"""Check whether a wikilink already exists inside the named section."""
303+
bounds = _get_section_bounds(lines, heading)
304+
if bounds is None:
305+
return False
306+
307+
start, end = bounds
308+
return any(link in line for line in lines[start:end])
309+
310+
311+
def _replace_section_entry(lines: list[str], heading: str, link: str, entry: str) -> bool:
312+
"""Replace the first matching entry within a specific section."""
313+
bounds = _get_section_bounds(lines, heading)
314+
if bounds is None:
315+
return False
316+
317+
start, end = bounds
318+
for i in range(start, end):
319+
if link in lines[i]:
320+
lines[i] = entry
321+
return True
322+
return False
323+
324+
325+
def _insert_section_entry(lines: list[str], heading: str, entry: str) -> bool:
326+
"""Insert a new entry at the top of a specific section."""
327+
bounds = _get_section_bounds(lines, heading)
328+
if bounds is None:
329+
return False
330+
331+
start, _ = bounds
332+
lines.insert(start, entry)
333+
return True
334+
335+
287336

288337
def _write_summary(wiki_dir: Path, doc_name: str, summary: str,
289338
doc_type: str = "short") -> None:
@@ -469,8 +518,9 @@ def _update_index(
469518
"""Append document and concept entries to index.md.
470519
471520
When ``doc_brief`` or entries in ``concept_briefs`` are provided, entries
472-
are written as ``- [[link]] (type) — brief text``. Existing entries are
473-
detected by the link part only and skipped to avoid duplicates.
521+
are written as ``- [[link]] (type) — brief text``. Existing entries are
522+
detected within their own section by the link part only and skipped to
523+
avoid duplicates.
474524
``doc_type`` is ``"short"`` or ``"pageindex"`` — shown in the entry so the
475525
query agent knows how to access detailed content.
476526
"""
@@ -485,32 +535,27 @@ def _update_index(
485535
)
486536

487537
text = index_path.read_text(encoding="utf-8")
538+
lines = text.split("\n")
488539

489540
doc_link = f"[[summaries/{doc_name}]]"
490-
if doc_link not in text:
541+
if not _section_contains_link(lines, "## Documents", doc_link):
491542
doc_entry = f"- {doc_link} ({doc_type})"
492543
if doc_brief:
493544
doc_entry += f" — {doc_brief}"
494-
if "## Documents" in text:
495-
text = text.replace("## Documents\n", f"## Documents\n{doc_entry}\n", 1)
545+
_insert_section_entry(lines, "## Documents", doc_entry)
496546

497547
for name in concept_names:
498548
concept_link = f"[[concepts/{name}]]"
499549
concept_entry = f"- {concept_link}"
500550
if name in concept_briefs:
501551
concept_entry += f" — {concept_briefs[name]}"
502-
if concept_link in text:
552+
if _section_contains_link(lines, "## Concepts", concept_link):
503553
if name in concept_briefs:
504-
lines = text.split("\n")
505-
for i, line in enumerate(lines):
506-
if concept_link in line:
507-
lines[i] = concept_entry
508-
break
509-
text = "\n".join(lines)
554+
_replace_section_entry(lines, "## Concepts", concept_link, concept_entry)
510555
else:
511-
if "## Concepts" in text:
512-
text = text.replace("## Concepts\n", f"## Concepts\n{concept_entry}\n", 1)
556+
_insert_section_entry(lines, "## Concepts", concept_entry)
513557

558+
text = "\n".join(lines)
514559
index_path.write_text(text, encoding="utf-8")
515560

516561

tests/test_compiler.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,54 @@ def test_backwards_compat_no_briefs(self, tmp_path):
221221
assert "[[summaries/my-doc]]" in text
222222
assert "[[concepts/attention]]" in text
223223

224+
def test_updates_concept_brief_only_inside_concepts_section(self, tmp_path):
225+
wiki = tmp_path / "wiki"
226+
wiki.mkdir()
227+
(wiki / "index.md").write_text(
228+
"# Index\n\n"
229+
"## Documents\n"
230+
"- [[summaries/my-doc]] (short) — Mentions [[concepts/attention]] here\n\n"
231+
"## Concepts\n"
232+
"- [[concepts/attention]] — Old brief\n\n"
233+
"## Explorations\n",
234+
encoding="utf-8",
235+
)
236+
237+
_update_index(
238+
wiki,
239+
"my-doc",
240+
["attention"],
241+
concept_briefs={"attention": "New brief"},
242+
)
243+
244+
text = (wiki / "index.md").read_text()
245+
assert "- [[summaries/my-doc]] (short) — Mentions [[concepts/attention]] here" in text
246+
assert "- [[concepts/attention]] — New brief" in text
247+
assert "- [[concepts/attention]] — Old brief" not in text
248+
249+
def test_adds_concept_entry_when_link_exists_outside_concepts_section(self, tmp_path):
250+
wiki = tmp_path / "wiki"
251+
wiki.mkdir()
252+
(wiki / "index.md").write_text(
253+
"# Index\n\n"
254+
"## Documents\n"
255+
"- [[summaries/my-doc]] (short) — Mentions [[concepts/attention]] here\n\n"
256+
"## Concepts\n\n"
257+
"## Explorations\n",
258+
encoding="utf-8",
259+
)
260+
261+
_update_index(
262+
wiki,
263+
"my-doc",
264+
["attention"],
265+
concept_briefs={"attention": "New brief"},
266+
)
267+
268+
text = (wiki / "index.md").read_text()
269+
assert "- [[summaries/my-doc]] (short) — Mentions [[concepts/attention]] here" in text
270+
assert "- [[concepts/attention]] — New brief" in text
271+
224272

225273
class TestReadWikiContext:
226274
def test_empty_wiki(self, tmp_path):

0 commit comments

Comments
 (0)