diff --git a/ingestion/internal/wiki/merge.go b/ingestion/internal/wiki/merge.go index 64fd226..20ca53f 100644 --- a/ingestion/internal/wiki/merge.go +++ b/ingestion/internal/wiki/merge.go @@ -108,9 +108,18 @@ func parseFrontmatterBlock(block string) (lines []fmLine, ok bool) { return nil, false } for _, line := range strings.Split(rest[:end], "\n") { + // An indented line is a continuation (a YAML block-list item, a + // wrapped value) never a top-level key, regardless of whether it + // happens to contain a colon — " - 'Chapter 3: The Reckoning'" + // is not a key named "- 'Chapter 3". Checked before Cut so a + // colon inside one never reaches the key/value path at all. + if line != "" && (line[0] == ' ' || line[0] == '\t') { + lines = append(lines, fmLine{raw: line}) + continue + } key, val, found := strings.Cut(line, ":") key = strings.TrimSpace(key) - if !found || key == "" { + if !found || key == "" || strings.HasPrefix(strings.TrimSpace(line), "#") { lines = append(lines, fmLine{raw: line}) continue } diff --git a/ingestion/internal/wiki/merge_test.go b/ingestion/internal/wiki/merge_test.go index 8f730cf..5bd962d 100644 --- a/ingestion/internal/wiki/merge_test.go +++ b/ingestion/internal/wiki/merge_test.go @@ -304,3 +304,31 @@ Rewritten by the model. want := "---\ntitle: 'X'\nlast_updated: 2026-08-22\nsource_hash: bbbb\n---\n\n## Summary\n\nOriginal.\n" assert.Equal(t, want, got) } + +func TestRefreshOwned_CommentLinesSurviveUnmangled(t *testing.T) { + // Third review round on PR #126: parseFrontmatterBlock dropped the + // sibling api package's comment guard (strings.HasPrefix(trimmed, "#")). + // Without it, a comment line containing a colon gets cut at the colon + // and re-rendered with collapsed whitespace instead of surviving + // verbatim. + a := "---\n#see also: https://x/y\ntitle: 'X'\n---\n" + b := "---\nsource_hash: bbbb\n---\n" + + got := refreshOwned(a, b) + assert.Equal(t, "---\n#see also: https://x/y\ntitle: 'X'\nsource_hash: bbbb\n---\n", got) +} + +func TestRefreshOwned_IndentedListItemsWithColonsAreNotMisparsedAsKeys(t *testing.T) { + // Same round: an indented YAML block-list item that happens to contain a + // colon (e.g. a title with a subtitle) is a continuation line, never a + // top-level key, regardless of what strings.Cut finds in it. Misparsing + // it as a key/value pair and re-rendering strips its indentation, + // de-indenting it out of the list it belongs to -- and refreshOwned + // re-renders this block on essentially every non-skipped ingest, so this + // is the normal path, not an edge case. + a := "---\naliases:\n - 'Chapter 3: The Reckoning'\ntitle: 'X'\n---\n" + b := "---\nsource_hash: bbbb\n---\n" + + got := refreshOwned(a, b) + assert.Equal(t, "---\naliases:\n - 'Chapter 3: The Reckoning'\ntitle: 'X'\nsource_hash: bbbb\n---\n", got) +}