Commit Graph
4 Commits
Author SHA1 Message Date
mathiasandClaude Sonnet 5 625955e944 fix(frontmatter): accept a closing fence at literal end-of-file
CI / Lint / Test / Vet (pull_request) Successful in 39s
CI / Mirror to GitHub (pull_request) Skipped
Round-1 review found a real regression: tightening Split's closing-fence
match to require a trailing newline (closing the "\n---extra" bypass)
also silently narrowed internal/frontmatter's own Lint/Fix behavior — a
document whose closing fence is literally the last bytes of the file
("---\ntitle: x\n---", no body at all) used to parse fine via this
package's private split, and now returns ok=false, contradicting this
PR's own "zero behavior change for Lint/Fix" claim. internal/api and
internal/mcp were unaffected (both already required the trailing newline
pre-consolidation), but cmd/frontmatterlint's Lint/Fix relied on the old,
looser acceptance.

Split now distinguishes "fence followed by a newline" (real body follows)
and "fence is the last bytes of the document" (no body, not a bypass
attempt) — both valid — from "fence followed by anything else" (the
actual bypass shape, still rejected). Regression test pins the
literal-EOF case; flip-tested to confirm it's load-bearing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 15:04:59 +02:00
mathiasandClaude Sonnet 5 a75fd95ab8 fix(frontmatter): consolidate 3 independent fence-parsers into one
CI / Lint / Test / Vet (pull_request) Successful in 34s
CI / Mirror to GitHub (pull_request) Skipped
hyperguild#138: internal/mcp/tools_notes.go's frontmatterBlock,
internal/api/frontmatter.go's parseFrontmatter, and
internal/frontmatter/frontmatter.go's split each independently detected a
note's --- fence, converging on the same shape through parallel bug-fixing
across today's #123/#126/#128 sagas. Each carried a subtly different gap:
internal/frontmatter's closing-fence match had no trailing-newline
requirement (a document ending "\n---extra" was treated as validly closed);
internal/mcp needed CRLF/BOM guards bolted on across two separate review
rounds after both silently defeated its own HasPrefix check; internal/api
never gained either fix, so the same encoding-dependent bypass was live
there too, just never triggered by a test.

internal/frontmatter is the leaf package (imports nothing internal), so it
becomes the canonical fence detector: exported Split(doc []byte) (block,
rest []byte, ok bool), BOM-stripped and CRLF-normalized, with the stricter
trailing-newline-required close. internal/api and internal/mcp now call it
instead of re-detecting the fence themselves, keeping only what's genuinely
package-specific (api's ordered fmLine round-trip shape, mcp's map + block-
scalar-fold logic).

Found two real regressions while wiring the callers, caught by each
package's own pre-existing byte-exactness tests going red immediately:
Split's block/rest boundary sits one byte differently than either caller's
prior contract (it keeps the fence-adjacent newline on each side, by design
for Fix's own reconstruction) — both callers now TrimSuffix/TrimPrefix
exactly that one byte to restore their exact prior semantics. Neither
regression would have been visible without those tests already existing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 14:57:34 +02:00
mathiasandClaude Opus 5 3da0eca9b4 fix(ingestion): decide quoting with YAML itself, not a colon heuristic (#90)
CI / Lint / Test / Vet (push) Successful in 29s
CI / Mirror to GitHub (push) Successful in 4s
`title: "Foo" — bar` opens with a quote and is not quoted; the previous
prefix check read it as already-quoted and left two notes broken. Probing
the value as the right-hand side of a mapping answers the real question —
does this load as written — and leaves anything that already parses alone,
whatever it parses into.

Repairs the last two quotable files in mathias/brain. Only wiki/index.md
remains unparseable, for an unrelated reason: a truncated UTF-8 sequence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m
2026-08-18 00:10:11 +02:00
mathiasandClaude Opus 5 7c9293f4ed feat(ingestion): lint and repair unparseable note frontmatter (#90)
Hand-authored and promoted notes skip pipeline.BuildPages, so nothing
quotes their scalars. A title carrying an unescaped ": " yields a block
yaml.Unmarshal rejects, and every consumer that reads frontmatter drops
the note silently — 18 files repo-wide today, up from the 8 in #90.

frontmatter.Lint reports it; frontmatter.Fix quotes only what quoting can
rescue and refuses to return a document that still fails to parse, so an
unfixable file comes back byte-identical rather than half-repaired. Scan
walks a tree and reports an unreadable file as a finding instead of
aborting, so one poisoned note cannot hide the rest.

Sequence items are deliberately left alone: "- foo: bar" is valid YAML
that silently becomes a map, which is a type corruption rather than a
parse failure, and repairing it would rewrite notes that currently load.

    task lint:frontmatter          # report
    task lint:frontmatter FIX=1    # repair in place

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m
2026-08-18 00:07:27 +02:00