Skip to content

Commit 021af74

Browse files
phernandezgithub-actions[bot]groksrc
authored
fix: Strip duplicate headers in edit_note replace_section (#396)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Drew Cain <groksrc@users.noreply.github.com>
1 parent 2ad0ee9 commit 021af74

2 files changed

Lines changed: 55 additions & 1 deletion

File tree

src/basic_memory/services/entity_service.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ def replace_section_content(
623623
Args:
624624
current_content: The current markdown content
625625
section_header: The section header to find and replace (e.g., "## Section Name")
626-
new_content: The new content to replace the section with
626+
new_content: The new content to replace the section with (should not include the header itself)
627627
628628
Returns:
629629
The updated content with the section replaced
@@ -635,6 +635,13 @@ def replace_section_content(
635635
if not section_header.startswith("#"):
636636
section_header = "## " + section_header
637637

638+
# Strip duplicate header from new_content if present (fix for issue #390)
639+
# LLMs sometimes include the section header in their content, which would create duplicates
640+
new_content_lines = new_content.lstrip().split("\n")
641+
if new_content_lines and new_content_lines[0].strip() == section_header.strip():
642+
# Remove the duplicate header line
643+
new_content = "\n".join(new_content_lines[1:]).lstrip()
644+
638645
# First pass: count matching sections to check for duplicates
639646
lines = current_content.split("\n")
640647
matching_sections = []

tests/services/test_entity_service.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,6 +1275,53 @@ async def test_edit_entity_replace_section_with_subsections(
12751275
assert "Other content" in file_content
12761276

12771277

1278+
@pytest.mark.asyncio
1279+
async def test_edit_entity_replace_section_strips_duplicate_header(
1280+
entity_service: EntityService, file_service: FileService
1281+
):
1282+
"""Test that replace_section strips duplicate header from content (issue #390)."""
1283+
# Create test entity with a section
1284+
content = dedent("""
1285+
# Main Title
1286+
1287+
## Testing
1288+
Original content
1289+
1290+
## Another Section
1291+
Other content
1292+
""").strip()
1293+
1294+
entity = await entity_service.create_entity(
1295+
EntitySchema(
1296+
title="Sample Note",
1297+
folder="docs",
1298+
entity_type="note",
1299+
content=content,
1300+
)
1301+
)
1302+
1303+
# Replace section with content that includes the duplicate header
1304+
# (This is what LLMs sometimes do)
1305+
updated = await entity_service.edit_entity(
1306+
identifier=entity.permalink,
1307+
operation="replace_section",
1308+
content="## Testing\nNew content for testing section",
1309+
section="## Testing",
1310+
)
1311+
1312+
# Verify that we don't have duplicate headers
1313+
file_path = file_service.get_entity_path(updated)
1314+
file_content, _ = await file_service.read_file(file_path)
1315+
1316+
# Count occurrences of "## Testing" - should only be 1
1317+
testing_header_count = file_content.count("## Testing")
1318+
assert testing_header_count == 1, f"Expected 1 '## Testing' header, found {testing_header_count}"
1319+
1320+
assert "New content for testing section" in file_content
1321+
assert "Original content" not in file_content
1322+
assert "## Another Section" in file_content # Other sections preserved
1323+
1324+
12781325
# Move entity tests
12791326
@pytest.mark.asyncio
12801327
async def test_move_entity_success(

0 commit comments

Comments
 (0)