Skip to content

fix: normalise YAML block scalars in LLM-generated frontmatter#561

Open
AndrewDongminYoo wants to merge 1 commit into
nashsu:mainfrom
AndrewDongminYoo:fix/normalise-yaml-block-scalars
Open

fix: normalise YAML block scalars in LLM-generated frontmatter#561
AndrewDongminYoo wants to merge 1 commit into
nashsu:mainfrom
AndrewDongminYoo:fix/normalise-yaml-block-scalars

Conversation

@AndrewDongminYoo

Copy link
Copy Markdown
Contributor

What

Add a normalizeBlockScalarsInFrontmatter step to sanitizeIngestedFileContent so LLM-generated frontmatter never carries YAML block scalars the renderer can't handle.

Why

LLMs sometimes emit >- (folded) or |- (literal) block scalars in frontmatter. LLM Wiki's renderer doesn't support them, so those pages render incorrectly.

How

  • Parse the frontmatter, collapse embedded newlines in string values to a single space, and re-serialise with lineWidth: -1 so js-yaml never reintroduces block scalars.
  • Fast-exits on pages with no block-scalar indicators — zero overhead for clean pages.
  • Composes with the existing code-fence and wikilink-list repairs in the same sanitizer.

Testing

  • ingest-sanitize.test.ts — 22 pass / 0 fail (7 new cases covering folded/literal scalars, clean-page fast-exit, and composition with existing repairs).
  • tsc --build clean.

@chuenchen309 chuenchen309 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is clean — nice that it round-trips through js-yaml with a fast-exit guard instead of regex-munging the values. One correctness issue I think is worth fixing before merge:

The + 4 offsets in the reconstruction assume the opening fence is exactly ---\n, but fmRe matches more than that. ^---\s*\r?\n also matches ---\r\n (CRLF) and --- \n (trailing space), where the opening delimiter is 5+ chars. In those cases:

content.slice(0, m.index! + 4) + newPayload + content.slice(m.index! + 4 + payload.length)

slices mid-delimiter — for CRLF input the prefix becomes ---\r (the \n is dropped) and the suffix start is off by one, so the last payload character gets duplicated. Net result is corrupted frontmatter on any Windows-line-ending or trailing-whitespace source. Repro is the existing folded-scalar test with \r\n line endings.

Since the exact fence text is already in the match, deriving its length (or capturing the opening/closing fences as regex groups) avoids the hardcoded 4. A CRLF case in the test suite would lock it in — the current tests all use \n so this slips through.

Minor / by design: collapsing |- literal scalars to spaces (per the test) is lossy for fields where line breaks are meaningful. Fine for description; just flagging in case any field is meant to preserve them.

@AndrewDongminYoo
AndrewDongminYoo force-pushed the fix/normalise-yaml-block-scalars branch from 50699b2 to 52b5633 Compare July 16, 2026 05:39
@AndrewDongminYoo

Copy link
Copy Markdown
Contributor Author

Fixed — the reconstruction now captures and reuses the exact opening and closing fence text instead of assuming ---\n is four characters.

I also added a regression test covering CRLF line endings and whitespace-padded fences, which verifies the frontmatter boundary and body remain intact.

@chuenchen309

Copy link
Copy Markdown
Contributor

Looks great — reusing m[1] / m[3] for the exact fences means CRLF and trailing-space delimiters round-trip cleanly, and tightening the padding to [ \t]* (from \s*) is a nice touch: it stops the opening match from greedily reaching into the payload. The new CRLF/whitespace test covers exactly the case I was worried about. Thanks for the quick turnaround!

@AndrewDongminYoo

Copy link
Copy Markdown
Contributor Author

Circling back on the second point, which I left unanswered earlier — the |- collapse is by design, and the constraint is broader than description.

FrontmatterValue is string | string[] (frontmatter.ts:3), so the frontmatter layer has no multi-line representation at all — there's no field a line break could survive into. None of the schema templates in templates.ts define one either; they're title / tags: [] / related: [] plus single-line scalars, and the panel renders each value on one line. So collapsing to spaces matches the invariant the rest of the layer already assumes rather than trading against it.

If a genuinely multi-line field is ever added, this normalizer would need a per-key exemption — that's the right moment to add one, not now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants