Project every page into markdown, and count the restatements (#217) - #218
Closed
bdelanghe wants to merge 2 commits into
Closed
Project every page into markdown, and count the restatements (#217)#218bdelanghe wants to merge 2 commits into
bdelanghe wants to merge 2 commits into
Conversation
"Also we repeat ourselves a lot the pages should project into markdown we could read those." Two asks, and the second is why the first matters. THE PROJECTION. HTML is a bad medium for reading your own writing. A diff on index.html is markup with sentences buried in it, and the rendered page is laid out to persuade rather than to be legible. There was no way to read what this site SAYS without a browser or a grep. Now every page has a .md at the same route — /index.md, /map.md, /desk.md, /blog/*.md — listed in llms.txt and covered by the signed whole-site manifest. One rule, two cases: a page authored in markdown serves its SOURCE (projecting the built HTML back would create a second copy of every post to keep in sync, which is the exact failure this change is about); every other page is projected from its BUILT html, so the generated regions are in it. The projection is also committed under content/pages/ and drift-checked, like every other projection here. That is the half that pays off in review: a copy change shows up as a prose diff next to the markup one. The stamp region is dropped from the projection. It is rewritten on every build, so carrying it into a committed file would make that file stale the moment anything else merged — and dropping it makes the projection a pure function of the source, which is what lets --check mean anything. THE MEASUREMENT. The obvious check — find sentences that appear twice — finds almost nothing: 2% of sentences repeat verbatim, and most of those are nav chrome. That number is reassuring and wrong. This site repeats itself by RESTATEMENT. The homepage says, in three places: "Every claim on this page is graded against the running code." "Every claim here is graded against the running code." "A solo project — every claim on this page graded against the code that backs it." Three phrasings, no two identical. So the comparison is over content words: strip stopwords and short words, take the set, score pairs by Jaccard overlap. It reads the markdown, not the HTML, so it sees the words a reader sees and nothing about markup. 19 restatement pairs — 8 within a page, 11 across pages. Ratcheted, not a cliff. That count is real and large; failing the build on it today would block every unrelated change, and a gate nobody can go green against gets switched off. The committed baseline is a ceiling: a change may not add repetition, and --update lowers it when a change removes some. A missing baseline is refused rather than silently written. That happened once here: `npm run check` ran before the projection existed, saw only the five blog posts, and wrote a ceiling of 4 that the next run then "regressed" against. A ratchet that picks its own starting point from whatever it saw first is worse than none. Both gates are mutation-tested in their lane before either is trusted — edit the projection, prove the drift check goes red; lower the ceiling, prove the ratchet goes red. Rule 3: a gate's own claim about itself is not evidence. NOT IN SCOPE: cutting the repetition. This lands the instrument and records the number. What to cut is editorial and should be decided while looking at the list, not bundled into the change that produced it. The projection also surfaced something worth fixing separately: the room/door/guest definitions are marked up as styled spans rather than a description list, which is why they need a special case here to avoid reading as "rooma named bundle of doors". Closes #217
cell(): a pipe ends a GFM table cell, so it has to be escaped — but the text reaching cell() has already been through esc(), which escaped its backslashes. `.replace(/\|/g, "\\|")` was therefore correct only by accident: an escaper that handles `|` but not `\` is wrong on its own terms, and re-running a backslash pass would double what esc() emitted. It now walks the string once, consuming a backslash together with whatever it escapes, so a bare pipe from a code span or a link destination — neither of which goes through esc() — is escaped exactly once and an already-escaped one is left alone. check-repetition: `<!--[\s\S]*?-->` left an UNTERMINATED comment in place, and its body was then counted as prose. `(?:-->|$)` is both the complete pattern and the more correct one — swallowing to end-of-file is what an unterminated comment actually means. Nothing here renders as HTML, so neither was an injection. Both were still real defects in code whose whole job is to be exact about text.
Contributor
Author
|
Superseded by #220, which merged this branch in whole — the legibility gate reads the markdown projection, so they had to travel together. Every commit here is in The repetition ratchet went with it, and the homepage rewrite immediately paid it down: 19 → 7 pairs, ceiling lowered. Closing #217 alongside this. Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #217.
Two asks, and the second is why the first matters.
The projection
Every page now has a
.mdat the same route —/index.md,/map.md,/desk.md,/blog/….md— indexed inllms.txtand covered by the signed whole-site manifest (58 files, up from 50).One rule, two cases:
blog/*.md) serves its source. Projecting the built HTML back would create a second copy of every post to keep in sync, which is the exact failure this change is about.The projection is also committed under
content/pages/and drift-checked, likegen-seams/gen-map/gen-desk/gen-ledgeralready are. That is the half that pays off in review: a copy change shows up as a prose diff next to the markup one.The honesty stamp region is dropped from the projection. It is rewritten on every build, so carrying it into a committed file would make that file stale the moment anything else merged — and dropping it makes the projection a pure function of the source, which is what lets
--checkmean anything.Sample,
content/pages/index.md:The measurement
The obvious check — find sentences that appear twice — finds almost nothing here: 2% of sentences repeat verbatim, and most of those are nav chrome. That number is reassuring and wrong.
This site repeats itself by restatement. The homepage says, in three places:
Three phrasings, no two identical. So the comparison is over content words, not strings: strip stopwords and short words, take the set, score pairs by Jaccard overlap. It reads the markdown, not the HTML, so it sees the words a reader sees and nothing about markup.
The top of the list:
npm run repetition -- --listprints all 19.Ratcheted, not a cliff. That count is real and large; failing the build on it today would block every unrelated change, and a gate nobody can go green against gets switched off. The committed baseline is a ceiling: a change may not add repetition, and
--updatelowers it when a change removes some.A missing baseline is refused rather than silently written. That happened once here:
npm run checkran before the projection existed, saw only the five blog posts, and wrote a ceiling of 4 that the next run then "regressed" against. A ratchet that picks its own starting point from whatever it saw first is worse than none.Honest about what this is
Vocabulary overlap is a proxy for restatement, not a judge of it. Two sentences can share their nouns and say opposite things, and a deliberate callback is repetition on purpose. The report prints the pairs, not just the number, because it is a reading list rather than a verdict.
The HTML parser is small and deliberately unambitious: it handles the tags this site uses and degrades unknown ones to their text rather than throwing, because a projection that fails closed on a formatting detail would block the build for no gain.
Both gates are mutation-tested
.github/workflows/markdown.ymlproves each can fail before trusting either: edit the projection → the drift check must go red; lower the ceiling → the ratchet must go red. Rule 3 ofdocs/agentic-code-hygiene.md— a gate's own claim about itself is not evidence. Restores are from copies, nevergit checkout, so running the script locally can't discard uncommitted work.Not in scope
Cutting the repetition. This lands the instrument and records the number. What to cut is editorial and should be decided while looking at the list, not bundled into the change that produced it.
Two things the projection surfaced
<span class="defn__term">/<span class="defn__body">— a description list wearing spans. In markdown they fuse into "rooma named bundle of doors", which is why the projector needs a pairing rule for them. The real fix is a<dl>in the HTML, which would also help screen readers. Worth its own ticket._headersalready carried/*.md → text/markdown; charset=utf-8before any.mdexisted to serve. That type is correct, and browsers download rather than render it.curland editors read it fine. Say the word if you'd rather they render inline — that's a one-line header change, but it's site-wide policy so I've left it alone.Verified locally
Run the way CI runs them —
node build.mjsplus the four brand tools from a clone ofbrandat the installed version:npm run check(now 23 gates, includingcheck-repetition) ✓node scripts/gen-markdown.mjs --check✓meta/content/coverage --colors/a11y✓axe-gate— 13 pages, 0 serious/critical ✓a11y-heuristic— 0 errors ✓structure-audit✓ (13 pages, unchanged hash — the.mdtwins are not pages in the graph)emit-catalog --check✓Generated by Claude Code