@@ -284,6 +284,57 @@ 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 an index entry 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+ entry_prefix = f"- { link } "
309+ return any (line .startswith (entry_prefix ) for line in lines [start :end ])
310+
311+
312+ def _replace_section_entry (lines : list [str ], heading : str , link : str , entry : str ) -> bool :
313+ """Replace the first matching entry within a specific section."""
314+ bounds = _get_section_bounds (lines , heading )
315+ if bounds is None :
316+ return False
317+
318+ start , end = bounds
319+ entry_prefix = f"- { link } "
320+ for i in range (start , end ):
321+ if lines [i ].startswith (entry_prefix ):
322+ lines [i ] = entry
323+ return True
324+ return False
325+
326+
327+ def _insert_section_entry (lines : list [str ], heading : str , entry : str ) -> bool :
328+ """Insert a new entry at the top of a specific section."""
329+ bounds = _get_section_bounds (lines , heading )
330+ if bounds is None :
331+ return False
332+
333+ start , _ = bounds
334+ lines .insert (start , entry )
335+ return True
336+
337+
287338
288339def _write_summary (wiki_dir : Path , doc_name : str , summary : str ,
289340 doc_type : str = "short" ) -> None :
@@ -460,7 +511,6 @@ def _backlink_concepts(wiki_dir: Path, doc_name: str, concept_slugs: list[str])
460511 text += f"\n \n ## Related Documents\n - { link } \n "
461512 path .write_text (text , encoding = "utf-8" )
462513
463-
464514def _update_index (
465515 wiki_dir : Path , doc_name : str , concept_names : list [str ],
466516 doc_brief : str = "" , concept_briefs : dict [str , str ] | None = None ,
@@ -469,8 +519,9 @@ def _update_index(
469519 """Append document and concept entries to index.md.
470520
471521 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.
522+ are written as ``- [[link]] (type) — brief text``. Existing entries are
523+ detected within their own section by exact entry prefix and skipped to
524+ avoid duplicates.
474525 ``doc_type`` is ``"short"`` or ``"pageindex"`` — shown in the entry so the
475526 query agent knows how to access detailed content.
476527 """
@@ -484,34 +535,27 @@ def _update_index(
484535 encoding = "utf-8" ,
485536 )
486537
487- text = index_path .read_text (encoding = "utf-8" )
538+ lines = index_path .read_text (encoding = "utf-8" ). 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
514- index_path .write_text (text , encoding = "utf-8" )
558+ index_path .write_text (" \n " . join ( lines ) , encoding = "utf-8" )
515559
516560
517561# ---------------------------------------------------------------------------
0 commit comments