fix(wiki): rewrite refreshOwned on a parsed line list, not fence-index tracking
Second review round on PR #126 found two more problems with the first append fix: 1. When a has no frontmatter fence at all, the fallback spliced raw, unfenced "key: value" text straight into the body -- worse than the original bug, since the next parseSections call wouldn't recognize it as frontmatter and would absorb it as preamble text, duplicating on every subsequent merge. 2. The regression test only asserted substrings (Contains), which doesn't prove structural position. Two independent mutations (closeIdx locking onto the wrong fence, a reversed sort) both survived the full suite undetected. Rewritten from string-index surgery (tracking a fence's position across two passes over split lines) to a small parsed representation: fmLine (key/value or raw), parseFrontmatterBlock, setLine (update in place or append -- the one behavior the original code got wrong), and renderFrontmatterBlock. Same shape as the sibling api package's own frontmatter type, reproduced locally rather than imported (it's unexported there, and this package's needs are smaller) because that set()-appends-if-absent behavior was already proven correct by that package's existing test suite -- this bug class shouldn't have existed here in the first place. A page with no fence gets one created to hold the new fields, instead of corrupting the body. Tests now assert exact frontmatter block output via assert.Equal, not Contains, and a new fixture (mixed replace+append in one call) covers both code paths together. Flip-tested: reversing the sort turns two of the new exact-match tests red (the Contains-based version of the same test missed this on the prior commit). The "wrong fence" bug class is now structurally impossible rather than merely untested -- there's no fence-position index left to get wrong. Also fixed a byte-stability regression the new render introduced and caught via the full pipeline suite: a key with no inline value (the YAML block-list shape, "aliases:" followed by indented "- item" lines) rendered as "aliases: " (trailing space) instead of "aliases:". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -244,9 +244,63 @@ Rewritten by the model.
|
||||
|
||||
got := Merge(existing, fresh).Content
|
||||
|
||||
assert.Contains(t, got, "source_hash: bbbb", "a generator-owned field a page never had before must be appended, not dropped")
|
||||
assert.Contains(t, got, "last_updated: 2026-08-22")
|
||||
assert.Contains(t, got, "title: 'X'", "a human-set title must still survive regeneration")
|
||||
assert.Contains(t, got, "Original.")
|
||||
assert.NotContains(t, got, "Renamed by the model")
|
||||
// Exact frontmatter block, not Contains: the second review round found
|
||||
// that a substring check doesn't prove the fields landed inside a valid
|
||||
// fence, in the right order, or that the fence itself wasn't corrupted —
|
||||
// two independent mutations (closeIdx targeting the wrong fence, reversed
|
||||
// sort) both survived a Contains-only version of this test.
|
||||
want := "---\ntitle: 'X'\nlast_updated: 2026-08-22\nsource_hash: bbbb\n---\n\n## Summary\n\nOriginal.\n"
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func TestMerge_GeneratorOwnedFieldsCreateAFrontmatterBlockWhenThePageHadNoneAtAll(t *testing.T) {
|
||||
// Second review round on PR #126: when a has no frontmatter fence at all,
|
||||
// the append fallback spliced raw, unfenced "key: value" lines straight
|
||||
// into the body -- worse than the original bug, because the next
|
||||
// parseSections call wouldn't recognize them as frontmatter (they don't
|
||||
// start with `---`), so they'd be absorbed as ordinary preamble text and
|
||||
// re-duplicate on every subsequent merge instead of updating in place.
|
||||
existing := Page{Path: "wiki/sources/x.md", Content: "## Summary\n\nOriginal.\n"}
|
||||
fresh := Page{Path: "wiki/sources/x.md", Content: `---
|
||||
last_updated: 2026-08-22
|
||||
source_hash: bbbb
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Rewritten by the model.
|
||||
`}
|
||||
|
||||
got := Merge(existing, fresh).Content
|
||||
|
||||
want := "---\nlast_updated: 2026-08-22\nsource_hash: bbbb\n---\n\n## Summary\n\nOriginal.\n"
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func TestMerge_GeneratorOwnedFieldsMixedReplaceAndAppend(t *testing.T) {
|
||||
// a already has last_updated (replace in place) but not source_hash
|
||||
// (append) -- the two code paths in the same call, together.
|
||||
existing := Page{Path: "wiki/sources/x.md", Content: `---
|
||||
title: 'X'
|
||||
last_updated: 2026-01-01
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Original.
|
||||
`}
|
||||
fresh := Page{Path: "wiki/sources/x.md", Content: `---
|
||||
last_updated: 2026-08-22
|
||||
source_hash: bbbb
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Rewritten by the model.
|
||||
`}
|
||||
|
||||
got := Merge(existing, fresh).Content
|
||||
|
||||
want := "---\ntitle: 'X'\nlast_updated: 2026-08-22\nsource_hash: bbbb\n---\n\n## Summary\n\nOriginal.\n"
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user