Generated from
cat-harness/skills/ui/ui-core/readme-sections.md— do not edit here.
A folio’s README — the folio owns the file, the platform owns the markers
Two tools divide the file between them, and between them no link in a folio README is unaccounted for: sync owns the marked regions, audit checks everything else. Neither ever rewrites the author’s prose.
| what it does | writes? | |
|---|---|---|
readme_sync · readme:sync | refresh each generated section | only between markers |
readme_audit · readme:audit | verify every Markdown link resolves | never |
readme:sync:check is the CI form; readme:sections lists what a README can
opt into. Registered among the generic tools — a document folio has
chapters, simulators and workflows for the same reason a paper folio does, and
simply never carries the Lean markers.
Opt-in is per section, and absence means “not opted in”
A section is written only where the README already carries its
<!-- marker:begin --> / <!-- marker:end --> pair. Nothing outside a marked
region is ever touched, and a README with no markers is a README this tool
leaves entirely alone. Adding a section is one registry entry: a marker, a
one-line summary, and a renderer returning Markdown plus operator notes — the
CLI, the MCP tool and the staleness check all read the registry.
“Could not determine” is a third state, everywhere
A section that cannot establish its answer returns skip, and the region is left exactly as it was. This is not decoration:
- A folio configuring its simulators under a platform submodule has no such directory until that submodule is checked out. The first version rendered directory absent as this folio has no simulators — replacing a correct nine-row table with a sentence.
- A shallow clone cannot read the publish ref. “Could not read it” must not silently blank a table that was right yesterday.
An empty directory is still a determined empty. Absent and empty are different answers, and only one of them is safe to render.
Resolve links; never compose them
The contents table’s predecessor built every PDF cell by convention —
<pages>/papers/<paper>/chapters/<dir>.pdf — and checked it against nothing.
The folio’s publish branch had no chapters/ directory, so all twenty-three
chapter links were 404 and always had been; three of six appendix links
happened to resolve. Every cell is now looked up in a real listing of the
publish ref, and a chapter with no published PDF renders —.
The audit half exists for the same reason and reports three states, never two: an external host, an in-page anchor, and a ref this checkout cannot read are all NOT CHECKED, so a shallow clone does not produce a wall of false findings.
On link style — raw is not the private-repo answer
A private folio whose README links to a Pages URL is unreachable for exactly the people who have repository access, and a raw-content host does not fix it: it 404s on a private repo without a token, and a browser session cookie does not authenticate it.
The default is blob — the forge’s own file view — which follows the
viewer’s session, works whether the repo is public or private, and renders PDFs
inline. The other styles remain configurable, and each prints a note under the
table saying who can follow its links.
Why generating the rest was the wrong instinct
Two defects, both easy to write again.
The predecessor described one folio from inside the platform. It ended in a whole-file copy, carrying one folio’s title, badges, a registry of that folio’s own indices, a project-structure table naming its directories, and a licence block. Run it in any other folio and the author loses their README. Only five of its sections were derived from the tree at all; prose about a folio belongs to that folio. Three literals went with it, each worth recognising in new code: a namespace prefix applied regardless of the folio’s actual build library (now read from the build file, and left unprefixed when none names one — a wrong namespace is worse than none, because it is what a reader pastes into an import); descriptions from a hardcoded map of one folio’s filenames consulted before the artefact’s own declared name; and a hardcoded simulator directory.
And the authored half is worth keeping. One folio’s Published Artefacts table listed two directories that had never existed on its publish branch — both rows dead in both columns. But its labels were prose a generator would have had to invent. The defect was never a stale layout; it was targets that do not resolve. So that half is audited, not generated.
cat-harness:instances — the root README indexes every instance
Added 2026-09-20 (issue #592). The owner:
find beans related to readme, associated to harness instances, they must add to main one. should provide both Agent links (agents.md , memories) AND human docuemntaion (docs/) link for each harness.
One row per instance declaring a declaration, with two entries because two
readers arrive: an agent entry (AGENTS.md, memories) and a human one
(README, docs). Both halves are resolved from that instance’s own declaration —
declared assets for the file links, declared graphs for the directory links.
Never composed from a convention. join(root, "AGENTS.md") would link a
file that may not be declared, and the criterion in check:subgraph-coverage
rests on an undeclared file being one no checker has a reason to look at.
Scope is honoured too: a directory declared scope: "repository" is linked at
the repository root, which is how memory/ was first rendered dead as
./cat-harness/memory/. A dead link in a generated table is worse than a
missing row — the row asserts the entry exists.
A gap is rendered AS a gap: an em dash plus a counted note under the table, naming the check that lists them. Never a blank cell, never a guessed path. Eight of eleven instances had no agent entry when this was written, and a table that quietly omitted them would have read as though the work were done.
Which README, and the --dir flag
readme:sync with no --dir resolves to whichever instance carries a folio/
— which is cat-harness, not the repository root. Before #592 the two were
one file (cat-harness declared the root’s README with scope: "repository"),
so the difference could not show. Use the pair:
readme:sync · readme:audit | the cat-harness instance’s README |
readme:sync:root · readme:audit:root | the REPOSITORY’s README |
readme:sync:all | EVERY instance’s README, each against its own declaration |
In an index checkout the root README is not authored at all. Its root
declares no instance (owner, 2026-10-08), so nothing could own the file, and
the owner ruled on 2026-10-09 that it and the root AGENTS.md are rendered:
bun run cat index:render writes both — with the CLAUDE.md and GEMINI.md
stubs — from index.config.json, each instance’s declared agent-instructions
and instance-readme assets, bootstrap’s AGENTS.md, and the sections above,
and index:render:check fails when any of the four is not its current output.
readme:sync:root still agrees with it, because the renderer fills the same
marker regions through the same registry.
Both are gated in code-quality-gates.yml, and that is not belt and braces:
when the split landed, the bare readme:audit silently narrowed to
cat-harness’s and took the root README’s 41 links out of the gate without
failing anything. A gate that stops covering something still reports green.
readme:toc — every harness README’s own table of contents
Owner, 2026-09-30: “generated README.md … missing TOC, probably need on all
harnesses”. A nested list of the README’s own h2/h3 headings, linked by the
anchor GitHub assigns them. Every instance README carries it under a bold
Contents line, above its first ##; the root README too.
Not folio:toc. The shared word hides two different sections: folio:toc
is a folio’s contents — papers, chapters, verified PDFs — and says nothing
about the file it sits in. readme:toc is the file’s outline and needs no
folio at all. Both may sit in one README.
Anchors are GitHub’s, not a tidy approximation of them (githubSlug,
github-slugger’s rule): lowercase, drop everything but letters, marks, digits,
_, - and space, then each space becomes - — not collapsed, so
Corpus → draft is #corpus--draft. Inline markup is stripped first, as the
renderer does (`code` → code, [x](y) → x). Duplicates become -1,
-2 counted over every heading level, h1 and h4+ included, because GitHub
counts them all; a suffixed anchor colliding with a literal heading is bumped
past it. A TOC link that is almost right is a dead link that reads as live.
What is not a heading is not in it: lines inside a code fence (closed only by a fence of the same character at least as long), inside an HTML comment, indented four or more spaces, and inside the TOC’s own region — so the section is never an input to itself and a second run is a no-op.
It is registered LAST, on purpose. It reads the README as the sections
before it have left it, so a heading another section generates (folio:toc’s
### <paper>) is outlined on the same run. Registered earlier, every sync
would leave the TOC one run behind and --check would fail on a README that
had just been synced.
Zero h2/h3 is a determined empty (“This README has no sections yet.”), and a README with nothing to outline is better without the marker — which is why the immunizations IG carries none. A renderer called with no README text is undetermined and leaves the region alone.
Gated by readme:sync:all:check, over every declared instance. The three
single-README checks above name one file each; a generated region in the
other instances that nothing checked would be a hand-kept list, wrong the
first time somebody added a heading.
kg:processes and kg:files — an instance’s diagrams and files, from its declaration
Two sections any instance’s README may carry, both read from the instance’s
own declaration and the files it names (bootstrap-tools/scripts/readme-graph-sections.ts).
Owner, 2026-09-29: “display bpmn(s) etc in README.md”, “part of readme.md
generation is to do rendering”, and the file list at the end must be “a
skill and tool in cat-harness”.
kg:processes— every.bpmnin the declared directories, the entry Process first (the one no other calls), each with its picture. The picture is the SVGrender:bpmnwrites beside the.bpmnfor an instance exempt fromworkflow-visualiser— one with no site of its own, so its README is where its diagrams are seen. This section reads the SVG and never draws; rendering needs a browser, sorender:bpmnmust run first. bootstrap’s own README is generated and checked in bootstrap’s repository (owner, 2026-10-01: each repo owns its README; litlfred/bootstrap#1). A diagram with no picture beside it leaves the region untouched — never a broken image.kg:files— every file, grouped by declared directory, with “what it is” read from the file itself (front-matterdescription, a Process’s name, a schema’stitle, a JSON file’s owndescriptionor$comment) and “used by” only where a diagram records the relation: a task naming a Skill, a call activity naming a Process. Blank otherwise, never guessed. A directory whose files all sit in subdirectories collapses to one row.
Why kg: and not cat-harness:. The marker is written into the
instance’s README, and the floor instance must not name the layer above it
— its leak test fails on the word. kg is vocabulary every instance has.
A blank “used by” is a finding, not decoration. On bootstrap it showed
skills/discussion.md used by nothing although a diagram names it: the file
has no front matter, so by bootstrap’s own rule it is not a Skill. A
hand-kept table had said “step 2” and hidden that.
subgraph-readmes — one README per declared directory
A large instance cannot list every file on one page, but it declares its
directories, and each declared directory gets its own README
(bun run cat readme:subgraphs, the subgraph-readmes Tool). The instance
README’s kg:files then links each directory to it, and above 200 files lists
directories instead of files. The README is part of the graph: the exported
Directory node carries readmePath.
Everything shown is read from the Knowledge Graph, never composed: the
heading is the directory’s declared title, the paragraph its description,
the kinds its graphTypologies, each file row what the file says it is, and “used
by” only a relation a diagram records.
The layout is Liquid, in bootstrap-tools/scripts/templates/readme/,
beside the writer in the tools repository (bean xsqm). cat-harness’s
readme:subgraphs resolves its own Extensions — a directory’s scope, an
absent directory, the declared README — and calls that writer. Templates may {% include %} one another, Jekyll
style, and read any declared field through kg. Change a template, run the
command, commit the result. How to write one is
liquid-templates.
It writes only between <!-- kg:subgraph:begin --> and :end. A README
with no markers is left alone and reported: somebody wrote it. A generator
that owns a whole README (bootstrap’s schema page) keeps that region intact.
A missing fact is a QA finding, not a blank. test/results/subgraph-readmes.qa-results.json records every directory with
no title, no description or a description over 60 words, every declared
directory absent from disk, and every unmarked README. Findings are reported,
not failed: filling a declaration is its owner’s work. --check fails on a
stale README — the READMEs are docs, committed — and judges the record
rather than comparing it: it fails on a declared directory newly absent or a
process newly unresolved against a baseline, and a missing baseline is
UNKNOWN and not gated (bean 0dav). The record itself is a derived QA result
whose home is the qa-reports branch (arc 3fva), so it is not something to
regenerate and commit. A qa or health directory absent from the checkout
is not an absent-directory finding: those kinds are leaving main
(mayLeaveMain). It runs in CI and in the pre-commit hook
(scripts/git-hooks/pre-commit).
Where the README render sits in the pipeline
Stage 1, after the current-state json/jsonld and before every other
harness render, and fatal on failure — it is the file a reader opens first.
The order and the reasoning are in
render-order; do not restate them here.
Defined terms are links, everywhere in a README (owner, 2026-09-29)
“terms in readme like Skills, Process, Role, etc should be links in README.md
s”. A capitalized term bootstrap defines links to its row in
bootstrap/schemas/README.md wherever it appears in prose — not only at first
use — so a reader who lands mid-page is one click from the definition.
- Generated READMEs link them as they are written:
subgraph-readmesrunslinkTerms(bootstrap-tools/scripts/term-links.ts) over each declared description and each file’s description, with links made relative to the README bybootstrapTermTargets. - Hand-written READMEs are not rewritten by a generator — the folio owns
the file. Link the terms as you write, and a guard catches a miss:
graph.test.tsfails on any unlinked term inbootstrap/README.md’s prose (unlinkedTerms). - Left alone: code, headings, table header rows, existing links, and
bold names — a Role’s name in a table (
**Knowledge Graph Data Store**) is a name of its own, and a link inside it would split it.
Maintaining these files is memory work, not documentation work
README.md and AGENTS.md are declared assets with a declared purpose
(ASSET_ROLES in schemas/cat-harness.ts — one place, per ROLE, not
per asset). Keeping them true is part of
agent-memory, and the owner put them there:
it is an asset and has a defined purpose (that is part of skills of mantiaing agent memroies)
The one distinction that governs how each is written: a file is read, memory
is injected. Nothing truncates a file, so AGENTS.md carries what an agent
must be able to look up; MEMORY.md carries what must arrive unasked and pays
a 200-line budget for it.