Files changed: - CHANGES.md - VERSION - kb/CONTRACT.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2
20 KiB
kb/ - Knowledge Layer Contract
The compiled knowledge layer, and the third stage of the pipeline
raw/ -> kb/ -> reports/. Everything here is written and maintained by the LLM from
material in raw/, and is expected to stay correct without being re-derived.
Quality goal: a page should answer a future question without re-reading the source it came from. If answering still requires the raw file, the page is incomplete.
This file holds the rules that apply in every collection and in every instance. That
second half is the cut: what is written here is enforced by tools/wikitool or follows from
how it works, so it is identical everywhere and dist export ships it verbatim.
What an instance decides for itself is next door, in
kb/CONVENTIONS.md - the language pages are written in, the headings its two
generated regions render under, the naming forms, the tone, the hedging rule. That file binds exactly as this one does; it is simply owned by the instance
rather than by the stack, so the distribution ships only its .template and the instance writes
the real one. Read both, plus the target collection's kb/<name>/COLLECTION.md (also
instance-owned), before writing or editing a page.
The split is by who may change the sentence, not by what it is about. Language, tone and
naming used to sit here, which meant every instance that answered "not German" to
setup-instance.md was locally editing a file the stack also ships - and a merge from upstream
would quietly hand it back.
Structural facts (which frontmatter fields exist, which are required, what the body skeleton
looks like) are in neither - they belong to the type-specs and are printed by
tools/wikitool types describe <type>. Never hand-write frontmatter; scaffold with
tools/wikitool new <type> --name "<Name>" --set field=value ....
Contents
- Collections
- Generated files
- Titles are identifiers
- Every page should
- Quotation cap
- Language and identifiers
- Linking
- Provenance and citation
- What does not belong here
Collections
kb/ is a namespace, not a collection. It carries no COLLECTION.md of its own.
A directory under kb/ is a collection exactly when it contains a COLLECTION.md. That
file is the local authoring contract for every page in the subtree, and it belongs to the
instance: it declares in its frontmatter which profile from
instructions/kb-profiles.md it adopted, and whether the stack
resolves against it by name.
| Field | Means |
|---|---|
profile: |
Which catalogue entry this contract started from, or none. Free text - the catalogue is a palette, not an enum, and a collection an instance invented has no entry to name |
required_by_stack: |
Whether wikitool itself depends on this collection by name. Not the instance's to choose: docs verify checks it against the stack's own list. kb/sources/ is true - sources coverage, [^cite-id] resolution and kb/provenance.md all resolve against that name - and everything else is false |
- A subdirectory inside a collection is an area. It inherits the enclosing contract and
must not carry a
COLLECTION.mdof its own -kb/entities/systems/is an area ofkb/entities/. - An area is as deep as a page goes.
kb/<collection>/<page>.mdandkb/<collection>/<area>/<page>.mdare the two depths a page may sit at; nothing goes a level deeper. A further subdirectory is not a second-level area - it is invisible to the generated catalog, which reads exactly two path segments belowkb/and folds anything past them into the area's own table silently, with no location of its own. That is whywikitool lint'snested_pagesfinding is a hard error rather than an advisory one likemisplaced_pages: a misplaced page still catalogs correctly from the wrong place, a nested one makes the catalog itself wrong. A grouping axis that does not come from a type-spec'slayout:- project owner was the case that surfaced this - does not earn a second directory level; it goes into frontmatter instead. - A
COLLECTION.mdnested inside another collection is invalid. COLLECTION.mdappears nowhere outsidekb/.raw/,types/,tools/,reports/andinstructions/are not collections and carry aCONTRACT.mdor a root type-spec instead.
tools/wikitool docs verify enforces all three.
| Collection | Holds | Contract |
|---|---|---|
kb/entities/ |
Concrete things: codebases, deployed systems, tools, technologies, people | entities/COLLECTION.md |
kb/concepts/ |
Architectures, patterns, protocols, workflows, decisions, recurring problems | concepts/COLLECTION.md |
kb/sources/ |
One summary page per ingested source, carrying its raw_files: provenance |
sources/COLLECTION.md |
kb/comparisons/ |
Structured comparisons of two or more existing pages | comparisons/COLLECTION.md |
kb/gtd/ |
One page per committed initiative (a GTD project): goal, participants, durable status, open loops | gtd/COLLECTION.md |
The five rows above are this instance's collections, not a fixed set. Adding one:
mkdir kb/<name> and write a kb/<name>/COLLECTION.md with the two fields above. Collections
are discovered by contract presence, so no code change is needed. A collection only becomes
writable once some type-spec declares a matching base_dir:. Renaming or dropping one is the
instance's call too - except where required_by_stack: true says otherwise.
Where a page goes is decided by its type-spec, never by hand - see types/type-spec.md.
Generated files
Never hand-edit these; they are produced by tools/wikitool:
| File | Produced by |
|---|---|
kb/index.md |
wikitool index rebuild - the catalog map: statistics, counts, links |
kb/<collection>/INDEX.md and kb/<collection>/<area>/INDEX.md |
wikitool index rebuild - the page tables |
kb/log.md |
wikitool log append |
kb/provenance.md |
wikitool sources rebuild-index |
To find a page, search rather than read the catalog: tools/wikitool search "<text>", or
tools/wikitool search --field <predicate> for a structured query over frontmatter.
Titles are identifiers
The filename stem is the page title, and [[wikilinks]] must match it exactly. That is
not a naming preference; it is the wiki's only way to address a page. wikitool lint reports an
H1 that stops matching its title, rename/rm rewrite every reference to a stem, and a
[^cite-id] resolves through one.
A wikilink is one token and is never wrapped across lines. When prose is broken at a fixed
column, the break goes before or after [[...]], never inside it: a renderer does not reliably
read a link with a line break in it as a link. wikitool lint reports one as a Wrapped
Wikilinks hard error, naming the title it folds to - the graph, rename and rm already read
it as that title, so the fix is only to put it back on one line.
A wikilink may name a section of its page: [[Title#Section]]. The title part is the
reference - the graph counts it as a link to Title, and rename carries the anchor along - so
an anchor never makes a link broken. It can make one stale: a section renamed, or promoted to a
page of its own (instructions/page-lifecycle.md), leaves the
link reaching the right page and the wrong place. wikitool lint reports an anchor that names no
heading on its page as Broken Anchors, advisory - compared at any heading level, without case,
inline-code backticks or extra whitespace. A section is never a target in related:; an edge
points at a page.
Which form those titles take - spaces or kebab-case, singular or plural, what prefixes a decision record - is the instance's, in kb/CONVENTIONS.md § Naming.
A title is also a file name, so it must be one on every platform - Windows and macOS as
well as Linux, checked wherever the command runs: a corpus written on Linux is checked out on
the others, and a title that Linux accepts and Windows refuses breaks every clone there. The full
title counts, after any title_prefix. A title must not:
- be empty, or end with a dot or a space
- contain
<>:"/\|?*or a control character - start, before its first dot and regardless of case, with a Windows device name (
CON,PRN,AUX,NUL,COM0-COM9,LPT0-LPT9, and the superscript formsCOM¹-COM³,LPT¹-LPT³) or with a name the stack itself keeps beside a page (INDEX,COLLECTION) - collide with another page once both are normalized to NFC and compared by
casefold- NTFS and APFS fold case, and APFS folds NFC and NFD as well
wikitool new and wikitool rename refuse such a title (new for every type, whatever root it
writes to; rename only for --to, so a page that already breaks the rule can always be renamed
away from it), and never write over an existing file. wikitool lint reports existing pages that
break the rule as Unportable Titles, a hard error at every kb_version.
A path has a budget too. Windows counts 259 characters for a whole path, the folder the
instance is checked out into included, and long paths are off on the target system. The path of
any file below the instance root - kb/ page or raw/ source - therefore stays at 160
characters or fewer, written with / and counted in UTF-16 code units, which is how Windows
counts: an emoji outside the Basic Multilingual Plane takes two. The folder limit that doctor
checks is the other half of the same sum.
wikitool new (every root), wikitool rename (--to only, also under --dry-run), wikitool move (a single page, and --reconcile, which skips and names such a target) and wikitool raw accept (the target under raw/; the remedy is renaming the file in incoming/ - for a folder,
a shorter folder name or shorter names inside it) refuse a path
over the budget before writing anything. wikitool lint reports existing files over it as Long
Paths - advisory, not a hard error, so a corpus that predates the budget still passes
--fail-on-error; the fix is wikitool rename.
Every page should
- Carry a clear, descriptive title and a summary near the top
- Use consistent terminology with the rest of the wiki
- Link to the entities and concepts it mentions, and declare an edge where the relationship is worth naming - in the direction this page asserts it, not in both
- Cite its hard facts (see Provenance and citation)
- Duplicate no existing page
- Appear in the catalog (guaranteed by
wikitool index rebuild)
Quotation cap
At most 2 blockquotes per page - a blockquote being a run of consecutive > lines, code masked
out first, so a > inside a fenced shell transcript is a prompt rather than a quotation.
wikitool lint reports overages as advisory, since exceeding the cap can be a legitimate
judgment call - but the page should carry the knowledge itself, not delegate it to quotations.
The cap is about how much of the page you let quotes carry, not how long a wrapped quotation
runs; it does not apply to text you are citing verbatim from a source.
The register those lines are written in - what counts as a buzzword, what filler is refused - is the instance's, in kb/CONVENTIONS.md § Tone.
Language and identifiers
Which language pages are written in is kb/CONVENTIONS.md's to say. What follows here is the part that is not a choice, because the tool resolves against it.
Every line of a page is either prose or an identifier. Only prose is translated.
Prose: descriptions and definitions, ## Key Information values, ## Details body text, a
source page's Summary / Key Takeaways / Action Items / Not Extracted, and summary:.
Identifiers - never translated, in any language:
| Identifier | Why |
|---|---|
| Page titles, and the H1 that repeats one | A title is the wiki's only identifier for a page and follows the subject's own established name - see Titles are identifiers. wikitool lint reports an H1 that stops matching its title |
The subtype value on the generated **Typ:** line |
It renders a schema enum value (technology, workflow), which search --field filters on. The label is prose; the value is not |
tags: |
Search keys, not prose |
| Commands, paths, config keys, hostnames, code | They are what they are |
| Quotations | Quoted verbatim in the source's own language |
Which foreign technical terms stay untranslated inside that prose is a judgment call the instance records - see kb/CONVENTIONS.md § Language.
A source in another language is still summarized in the KB language: a source page is
evidence about a source, not a substitute for it. Quote verbatim in the original language and
record the raw file's language in source_language:.
Generated regions
Two regions of a page body are generated, not authored: the links region xref owns and the
footnotes region cite owns. Each sits between a marker pair:
<!-- wikitool:links -->
## Beziehungen
- **depends-on:** [[Hermes]]
<!-- /wikitool:links -->
The marker is what the tool locates the region by, and everything between the markers -
heading included - is replaced wholesale on the next write. An author never edits inside
them; anything left there is overwritten without warning, exactly as in kb/index.md. A region
with nothing to show is absent rather than empty.
The heading is therefore a rendering value, taken from kb/CONVENTIONS.md's sections:. No
heading text exists in the compiler, and nothing matches on it: changing the declaration
re-renders the words on the next write and cannot split a page.
That is not how it used to work. The tool located these regions by matching their heading text, which made a translated heading a structural fact - and made the region's end a guess. It ran to the next heading, and before that to the end of the file, which silently deleted whatever sat after it on eight pages. Any other heading a page carries is ordinary prose.
Linking
An edge is authored in one direction, on the page that asserts it, and carries a label that is a machine value rather than prose:
related:
- depends-on: Hermes
Created with tools/wikitool xref add --a "<A>" --b "<B>" --rel <label>, never by hand-editing
related: or the rendered bullet. Say the sentence before choosing the label - [A] <label> [B] - and if it only reads true backwards, the edge belongs on the other page.
A reverse edge is a separate decision, not a mirror. Write one when it independently helps a
reader at the other end; do not write one to make the graph symmetric. Navigation does not
depend on it either way: index rebuild renders the inbound view from the graph, completely and
without maintenance.
Which labels exist is instructions/link-taxonomy.md, a
palette that binds nothing. Which of them a page may use is its own collection's outbound:
block, per destination - the source collection decides, because the rules that govern an
edge are the rules of the collection asserting it. xref add refuses an unauthorised label and
lint reports one.
A page is expected to have at least one inbound edge; wikitool lint reports orphans.
Comparison pages are exempt - they are reached through the catalog. Directional edges mean more
pages qualify than under the old mirrored model, and that is the check measuring reachability
rather than measuring whether xref ran.
Renaming a page, deleting one, or dropping a single reference are tool operations with their own procedure: see instructions/page-lifecycle.md.
Provenance and citation
Every claim is either traceable to a raw file or explicitly marked as not.
provenance:on every entity/concept page -sourced(every substantive claim traces to a raw file),general(LLM general knowledge, no raw backing), ormixed(both; put the unsourced part under a## General Guidance (unsourced)heading).raw_files:on every source page - concrete existing file paths underraw/, never a directory and never a bare URL. For an external article also setsource_url:, butraw_files:must still point at the local copy underraw/- for a page captured withraw fetch, both the received.htmland the derived.md.- One source page may cover many raw files. A folder of related documents becomes a single page listing all of them, not one page per file.
- A
[^cite-id]footnote appended to any specific hard fact: an IP, port, version, path, command, or config value.tools/wikitool cite add --page "<Title>" --source "Source - X" [--file <qualifier>]mints the id, upserts its[[Source - X]](or[[Source - X|storage-model.md]]for a multi-file source) definition in the page's trailing Footnotes block (named per Section headings), and addsSource - Xtosources:- it prints the marker to paste at the fact; placing it is still manual. Never hand-type a cite-id (AGENTS.md invariant 1). This differs from a plain[[Source - X]]link, which only means "related to". - Notation inside code is notation, not a reference. A
[^cite-id]or a[[wikilink]]written in backticks or a fenced block is read as an example: the citation does not count and the link does not exist. That is what lets a page document this stack's own syntax. It also means a marker appended to a line inside a fence cites nothing - put it on a source line under the block (<source-word>: [^cite-id], in the KB language), where it renders as a footnote instead of travelling with the command when someone copies it. - A source cited inline must also appear in the page's frontmatter
sources:list;wikitool lintchecks this in both directions, and hard-errors on a leftover pre-migration^[[...]]marker, an undefined[^cite-id]reference, or an orphaned Footnotes definition.tools/wikitool cite syncreconciles a page's block after a prose edit changes which ids are actually referenced. tools/wikitool xref link-source --source "Source - X" --entities A,B,Cadds a new source to every page it backs in one pass.- Every raw file is expected to be claimed by some source page;
tools/wikitool sources coveragelists the ones that are not.
If no raw file or existing page backs an answer, say so explicitly rather than synthesizing one - and never file the synthesized version back into the wiki.
What does not belong here
- Raw source material - it stays immutable under
raw/. - Type definitions, frontmatter contracts, or templates - those live in
types/. - Procedures for operating the tooling - those live in
instructions/. - Anything an instance would have to rewrite for itself - language, naming forms, tone,
relationship labels, the hedging rule. Those are
kb/CONVENTIONS.md's, and a sentence of that kind here is a sentence the stack ships over the instance's own answer. - Rules that apply to only one collection - those belong in that collection's
COLLECTION.md. - Hand-edited generated files - see Generated files.
- Generated lint reports - they are written to
reports/and are not pages. - Answers with no source behind them.