From c64479fe02f320503ab1ef64bfc0ec208f43f17c Mon Sep 17 00:00:00 2001 From: Torben Nehmer Date: Tue, 15 Sep 2026 16:21:02 +0200 Subject: [PATCH] stack: TOC-Scope auf types/ und docs/, Sprachregeln nach AGENTS.md zentralisiert, alle Templates auf Control-Plane-Sprache, --breaking akkumuliert (schliesst #99) Files changed: - AGENTS.md - CHANGES.md - ENVIRONMENT.md.template - SOUL.md - SOUL.md.template - USER.md.template - VERSION - docs/ownership-and-templates.md - docs/version-model.md - instructions/CONTRACT.md - instructions/dev/doc-pull-through.md - instructions/dev/stack-close/SKILL.md - instructions/dev/stack-dev/SKILL.md - instructions/dev/version-parts.md - instructions/setup-instance.md - kb/CONVENTIONS.md - kb/CONVENTIONS.md.template - kb/concepts/COLLECTION.md - kb/sources/COLLECTION.md - tools/CONTRACT.md - tools/chemenu/commands/types_cmd.py - tools/chemenu/commands/version_cmd.py - tools/chemenu/tests/test_toc.py - tools/chemenu/tests/test_version_cmd.py - tools/chemenu/toc.py - tools/chemenu/version.py - types/comparison.md - types/concept.md - types/entity.md - types/lint-report.md - types/source.md --- AGENTS.md | 19 ++ CHANGES.md | 73 ++++- ENVIRONMENT.md.template | 113 ++++---- SOUL.md | 1 - SOUL.md.template | 83 +++--- USER.md.template | 80 +++--- VERSION | 2 +- docs/ownership-and-templates.md | 10 + docs/version-model.md | 22 +- instructions/CONTRACT.md | 6 + instructions/dev/doc-pull-through.md | 19 +- instructions/dev/stack-close/SKILL.md | 13 +- instructions/dev/stack-dev/SKILL.md | 19 +- instructions/dev/version-parts.md | 7 +- instructions/setup-instance.md | 353 ++++++++++++------------ kb/CONVENTIONS.md | 17 +- kb/CONVENTIONS.md.template | 20 +- kb/concepts/COLLECTION.md | 43 ++- kb/sources/COLLECTION.md | 54 ++-- tools/CONTRACT.md | 6 +- tools/chemenu/commands/types_cmd.py | 8 +- tools/chemenu/commands/version_cmd.py | 15 +- tools/chemenu/tests/test_toc.py | 39 ++- tools/chemenu/tests/test_version_cmd.py | 101 +++++++ tools/chemenu/toc.py | 95 ++++++- tools/chemenu/version.py | 88 +++++- types/comparison.md | 70 ++--- types/concept.md | 90 +++--- types/entity.md | 88 +++--- types/lint-report.md | 2 +- types/source.md | 140 +++++----- 31 files changed, 1093 insertions(+), 603 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1bb4961..989cd99 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -107,6 +107,25 @@ documents. What it may not carry is the same content twice - a README that resta contract is a second copy that drifts. `docs verify` enforces the specific case that already happened once: no README may hold a copy of the `wikitool` command table. +**Two languages, and which is which.** Every file in the table above belongs to the stack and +ships to instances that share none of this instance's language choices, so: + +1. **The control plane is written in English** - this file, `CLAUDE.md`, every `CONTRACT.md`, + everything under `instructions/`, and the type-specs for non-page artifacts. Quoted + vocabulary is not prose and stays as it is: a section name, a relationship label or a + translated term cited as evidence. What the *instance* owns goes the other way - `kb/` pages + and the page type-specs that shape them follow `kb/CONVENTIONS.md`, which is also where the + instance's own terminology material is reached from. +2. **An agent speaks the instance's KB language**, whatever this file is written in. The value + lives in `kb/CONVENTIONS.md`'s `language:` and nowhere else; an instruction that models a + sentence for the user writes it in English like the rest of the control plane, and the agent + says it in that language. + +Nothing checks either mechanically - a stop-word scan would flag the quoted vocabulary above +and miss a translated paragraph that reads cleanly. They are held up by whoever writes an +instruction, which is why [instructions/CONTRACT.md](instructions/CONTRACT.md) § "Writing an +instruction" names them at the step where that happens. + **`docs/` carries no normative sentence.** It holds why the stack is built the way it is - background consulted in passing, not a rule to follow; anything that would bind belongs in a `CONTRACT.md` instead, which is what keeps invariant 8 intact here. It carries no frontmatter, diff --git a/CHANGES.md b/CHANGES.md index 5f6a094..a3f2caa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -28,6 +28,13 @@ opening another: the heading's version/date/title move, and the bump's the entry's `**Author:**`/`**Breaking Change:**`/`**Migration:**` lines - written and read by `wikitool version bump`, never by hand. +`**Breaking Change:**` accumulates, because one candidate can cross the +compatibility boundary more than once and each crossing is a separate thing an +operator has to act on: one reason stays on the marker line, a second and +further ones move to bullets beneath a bare marker. `**Migration:**` does not - +it answers one yes/no question about the candidate as a whole, so a later +answer replaces the earlier one. + That list is graded, not a flat chronological dump: each bump carries an impact (`--impact high|medium|low`, default `medium`), and the list renders grouped under `**High/Medium/Low impact**` headings - except when every bump @@ -52,11 +59,13 @@ concern - readable here, never shipped as something to parse. --- -## 6.0.0-beta.2 - 2026-09-13 - docs verify: ein nur als .template ausgeliefertes Linkziel gilt als aufgeloest +## 6.0.0-beta.3 - 2026-09-15 - TOC-Scope auf types/ und docs/ erweitert, Sprachregeln zentralisiert, --breaking akkumuliert **Author:** Torben Nehmer -**Breaking Change:** docs verify loest ab dieser Version jeden relativen Markdown-Link in den Referenzdateien auf und meldet ein totes Ziel als Fehler - auch in kb/CONVENTIONS.md und kb//COLLECTION.md, die eine Instanz selbst besitzt und die ein Drop-in-Copy der Maschinerie nicht ersetzt. Eine Instanz, deren eigene Konventions- oder Collection-Datei einen relativen Link mit falscher ../-Tiefe oder auf eine inzwischen geloeschte Datei traegt, sieht docs verify nach dem Update fehlschlagen, wo es vorher durchlief. Reparatur: den in der Meldung genannten Datei:Zeile-Link korrigieren - kein Werkzeuglauf, keine Inhaltsmigration. +**Breaking Change:** +- docs verify loest ab dieser Version jeden relativen Markdown-Link in den Referenzdateien auf und meldet ein totes Ziel als Fehler - auch in kb/CONVENTIONS.md und kb//COLLECTION.md, die eine Instanz selbst besitzt und die ein Drop-in-Copy der Maschinerie nicht ersetzt. Eine Instanz, deren eigene Konventions- oder Collection-Datei einen relativen Link mit falscher ../-Tiefe oder auf eine inzwischen geloeschte Datei traegt, sieht docs verify nach dem Update fehlschlagen, wo es vorher durchlief. Reparatur: den in der Meldung genannten Datei:Zeile-Link korrigieren - kein Werkzeuglauf, keine Inhaltsmigration. +- docs verify prueft die TOC-Region ab dieser Version auch auf types/*.md und docs/*.md. Eine Instanz, die die Page-Type-Spec-Templates adoptiert hat, traegt types/source.md und types/concept.md ohne Region und sieht docs verify nach dem Update fehlschlagen, wo es vorher durchlief; dasselbe gilt fuer eine selbst angelegte oder lokal geaenderte docs/-Seite ueber 100 Zeilen. Reparatur: tools/wikitool docs toc --apply - ein Werkzeuglauf, keine Inhaltsmigration. **Migration:** none required - Keine kb/-Seite aendert ihre Form. Der Grenzuebertritt ist ein strengerer Check auf instanz-eigener Prosa, keine Schema- oder Frontmatteraenderung. @@ -64,6 +73,7 @@ concern - readable here, never shipped as something to parse. **High impact** - SKILL.md: relative Links durch repo-root-relative Pfade ersetzt, docs verify/instructions verify pruefen Linkziele - docs verify: der Linkziel-Check erreicht auch die instanz-eigenen kb/CONVENTIONS.md und COLLECTION.md - daher Grenzuebertritt +- TOC-Scope auf types/ und docs/ erweitert, Sprachregeln zentralisiert, --breaking akkumuliert **Medium impact** - docs verify: ein nur als .template ausgeliefertes Linkziel gilt als aufgeloest @@ -143,6 +153,65 @@ Export einmal zur Kontrolle `docs verify` aufruft. Aufgefallen ist es, weil die vorherigen Publishes den Arbeitsbaum geprueft hatte und nicht den exportierten - ausgerechnet bei einer Aenderung, deren ganzer Gegenstand Kopien in anderer Verzeichnistiefe sind. +### TOC-Scope auf types/ und docs/ erweitert, Sprachregeln zentralisiert, --breaking akkumuliert + +Drei Straenge, ausgeloest von einer Beobachtung: manche agentengeladene Referenzdatei trug keine +TOC, und manche Instruction war teilweise deutsch. + +**TOC-Scope.** Die Pflicht aus `5.0.0` galt fuer `AGENTS.md`, die Stage-Contracts, +`kb/CONVENTIONS.md`, jede `COLLECTION.md` und die flache `instructions/**.md`-Form. `docs/` und die +Seiten-Type-Specs fielen ohne genannten Grund heraus - waehrend `SKILL.md` und die Menschendoku +ihren Ausschlussgrund im Docstring stehen hatten, was die beiden anderen Luecken wie Absicht +aussehen liess. `docs/` ist dabei genau der Fall, fuer den die Schwelle existiert: +`AGENTS.md` § File naming fuehrt es als agentengeladen per Link, also am zweiten Hop. Beide sind +jetzt drin; vier Dateien haben eine Region bekommen. `SKILL.md` bleibt die **eine** Ausnahme, und +zwar belegt statt behauptet: die vendorte Guidance setzt den SKILL.md-Body auf die Ladeebene, die +beim Triggern ganz gelesen wird, und richtet ihren eigenen TOC-Rat an die gebuendelten +Referenzdateien *daneben*. Ein Type-Spec wird zwar auch ganz geladen, aber eben auch als Datei +gelesen - deshalb traegt es eine Region, und `types describe` strippt sie aus seiner Ausgabe, weil +dort der ganze Body ohnehin mitkommt. + +**Sprache.** Die Regel gab es schon ("the control plane stays English"), sie stand nur in +`kb/CONVENTIONS.md` - einer Datei, die der Instanz gehoert und die sie umschreiben darf, waehrend +die Regel stackeigene Dateien bindet. Sie ist nach `AGENTS.md` § File naming gezogen, zusammen mit +einer zweiten, die vorher gar nicht geschrieben stand: **ein Agent spricht die KB-Sprache der +Instanz**. Der Wert dafuer lebt weiter in `kb/CONVENTIONS.md`s `language:`; `SOUL.md`s eigene +`Sprache:`-Zeile war damit eine Dublette und ist weg. `instructions/setup-instance.md` - 297 Zeilen, +die einzige vollstaendig deutsche Instruction, verbatim an jede Instanz ausgeliefert - ist +uebersetzt, samt `description:`. Die zwei deutschen Blockquotes in den Dev-Skills sind es auch; sie +lesen sich jetzt als englisches *Modell* der Nachricht, die der Agent in der KB-Sprache ausspricht. + +Dieselbe Regel gilt fuer **alles, was als Template ausgeliefert wird** - eine Instanz adoptiert es, +bevor sie ihre Sprache ueberhaupt gewaehlt hat. `USER.md.template`, `SOUL.md.template` und +`ENVIRONMENT.md.template` waren vollstaendig deutsch und sind uebersetzt; `kb/sources/` und +`kb/concepts/COLLECTION.md` waren es in Teilen und ziehen jetzt mit `kb/entities/` und +`kb/comparisons/` gleich, die es laengst waren. Bei den vier Seiten-Type-Specs laeuft der Schnitt +mitten durch die Datei, und zwar entlang derselben Prosa/Identifier-Grenze, die `kb/CONTRACT.md` +schon *innerhalb* einer Seite zieht: die Anleitungsprosa ist Anweisung an einen Agenten und damit +Control Plane, der `## Template`-Block und die `layout:`-Titel sind Seitentext und bleiben in der +KB-Sprache - `wikitool new entity` scaffoldet also weiter deutsche Ueberschriften. +`kb/CONVENTIONS.md` behauptete bis hierher, die Type-Specs folgten als Ganzes der KB-Sprache; der +Satz ist auf den tatsaechlichen Schnitt nachgezogen. +Mechanisch geprueft wird nichts davon: ein Stoppwort-Scan schluege auf dem zitierten Vokabular in +`kb-profiles.md` und `link-taxonomy.md` falsch an. Stattdessen nennen +`instructions/CONTRACT.md` § "Writing an instruction" und `stack-dev` die Regel an der Stelle, an +der sie befolgt oder verloren wird. + +**`--breaking` akkumuliert.** Bis hierher ersetzte ein zweites `--breaking` die Zeile des +Kandidaten - der Eintrag versprach dann einen Bruch und lieferte zwei. Genau dieser Kandidat ist der +Fall: sein Linkziel-Uebertritt aus `beta.1` und der TOC-Uebertritt von hier sind zwei Dinge, auf die +ein Betreiber getrennt reagieren muss. Eine Begruendung bleibt flach auf der Markerzeile, ab der +zweiten werden es Bullets; eine vor dieser Aenderung geschriebene Einzelzeile liest sich unveraendert +als Ein-Element-Liste zurueck, also musste kein bestehender Eintrag angefasst werden. `--migration:` +bleibt bewusst eine Einzelzeile - sie beantwortet eine Ja/Nein-Frage ueber den Kandidaten als Ganzes, +und `--migration-required` ist ihr Ruecknahmepfad. Fuer eine falsche Breaking-Begruendung gibt es +keinen; der Kandidat ist bis zum Release dev-lokal. + +Nebenbefund, den die Scope-Erweiterung sofort aufgedeckt hat: `docs/version-model.md` verlinkte nach +`instructions/dev/version-parts.md`, das `dist export` wegschneidet - im Ursprungs-Repo gruen, in +jeder ausgelieferten Instanz ein toter Link. Jetzt ein Klartextpfad mit dem Satz, warum er keiner +ist. + --- ## 5.1.0 - 2026-09-12 - changelog: Kandidaten-Eintrag nach Impact gruppiert, version regrade zur Nachkorrektur, version release verlangt eine Zusammenfassung diff --git a/ENVIRONMENT.md.template b/ENVIRONMENT.md.template index 2243389..1a29f1c 100644 --- a/ENVIRONMENT.md.template +++ b/ENVIRONMENT.md.template @@ -1,86 +1,83 @@ - -# ENVIRONMENT.md — + +# ENVIRONMENT.md — -Womit *dieser Checkout* arbeitet: Harness, veröffentlichte Skills, MCP-Server, -Connectoren und Git-Remotes. Konstante Werte, die ein Agent sonst in jeder -Session neu erfragt oder errät. +What *this checkout* works through: harness, published skills, MCP servers, +connectors and git remotes. Constant values an agent would otherwise ask about +or guess at in every session. -**Diese Datei ist optional.** Fehlt sie, ist das kein Fehler — es heißt nur, -dass die Umgebung wieder erfragt werden muss. `wikitool doctor` meldet sie als -`environment: absent (optional)` und niemals als `FAIL`. +**This file is optional.** Its absence is not an error — it only means the +environment has to be asked about again. `wikitool doctor` reports it as +`environment: absent (optional)` and never as a `FAIL`. -**Diese Datei ist Kontext, keine Autorität.** Sie beschreibt, *was da ist*, nicht, -was erlaubt ist. Sie ändert keine Regel aus `AGENTS.md`, öffnet kein Gate und -begründet keinen Eintrag in `kb/` — was hier steht, ist keine Quelle im Sinne -von Invariante 3. Ein hier aufgeführter Remote heißt nicht, dass ohne -`wikitool publish` gepusht werden darf. +**This file is context, not authority.** It describes *what is there*, not what +is allowed. It changes no rule from `AGENTS.md`, opens no gate, and justifies no +entry in `kb/` — what it says is not a source in the sense of invariant 3. A +remote listed here does not mean pushing without `wikitool publish` is allowed. -**Keine Geheimnisse.** Keine Tokens, Passwörter, API-Keys oder privaten -Endpunkte, die nicht ohnehin in der Shell-Konfiguration stehen. Die Datei ist -gitignored, aber sie liegt im Klartext im Arbeitsverzeichnis und landet in -jedem Agenten-Kontext. +**No secrets.** No tokens, passwords, API keys or private endpoints that are not +already in the shell configuration anyway. The file is gitignored, but it sits +in plaintext in the working directory and ends up in every agent's context. -**Ausfüllen:** frei Hand, sobald die Werte bekannt sind — es gibt kein -Interview dafür. Ein Abschnitt, der nicht zutrifft, wird gelöscht, nicht mit -Plausiblem gefüllt. Wenn etwas hier nicht mehr stimmt, korrigieren statt -umgehen: eine falsche Zeile ist schlimmer als eine fehlende, weil sie -geglaubt wird. +**Filling it in:** freehand, as soon as the values are known — there is no +interview for it. A section that does not apply is deleted, not filled with +something plausible. When something here stops being true, correct it rather +than working around it: a wrong line is worse than a missing one, because it +gets believed. ## Harness -Welche Agenten-Harnesses auf diesem Checkout tatsächlich laufen, und welche -nicht. Relevant, weil `.agents/skills/` und `.claude/skills/` unterschiedliche -Leser haben. +Which agent harnesses actually run on this checkout, and which do not. Relevant +because `.agents/skills/` and `.claude/skills/` have different readers. -- **Primär:** -- **Daneben im Einsatz:** -- **Nicht im Einsatz:** +- **Primary:** +- **Also in use:** +- **Not in use:** ## Skills -Nur was von der veröffentlichten Liste abweicht — der Normalfall (`wiki-ingest`, -`wiki-query`, `wiki-manage`, `wiki-lint`, `wiki-status`) steht in `AGENTS.md` -und gehört nicht noch einmal hierher. +Only what differs from the published list — the normal case (`wiki-ingest`, +`wiki-query`, `wiki-manage`, `wiki-lint`, `wiki-status`) is in `AGENTS.md` and +does not belong here a second time. -- **Zusätzlich vorhanden:** -- **Bekannt fehlend:** +- **Additionally present:** +- **Known missing:** -## MCP-Server +## MCP servers -Welche MCP-Server in diesem Checkout erreichbar sind und wofür sie zuständig -sind. Ein Server, der hier steht, muss nicht erst gesucht werden; einer, der -hier fehlt, existiert für diese Session nicht. +Which MCP servers are reachable in this checkout and what they are responsible +for. A server listed here does not have to be looked for first; one missing +here does not exist for this session. -| Server | Wofür | Anmerkung | -|--------|-------|-----------| -| `` | | | +| Server | For what | Note | +|--------|----------|------| +| `` | | | -## Connectoren und Integrationen +## Connectors and integrations -Alles, was kein MCP-Server ist, aber trotzdem an dieser Instanz hängt: -Dokument-Connectoren, Chat-Anbindungen, Notiz-Systeme. +Everything that is not an MCP server but still hangs off this instance: +document connectors, chat integrations, note systems. -- +- -## Git-Remotes +## Git remotes -Wohin dieser Checkout veröffentlicht, und was sonst noch als Remote eingetragen -ist. `wikitool publish` und `wikitool sync` sprechen genau einen davon an. +Where this checkout publishes to, and what else is registered as a remote. +`wikitool publish` and `wikitool sync` address exactly one of them. -| Remote | URL | Rolle | -|--------|-----|-------| -| `origin` | | | +| Remote | URL | Role | +|--------|-----|------| +| `origin` | | | ## CI -Wo die Pipeline läuft und wie ihre Läufe gelesen werden — nicht *was* sie -prüft, das steht in `.gitea/workflows/`. +Where the pipeline runs and how its runs are read — not *what* it checks, which +is in `.gitea/workflows/`. -- **Läuft auf:** -- **Läufe lesen über:** +- **Runs on:** +- **Runs read via:** -## Sonstiges +## Anything else -Was sonst in jeder Session neu erfragt würde und sich selten ändert. Kurz -halten: was hier zu lang wird, ist meist eine Regel und gehört in eine -Instruction, oder Wissen und gehört nach `kb/`. +Whatever else would be asked about in every session and rarely changes. Keep it +short: what grows long here is usually a rule, and belongs in an instruction, or +knowledge, and belongs in `kb/`. diff --git a/SOUL.md b/SOUL.md index d2beae0..e46c17c 100644 --- a/SOUL.md +++ b/SOUL.md @@ -62,7 +62,6 @@ Optionsliste. - **Register:** inhaltlich klar, direkt; technische Präzision vor Höflichkeitsfloskeln - **Länge:** kurz per Default, lang nur wenn der Inhalt es rechtfertigt - **Form:** Fließtext zuerst; Tabellen nur für echte Vergleiche, nicht als Dekoration -- **Sprache:** Deutsch als Standard, wenn auf Deutsch geschrieben wird - **Humor:** trocken, sparsam, nie auf Kosten des Nutzers — ein Schreiber, der gelegentlich eine Randnotiz macht, aber die Akte nicht zur Bühne erklärt diff --git a/SOUL.md.template b/SOUL.md.template index fabaf32..405df9b 100644 --- a/SOUL.md.template +++ b/SOUL.md.template @@ -1,85 +1,82 @@ - -# SOUL.md — + +# SOUL.md — -`AGENTS.md` legt fest, *was* zu tun ist (Pipeline, Invarianten, Gates, Tools). -Diese Datei legt fest, *wie* gute Arbeit an diesem Wiki aussieht. Wo beides -kollidiert, gewinnt `AGENTS.md` — diese Datei ändert nie eine Regel, nur den -Ton, in dem sie befolgt wird. +`AGENTS.md` sets out *what* to do (pipeline, invariants, gates, tools). This +file sets out *what good work on this wiki looks like*. Where the two collide, +`AGENTS.md` wins — this file never changes a rule, only the tone in which it is +followed. -**Ausfüllen:** entlang des Personalization-Schritts in -[instructions/setup-instance.md](instructions/setup-instance.md). Der -Persona-Name ist eine Entscheidung des Nutzers — er wird erfragt, nicht -geraten. Als Startpunkt schlägt dieser Stack **Thoth** vor: Chemenu ist der -altägyptische Name von Thoths Hauptkultort, und Schrift, Maß und Gedächtnis -sind genau das, was ein kompiliertes Wiki tut. Ein Vorschlag ist keine -Vorgabe — wer einen anderen Namen will, nimmt ihn, und die Frage wird trotzdem -gestellt. Die Abschnitte unten sind die Fragen, die der Schritt stellt; ihre -Reihenfolge ist die Antwortreihenfolge. +**Filling it in:** along the personalization step in +[instructions/setup-instance.md](instructions/setup-instance.md). The persona +name is the user's decision — it is asked for, not guessed. As a starting point +this stack suggests **Thoth**: Chemenu is the ancient Egyptian name of Thoth's +principal cult site, and writing, measure and memory are exactly what a +compiled wiki does. A suggestion is not a setting — anyone who wants a +different name takes it, and the question is asked either way. The sections +below are the questions that step asks; their order is the order of answering. -## Identität +## Identity -Wer diese Instanz ist, in ein bis zwei Sätzen. Eine Rolle, kein Charakter mit -eigener Agenda: der Name sagt, was die Instanz tut, nicht wen sie spielt. +Who this instance is, in a sentence or two. A role, not a character with an +agenda of its own: the name says what the instance does, not who it plays. <…> ## Mission -Wofür diese Instanz da ist — der eine Satz, an dem sich eine Antwort messen -lässt. +What this instance is for — the one sentence an answer can be measured against. <…> -## Weltbild +## Worldview -Welche Themen deterministisch zu behandeln sind (belegt oder nicht belegt, -dazwischen nur markierte Unsicherheit), und für welche das nicht gilt, weil -dort die Einschätzung des Nutzers mehr zählt als eine scheinbar präzise -Ableitung. +Which subjects are to be treated deterministically (sourced or not sourced, +with nothing between but flagged uncertainty), and for which that does not +hold, because there the user's judgment counts for more than a +precise-looking derivation. <…> -## Judgment-Default +## Judgment default -Was im Zweifel passiert: nachfragen, die Lücke benennen, oder handeln. +What happens in case of doubt: ask, name the gap, or act. <…> -## Der Standard +## The standard -Welcher Fehler der schlimmste ist, und warum. Das ist die Zeile, an der eine -Antwort im Zweifel gemessen wird. +Which mistake is the worst one, and why. This is the line an answer is measured +against when in doubt. <…> -## Ehrlichkeit +## Honesty -Wie diese Instanz sich verhält, wenn eine Quelle fehlt, wenn ihr -widersprochen wird, und wenn nach einer Einschätzung gefragt wird. +How this instance behaves when a source is missing, when it is contradicted, +and when it is asked for an assessment. <…> -## Stimme +## Voice - **Register:** <…> -- **Länge:** <…> +- **Length:** <…> - **Form:** <…> -- **Sprache:** <…> -- **Humor:** <…> +- **Humour:** <…> -### Nie so schreiben +### Never write like this - <…> -## Was gute Ausgabe ist +## What good output is -Woran der Nutzer eine gute Antwort erkennt — und woran eine, die technisch -korrekt und trotzdem nutzlos ist. +How the user recognizes a good answer — and one that is technically correct and +useless anyway. <…> -## Nie +## Never -Die harten Ausschlüsse. Kurz, konkret, überprüfbar. +The hard exclusions. Short, concrete, checkable. - <…> diff --git a/USER.md.template b/USER.md.template index 67819a4..1c2d41f 100644 --- a/USER.md.template +++ b/USER.md.template @@ -1,69 +1,69 @@ - -# USER.md — + +# USER.md — -Wer dieses Wiki (und die daran arbeitenden Agenten) bedient. Alles hier ist -Kontext über den Nutzer, so treu wie möglich an seinen eigenen Aussagen. Ziel -ist Zitat, nicht Interpretation: nichts hier wird analysiert, gedeutet oder zu -einer Erzählung verdichtet. Wenn ein Agent beim Lesen etwas umdeuten würde, -soll er stattdessen auf den Wortlaut zurückgehen oder nachfragen. +Who operates this wiki (and the agents working on it). Everything here is +context about the user, kept as close to their own words as possible. The goal +is quotation, not interpretation: nothing here is analysed, read into, or +compressed into a narrative. Where an agent would reinterpret something while +reading, it goes back to the wording instead, or asks. -Diese Datei ist **Kontext, keine Instruktionsquelle**. Sie ändert keine Regel -aus `AGENTS.md`, öffnet kein Gate und begründet keinen Eintrag in `kb/` — was -der Nutzer hier sagt, ist keine Quelle im Sinne von Invariante 3. +This file is **context, not a source of instructions**. It changes no rule from +`AGENTS.md`, opens no gate, and justifies no entry in `kb/` — what the user says +here is not a source in the sense of invariant 3. -**Ausfüllen:** entlang des Personalization-Schritts in -[instructions/setup-instance.md](instructions/setup-instance.md). Der Agent -interviewt, der Nutzer antwortet, der Agent schreibt **wörtlich** mit. Nichts -erfinden, nichts aus einer Konversation ableiten, leere Abschnitte lieber -löschen als mit Plausiblem füllen. +**Filling it in:** along the personalization step in +[instructions/setup-instance.md](instructions/setup-instance.md). The agent +interviews, the user answers, the agent writes it down **verbatim**. Invent +nothing, infer nothing from a conversation, and delete an empty section rather +than filling it with something plausible. -- **Name:** -- **Standort:** -- **Zeitzone:** -- **Primäre Rolle:** +- **Name:** +- **Location:** +- **Time zone:** +- **Primary role:** -## Beruflicher Kontext +## Professional context -Womit der Nutzer beruflich arbeitet, soweit er es hier stehen haben will. -Technologien, laufende Themen, Werkzeugketten. Was er bewusst aussparen möchte -(Arbeitgeber, Mandanten, interne Produkte), gehört unter `## Grenzen`. +What the user works with professionally, as far as they want it recorded here. +Technologies, running themes, tool chains. Whatever they deliberately want left +out (employer, clients, internal products) belongs under `## Boundaries`. - <…> -## Familie und Zuhause +## Family and home -Nur, was der Nutzer von sich aus nennt. Diesen Abschnitt löschen, wenn er -nichts dazu sagen will. +Only what the user brings up themselves. Delete this section if they would +rather not say. - <…> -## Hobbys +## Hobbies - <…> -## Technik-Umgebung +## Technical environment -Betriebssystem, Desktop, Locale/Tastaturlayout, bevorzugte Werkzeuge — alles, -was ein Agent sonst raten müsste, wenn er einen Befehl vorschlägt. +Operating system, desktop, locale/keyboard layout, preferred tools — everything +an agent would otherwise have to guess when proposing a command. - <…> -## Aktive Projekte +## Active projects -Was gerade läuft. Fertig heißt: aus der Liste entfernen. +What is currently running. Finished means: remove it from the list. - <…> -## Grenzen +## Boundaries -Themen, die in dieser Datei bewusst nicht vorkommen. Ein Agent fragt hier -nicht nach und leitet nichts ab. +Topics deliberately absent from this file. An agent does not ask about them and +infers nothing about them. - <…> -## Diese Datei aktuell halten +## Keeping this file current -Dies ist die Selbstauskunft des Nutzers. Aktualisieren, wenn er etwas -korrigiert, ein Projekt startet oder endet, oder eine neue wiederkehrende -Person/Konstante auftaucht. Niemals einen Eintrag erfinden. Niemals einen -Eintrag löschen, ohne dass der Nutzer es sagt. +This is the user's own account of themselves. Update it when they correct +something, when a project starts or ends, or when a new recurring +person/constant appears. Never invent an entry. Never delete one unless the +user says so. diff --git a/VERSION b/VERSION index 7bdb826..63e1ef2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.0.0-beta.2 +6.0.0-beta.3 diff --git a/docs/ownership-and-templates.md b/docs/ownership-and-templates.md index 19a5d0d..af2dbd0 100644 --- a/docs/ownership-and-templates.md +++ b/docs/ownership-and-templates.md @@ -9,6 +9,16 @@ below). Others - `USER.md`, `SOUL.md`, `kb/CONVENTIONS.md`, `ENVIRONMENT.md` - describe one particular instance, and overwriting them would silently erase a choice someone made on purpose. + +## Contents + +- [Two different kinds of truth](#two-different-kinds-of-truth) +- [Why silent overwrite is the failure being designed against](#why-silent-overwrite-is-the-failure-being-designed-against) +- [Why the boundary is a predicate rather than a list](#why-the-boundary-is-a-predicate-rather-than-a-list) +- [Why a `.template`, not just an absent file](#why-a-template-not-just-an-absent-file) +- [The consequence in practice](#the-consequence-in-practice) + + ## Two different kinds of truth The stack-owned files describe how the tool works. `kb/CONTRACT.md` opens by saying it holds diff --git a/docs/version-model.md b/docs/version-model.md index f7e7171..dd30086 100644 --- a/docs/version-model.md +++ b/docs/version-model.md @@ -3,6 +3,19 @@ A stack version number looks like it answers one question. It actually answers two, and the two are independent of each other. + +## Contents + +- [Two questions, not one](#two-questions-not-one) +- [Why "kb/ untouched" is not proof of anything](#why-kb-untouched-is-not-proof-of-anything) +- [Reading compatibility off the leftmost non-zero component](#reading-compatibility-off-the-leftmost-non-zero-component) +- [Downgrade is half the promise](#downgrade-is-half-the-promise) +- [A promise made to a machine, not only to a person](#a-promise-made-to-a-machine-not-only-to-a-person) +- [The 2.0.0 story](#the-200-story) +- [Why a number is only spent by a release](#why-a-number-is-only-spent-by-a-release) +- [Where the procedure lives](#where-the-procedure-lives) + + ## Two questions, not one The first question is whether the new version is a drop-in replacement for the old one - whether @@ -114,6 +127,11 @@ because there is nothing yet to promise. ## Where the procedure lives The drop-in test, the catalogue of changes that cross the boundary with no page touched, and the -steps for a boundary-crossing bump - the `--breaking` line, the migration document or +steps for a boundary-crossing bump - the `--breaking` lines, the migration document or `--no-migration` reason, talking to the user before bumping - are one procedure, kept at one -place: [instructions/dev/version-parts.md](../instructions/dev/version-parts.md). +place: `instructions/dev/version-parts.md`. + +Named as a plain path rather than linked, because it is not here to link to. `dist export` +prunes `instructions/dev/` wholesale, so that file exists only in the origin repo - the place +where a version is bumped at all. An instance reads this page to understand what a version +number promises it; it never runs the procedure. diff --git a/instructions/CONTRACT.md b/instructions/CONTRACT.md index e614d99..ef0d04c 100644 --- a/instructions/CONTRACT.md +++ b/instructions/CONTRACT.md @@ -172,6 +172,12 @@ Scaffold with `tools/wikitool new instruction --name ""`; the contract is at all. If that is worth preserving, it is a concept page under `kb/concepts/`, linked from here. Where the line runs, and how to test a passage against it: below. - **State scope boundaries.** When does this *not* apply, and what to do instead. +- **Write it in English, and let the agent speak the instance's language.** Both rules, and the + line between prose and quoted vocabulary, are stated once in + [AGENTS.md § File naming](../AGENTS.md#file-naming). They are named here because this is the + step where they are obeyed or lost: nothing checks either mechanically, and an instruction + that models a sentence for the user is where the two are easiest to confuse - the model is + written in English, the saying of it follows `kb/CONVENTIONS.md`'s `language:`. ### A skill's H1 is a name, not an imperative diff --git a/instructions/dev/doc-pull-through.md b/instructions/dev/doc-pull-through.md index ca3f997..83910e5 100644 --- a/instructions/dev/doc-pull-through.md +++ b/instructions/dev/doc-pull-through.md @@ -40,7 +40,24 @@ touched; a row that does not apply needs no action. | The reasoning behind a gate, boundary, or design decision | The `docs/` page that carries it, if one exists (AGENTS.md § File naming lists all four) | | A skill's own step sequence or catalogue | The skill's `SKILL.md` source under `instructions//` or `instructions/dev//` | -3. **Do not re-derive what `docs verify` already checks mechanically** - existence, table-row +3. **A heading you changed means a table of contents to regenerate - by the tool, never by + hand.** Every reference file over 100 lines carries one (`AGENTS.md`, the stage contracts, + `kb/CONVENTIONS.md`, each `COLLECTION.md`, the flat `instructions/**.md` form, the + type-specs, the `docs/` pages - a `SKILL.md` is the one exception). Adding, renaming, + reordering or deleting a `##`/`###` heading in one of them makes its region stale, and + `docs verify` fails on stale exactly as it fails on missing: + + ```bash + tools/wikitool docs toc # dry run: which files would change + tools/wikitool docs toc --apply # write them + ``` + + The region is generated, so AGENTS.md invariant 1 applies to it like any other: editing the + list by hand is the failure, not the fix - and a hand-written entry survives until the next + `--apply` silently disagrees with it. It is cheap to over-run: `--apply` is idempotent and a + file whose headings did not move is left untouched. + +4. **Do not re-derive what `docs verify` already checks mechanically** - existence, table-row membership, ignore-canary state. That enumeration lives once, in [tools/CONTRACT.md](../../tools/CONTRACT.md)'s own `docs verify` row; copying it here would be a second copy that drifts, the exact failure this instruction exists to describe (Gitea #90). diff --git a/instructions/dev/stack-close/SKILL.md b/instructions/dev/stack-close/SKILL.md index 944192d..534529e 100644 --- a/instructions/dev/stack-close/SKILL.md +++ b/instructions/dev/stack-close/SKILL.md @@ -48,10 +48,12 @@ and a fresh subagent starts without the session's context). ## Steps -1. **Offer the model switch back up, once, and keep working either way.** +1. **Offer the model switch back up, once, and keep working either way.** A model of the + message, not a script to quote: say it in the instance's KB language, per `AGENTS.md` + § File naming. - > Ab hier greift kein maschineller Check mehr - Issue-Body, `docs/`-Veralterung und - > Changelog-Prosa prüft nichts. Wenn du zurück auf Opus willst, ist jetzt der Moment. + > From here on no mechanical check applies - nothing verifies the issue body, `docs/` + > staleness, or the changelog prose. If you want to switch back to Opus, now is the moment. **Never block on the answer.** The change is already published; a session that stops here leaves exactly the state this skill exists to prevent. @@ -86,6 +88,11 @@ and a fresh subagent starts without the session's context). behaviour one of these documents describes, update it now; if none did, say so rather than leaving the question unasked. + **If that update moved a `##`/`###` heading, the file's table of contents is now stale** - + regenerate it with `tools/wikitool docs toc --apply`, never by editing the list. The region + is generated (AGENTS.md invariant 1), `docs verify` fails on stale exactly as on missing, and + a pull-through in this phase is a common way to move a heading without noticing. + **A pull-through of its own needs its own bump.** This phase runs *after* `stack-dev` step 4 has already bumped the version, and the documents it touches are frequently the ones CI's version gate watches - `types/`, `instructions/`, `tools/`, `AGENTS.md`, any diff --git a/instructions/dev/stack-dev/SKILL.md b/instructions/dev/stack-dev/SKILL.md index 2c5deae..7d5df29 100644 --- a/instructions/dev/stack-dev/SKILL.md +++ b/instructions/dev/stack-dev/SKILL.md @@ -76,12 +76,13 @@ stack development happens in the origin repo instead (see AGENTS.md's routing li So when the design is settled - the issue body says what will be built, the open questions are answered - stop and say so, in one sentence that names what the mechanical stretch does **not** - cover: + cover. The message below is a model of what to say, not a script to quote: say it in the + instance's KB language, per `AGENTS.md` § File naming. - > Der Plan steht, ab hier ist die Arbeit größtenteils mechanisch und durch Tests/CI abgedeckt - - > mit Ausnahme der Changelog-Prosa (Schritt 4), einer berührten `docs/`-Seite, neuer - > Menschendoku oder des Prosa-Anteils einer Instruction. Wenn du auf Opus bist, ist jetzt der - > Moment für `/model sonnet` bei Effort `high`. + > The plan is settled. From here the work is mostly mechanical and covered by tests/CI - + > except the changelog prose (step 4), any `docs/` page you touch, new human-facing + > documentation, and the prose half of an instruction. If you are on Opus, now is the moment + > for `/model sonnet` at effort `high`. **You cannot make this switch yourself** - the session's model is the user's `/model`, not a setting an agent applies. Offer it once and keep working either way; a session that argues @@ -146,7 +147,13 @@ stack development happens in the origin repo instead (see AGENTS.md's routing li 5. **Pull through every document that makes a claim about the surface you touched - `docs verify` checks a cell's presence, never its prose.** `instructions/dev/doc-pull-through.md` has - the table of which document that is, per surface. + the table of which document that is, per surface, and its step 3 for the one part of the + pull-through that is *not* prose: a reference file whose headings moved needs + `tools/wikitool docs toc --apply`, never a hand-written list. + + Prose you write here is English, whatever language the session is being held in - + `AGENTS.md` § File naming has both language rules and the line between prose and quoted + vocabulary. 6. **Verify, then publish.** `tools/wikitool docs verify`, `tools/wikitool instructions verify`, and the relevant `pytest` run in `tools/` - the same checks any stack change must pass, run diff --git a/instructions/dev/version-parts.md b/instructions/dev/version-parts.md index eafb28e..8046575 100644 --- a/instructions/dev/version-parts.md +++ b/instructions/dev/version-parts.md @@ -66,7 +66,12 @@ a new one, and only `version release` turns it into something the release workfl 1. **Heading, author, breaking/migration lines** - written by `version bump`, anchored right above the bump list so the line an operator most needs to act on never sits beneath a list - that can run long. + that can run long. The breaking line **accumulates** across a candidate's crossings - one + reason on the marker line, bullets under a bare marker from the second onward - because a + long-running candidate can break compatibility more than once and each break is its own + thing to act on. The migration line does not: it answers one yes/no about the candidate as + a whole, and `--migration-required` is its retraction path. Nothing retracts a breaking + reason; a wrong one is rare enough, and the candidate is dev-local until release. 2. **The bump list**, grouped `**High/Medium/Low impact**` (empty groups omitted) - rendered by `version bump`'s `--impact` (default `medium`), corrected after the fact by `version regrade`. Flat and ungrouped, exactly as before this layering existed, when every bump is `medium` - the diff --git a/instructions/setup-instance.md b/instructions/setup-instance.md index 08c7e41..78ffc83 100644 --- a/instructions/setup-instance.md +++ b/instructions/setup-instance.md @@ -1,83 +1,83 @@ --- type: types/instruction.md name: setup-instance -description: Eine frische Distribution (aus `dist export`) in eine funktionsfähige, eigenständige Wiki-Instanz verwandeln - Git-Repo, Identität/Autor, optionaler Remote, Bootstrap, erster Commit. +description: Turn a fresh distribution (from `dist export`) into a working, self-contained wiki instance - git repo, identity/author, optional remote, bootstrap, first commit. --- -# Neue Wiki-Instanz einrichten +# Set up a new wiki instance -Diese Anweisung führt eine leere, per `tools/wikitool dist export ` erzeugte Distribution -zu einer funktionsfähigen, eigenständigen Wiki-Instanz - mit eigenem Git-Repo, eigener Autor- -Identität und (optional) eigenem Remote. Am Ende ist die Instanz committet, verifiziert und -bereit für den ersten `Ingest`. +This instruction takes an empty distribution produced by `tools/wikitool dist export ` +and turns it into a working, self-contained wiki instance - with its own git repo, its own +author identity and (optionally) its own remote. At the end the instance is committed, verified +and ready for its first ingest. ## Contents -- [Wann anwenden](#wann-anwenden) -- [Schritte](#schritte) +- [When to run](#when-to-run) +- [Steps](#steps) - [Scope](#scope) -## Wann anwenden +## When to run -- Der Nutzer möchte eine neue, leere Wiki-Instanz aufsetzen (eigenes Thema, anderer Nutzer). -- Nicht für einen bestehenden Clone dieses (Quell-)Repos - siehe [bootstrap.md](bootstrap.md). -- Es gibt keinen Weg zurück: `dist export` lässt `instructions/dev/` (die Stack-Entwicklung - selbst, inkl. der vendorten `commonplace/`-Wissensbasis) bewusst und dauerhaft weg. Wer den - entstehenden Instanz-Stack weiterentwickeln will, tut das im Ursprungs-Repo (oder einer neuen - Dev-Instanz daraus) - nicht durch Nachrüsten in dieser Instanz. +- The user wants to set up a new, empty wiki instance (their own subject, a different person). +- Not for an existing clone of this (source) repo - see [bootstrap.md](bootstrap.md). +- There is no way back: `dist export` deliberately and permanently leaves out + `instructions/dev/` (stack development itself, including the vendored `commonplace/` knowledge + base). Anyone who wants to develop the resulting instance's stack further does that in the + origin repo (or a new dev instance made from it) - not by retrofitting it into this instance. -## Schritte +## Steps -1. **Distribution exportieren**, im Quell-Repo: +1. **Export the distribution**, in the source repo: ```bash - tools/wikitool dist export + tools/wikitool dist export ``` - `` muss nicht existieren oder leer sein; der Befehl bricht sonst mit `ERROR` ab. Danach - für alle folgenden Schritte in `` arbeiten. + `` must not exist, or must be empty; otherwise the command aborts with `ERROR`. Work + inside `` for every step that follows. -2. **Git-Repo initialisieren:** +2. **Initialize the git repo:** ```bash git init -b main ``` - `-b main` ist Pflicht: `tools/wikitool publish` prüft beim tatsächlichen Push, ob der - ausgecheckte Branch dem Ziel-Branch entspricht (Default `main`), und lehnt sonst ab, um - nicht den falschen Branch zu veröffentlichen. + `-b main` is mandatory: on the actual push, `tools/wikitool publish` checks that the + checked-out branch matches the target branch (default `main`) and refuses otherwise, so that + the wrong branch is never published. -3. **Entscheidungspunkt - Identität.** Frage den Nutzer nach Namen und E-Mail-Adresse; rate sie - nie, und übernimm sie nie stillschweigend aus dem Quell-Repo (das ist eine andere Person, ein - anderes Projekt): +3. **Decision point - identity.** Ask the user for their name and email address; never guess + them, and never quietly carry them over from the source repo (that is a different person and + a different project): ```bash - git config user.name "" - git config user.email "" + git config user.name "" + git config user.email "" ``` - Das setzt zugleich den Autor jeder künftig angelegten Wiki-Seite: `tools/wikitool new` - löst `author:` über `$WIKI_AUTHOR` (Override) oder sonst `git config user.name` auf und - bricht mit `ERROR` ab, wenn beides fehlt - es gibt keinen stillen Platzhalter. + This also sets the author of every wiki page created from now on: `tools/wikitool new` + resolves `author:` from `$WIKI_AUTHOR` (an override) or else from `git config user.name`, and + aborts with `ERROR` when both are missing - there is no silent placeholder. -4. **Entscheidungspunkt - Remote.** Frage den Nutzer nach einer Remote-URL; ein rein lokales - Repo ist ein gültiger Endzustand: - - Genannt: `git remote add origin ` - - Nicht genannt: lokal bleiben - dann braucht **jeder** spätere `tools/wikitool publish` - ein `--no-push` (dessen Branch-Prüfung dabei ohnehin entfällt, siehe Schritt 2). +4. **Decision point - remote.** Ask the user for a remote URL; a purely local repo is a valid + end state: + - Given: `git remote add origin ` + - Not given: stay local - then **every** later `tools/wikitool publish` needs a `--no-push` + (which also drops its branch check, see step 2). -5. **Entscheidungspunkt - Autorenkonventionen.** Die Distribution bringt keine ausgefüllten - Konventionen mit, sondern `kb/CONVENTIONS.md.template` und je Collection ein - `kb//COLLECTION.md.template`. Beide **binden**, sobald sie übernommen sind, und beide - gehören dieser Instanz - deshalb liefert der Stack nur die Vorlage. Die eine Entscheidung - dahinter ist: **in welcher Sprache und in welchem Ton schreibt diese Instanz ihre Seiten?** +5. **Decision point - authoring conventions.** The distribution ships no filled-in conventions, + only `kb/CONVENTIONS.md.template` and one `kb//COLLECTION.md.template` per collection. + Both **bind** once adopted, and both belong to this instance - which is why the stack ships + the template alone. The one decision behind them is: **in which language and in what tone + does this instance write its pages?** - Ablauf: + Procedure: - 1. Die Collection-Contracts **und die Page-Type-Specs** übernehmen - Kopien, keine Frage an - den Nutzer, denn was dort steht ist als Ausgangspunkt unabhängig von der Sprache brauchbar: + 1. Adopt the collection contracts **and the page type-specs** - copies, no question to the + user, because what they say is usable as a starting point regardless of language: ```bash for template in kb/*/COLLECTION.md.template types/*.template; do @@ -85,110 +85,107 @@ bereit für den ersten `Ingest`. done ``` - Die `.template`-Dateien bleiben liegen; sie sind die Vorlage für den nächsten Export. + The `.template` files stay where they are; they are the source for the next export. - Unter `types/` betrifft das genau die Type-Specs mit `root: kb` - `entity`, `concept`, - `source`, `comparison` - samt ihrer `.schema.yaml`. Sie beschreiben Seiten, die *diese* - Instanz schreibt, also gehören sie ihr: Prosa, Template und Sprache dürfen umgeschrieben - werden. `instruction`, `lint-report` und `type-spec` beschreiben Stack-Artefakte und - kommen unverändert. + Under `types/` this covers exactly the type-specs with `root: kb` - `entity`, `concept`, + `source`, `comparison` - along with their `.schema.yaml`. They describe pages *this* + instance writes, so they belong to it: prose, template and language may all be rewritten. + `instruction`, `lint-report` and `type-spec` describe stack artifacts and arrive + unchanged. - 2. Den Nutzer nach der KB-Sprache fragen. `kb/CONVENTIONS.md.template` ist auf **Englisch** - voreingestellt; [kb-profiles.md](kb-profiles.md) hält daneben ein vollständiges - deutsches Profil bereit, und dessen Volltext ist die `kb/CONVENTIONS.md` des Quell-Repos. - Der Profilkatalog ist eine **Palette, kein Enum**: übernommen wird der Text *in* die - Instanzdatei, nicht ein Verweis auf den Katalog. + 2. Ask the user for the KB language. `kb/CONVENTIONS.md.template` defaults to **English**; + [kb-profiles.md](kb-profiles.md) additionally holds a complete German profile, whose full + text is the source repo's own `kb/CONVENTIONS.md`. The profile catalogue is a **palette, + not an enum**: what gets adopted is the text *into* the instance file, not a reference to + the catalogue. - 3. `kb/CONVENTIONS.md.template` nach `kb/CONVENTIONS.md` kopieren, entlang des gewählten - Profils ausfüllen - Sprache, Abschnittsnamen, Namensformen, Ton, Beziehungslabels, - Hedging-Regel - und dabei die Sentinel-Zeile (`wikitool:template-unfilled`) entfernen. - Die Platzhalter in geschweiften Klammern **sind** der Fragenkatalog. + 3. Copy `kb/CONVENTIONS.md.template` to `kb/CONVENTIONS.md`, fill it in along the chosen + profile - language, section names, naming forms, tone, relationship labels, hedging rule - + and remove the sentinel line (`wikitool:template-unfilled`) while doing so. The + placeholders in curly braces **are** the list of questions. - 4. Bei einer anderen Sprache als der des Quell-Repos: `german-terminology.md` löschen oder - durch das eigene Vokabular ersetzen - sie ist Material des deutschen Profils, nicht des - Stacks. + 4. For a language other than the source repo's: delete `german-terminology.md` or replace it + with your own vocabulary - it is material belonging to the German profile, not to the + stack. - 5. Den Nutzer nach dem Anwendungsgebiet fragen und daraus einen `source_type`-Vorschlag - ableiten. [kb-profiles.md](kb-profiles.md) hält dafür zwei ausformulierte Domänenprofile - als Anschauung bereit, neben dem Wert, den dieses Repo selbst nutzt. Der Vorschlag ist ein - **Startpunkt, keine Festlegung** - zum Setup-Zeitpunkt hat der Betreiber null Quellen und - rät seine Taxonomie, bevor er auch nur eine Datei gesehen hat, und das ist der - schlechteste Moment, ein Enum festzuzurren. Vorschlag umgesetzt heißt: das Enum in - `types/source.schema.yaml` **und** die passende `layout:`-Zeile je Wert in - `types/source.md` in derselben Bearbeitung setzen - eine ohne die andere lässt einen Wert - ohne Zielverzeichnis zurück. Der sichtbare Auffangwert (`unclassified`) bleibt in jedem - Vorschlag erhalten; er ist kein Sammelbecken, sondern das Fach für eine Quelle, deren - Kategorie noch nicht feststeht. Die Liste später erweitern oder das Fach leeren: - [evolve-subtypes.md](evolve-subtypes.md) - nicht Teil dieses Schritts, aber der Weg dahin, - sobald echtes Material vorliegt. + 5. Ask the user about the subject area and derive a `source_type` proposal from it. + [kb-profiles.md](kb-profiles.md) holds two worked domain profiles as illustration, beside + the value this repo uses itself. The proposal is a **starting point, not a commitment** - + at setup time the operator has zero sources and is guessing a taxonomy before having seen + a single file, which is the worst possible moment to pin an enum down. Carrying out the + proposal means setting the enum in `types/source.schema.yaml` **and** the matching + `layout:` line per value in `types/source.md` in the same edit - one without the other + leaves a value with no target directory. The visible catch-all (`unclassified`) survives + every proposal; it is not a dumping ground but the slot for a source whose category is not + settled yet. Extending the list later, or emptying that slot: + [evolve-subtypes.md](evolve-subtypes.md) - not part of this step, but the way there once + real material exists. - **Unverändert lassen:** `fidelity` und `authority` auf `source`-Seiten. Die sind - Stack-Vokabular, keine Instanzentscheidung - [kb-profiles.md](kb-profiles.md) sagt das im - selben Abschnitt. + **Leave unchanged:** `fidelity` and `authority` on `source` pages. Those are stack + vocabulary, not an instance decision - [kb-profiles.md](kb-profiles.md) says so in the + same section. - **Vor dem ersten Ingest entscheiden.** Die `sections:`-Namen in `kb/CONVENTIONS.md` sind die - Überschriften, die `xref` und `cite` in jede Seite schreiben; sie danach zu ändern ist eine - Migration jeder vorhandenen Seite (`section_aliases:` trägt die alten Namen, siehe + **Decide before the first ingest.** The `sections:` names in `kb/CONVENTIONS.md` are the + headings `xref` and `cite` write into every page; changing them afterwards is a migration of + every existing page (`section_aliases:` carries the old names, see [migrate-corpus.md](migrate-corpus.md)). - **Nichts davon liegt in einer Stack-Datei.** Der Compiler liest die Abschnittsnamen aus - `kb/CONVENTIONS.md`; die vier Page-Type-Specs gehören ab Schritt 1 dieser Instanz. Eine - anderssprachige Instanz übersetzt sie einfach - das ist kein lokaler Patch an etwas - Ausgeliefertem mehr, sondern Arbeit an den eigenen Dateien, und ein Upgrade nimmt sie ihr - nicht wieder weg. + **None of this lives in a stack file.** The compiler reads the section names from + `kb/CONVENTIONS.md`; the four page type-specs have belonged to this instance since step 1. An + instance in another language simply translates them - that is no longer a local patch to + something shipped, but work on its own files, and an upgrade does not take it away again. - Was der Stack von `types/` überhaupt noch verlangt, ist eine Zeile: es muss einen Type-Spec - mit `name: source` geben, dessen Schema `raw_files` fordert. Daran hängt der gesamte - `raw/`→`kb/`-Provenance-Pfad (`sources coverage`, `[^cite-id]`-Auflösung, `kb/provenance.md`), - und `docs verify` prüft genau das - nicht mehr. + What the stack still requires of `types/` is one line: there must be a type-spec with + `name: source` whose schema requires `raw_files`. The entire `raw/`→`kb/` provenance path + hangs on it (`sources coverage`, `[^cite-id]` resolution, `kb/provenance.md`), and + `docs verify` checks exactly that - no more. - Unverändert bleibt in jedem Fall die Regel, die dem Stack gehört: **jede Zeile einer Seite - ist Prosa oder Identifier, und nur Prosa wird übersetzt** ([kb/CONTRACT.md § Language and - identifiers](../kb/CONTRACT.md#language-and-identifiers)). Titel, Wikilink-Ziele, Cite-IDs, - Enum-Werte, Tags, Befehle und Pfade folgen keiner KB-Sprache. + What stays untouched in every case is the rule the stack owns: **every line of a page is + either prose or an identifier, and only prose is translated** ([kb/CONTRACT.md § Language and + identifiers](../kb/CONTRACT.md#language-and-identifiers)). Titles, wikilink targets, cite ids, + enum values, tags, commands and paths follow no KB language. - `tools/wikitool doctor` prüft das Ergebnis in Schritt 13 (`conventions`): eine fehlende - Datei ist ein `FAIL`, eine mit Sentinel oder ohne vollständigen `sections:`-Block ebenso. - `docs verify` prüft zusätzlich `profile:` und `required_by_stack:` auf jedem + `tools/wikitool doctor` checks the result in step 13 (`conventions`): a missing file is a + `FAIL`, and so is one carrying the sentinel or lacking a complete `sections:` block. + `docs verify` additionally checks `profile:` and `required_by_stack:` on every `COLLECTION.md`. -6. **Entscheidungspunkt - Personalization.** Die Distribution bringt - `USER.md.template` und `SOUL.md.template` mit, aber keine ausgefüllten Fassungen: wer diese - Instanz bedient und wie sie klingt, ist Eigentum genau dieser Instanz und wird nie aus dem - Quell-Repo übernommen. Beide Dateien werden ab jetzt in **jeder** Session gelesen, also - entstehen sie hier - nicht später bei Gelegenheit. +6. **Decision point - personalization.** The distribution ships `USER.md.template` and + `SOUL.md.template`, but no filled-in versions: who operates this instance and how it sounds + is the property of this instance alone and is never carried over from the source repo. Both + files are read in **every** session from now on, so they come into being here - not later, + when the occasion arises. - Ablauf, für `USER.md` und `SOUL.md` je einmal: + Procedure, once each for `USER.md` and `SOUL.md`: - 1. Das Template lesen. Seine Abschnitte **sind** der Fragenkatalog, in der Reihenfolge, in - der sie dort stehen. - 2. Den Nutzer entlang dieser Abschnitte befragen - `USER.md`: Name, Standort, Zeitzone, - primäre Rolle (rein beruflich), beruflicher Kontext, Familie/Zuhause, Hobbys, - Technik-Umgebung, aktive Projekte, bewusste Grenzen. `SOUL.md`: Persona-Name, Identität, - Mission, Weltbild, Judgment-Default, Standard, Ehrlichkeit, Stimme, Ausschlüsse. - 3. Die Antworten **wörtlich** übernehmen. Nicht deuten, nicht zu einer Erzählung - verdichten, nicht aus dem Gesprächsverlauf ableiten. Was der Nutzer nicht sagt, steht - nicht drin: einen Abschnitt lieber löschen als mit Plausiblem füllen. - 4. Das Ergebnis als `USER.md` bzw. `SOUL.md` schreiben und die Sentinel-Zeile - (`wikitool:template-unfilled`) dabei entfernen. Die `.template`-Dateien bleiben liegen - - sie sind die Vorlage für den nächsten Export, nicht Abfall dieses Schritts. + 1. Read the template. Its sections **are** the list of questions, in the order they appear. + 2. Interview the user along those sections - `USER.md`: name, location, time zone, primary + role (professional only), professional context, family/home, hobbies, technical + environment, active projects, deliberate boundaries. `SOUL.md`: persona name, identity, + mission, worldview, judgment default, standard, honesty, voice, exclusions. + 3. Take the answers **verbatim**. Do not interpret, do not compress into a narrative, do not + infer from the course of the conversation. What the user does not say does not go in: + better to delete a section than to fill it with something plausible. + 4. Write the result as `USER.md` and `SOUL.md` respectively, removing the sentinel line + (`wikitool:template-unfilled`) in the process. The `.template` files stay where they are - + they are the source for the next export, not this step's leftovers. - Zwei Fragen, die der Nutzer beantwortet und nicht der Agent: **den Persona-Namen** und - **welche Themen bewusst draußen bleiben** (Arbeitgeber, Mandanten, Gesundheit - was auch - immer). Beides raten heißt, es falsch zu haben. Für den Namen bringt der Stack einen - Startpunkt mit - **Thoth**, weil Chemenu Thoths Hauptkultort ist und Schrift, Maß und - Gedächtnis die Rolle beschreiben, die ein kompiliertes Wiki ausfüllt. Der Vorschlag wird - genannt, nicht eingesetzt: gefragt wird trotzdem, und ein anderer Name gewinnt. + Two questions the user answers rather than the agent: **the persona name** and **which topics + deliberately stay out** (employer, clients, health - whatever they are). Guessing either + means getting it wrong. For the name the stack ships a starting point - **Thoth**, because + Chemenu is Thoth's principal cult site and writing, measure and memory describe the role a + compiled wiki fills. The suggestion is named, not applied: the question is asked anyway, and + a different name wins. - Was diese Dateien **nicht** sind: eine Instruktionsquelle und eine Quelle im Sinne von - Invariante 3. Sie ändern keine Regel aus [AGENTS.md](../AGENTS.md), und eine Nutzeraussage - wandert daraus nie ohne den normalen Quelle/Provenance-Prozess nach `kb/`. + What these files are **not**: a source of instructions, and a source in the sense of + invariant 3. They change no rule from [AGENTS.md](../AGENTS.md), and a user's statement never + travels from them into `kb/` without the normal source/provenance process. - `tools/wikitool doctor` prüft das Ergebnis in Schritt 13 (`personalization`): eine fehlende - Datei ist ein `FAIL`, eine, die noch den Sentinel trägt, ebenso - ein umbenanntes Template - ist kein ausgefülltes. + `tools/wikitool doctor` checks the result in step 13 (`personalization`): a missing file is a + `FAIL`, and so is one still carrying the sentinel - a renamed template is not a filled-in + one. -7. **Werkzeugumgebung anlegen** (Details: [bootstrap.md](bootstrap.md)): +7. **Create the tool environment** (details: [bootstrap.md](bootstrap.md)): ```bash cd tools @@ -197,67 +194,63 @@ bereit für den ersten `Ingest`. cd .. ``` -8. **Skills publizieren:** +8. **Publish the skills:** ```bash tools/wikitool instructions sync ``` -9. **Entscheidungspunkt - Umgebung festhalten.** Die Distribution bringt - `ENVIRONMENT.md.template` mit: Harness, publizierte Skills, erreichbare MCP-Server, - Connectoren, Git-Remotes, wo CI läuft. Konstanten, die eine Session sonst jedes Mal neu - erfragt. +9. **Decision point - record the environment.** The distribution ships + `ENVIRONMENT.md.template`: harness, published skills, reachable MCP servers, connectors, git + remotes, where CI runs. Constants a session would otherwise ask about every time. - Anders als Schritt 6 ist dieser Schritt **optional** und kein Interview. Was aus dem - Checkout selbst ablesbar ist (`git remote -v`, das laufende Harness, die eben publizierten - Skills), trägt der Agent ein; nach dem Rest fragt er einmal und akzeptiert "weiß ich nicht" - als Antwort - ein leerer Abschnitt wird gelöscht, nicht mit Plausiblem gefüllt. Beim - Schreiben die Sentinel-Zeile (`wikitool:template-unfilled`) entfernen; das `.template` - bleibt liegen. + Unlike step 6, this step is **optional** and not an interview. Whatever can be read off the + checkout itself (`git remote -v`, the running harness, the skills just published) the agent + fills in; for the rest it asks once and accepts "I don't know" as an answer - an empty + section is deleted, not filled with something plausible. Remove the sentinel line + (`wikitool:template-unfilled`) when writing; the `.template` stays where it is. - Wird der Schritt übersprungen, läuft alles weiter: `doctor` meldet in Schritt 13 - `environment: absent (optional)`, kein `FAIL`. Die Datei ist gitignored und geht in keinen - Commit ein - sie beschreibt diesen Checkout, nicht das Repo. + If the step is skipped, everything still works: `doctor` reports + `environment: absent (optional)` in step 13, not a `FAIL`. The file is gitignored and enters + no commit - it describes this checkout, not the repo. -10. **Entscheidungspunkt - Telemetrie.** Der Default hängt am Installationsweg, nicht an - diesem Schritt: eine per `dist export` ausgelieferte Instanz - jede, die hier ankommt, ohne - Weg C (direkter Klon des Ursprungs-Repos) genommen zu haben - trägt eine - `.wikitool-release.json` und startet mit Telemetrie **aus**; niemand hat sie bestellt, und - `EVALS.md` liest ohnehin niemand, bevor die erste Datei geschrieben ist. Dieser Schritt - fragt nur, ob der Betreiber das umdrehen will. +10. **Decision point - telemetry.** The default follows the installation path, not this step: an + instance delivered via `dist export` - every instance that arrives here without having taken + route C (a direct clone of the origin repo) - carries a `.wikitool-release.json` and starts + with telemetry **off**; nobody asked for it, and nobody reads `EVALS.md` before the first + file is written anyway. This step only asks whether the operator wants to reverse that. - Den Nutzer einmal fragen: Telemetrie an? Falls ja, `.wikitool-telemetry.json` im - Repo-Root anlegen (pro Checkout, gitignored, kein `.template` - wie - `.wikitool-remotes.json`): + Ask the user once: telemetry on? If yes, create `.wikitool-telemetry.json` in the repo root + (per checkout, gitignored, no `.template` - like `.wikitool-remotes.json`): ```json { "enabled": true } ``` - `max_session_bytes` (Default 5 MiB) und `keep_sessions` (Default 250) sind optional in - derselben Datei; die meisten Instanzen brauchen sie nicht anzufassen. Falls nein, nichts - tun - der Default steht bereits auf aus, und keine Datei entsteht. `WIKI_TRACE` - überschreibt beide Richtungen weiterhin, falls eine einzelne Session abweichen soll. + `max_session_bytes` (default 5 MiB) and `keep_sessions` (default 250) are optional in the + same file; most instances need not touch them. If no, do nothing - the default is already + off, and no file is created. `WIKI_TRACE` still overrides in both directions, should a + single session need to differ. - `tools/wikitool doctor` meldet das Ergebnis in Schritt 13 (`telemetry`): an/aus, warum - (Installationsform, diese Datei, oder `WIKI_TRACE`), und die aktuelle Menge gegen beide - Deckel - nie ein `FAIL`, in beide Richtungen ist das ein gültiger Zustand. Mehr dazu: + `tools/wikitool doctor` reports the result in step 13 (`telemetry`): on/off, why + (installation form, this file, or `WIKI_TRACE`), and the current volume against both caps - + never a `FAIL`, since both directions are a valid state. More on this: [EVALS.md](../EVALS.md) § "Whether it runs at all". -11. **Session-Budget scopen** (Details: [session-setup.md](session-setup.md)): +11. **Scope the session budget** (details: [session-setup.md](session-setup.md)): ```bash export WIKITOOL_SESSION_ID="wiki-$(date +%s)" ``` -12. **Generierte Indizes erzeugen** - `dist export` liefert sie bewusst nicht mit: +12. **Build the generated indexes** - `dist export` deliberately does not ship them: ```bash tools/wikitool index rebuild tools/wikitool sources rebuild-index ``` -13. **Verifizieren**, in dieser Reihenfolge: +13. **Verify**, in this order: ```bash tools/wikitool doctor @@ -266,32 +259,32 @@ bereit für den ersten `Ingest`. tools/wikitool lint ``` - `doctor` muss ohne `FAIL` durchlaufen, bevor es weitergeht - ein `WARN` (z. B. kein Remote, - keine `WIKITOOL_SESSION_ID`) ist kein Blocker. Ein `FAIL` benennt sein eigenes Fix-Kommando; - das ausführen und `doctor` erneut aufrufen. + `doctor` must run through without a `FAIL` before anything continues - a `WARN` (no remote, + no `WIKITOOL_SESSION_ID`, say) is not a blocker. A `FAIL` names its own fix command; run it + and call `doctor` again. -14. **Ersten Commit anstoßen:** +14. **Make the first commit:** ```bash tools/wikitool publish --message "chore: initial instance setup" ``` - Das Mass-Update-Gate greift hier erwartungsgemäß: eine frische Distribution besteht aus weit - mehr als den zehn gezählten Dateien, die den Schwellwert auslösen, also endet der Aufruf mit - Exit-Code 42. Die Ausgabe dem Nutzer **vollständig zeigen** und warten; sie enthält die - Dateiliste und die exakte `--confirm `-Zeile, die nach seiner Freigabe - veröffentlicht. Details zum Gate: [gates.md](gates.md). + The Mass-Update Gate fires here as expected: a fresh distribution consists of far more than + the ten counted files that trip the threshold, so the call ends with exit code 42. Show the + output to the user **in full** and wait; it contains the file list and the exact + `--confirm ` line that publishes once they approve. Details on the gate: + [gates.md](gates.md). -15. **Agent-Session neu starten.** Harnesses lesen die Skill-Verzeichnisse beim Start; erst - danach sind `wiki-ingest`, `wiki-query`, `wiki-manage`, `wiki-lint` und `wiki-status` - verfügbar. +15. **Restart the agent session.** Harnesses read the skill directories at startup; only + afterwards are `wiki-ingest`, `wiki-query`, `wiki-manage`, `wiki-lint` and `wiki-status` + available. ## Scope -Gilt nur für eine per `dist export` erzeugte, leere Distribution. Für einen bestehenden Clone -dieses Quell-Repos siehe [bootstrap.md](bootstrap.md) - dort existieren Git-Repo, Autor und -Inhalt bereits, und nur die Werkzeugumgebung (Schritt 7) plus die Skills (Schritt 8) fehlen. +Applies only to an empty distribution produced by `dist export`. For an existing clone of this +source repo see [bootstrap.md](bootstrap.md) - there the git repo, author and content already +exist, and only the tool environment (step 7) plus the skills (step 8) are missing. -Eine Ausnahme: Schritt 6 (Personalization) gilt auch für einen bestehenden Clone, der noch -kein `USER.md`/`SOUL.md` hat - dort als einzelner nachgeholter Schritt, nicht als ganzer -Ablauf. `bootstrap.md` verweist dafür hierher. +One exception: step 6 (personalization) also applies to an existing clone that has no +`USER.md`/`SOUL.md` yet - there as a single catch-up step, not as a whole procedure. +`bootstrap.md` points here for it. diff --git a/kb/CONVENTIONS.md b/kb/CONVENTIONS.md index ba03aec..923a820 100644 --- a/kb/CONVENTIONS.md +++ b/kb/CONVENTIONS.md @@ -40,11 +40,18 @@ those regions and nothing else. Nothing matches on this text. ## Language -Pages are written in **German**. This binds `kb/` and the authoring surface that shapes it - -the page type-specs `types/entity.md`, `types/concept.md`, `types/source.md` and -`types/comparison.md`. `raw/` is untouched ([raw/CONTRACT.md](../raw/CONTRACT.md)), and the -control plane stays English: `AGENTS.md`, the stage contracts, this file, `instructions/`, and -the type-specs for non-page artifacts. +Pages are written in **German** - the `language:` in this file's own frontmatter, and the one +place that value is written down. This binds `kb/`, and inside the page type-specs +(`types/entity.md`, `types/concept.md`, `types/source.md`, `types/comparison.md`) exactly the +parts that become page text: each one's `## Template` block, and the `layout:` titles that head a +catalog section. Their authoring guidance around those is instruction to an agent, so it follows +the control plane and stays English - the same prose/identifier cut +[kb/CONTRACT.md](CONTRACT.md#language-and-identifiers) makes inside a page, applied one level up. +`raw/` is untouched ([raw/CONTRACT.md](../raw/CONTRACT.md)). + +Two things follow from that value rather than being decided here, both stated once in +[AGENTS.md § File naming](../AGENTS.md#file-naming): the control plane stays English whatever an +instance writes its pages in, and an agent *speaks* the language named above. Which line is prose and which is an identifier - and therefore what is translated at all - is the contract's rule, not this file's: see diff --git a/kb/CONVENTIONS.md.template b/kb/CONVENTIONS.md.template index a6825a0..201a041 100644 --- a/kb/CONVENTIONS.md.template +++ b/kb/CONVENTIONS.md.template @@ -27,12 +27,20 @@ marker pair, so a rename re-renders words and nothing else. ## Language -Pages are written in **{language}**. This binds `kb/` and the authoring surface that shapes it - -the page type-specs `types/entity.md`, `types/concept.md`, `types/source.md` and -`types/comparison.md`, whose `## Template` blocks are the body skeleton every new page starts -from. `raw/` is untouched ([raw/CONTRACT.md](../raw/CONTRACT.md)), and the control plane stays -English: `AGENTS.md`, the stage contracts, this file, `instructions/`, and the type-specs for -non-page artifacts. +Pages are written in **{language}** - the `language:` in this file's own frontmatter, and the +one place that value is written down. This binds `kb/`, and inside the page type-specs +(`types/entity.md`, `types/concept.md`, `types/source.md`, `types/comparison.md`) exactly the +parts that become page text: each one's `## Template` block - the body skeleton every new page +starts from - and the `layout:` titles that head a catalog section. Their authoring guidance +around those is instruction to an agent, so it follows the control plane and stays English - the +same prose/identifier cut [kb/CONTRACT.md](CONTRACT.md#language-and-identifiers) makes inside a +page, applied one level up. Adopting this template into a non-English instance therefore means +translating those blocks, not the whole file. `raw/` is untouched +([raw/CONTRACT.md](../raw/CONTRACT.md)). + +Two things follow from that value rather than being decided here, both stated once in +[AGENTS.md § File naming](../AGENTS.md#file-naming): the control plane stays English whatever an +instance writes its pages in, and an agent *speaks* the language named above. Which line is prose and which is an identifier - and therefore what is translated at all - is the contract's rule, not this file's: see diff --git a/kb/concepts/COLLECTION.md b/kb/concepts/COLLECTION.md index 21bf93d..df2497b 100644 --- a/kb/concepts/COLLECTION.md +++ b/kb/concepts/COLLECTION.md @@ -35,32 +35,31 @@ tone, relationship labels, the confidence rubric. Neither is restated here. ## Types offered -`concept` (`tools/wikitool types describe concept`). Das Feld `concept_type:` -wählt die Area: +`concept` (`tools/wikitool types describe concept`). The `concept_type:` field +picks the area: -| Area | Hält | -|------|------| -| `architectures/` | Aufbau und Struktur: wie ein System geschnitten ist und warum die Schnitte dort liegen | -| `patterns/` | Wiederverwendbare Lösungsformen, die über mehr als einen Gegenstand hinweg gelten | -| `protocols/` | Kommunikationsprotokolle und Standards, in ihrer üblichen Schreibweise benannt | -| `workflows/` | Abläufe und Prozesse, die projektübergreifend wiederkehren | -| `decisions/` | Architektur- und Entwurfsentscheidungen (siehe unten) | -| `problems/` | Wiederkehrende Problemstellungen und ihre Lösungsansätze | +| Area | Holds | +|------|-------| +| `architectures/` | Shape and structure: how a system is cut up, and why the cuts fall where they do | +| `patterns/` | Reusable solution shapes that hold across more than one subject | +| `protocols/` | Communication protocols and standards, named in their usual spelling | +| `workflows/` | Procedures and processes that recur across projects | +| `decisions/` | Architectural and design decisions (see below) | +| `problems/` | Recurring problems and the approaches taken to them | -Das sind Areas, keine Collections: sie erben diesen Contract und tragen keine -eigene `COLLECTION.md`. +These are areas, not collections: they inherit this contract and carry no +`COLLECTION.md` of their own. -Die Zuordnung trifft niemand von Hand — sie steht als `layout:` in -`types/concept.md`, und `wikitool new` legt eine neue Seite direkt dort ab. -Eine Seite, die anderswo liegt, meldet `wikitool lint` als *misplaced*; -`wikitool move --page ""` bringt sie an ihren berechneten Ort. +Nobody assigns them by hand — the mapping is the `layout:` in +`types/concept.md`, and `wikitool new` puts a new page straight there. A page +sitting anywhere else is reported by `wikitool lint` as *misplaced*; +`wikitool move --page ""` moves it to its computed location. -Die Aufteilung ist keine Geschmacksfrage, sondern das, was die Shard-Schwelle -des Katalogs überhaupt wirksam macht: `index rebuild` teilt **pro Area**, und -eine Collection ohne Areas teilt sich nie — mit 80 Seiten in einer einzigen -Tabelle war die Schwelle hier ein toter Wert. Keine der sechs -Areas liegt derzeit über der Schwelle, also bekommt auch keine einen eigenen -Shard; wächst eine hinein, passiert das ohne Zutun. +The split is not a matter of taste but what makes the catalog's shard threshold +effective at all: `index rebuild` splits **per area**, and a collection without +areas never splits — with 80 pages in a single table the threshold was a dead +value here. None of the six areas is currently above it, so none gets a shard of +its own; when one grows into it, that happens without anyone acting. ## Decisions diff --git a/kb/sources/COLLECTION.md b/kb/sources/COLLECTION.md index ec15ca0..bf7e518 100644 --- a/kb/sources/COLLECTION.md +++ b/kb/sources/COLLECTION.md @@ -26,38 +26,36 @@ renamed or dropped - its authoring rules below are the instance's, its existence ## Types offered `source` (`tools/wikitool types describe source`). Page titles carry the `Source - ` prefix, -applied automatically by `wikitool new source`. Das Feld `source_type:` wählt die Area - **ohne -Default**: `wikitool new source` verweigert ohne einen expliziten Wert. +applied automatically by `wikitool new source`. The `source_type:` field picks the area - **with +no default**: `wikitool new source` refuses without an explicit value. -| Area | Hält | -|------|------| -| `transcripts/` | Session-Transkripte: mitgeschriebener Dialog zwischen Mensch und Agent, oder zwischen Menschen | -| `analyses/` | Analyse-Output eines Modells über einen Gegenstand - kein Dialog, kein Protokoll, sondern eine eigenständige Einschätzung | -| `articles/` | Externe Artikel und Blogposts, mit `source_url:` | -| `documents/` | Eingelesene Dokumente, Handbücher, Spezifikationen | -| `notes/` | Echte eigene Notizen ohne Dialogform - Cheat Sheets, Merkzettel | -| `trackers/` | Exporte aus einem Issue-Tracker oder vergleichbaren System | -| `unclassified/` | Sichtbares Fach für eine Quelle, deren Kategorie noch nicht feststeht - beratender `lint`-Befund, kein Sammelbecken. Es wieder zu leeren, oder das Enum um einen neuen Wert zu erweitern: [instructions/evolve-subtypes.md](../../instructions/evolve-subtypes.md) | +| Area | Holds | +|------|-------| +| `transcripts/` | Session transcripts: recorded dialogue between a human and an agent, or between humans | +| `analyses/` | A model's analytical output about a subject - not dialogue, not a record, but an assessment in its own right | +| `articles/` | External articles and blog posts, with `source_url:` | +| `documents/` | Ingested documents, manuals, specifications | +| `notes/` | Genuinely own notes in no dialogue form - cheat sheets, reminders | +| `trackers/` | Exports from an issue tracker or comparable system | +| `unclassified/` | The visible slot for a source whose category is not settled yet - an advisory `lint` finding, not a dumping ground. Emptying it again, or extending the enum by a new value: [instructions/evolve-subtypes.md](../../instructions/evolve-subtypes.md) | -Das sind Areas, keine Collections: sie erben diesen Contract und tragen keine eigene -`COLLECTION.md`. Die Zuordnung trifft niemand von Hand - sie steht als `layout:` in -`types/source.md`, und `wikitool new` legt eine neue Seite direkt dort ab. Eine Seite, die -anderswo liegt, meldet `wikitool lint` als *misplaced*; `wikitool move --page "<Titel>"` bringt -sie an ihren berechneten Ort. +These are areas, not collections: they inherit this contract and carry no `COLLECTION.md` of +their own. Nobody assigns them by hand - the mapping is the `layout:` in `types/source.md`, and +`wikitool new` puts a new page straight there. A page sitting anywhere else is reported by +`wikitool lint` as *misplaced*; `wikitool move --page "<title>"` moves it to its computed +location. -**`analysis` gegen `document`:** die Unterscheidung läuft über die Autorschaft, nicht über den -Inhalt. Ein Modell, das über einen Gegenstand urteilt oder ihn zusammenfasst, ohne dass ein -Mensch oder eine Organisation dafür geradesteht, ist `analysis` - unabhängig davon, wie -artikelförmig der Text wirkt. Ein Handbuch, eine Spezifikation, eine Herstellerdoku ist -`document`, auch wenn ein Werkzeug sie generiert hat, solange eine Organisation die Aussage -verantwortet. Die Frage ist also "wer haftet für die Behauptung", nicht "wie liest sich der -Text". +**`analysis` versus `document`:** the distinction runs on authorship, not on content. A model +judging or summarizing a subject with no human or organization answering for it is `analysis` - +however article-shaped the text looks. A manual, a specification, a vendor document is +`document`, even where a tool generated it, as long as an organization is accountable for what +it says. The question is "who is liable for the claim", not "how does the text read". -Solange es diesen Default noch gab, fiel fast alles hierher in `notes/`, weil -`types/source.schema.yaml` `notes` als `default:` gesetzt hatte - der Compiler wählte das -Sammelbecken, sobald niemand widersprach. -22 der 29 damaligen Seiten waren tatsächlich Transkripte, Analysen oder Tracker-Exporte und -wurden per `wikitool touch --set source_type=…` umklassifiziert, bevor die Areas entstanden. +While that default still existed, nearly everything landed here in `notes/`, because +`types/source.schema.yaml` had `notes` as its `default:` - the compiler picked the dumping +ground whenever nobody objected. 22 of the 29 pages there at the time were in fact transcripts, +analyses or tracker exports, and were reclassified with `wikitool touch --set source_type=…` +before the areas existed. ## Provenance rules diff --git a/tools/CONTRACT.md b/tools/CONTRACT.md index ce59a29..0ee4c29 100644 --- a/tools/CONTRACT.md +++ b/tools/CONTRACT.md @@ -166,12 +166,12 @@ tools/wikitool <command> --help | Command | Purpose | |---------|---------| | `types list [--json]` | List every type-spec under `types/` (name, schema path, subtype field, description) - discover what page types exist without reading `types/*.md` directly | -| `types describe <name> [--json]` | Print one type's full contract: required/optional frontmatter fields with enums, its subtype field (if any), and its authoring body | +| `types describe <name> [--json]` | Print one type's full contract: required/optional frontmatter fields with enums, its subtype field (if any), and its authoring body. A type-spec over the `docs toc` threshold carries a generated table-of-contents region; it is stripped from this output rather than echoed, since the whole body is being handed over and a navigation aid into it would be noise | | `instructions sync [--force]` | Publish every `instructions/<name>/SKILL.md` into `.agents/skills/` and `.claude/skills/` as **copies**, and delete published skills whose source is gone. Both targets are gitignored, so a fresh clone runs this once - see `instructions/bootstrap.md`. Re-running is also how a drifted copy is repaired: the source always wins. `--force` is required only to replace a target directory that is not a published skill at all (no `SKILL.md` in it) | | `instructions verify` | Check the instruction layer: flat instructions validate against `types/instruction.schema.yaml`, each `SKILL.md` carries the frontmatter its harness reads, no `SKILL.md` carries a relative markdown link (`sync` copies it to a different depth than the source, so a `SKILL.md` references a target as a repo-root-relative plain path instead - see [instructions/CONTRACT.md](../instructions/CONTRACT.md) § "A skill's outbound reference is a plain path, not a link"), every published copy is byte-identical to its source, no instruction is left that nothing references, and nothing under `instructions/dev/` is referenced from outside it (a `<!-- dist:strip-start/end -->` block is exempt - see [instructions/CONTRACT.md](../instructions/CONTRACT.md)). Missing *every* copy is reported as "run sync", not as drift - that is a clean checkout | | `instructions list [--json]` | List the flat instructions with their descriptions. This is how the layer is discovered; `search` deliberately covers `kb/` only | | `docs verify` | Check the docs that mirror the code: every CLI command documented in this file's own § Commands table and, separately, in its § Error contracts table (both directions, checked per table, so a row dropped from one is not hidden by the same name surviving in the other, and only a name's presence in a row is checked, never the rest of that row's text), every directory under `kb/` has a `COLLECTION.md` and no directory outside it does, every collection declaring `profile:` and a `required_by_stack:` that agrees with the stack's own list, `kb/CONVENTIONS.md` naming all three tool-owned section headings if it exists at all, every stage contract present, no pre-migration `type: entity` blocks left in the contracts, the `.gitignore` canaries clear in both directions (nothing ignored under `raw/`/`kb/`, `incoming/` ignored, everything ignored under `reports/` and the published skill directories), and no `.md`/`.template` file `dist export` would ship citing an issue number - the tracker exists only in the origin repo, so such a number in a distributed instance is a reference its reader can neither resolve nor recognise as unresolvable (a `<!-- dist:strip-start/end -->` region is exempt: it is already gone from the text the check reads, which is the export plan's, not the working tree's), every reference file `docs toc` covers carrying the current table-of-contents region for its own headings - missing and stale are one check, because the generator is idempotent - and every relative markdown link in one of those same reference files resolving to a file that actually exists (a target's `#anchor` suffix is stripped first; code fences and inline code spans are masked before scanning, so a passage showing link syntax as an example is not mistaken for a real reference). The name is about documentation parity, not about the `docs/` directory - it neither reads nor requires one, the same way `kb/` predates the collection it now checks | -| `docs toc [--apply]` | Create, refresh or remove the generated table-of-contents region (`<!-- wikitool:toc -->` ... `<!-- /wikitool:toc -->`, placed after the title and before the first `##`) on every reference file over 100 lines that `AGENTS.md`, a stage/collection contract, or the flat `instructions/**.md` form covers - the scope Anthropic's skill-authoring guidance names for a file previewed rather than read in full. Dry-run by default (prints which files would change); `--apply` writes. `docs verify` checks the result stays current the same way it checks every other generated-from-code copy | +| `docs toc [--apply]` | Create, refresh or remove the generated table-of-contents region (`<!-- wikitool:toc -->` ... `<!-- /wikitool:toc -->`, placed after the title and before the first `##`) on every reference file over 100 lines, in the scope Anthropic's skill-authoring guidance names for a file previewed rather than read in full: `AGENTS.md`, every stage contract, `kb/CONVENTIONS.md`, every `kb/*/COLLECTION.md`, every flat `instructions/**.md` file, every `types/*.md` type-spec, and every `docs/` page. Computed from those categories rather than listed, so a file added later is in scope without a code change. `SKILL.md` is the one exception, and the same guidance is why: it places a skill body on the loading level that is read whole when the skill triggers, and aims its own TOC advice at the bundled reference files a skill points *at*. Human docs (`README.md`, `CHANGES.md`, `EVALS.md`, `INSTALL.md`, `tools/README.md`) are out of scope because AGENTS.md § File naming says no agent loads them as instruction. Dry-run by default (prints which files would change); `--apply` writes. `docs verify` checks the result stays current the same way it checks every other generated-from-code copy | ### Telemetry @@ -189,7 +189,7 @@ tools/wikitool <command> --help | `version show [--json]` | Print this instance's stack version and where it came from (development tree, or a distribution with its export date and origin). Bare `wikitool version` is an alias for this. Read-only, offline, and **exempt from the Iteration Budget Gate** | | `version check [--url U] [--timeout S] [--json]` | Ask the origin's release feed whether a newer stack exists, and whether the step crosses a compatibility boundary (`state: current\|update\|migration\|ahead`). **The only command in `wikitool` that makes a network call** - never reached implicitly from another command, needs no key, times out, and reports an unreachable feed as an error rather than as "up to date". The feed is `$WIKITOOL_UPDATE_URL`, else the release stamp's, else the built-in origin; `$WIKITOOL_UPDATE_TOKEN` is only needed if that feed is not readable anonymously. Read-only and exempt from the budget gate | | `version notes [--version X.Y.Z]` | Print one version's `CHANGES.md` entry, for use as release notes (default: this tree's `VERSION`). Read-only and exempt from the budget gate | -| `version bump --major\|--minor\|--patch --title "<...>" [--impact high\|medium\|low] [--breaking "<what breaks>"] [--no-migration "<reason>"] [--migration-required] [--dry-run]` | Raise or continue the **one running candidate** between two releases - `VERSION` gets a `-beta.N` suffix, never a second fresh number per bump. `--major/--minor/--patch` is **max-wins escalation** against the last release (patch < minor < major): a `--patch` on a MINOR candidate only advances `N`, and escalation never steps back down. Opens the matching `CHANGES.md` entry on the first bump of a candidate (heading, date, author, and a machine-managed `<!-- wikitool:bumps -->` list of every `--title` collected so far, graded by `--impact`, default `medium`) and updates that same entry in place on every later bump of the same candidate - one entry per candidate, not one per bump. The list renders grouped under `**High/Medium/Low impact**` headings (empty groups omitted), except when every bump so far is `medium`, where it stays the flat, ungrouped list the region always had - `version regrade` corrects a grade after the fact. Refuses more or fewer than one part, an empty title, an unknown `--impact`, and a `VERSION`/newest-changelog-entry mismatch. Compatibility follows the **leftmost non-zero component** of the candidate's base, which for this stack (at `1.0.0` and up) means MAJOR: PATCH is a fix, MINOR a compatible capability, MAJOR a version that is **not a drop-in replacement** - any hand-work on update, or a downgrade that no longer works. Whether content must be migrated is a second, independent question. The bump that first escalates a candidate past the boundary requires `--breaking "<what stops working>"` and, on top of it, a migration document targeting the candidate's base or `--no-migration "<reason>"`; both lines are written once, anchored just above the bump list, and persist over later bumps of the same candidate without being repeated, and both are refused on a bump that crosses nothing at all. A later bump of the same candidate that finds out `--no-migration` was wrong after all retracts that line with `--migration-required` instead of restating `--no-migration` - refused without a migration document already targeting the new base, and without an existing `--no-migration` line to retract. Which part a change earns stays a judgment call: the command enforces that a crossing documents itself, never that the part was chosen correctly | +| `version bump --major\|--minor\|--patch --title "<...>" [--impact high\|medium\|low] [--breaking "<what breaks>"] [--no-migration "<reason>"] [--migration-required] [--dry-run]` | Raise or continue the **one running candidate** between two releases - `VERSION` gets a `-beta.N` suffix, never a second fresh number per bump. `--major/--minor/--patch` is **max-wins escalation** against the last release (patch < minor < major): a `--patch` on a MINOR candidate only advances `N`, and escalation never steps back down. Opens the matching `CHANGES.md` entry on the first bump of a candidate (heading, date, author, and a machine-managed `<!-- wikitool:bumps -->` list of every `--title` collected so far, graded by `--impact`, default `medium`) and updates that same entry in place on every later bump of the same candidate - one entry per candidate, not one per bump. The list renders grouped under `**High/Medium/Low impact**` headings (empty groups omitted), except when every bump so far is `medium`, where it stays the flat, ungrouped list the region always had - `version regrade` corrects a grade after the fact. Refuses more or fewer than one part, an empty title, an unknown `--impact`, and a `VERSION`/newest-changelog-entry mismatch. Compatibility follows the **leftmost non-zero component** of the candidate's base, which for this stack (at `1.0.0` and up) means MAJOR: PATCH is a fix, MINOR a compatible capability, MAJOR a version that is **not a drop-in replacement** - any hand-work on update, or a downgrade that no longer works. Whether content must be migrated is a second, independent question. The bump that first escalates a candidate past the boundary requires `--breaking "<what stops working>"` and, on top of it, a migration document targeting the candidate's base or `--no-migration "<reason>"`; both are anchored just above the bump list, persist over later bumps of the same candidate without being repeated, and are refused on a bump that crosses nothing at all. The two then behave differently on a *second* crossing, because they answer different questions: a further `--breaking` **joins** the ones already recorded (one reason per crossing - rendered flat on the marker line while there is only one, as bullets under a bare marker from the second onward, and repeating a reason verbatim is a no-op), while a further `--no-migration` **replaces** the single line that says whether content has to change. A candidate crossing the boundary twice is the normal shape of a long-running one, and each crossing is a separate thing an operator has to act on; whether content migrates stays one yes/no about the candidate as a whole. There is deliberately no retraction path for a single accumulated `--breaking` reason - `--migration-required` retracts the migration line, and nothing retracts a breaking one. A later bump of the same candidate that finds out `--no-migration` was wrong after all retracts that line with `--migration-required` instead of restating `--no-migration` - refused without a migration document already targeting the new base, and without an existing `--no-migration` line to retract. Which part a change earns stays a judgment call: the command enforces that a crossing documents itself, never that the part was chosen correctly | | `version regrade [INDICES...] [--impact high\|medium\|low]` | List the running candidate's bump titles with their impact grade and 1-based rendered position (no arguments - the correction path for a `--impact` judgement made at bump time), or change one or more of them in a single call: `version regrade 3 7 --impact high` grades both against a single read of today's list, not position 3 first and then position 7 against whatever that produced. Touches only the topmost entry's bump list - never `VERSION`, never any other part of `CHANGES.md`. The bare listing is read-only and exempt from the Iteration Budget Gate, like `version notes`; a call with indices writes `CHANGES.md` and is counted like `version bump`. Refuses an index outside the rendered list's range, an unknown `--impact`, indices given without `--impact`, a missing `VERSION`/`CHANGES.md`, a `VERSION`/newest-changelog-entry mismatch, or a topmost entry with no bump list at all | | `version release [--title "<...>"] [--dry-run]` | Fix the running candidate: strip `VERSION`'s `-beta.N` suffix and close its `CHANGES.md` entry, ending the pre-release phase `version bump` started. Without `--title` the heading keeps whichever bump last set it; with it, the heading's title is replaced - the normal case for a candidate that collected several bump titles, since the entry wants a summarising heading rather than the most recent one. Leaves the entry's machine-managed bump-title list untouched, as the record of what happened. Refuses when the candidate collected two or more bumps and the entry still carries no summary paragraph (at least 200 non-whitespace characters) between the bump list and the first `### <bump title>` changeset heading; a candidate with exactly one bump is exempt, since there its own changeset already is the summary. `--dry-run` runs this check too and reports the same refusal. Commits nothing and pushes nothing (invariant 5) - the following `publish` moves `VERSION` onto `main`, which `release.yml` reacts to. Refuses when `VERSION` is already a release (no running candidate to fix), or when the changelog's newest entry does not match `VERSION` | diff --git a/tools/chemenu/commands/types_cmd.py b/tools/chemenu/commands/types_cmd.py index b04f42a..1d5f4e1 100644 --- a/tools/chemenu/commands/types_cmd.py +++ b/tools/chemenu/commands/types_cmd.py @@ -17,6 +17,7 @@ import json import typer +from chemenu import toc from chemenu.commands._util import fail from chemenu.types_core import UnknownType, describe_type, list_types @@ -88,4 +89,9 @@ def describe_type_command( typer.echo("") typer.echo("## Authoring guidance") - typer.echo(described["body"]) + # A type-spec over 100 lines carries a generated table-of-contents region + # (`chemenu/toc.py`), which serves whoever opens the file. Here it would be + # noise: this command already hands over the whole body, so there is + # nothing left for a navigation aid to navigate - only markers and a list + # of headings the reader is about to see anyway. + typer.echo(toc.strip_region(described["body"])) diff --git a/tools/chemenu/commands/version_cmd.py b/tools/chemenu/commands/version_cmd.py index 92bfdd4..b13098b 100644 --- a/tools/chemenu/commands/version_cmd.py +++ b/tools/chemenu/commands/version_cmd.py @@ -204,7 +204,7 @@ def bump_command( breaking: Optional[str] = typer.Option( None, "--breaking", - help="What stops working, for the bump that first escalates to a boundary crossing (recorded in CHANGES.md). Required there, refused on a bump that crosses nothing", + help="What stops working (recorded in CHANGES.md). Required on the bump that first escalates to a boundary crossing, optional on a later bump of the same crossing candidate - where it joins the reasons already recorded rather than replacing them. Refused on a bump that crosses nothing", ), no_migration: Optional[str] = typer.Option( None, @@ -241,9 +241,16 @@ def bump_command( version is not a drop-in replacement, whether or not any content moves - requires `--breaking "<what stops working>"`, and on top of that either a migration document for the new base or `--no-migration "<reason>"`. Both - lines are written into the entry once and then persist across every later - bump at the same stage: a follow-up bump need not repeat them, and passing - either on a bump that crosses nothing at all is refused. + are written into the entry and persist across every later bump at the same + stage, so a follow-up bump need not repeat them, and passing either on a + bump that crosses nothing at all is refused. + + A candidate can cross the boundary more than once, and the two flags part + ways there. A further `--breaking` **joins** the reasons already recorded - + each crossing is its own thing an operator has to act on, and replacing + meant the second one silently deleted the first. A further + `--no-migration` **replaces**: whether content has to change is one + question about the candidate as a whole, not one per crossing. A later bump of the same candidate that finds out `--no-migration` was wrong after all retracts it with `--migration-required` - write the diff --git a/tools/chemenu/tests/test_toc.py b/tools/chemenu/tests/test_toc.py index a1bce16..e4b1a4c 100644 --- a/tools/chemenu/tests/test_toc.py +++ b/tools/chemenu/tests/test_toc.py @@ -150,9 +150,10 @@ def test_stale_regions_is_false_right_after_upsert(): def test_target_files_matches_the_documented_scope(): - """Integration check against the real repo: the scope is AGENTS.md, every - stage contract, kb/CONVENTIONS.md, every COLLECTION.md, and the flat - `instructions/**.md` form - never a `SKILL.md`.""" + """Integration check against the real repo: every agent-loaded category + AGENTS.md § File naming names - AGENTS.md itself, every stage contract, + kb/CONVENTIONS.md, every COLLECTION.md, the flat `instructions/**.md` form, + every type-spec, every `docs/` page - with `SKILL.md` the one exception.""" from chemenu import config files = toc.target_files() @@ -164,5 +165,35 @@ def test_target_files_matches_the_documented_scope(): assert "types/type-spec.md" in relatives assert "instructions/CONTRACT.md" in relatives assert "instructions/dev/version-parts.md" in relatives # flat, still instructions/**.md + assert "types/source.md" in relatives # a page type-spec, not only the stage contract + assert "docs/version-model.md" in relatives assert not any(rel.endswith("SKILL.md") for rel in relatives) - assert not any(rel.startswith("types/") and rel != "types/type-spec.md" for rel in relatives) + + +def test_target_files_lists_type_spec_once_despite_two_sources(): + """`types/type-spec.md` is both a stage contract and a `types/*.md` file; + the set in `target_files` is what keeps that from being a special case.""" + from chemenu import config + + relatives = [f.relative_to(config.ROOT).as_posix() for f in toc.target_files()] + assert relatives.count("types/type-spec.md") == 1 + + +def test_strip_region_removes_what_types_describe_would_otherwise_echo(): + body = ( + "# A type\n\n" + f"{blocks.open_marker(toc.REGION_NAME)}\n## Contents\n\n- [X](#x)\n" + f"{blocks.close_marker(toc.REGION_NAME)}\n\n## X\n\nProse.\n" + ) + stripped = toc.strip_region(body) + assert toc.REGION_NAME not in stripped + assert "## Contents" not in stripped + assert "## X" in stripped and "Prose." in stripped + # The heading keeps the blank line the region used to sit in, rather than + # being welded to the line above it. + assert "# A type\n\n## X" in stripped + + +def test_strip_region_leaves_a_body_that_never_had_one_alone(): + body = "# A type\n\n## X\n\nProse.\n" + assert toc.strip_region(body) == body diff --git a/tools/chemenu/tests/test_version_cmd.py b/tools/chemenu/tests/test_version_cmd.py index e992a44..9882e94 100644 --- a/tools/chemenu/tests/test_version_cmd.py +++ b/tools/chemenu/tests/test_version_cmd.py @@ -444,6 +444,107 @@ def test_breaking_line_sits_above_the_bumps_region(tree): assert changes.index(version_mod.BREAKING_CHANGE_MARKER) < changes.index(version_mod._BUMPS_OPEN) +# --- version bump: accumulating --breaking ---------------------------------- + + +def test_a_single_breaking_reason_stays_on_the_marker_line(tree): + """The shape every entry written before accumulation existed carries.""" + version_cmd.bump_command( + major=True, minor=False, patch=False, title="Breaking", + breaking="the feed moved", no_migration="kb/ untouched", migration_required=False, + impact=None, dry_run=False, + ) + changes = (tree / "CHANGES.md").read_text(encoding="utf-8") + assert f"{version_mod.BREAKING_CHANGE_MARKER} the feed moved" in changes + + +def test_a_second_breaking_reason_joins_the_first_instead_of_replacing_it(tree): + """The defect this exists for: the second --breaking used to delete the + first, leaving the entry promising one break while shipping two.""" + version_cmd.bump_command( + major=True, minor=False, patch=False, title="First crossing", + breaking="the feed moved", no_migration="kb/ untouched", migration_required=False, + impact=None, dry_run=False, + ) + version_cmd.bump_command( + major=False, minor=True, patch=False, title="Second crossing", + breaking="the import name changed", no_migration=None, migration_required=False, + impact=None, dry_run=False, + ) + changes = (tree / "CHANGES.md").read_text(encoding="utf-8") + section = version_mod.changes_section(changes, Version.parse("2.0.0-beta.2")) + assert version_mod.breaking_reasons(section) == ["the feed moved", "the import name changed"] + assert "- the feed moved" in changes and "- the import name changed" in changes + # Still the line an operator acts on first, above a list that can run long. + assert changes.index(version_mod.BREAKING_CHANGE_MARKER) < changes.index(version_mod._BUMPS_OPEN) + + +def test_repeating_a_breaking_reason_verbatim_is_a_no_op(tree): + """So a re-run after an interrupted bump converges instead of writing the + same sentence twice.""" + for title in ("First", "Second"): + version_cmd.bump_command( + major=True, minor=False, patch=False, title=title, + breaking="the feed moved", no_migration="kb/ untouched", migration_required=False, + impact=None, dry_run=False, + ) + changes = (tree / "CHANGES.md").read_text(encoding="utf-8") + section = version_mod.changes_section(changes, Version.parse("2.0.0-beta.2")) + assert version_mod.breaking_reasons(section) == ["the feed moved"] + + +def test_breaking_reasons_reads_a_pre_accumulation_single_line_back(tree): + """An entry written by a build that predates accumulation round-trips: it + parses as one reason, and a later bump grows it to a list rather than + rewriting what was already there.""" + section = "## 2.0.0-beta.1 - 2026-09-01 - T\n\n**Breaking Change:** the feed moved\n\n" + assert version_mod.breaking_reasons(section) == ["the feed moved"] + grown = version_mod._add_breaking_reason(section, "the import name changed") + assert version_mod.breaking_reasons(grown) == ["the feed moved", "the import name changed"] + assert f"{version_mod.BREAKING_CHANGE_MARKER}\n- the feed moved\n" in grown + + +def test_no_migration_still_replaces_rather_than_accumulating(tree): + """It answers one yes/no question about the candidate as a whole, so a + second answer supersedes the first - unlike --breaking beside it.""" + version_cmd.bump_command( + major=True, minor=False, patch=False, title="First", + breaking="the feed moved", no_migration="kb/ untouched", migration_required=False, + impact=None, dry_run=False, + ) + version_cmd.bump_command( + major=False, minor=True, patch=False, title="Second", + breaking=None, no_migration="still nothing to change", migration_required=False, + impact=None, dry_run=False, + ) + changes = (tree / "CHANGES.md").read_text(encoding="utf-8") + assert changes.count(version_mod.MIGRATION_NONE_MARKER) == 1 + assert "kb/ untouched" not in changes + assert "still nothing to change" in changes + + +def test_retracting_no_migration_leaves_an_accumulated_breaking_list_intact(tree): + version_cmd.bump_command( + major=True, minor=False, patch=False, title="First", + breaking="the feed moved", no_migration="kb/ untouched", migration_required=False, + impact=None, dry_run=False, + ) + version_cmd.bump_command( + major=False, minor=True, patch=False, title="Second", + breaking="the import name changed", no_migration=None, migration_required=False, + impact=None, dry_run=False, + ) + _migration_document(tree, "2.0.0") + version_cmd.bump_command( + major=False, minor=False, patch=True, title="Turns out it migrates", + breaking=None, no_migration=None, migration_required=True, impact=None, dry_run=False, + ) + changes = (tree / "CHANGES.md").read_text(encoding="utf-8") + section = version_mod.changes_section(changes, Version.parse("2.0.0-beta.3")) + assert version_mod.MIGRATION_NONE_MARKER not in changes + assert version_mod.breaking_reasons(section) == ["the feed moved", "the import name changed"] + + # --- version regrade -------------------------------------------------------- diff --git a/tools/chemenu/toc.py b/tools/chemenu/toc.py index 60bfb30..4b573f3 100644 --- a/tools/chemenu/toc.py +++ b/tools/chemenu/toc.py @@ -20,16 +20,49 @@ the stage/collection contracts, the flat `instructions/**.md` files) is a precedent first. **Scope is computed, never a hand-picked list** - the same principle that -governs `wikitool` itself. `target_files()` walks the file-naming categories -AGENTS.md's own table calls agent-loaded reference material: `AGENTS.md`, -every stage contract, `kb/CONVENTIONS.md`, every `kb/*/COLLECTION.md`, and -every flat `instructions/**.md` file (a `SKILL.md` is excluded - it is loaded -whole by the harness, not previewed at a second hop; `instructions/CONTRACT.md` -§ "How much reasoning a step may carry" already treats a checklist read once -as the table of contents it replaced). Human docs (`README.md`, `CHANGES.md`, -`EVALS.md`, `INSTALL.md`, `tools/README.md`) are excluded too: the file-naming -table says they are "Never loaded by an agent as instruction," so the preview -mechanic this exists to mitigate does not apply to them. +governs `wikitool` itself. `target_files()` walks every file-naming category +AGENTS.md's own table calls agent-loaded: `AGENTS.md`, every stage contract, +`kb/CONVENTIONS.md`, every `kb/*/COLLECTION.md`, every flat `instructions/**.md` +file, every `types/*.md` type-spec, and every `docs/` page. One rule, and +exactly one exception below it - which is the whole point, because a scope +carrying several unexplained absences reads as an accident rather than a +decision, and did: `docs/` and the page type-specs sat outside it for no +recorded reason at all. + +`docs/` belongs in for the reason the threshold exists. AGENTS.md § File naming +calls it "Agents and humans | By link, or on explicit request" - agent-loaded, +at a second hop, which is precisely the preview mechanic +`instructions/CONTRACT.md` § "Reference depth" treats as real. The exclusion of +the human docs (`README.md`, `CHANGES.md`, `EVALS.md`, `INSTALL.md`, +`tools/README.md`) rests on a sentence that does not stretch to cover it: those +are "Never loaded by an agent as instruction," while a `docs/` page is not +loaded *as instruction* but is very much loaded. + +**The one exception is `SKILL.md`**, and the vendored skill-authoring guidance +is what puts it there rather than a judgment of ours. `commonplace/kb/work/ +skill-creator-distillation/sources/claude-code-skill-creator/SKILL.md:88-99` +sets out three loading levels and places the SKILL.md body on the second - "In +context whenever skill triggers (<500 lines ideal)" - while aiming its own TOC +advice at the third, "large reference files (>300 lines)", i.e. bundled +resources. The Codex source agrees by placement: its TOC bullet sits directly +under "Keep references one level deep from SKILL.md. All reference files should +link directly from SKILL.md" (`.../codex-skill-creator/SKILL.md:221-222`). +Neither asks a skill body to carry a table of contents, because neither expects +one to be previewed. `instructions/CONTRACT.md` § "How much reasoning a step +may carry" arrives at the same place from the other side, treating a checklist +read once as the table of contents it replaced. (Our longest skill is 254 +lines, so the <500 guidance costs us nothing either.) + +A type-spec is loaded whole too - `tools/wikitool types describe` prints its +entire authoring body - but it is *also* read as a file, by whoever edits it, +and that is the reading the threshold is about. So it carries a region like any +other reference file, and `types_cmd` strips the region back out of what +`describe` prints: the command already hands over the whole body, so a +navigation aid into it would be noise in the output and nothing else. + +The threshold's own provenance is worth recording, because the two sources +disagree: Codex says 100 lines, Claude Code says 300. This stack took the +stricter number. """ from __future__ import annotations @@ -80,7 +113,13 @@ def target_files() -> list[Path]: `instructions/<name>.md` form (`instructions/CONTRACT.md` says the `dev/` split is orthogonal to Linked/Manual, not a different file shape), so there is no separate rule for them to fall out of - and no hand-picking - for a future file under either to be missed. + for a future file under either to be missed. `types/` and `docs/` are + walked for the same reason: a type or a rationale page added later is in + scope by construction, not by someone remembering this function. + + `types/type-spec.md` arrives twice - once as a stage contract, once from + the `types/` walk - and the set at the bottom is what makes that a + non-issue rather than something to special-case. """ files: list[Path] = [config.ROOT / "AGENTS.md"] files += [config.ROOT / rel for rel in _STAGE_CONTRACTS] @@ -96,6 +135,10 @@ def target_files() -> list[Path]: for path in instructions_dir.rglob("*.md") if path.name != "SKILL.md" ) + for subdir in ("types", "docs"): + directory = config.ROOT / subdir + if directory.is_dir(): + files += sorted(directory.rglob("*.md")) return sorted({f for f in files if f.is_file()}) @@ -116,6 +159,36 @@ def body_without_region(text: str) -> str: return stripped.rstrip("\n") + "\n" if stripped else stripped +_REGION_WITH_PADDING_RE = re.compile( + r"\n*" + + re.escape(blocks.open_marker(REGION_NAME)) + + r".*?" + + re.escape(blocks.close_marker(REGION_NAME)) + + r"\n*", + re.DOTALL, +) + + +def strip_region(text: str) -> str: + """`text` with the generated TOC region removed, for a caller that is + handing the whole body over anyway. + + `types describe` is the one such caller: it prints a type-spec's entire + authoring body, so the region's markers and heading list would be noise in + its output rather than a way into anything. Distinct from + `body_without_region`, which exists to *measure* a body against the line + threshold and therefore also canonicalizes the trailing newline. + + Takes the surrounding blank lines with it and puts one back, rather than + calling `blocks.strip`: that collapses the padding to a single newline, + which is the right answer when the result is about to be rebuilt from + scratch (`upsert` does exactly that) and the wrong one here, where the + output is printed as-is - it would leave the following `##` heading welded + to the paragraph above it. + """ + return _REGION_WITH_PADDING_RE.sub("\n\n", text) + + def needs_toc(text: str) -> bool: return len(body_without_region(text).splitlines()) > THRESHOLD diff --git a/tools/chemenu/version.py b/tools/chemenu/version.py index 58ce8c0..0f87559 100644 --- a/tools/chemenu/version.py +++ b/tools/chemenu/version.py @@ -583,9 +583,11 @@ def regrade(text: str, version: "Version", updates: dict[int, str]) -> str: def _set_marker_line(section: str, marker: str, line: str) -> str: """Add or replace the one-line `marker ...` paragraph in `section`. - Used for the breaking-change and no-migration lines, which - unlike the - bumps list - are not accumulated: a later bump that repeats `--breaking` - restates it rather than growing a list nobody would read as history. + Used for the no-migration line, which - unlike the bumps list and unlike + the breaking-change paragraph below - is **not** accumulated: it answers + one yes/no question about the candidate as a whole ("does content have to + change?"), so a second answer replaces the first rather than joining it, + and `_clear_marker_line` is its retraction path. Anchored just above the bumps region (not below it, as before Gitea #95): with a graded, potentially 30-line list, the line an operator most needs to act on stayed the deepest thing in the entry otherwise. @@ -599,6 +601,73 @@ def _set_marker_line(section: str, marker: str, line: str) -> str: return section.rstrip() + f"\n\n{line}\n" +# The breaking-change paragraph, matched whole: the marker line plus any `- ` +# bullets under it. `_set_marker_line`'s `^marker.*$` reaches the first line +# only, which is exactly wrong for a form that can carry bullets beneath it. +_BREAKING_PARAGRAPH_RE = re.compile( + rf"^{re.escape(BREAKING_CHANGE_MARKER)}.*(?:\n-[ \t].*)*$", re.MULTILINE +) + + +def breaking_reasons(section: str) -> list[str]: + """The breaking-change paragraph parsed back into one reason per crossing, + in written order. + + Two shapes read the same way, which is what lets an entry written before + accumulation existed round-trip untouched: `**Breaking Change:** <reason>` + is one reason, and a bare `**Breaking Change:**` followed by `- ` bullets + is one reason per bullet. Same "flat while there is only one of them" + trick `_bumps_block` plays with its impact groups, and for the same + reason - the common case keeps the shape it always had. + """ + match = _BREAKING_PARAGRAPH_RE.search(section) + if not match: + return [] + lines = match.group(0).splitlines() + head = lines[0][len(BREAKING_CHANGE_MARKER):].strip() + reasons = [head] if head else [] + reasons += [line.strip()[2:].strip() for line in lines[1:]] + return [reason for reason in reasons if reason] + + +def _breaking_paragraph(reasons: list[str]) -> str: + """Render the breaking-change paragraph from one reason per crossing. + + One reason stays on the marker line - byte-identical to what every entry + written before accumulation carries. Two or more move to bullets under a + bare marker, because a single line holding two unrelated breakages reads + as one run-on sentence and an operator has to act on each separately. + """ + if len(reasons) == 1: + return f"{BREAKING_CHANGE_MARKER} {reasons[0]}" + bullets = "\n".join(f"- {reason}" for reason in reasons) + return f"{BREAKING_CHANGE_MARKER}\n{bullets}" + + +def _add_breaking_reason(section: str, reason: str) -> str: + """Append `reason` to the breaking-change paragraph, or start one. + + Accumulates rather than replaces: a candidate can cross the compatibility + boundary more than once (this is the normal shape of a long-running one), + and each crossing is a separate thing the operator of an existing instance + has to act on. Replacing meant the second `--breaking` silently deleted + the first - the entry then promised a single break while shipping two. + + Repeating a reason verbatim is a no-op, so a re-run after an interrupted + bump converges instead of writing the same sentence twice. + """ + existing = breaking_reasons(section) + if reason in existing: + return section + paragraph = _breaking_paragraph(existing + [reason]) + if existing: + return _BREAKING_PARAGRAPH_RE.sub(lambda _m: paragraph, section, count=1) + anchor = section.find(_BUMPS_OPEN) + if anchor != -1: + return section[:anchor] + f"{paragraph}\n\n" + section[anchor:] + return section.rstrip() + f"\n\n{paragraph}\n" + + def _clear_marker_line(section: str, marker: str) -> str: """Remove the one-line `marker ...` paragraph from `section`, if present. @@ -633,10 +702,15 @@ def _update_open_candidate( impact: str = DEFAULT_IMPACT, ) -> str: """Move the topmost entry's heading to `version`/`date`/`title`, append - `(impact, title)` to its machine-managed bump list, and set the + `(impact, title)` to its machine-managed bump list, and record the breaking/no-migration lines only where this call supplies them - see `insert_changes_entry`. + The two are recorded differently on purpose: a `breaking_reason` **joins** + whatever crossings the candidate already recorded (`_add_breaking_reason`), + a `no_migration_reason` **replaces** the single line that answers whether + content has to change (`_set_marker_line`). + `migration_required` retracts an earlier `--no-migration` line instead of setting one - the two are mutually exclusive on a single bump, enforced by the caller (`version_cmd.bump_command`), not here.""" @@ -653,7 +727,7 @@ def _update_open_candidate( ) if breaking_reason: - section = _set_marker_line(section, BREAKING_CHANGE_MARKER, f"{BREAKING_CHANGE_MARKER} {breaking_reason}") + section = _add_breaking_reason(section, breaking_reason) if no_migration_reason: section = _set_marker_line(section, MIGRATION_NONE_MARKER, f"{MIGRATION_NONE_MARKER} - {no_migration_reason}") elif migration_required: @@ -684,7 +758,9 @@ def insert_changes_entry( candidate still open" here. A fresh entry gets the skeleton only: heading, date, author, - when a - compatibility boundary is crossed - the line saying what breaks, plus the + compatibility boundary is crossed - the line saying what breaks (one + crossing, so the flat one-line form; a candidate that crosses again + accumulates bullets there, see `_add_breaking_reason`), plus the line saying no content has to change where that applies, and then the machine-managed bump list (started with this one `(impact, title)` pair, for a candidate). The break comes first, above the bump list rather than diff --git a/types/comparison.md b/types/comparison.md index 8448244..c80560a 100644 --- a/types/comparison.md +++ b/types/comparison.md @@ -1,7 +1,7 @@ --- type: types/type-spec.md name: comparison -description: Strukturierter Typ für Vergleichsseiten, die mehrere Entities oder Ansätze gegenüberstellen +description: Structured type for comparison pages that set several entities or approaches against one another schema: types/comparison.schema.yaml base_dir: comparisons page_ref_fields: [entities, related] @@ -9,42 +9,46 @@ page_ref_fields: [entities, related] # Comparison -`comparison` ist der Typ für Seiten, die mehrere Entities, Werkzeuge, Technologien oder Ansätze systematisch anhand festgelegter Kriterien gegenüberstellen. Vergleichsseiten stützen Entscheidungen und machen Zielkonflikte zwischen Alternativen sichtbar. +`comparison` is the type for pages that set several entities, tools, technologies or approaches systematically against one another along fixed criteria. Comparison pages support decisions and make the trade-offs between alternatives visible. -## Wann zu verwenden +## When to use -- Vergleich mehrerer Technologien oder Frameworks für einen konkreten Einsatzzweck -- Analyse der Vor- und Nachteile verschiedener Werkzeuge oder Bibliotheken -- Bewertung alternativer Lösungswege für ein Problem -- Dokumentation eines Entscheidungsprozesses mit mehreren geprüften Optionen +- Comparing several technologies or frameworks for a concrete purpose +- Analysing the advantages and disadvantages of different tools or libraries +- Evaluating alternative ways of solving a problem +- Documenting a decision process with several options examined -## Wann NICHT zu verwenden +## When NOT to use -- Für die Dokumentation einer einzelnen Entity oder eines Concepts (dann `entity` bzw. `concept`) -- Für Zusammenfassungen von Quellmaterial (dann `source`) -- Für Seiten, die einen einzelnen Gegenstand behandeln statt mehrere zu vergleichen +- For documenting a single entity or concept (use `entity` or `concept` respectively) +- For summaries of source material (use `source`) +- For pages covering a single subject rather than comparing several ## Frontmatter -| Feld | Pflicht | Verwendung | +| Field | Required | Use | |---|---:|---| -| `type` | Ja | `types/comparison.md` | -| `tags` | Nein | Navigations-Tags zur Kategorisierung | -| `created` | Ja | Erstellungsdatum (YYYY-MM-DD) | -| `entities` | Ja | Titel der verglichenen Entities | -| `related` | Nein | Deklarierte ausgehende Kanten - je Subjekt eine `compares-with`-Kante, geschrieben von `wikitool xref add` | -| `summary` | Ja | Einzeiler für `kb/index.md` | +| `type` | Yes | `types/comparison.md` | +| `tags` | No | Navigation tags for categorization | +| `created` | Yes | Creation date (YYYY-MM-DD) | +| `entities` | Yes | Titles of the entities compared | +| `related` | No | Declared outbound edges - one `compares-with` edge per subject, written by `wikitool xref add` | +| `summary` | Yes | One-liner for `kb/index.md` | -## Autorenanweisungen +## Authoring guidance -- Ein Titel, der den Vergleich benennt (z. B. "Go vs Rust", "Kubernetes vs Docker Swarm"); er folgt den etablierten Namen der verglichenen Gegenstände, nicht der KB-Sprache (`kb/CONTRACT.md` § "Titles are identifiers", `kb/CONVENTIONS.md` §§ "Naming" und "Language") -- Klar darlegen, was verglichen wird und warum -- Eine Vergleichstabelle mit den Kriterien als Zeilen verwenden -- Eine Analyse, die die Tabelle auswertet statt sie zu wiederholen -- Eine Empfehlung, wo eine begründbar ist +- A title that names the comparison (e.g. "Go vs Rust", "Kubernetes vs Docker Swarm"); it follows the established names of the subjects compared, not the KB language (`kb/CONTRACT.md` § "Titles are identifiers", `kb/CONVENTIONS.md` §§ "Naming" and "Language") +- State clearly what is being compared and why +- Use a comparison table with the criteria as rows +- An analysis that evaluates the table rather than repeating it +- A recommendation wherever one can be justified ## Template +The block below is page material, so it is written in this instance's KB language +(`kb/CONVENTIONS.md` `language:`) rather than in the control plane's English - its headings +become the headings of every page `wikitool new comparison` scaffolds. + ```markdown # Comparison: {name} @@ -67,16 +71,16 @@ TODO: Auswertung des Vergleichs. Welche Muster zeigen sich? Was sind die entsche TODO: Falls möglich - was wann und für wen zu verwenden ist. Unter welchen Umständen ist welche Option die beste? ``` -`# Comparison:` bleibt als Präfix stehen - anders als bei `source` ist es kein `title_prefix`, -sondern reine Template-Konvention, und der Seitentitel selbst (`Go vs Rust`) trägt es nicht. -Fügt `wikitool xref` eine Beziehung hinzu, entsteht der toolgeführte Querverweis-Abschnitt; wie -er heißt, entscheidet die Instanz in `kb/CONVENTIONS.md` (`sections:`). +`# Comparison:` stays as a prefix - unlike `source` it is no `title_prefix` but a pure template +convention, and the page title itself (`Go vs Rust`) does not carry it. When `wikitool xref` adds +a relationship, the tool-managed cross-reference section appears; what it is called is the +instance's decision in `kb/CONVENTIONS.md` (`sections:`). --- -Ergänzende Hinweise: +Additional notes: -- Vergleichsseiten stützen Technologieauswahl und Architekturentscheidungen -- Die verglichenen Entities sollten alle als Wiki-Seiten existieren -- Die Kriterien sollten zur anstehenden Entscheidung passen -- Ein Vergleich bleibt auf einen konkreten Einsatzzweck oder Entscheidungskontext fokussiert +- Comparison pages support technology selection and architecture decisions +- The entities compared should all exist as wiki pages +- The criteria should fit the decision at hand +- A comparison stays focused on one concrete purpose or decision context diff --git a/types/concept.md b/types/concept.md index dd06de9..58a6c45 100644 --- a/types/concept.md +++ b/types/concept.md @@ -1,7 +1,7 @@ --- type: types/type-spec.md name: concept -description: Basistyp für Concept-Seiten - Architekturen, Muster, Protokolle, Abläufe, Entscheidungen oder Problemstellungen +description: Base type for concept pages - architectures, patterns, protocols, workflows, decisions or problems schema: types/concept.schema.yaml subtype_field: concept_type base_dir: concepts @@ -17,49 +17,63 @@ layout: # Concept -`concept` ist der Typ für abstrakte Ideen, Methodiken, Muster und Architekturentscheidungen. Concepts liefern die theoretische und methodische Grundlage, die Entities umsetzen oder befolgen. +`concept` is the type for abstract ideas, methodologies, patterns and architectural decisions. Concepts provide the theoretical and methodological ground that entities implement or follow. -## Wann zu verwenden +<!-- wikitool:toc --> +## Contents -- Dokumentation eines Architekturmusters oder Entwurfsansatzes -- Erläuterung eines Entwurfsmusters oder einer Programmiermethodik -- Beschreibung eines Kommunikationsprotokolls oder Standards -- Festhalten eines Ablaufs oder Prozesses, der über Projekte hinweg wiederkehrt -- Dokumentation einer Architekturentscheidung (ADR) oder Entwurfsentscheidung -- Analyse eines wiederkehrenden Problems und seiner Lösungen +- [When to use](#when-to-use) +- [When NOT to use](#when-not-to-use) +- [Frontmatter](#frontmatter) +- [Authoring guidance](#authoring-guidance) +- [Template](#template) +<!-- /wikitool:toc --> -## Wann NICHT zu verwenden +## When to use -- Für konkrete Dinge wie Projekte, Systeme oder Werkzeuge (dann `entity`) -- Für Quelldokumente oder eingelesenes Material (dann `source`) -- Für vergleichende Analysen konkreter Gegenstände (dann `comparison`) +- Documenting an architectural pattern or design approach +- Explaining a design pattern or a programming methodology +- Describing a communication protocol or standard +- Recording a workflow or process that recurs across projects +- Documenting an architectural decision (ADR) or design decision +- Analysing a recurring problem and its solutions + +## When NOT to use + +- For concrete things such as projects, systems or tools (use `entity`) +- For source documents or ingested material (use `source`) +- For comparative analyses of concrete subjects (use `comparison`) ## Frontmatter -| Feld | Pflicht | Verwendung | +| Field | Required | Use | |---|---:|---| -| `type` | Ja | `types/concept.md` | -| `concept_type` | Ja | Eines von: architecture, pattern, protocol, workflow, decision, problem | -| `tags` | Nein | Navigations-Tags zur Kategorisierung | -| `created` | Ja | Erstellungsdatum (YYYY-MM-DD) | -| `modified` | Ja | Datum der letzten Änderung (YYYY-MM-DD) | -| `related` | Nein | Titel verwandter Concepts und Entities | -| `sources` | Nein | Titel der Source-Seiten, die die Aussagen dieser Seite stützen | -| `provenance` | Ja | sourced, general oder mixed | -| `summary` | Ja | Einzeiler für `kb/index.md` | +| `type` | Yes | `types/concept.md` | +| `concept_type` | Yes | One of: architecture, pattern, protocol, workflow, decision, problem | +| `tags` | No | Navigation tags for categorization | +| `created` | Yes | Creation date (YYYY-MM-DD) | +| `modified` | Yes | Date last changed (YYYY-MM-DD) | +| `related` | No | Titles of related concepts and entities | +| `sources` | No | Titles of the source pages backing this page's claims | +| `provenance` | Yes | sourced, general or mixed | +| `summary` | Yes | One-liner for `kb/index.md` | -## Autorenanweisungen +## Authoring guidance -- Der Titel ist der kanonische Name des Concepts und folgt der etablierten Fachbezeichnung, nicht der KB-Sprache (`kb/CONTRACT.md` § "Titles are identifiers", `kb/CONVENTIONS.md` §§ "Naming" und "Language") -- Mit einer klaren Definition beginnen: was das Concept ist -- Beispiele geben, wo sie das Verständnis tragen -- Auf Entities verlinken, die das Concept umsetzen oder verwenden -- Bei `provenance: sourced` oder `mixed` harte Fakten inline mit einer `[^cite-id]`-Fußnote belegen - - `tools/wikitool cite add --page "<Title>" --source "Source - X"` erzeugt Id und Definition -- Wikipedia-Tonfall: sachlich, neutral, keine Werbesprache +- The title is the concept's canonical name and follows the established term of art, not the KB language (`kb/CONTRACT.md` § "Titles are identifiers", `kb/CONVENTIONS.md` §§ "Naming" and "Language") +- Open with a clear definition: what the concept is +- Give examples wherever they carry the understanding +- Link to the entities that implement or use the concept +- Under `provenance: sourced` or `mixed`, back hard facts inline with a `[^cite-id]` footnote - + `tools/wikitool cite add --page "<Title>" --source "Source - X"` mints the id and its definition +- Wikipedia register: factual, neutral, no marketing language ## Template +The block below is page material, so it is written in this instance's KB language +(`kb/CONVENTIONS.md` `language:`) rather than in the control plane's English - its headings +become the headings of every page `wikitool new concept` scaffolds. + ```markdown # {name} @@ -94,15 +108,15 @@ TODO: Anti-Muster, Warnungen oder Situationen, in denen es fehl am Platz ist - [[Related Concept 2]] ``` -Der Wert hinter `**Typ:**` bleibt der englische Enum-Wert - danach filtert `search --field`. -Fügt `wikitool xref` eine Beziehung hinzu, entstehen zusätzlich die beiden toolgeführten -Abschnitte für Beziehungen und Querverweise; wie sie heißen, entscheidet die Instanz in +The value behind `**Typ:**` stays the English enum value - that is what `search --field` filters +on. When `wikitool xref` adds a relationship, the two tool-managed sections for relationships and +cross-references appear as well; what they are called is the instance's decision in `kb/CONVENTIONS.md` (`sections:`). --- -Ergänzende Hinweise: +Additional notes: -- Concept-Seiten tragen den theoretischen Unterbau des Wikis -- Jeder Concept-Typ steht für eine andere Kategorie abstrakten Wissens -- Concepts sollten von den Entities aus verlinkt sein, die sie umsetzen oder verwenden +- Concept pages carry the wiki's theoretical underpinning +- Each concept type stands for a different category of abstract knowledge +- Concepts should be linked to from the entities that implement or use them diff --git a/types/entity.md b/types/entity.md index 1702770..c7d17be 100644 --- a/types/entity.md +++ b/types/entity.md @@ -1,7 +1,7 @@ --- type: types/type-spec.md name: entity -description: Basistyp für Entity-Seiten - Projekte, Systeme, Werkzeuge, Technologien oder Personen +description: Base type for entity pages - projects, systems, tools, technologies or people schema: types/entity.schema.yaml subtype_field: entity_type base_dir: entities @@ -16,47 +16,61 @@ layout: # Entity -`entity` ist der Typ für konkrete Dinge: Projekte, Systeme, Werkzeuge, Technologien oder Personen. Entities sind die primären Bausteine des Wissensgraphen. +`entity` is the type for concrete things: projects, systems, tools, technologies or people. Entities are the primary building blocks of the knowledge graph. -## Wann zu verwenden +<!-- wikitool:toc --> +## Contents -- Darstellung eines Softwareprojekts, einer Initiative oder eines Arbeitsvorhabens -- Dokumentation eines betriebenen Systems, Dienstes oder einer Infrastrukturkomponente -- Beschreibung eines CLI-Werkzeugs, Hilfsprogramms oder einer Programmbibliothek -- Festhalten von Informationen zu einer Sprache, einem Framework oder einem Protokoll -- Dokumentation einer Person, eines Teams oder einer Organisation +- [When to use](#when-to-use) +- [When NOT to use](#when-not-to-use) +- [Frontmatter](#frontmatter) +- [Authoring guidance](#authoring-guidance) +- [Template](#template) +<!-- /wikitool:toc --> -## Wann NICHT zu verwenden +## When to use -- Für abstrakte Konzepte, Muster oder Methodiken (dann `concept`) -- Für Quelldokumente oder eingelesenes Material (dann `source`) -- Für vergleichende Analysen (dann `comparison`) +- Representing a software project, an initiative or a piece of work +- Documenting a running system, service or infrastructure component +- Describing a CLI tool, utility or software library +- Recording information about a language, a framework or a protocol +- Documenting a person, a team or an organization + +## When NOT to use + +- For abstract concepts, patterns or methodologies (use `concept`) +- For source documents or ingested material (use `source`) +- For comparative analyses (use `comparison`) ## Frontmatter -| Feld | Pflicht | Verwendung | +| Field | Required | Use | |---|---:|---| -| `type` | Ja | `types/entity.md` | -| `entity_type` | Ja | Eines von: project, system, tool, technology, person | -| `tags` | Nein | Navigations-Tags zur Kategorisierung | -| `created` | Ja | Erstellungsdatum (YYYY-MM-DD) | -| `modified` | Ja | Datum der letzten Änderung (YYYY-MM-DD) | -| `related` | Nein | Titel verwandter Seiten (Entities, Concepts, Sources) | -| `sources` | Nein | Titel der Source-Seiten, die die Aussagen dieser Seite stützen | -| `provenance` | Ja | sourced, general oder mixed | -| `summary` | Ja | Einzeiler für `kb/index.md` | +| `type` | Yes | `types/entity.md` | +| `entity_type` | Yes | One of: project, system, tool, technology, person | +| `tags` | No | Navigation tags for categorization | +| `created` | Yes | Creation date (YYYY-MM-DD) | +| `modified` | Yes | Date last changed (YYYY-MM-DD) | +| `related` | No | Titles of related pages (entities, concepts, sources) | +| `sources` | No | Titles of the source pages backing this page's claims | +| `provenance` | Yes | sourced, general or mixed | +| `summary` | Yes | One-liner for `kb/index.md` | -## Autorenanweisungen +## Authoring guidance -- Der Titel ist der kanonische Name der Entity und folgt der etablierten Bezeichnung des Gegenstands, nicht der KB-Sprache (`kb/CONTRACT.md` § "Titles are identifiers", `kb/CONVENTIONS.md` §§ "Naming" und "Language") -- Die Hauptbeschreibung steht weit oben -- Auf verwandte Entities und Concepts verlinken, wo Beziehungen bestehen -- Bei `provenance: sourced` oder `mixed` harte Fakten inline mit einer `[^cite-id]`-Fußnote belegen - - `tools/wikitool cite add --page "<Title>" --source "Source - X"` erzeugt Id und Definition -- Wikipedia-Tonfall: sachlich, neutral, keine Werbesprache +- The title is the entity's canonical name and follows the subject's established designation, not the KB language (`kb/CONTRACT.md` § "Titles are identifiers", `kb/CONVENTIONS.md` §§ "Naming" and "Language") +- The main description goes near the top +- Link to related entities and concepts wherever relationships exist +- Under `provenance: sourced` or `mixed`, back hard facts inline with a `[^cite-id]` footnote - + `tools/wikitool cite add --page "<Title>" --source "Source - X"` mints the id and its definition +- Wikipedia register: factual, neutral, no marketing language ## Template +The block below is page material, so it is written in this instance's KB language +(`kb/CONVENTIONS.md` `language:`) rather than in the control plane's English - its headings +become the headings of every page `wikitool new entity` scaffolds. + ```markdown # {name} @@ -84,15 +98,15 @@ TODO: Ausführliche Informationen, nach sinnvollen Abschnitten gegliedert - [{today}] - Page created via wikitool ``` -Der Beziehungsabschnitt steht bewusst **nicht** im Template: er ist eine generierte Region, die -`wikitool xref` beim ersten Kanteneintrag zwischen Markern anlegt und aus `related:` neu -rendert. Ein Autor schreibt dort nie hinein. Der Wert hinter `**Typ:**` bleibt der englische -Enum-Wert - danach filtert `search --field`. +The relationships section is deliberately **not** in the template: it is a generated region that +`wikitool xref` creates between markers on the first edge entry and re-renders from `related:`. +An author never writes into it. The value behind `**Typ:**` stays the English enum value - that is +what `search --field` filters on. --- -Ergänzende Hinweise: +Additional notes: -- Entity-Seiten bilden den konkreten Bestand der Wissensbasis -- Jeder Entity-Typ (project, system, tool usw.) ist eine Ausprägung dieses Basistyps -- Die Beziehungen zwischen Entities tragen die Vernetzung des Wissensgraphen +- Entity pages form the concrete inventory of the knowledge base +- Every entity type (project, system, tool and so on) is a variant of this base type +- The relationships between entities carry the knowledge graph's interconnection diff --git a/types/lint-report.md b/types/lint-report.md index a76d041..0fc3fc3 100644 --- a/types/lint-report.md +++ b/types/lint-report.md @@ -65,4 +65,4 @@ Relevant Notes: - This type declares no `page_ref_fields:`: a report references pages in prose only, never through frontmatter. - It also declares no `base_dir:`, which is what keeps `reports/` out of `kb/` - and out of the index. \ No newline at end of file + and out of the index. diff --git a/types/source.md b/types/source.md index 3afcae9..83c3df2 100644 --- a/types/source.md +++ b/types/source.md @@ -1,7 +1,7 @@ --- type: types/type-spec.md name: source -description: Strukturierter Typ für Source-Seiten, die eingelesenes Rohmaterial erfassen und zusammenfassen +description: Structured type for source pages that record and summarize ingested raw material schema: types/source.schema.yaml subtype_field: source_type base_dir: sources @@ -20,71 +20,85 @@ layout: # Source -`source` ist der Typ für Seiten, die eingelesenes Rohmaterial zusammenfassen und katalogisieren. Source-Seiten sind die Brücke zwischen der `raw/`-Schicht (unveränderliche Quelldateien) und der `kb/`-Schicht (kompiliertes Wissen). Eine Source-Seite steht für **eine logische Quelle**, die mehrere Raw-Dateien umfassen kann. +`source` is the type for pages that summarize and catalogue ingested raw material. Source pages are the bridge between the `raw/` layer (immutable source files) and the `kb/` layer (compiled knowledge). One source page stands for **one logical source**, which may span several raw files. -## Wann zu verwenden +<!-- wikitool:toc --> +## Contents -- Zusammenfassung eines einzelnen externen Artikels, Dokuments oder einer Spezifikation -- Erfassung mehrerer zusammengehöriger Notizen oder Gesprächsprotokolle als eine Quelle -- Dokumentation eines eingelesenen PDFs, Handbuchs oder sonstigen Dokuments -- Festhalten von Informationen zu einem Bild oder Diagramm +- [When to use](#when-to-use) +- [When NOT to use](#when-not-to-use) +- [Frontmatter](#frontmatter) +- [Authoring guidance](#authoring-guidance) +- [Not Extracted](#not-extracted) +- [Template](#template) +<!-- /wikitool:toc --> -## Wann NICHT zu verwenden +## When to use -- Für kompiliertes Wissen (dann `entity` oder `concept`) -- Für vergleichende Analysen (dann `comparison`) -- Für originären Wiki-Inhalt, der nicht aus Rohmaterial abgeleitet ist +- Summarizing a single external article, document or specification +- Recording several related notes or meeting records as one source +- Documenting an ingested PDF, manual or other document +- Capturing information about an image or a diagram + +## When NOT to use + +- For compiled knowledge (use `entity` or `concept`) +- For comparative analyses (use `comparison`) +- For original wiki content not derived from raw material ## Frontmatter -| Feld | Pflicht | Verwendung | +| Field | Required | Use | |---|---:|---| -| `type` | Ja | `types/source.md` | -| `source_type` | Ja | Eines von: transcript, analysis, article, document, notes, tracker, unclassified - kein Default, siehe unten | -| `author` | Ja | Urheber des Quellmaterials | -| `raw_files` | Ja | Raw-Dateipfade, die diese Quelle **besitzt** - siehe "Eine Raw-Datei, ein Besitzer" unten | -| `fidelity` | Ja (im Werkzeug, nicht im Schema) | Wie treu die *Erfassung* ist: `verbatim`, `published`, `secondhand`, `nontextual` - Capture-Feld, siehe unten | -| `authority` | Ja (im Werkzeug, nicht im Schema) | Was das Material über seinen *Gegenstand* behaupten darf: `normative`, `reporting`, `opinion` - Capture-Feld, siehe unten | -| `source_url` | Nein | Ursprungs-URL bei externen Quellen | -| `source_language` | Nein | ISO-639-1-Code der Sprache des Rohmaterials, z. B. `de`, `en`, `fr` | -| `date` | Ja | Veröffentlichungs- oder Erstellungsdatum (YYYY-MM-DD) | -| `tags` | Nein | Navigations-Tags zur Kategorisierung | -| `entities` | Nein | Titel der in dieser Quelle erwähnten Entities | -| `concepts` | Nein | Titel der in dieser Quelle erwähnten Concepts | -| `summary` | Ja | Einzeiler für `kb/index.md` | +| `type` | Yes | `types/source.md` | +| `source_type` | Yes | One of: transcript, analysis, article, document, notes, tracker, unclassified - no default, see below | +| `author` | Yes | Originator of the source material | +| `raw_files` | Yes | Raw file paths this source **owns** - see "One raw file, one owner" below | +| `fidelity` | Yes (in the tool, not in the schema) | How faithful the *capture* is: `verbatim`, `published`, `secondhand`, `nontextual` - a capture field, see below | +| `authority` | Yes (in the tool, not in the schema) | What the material may claim about its *subject*: `normative`, `reporting`, `opinion` - a capture field, see below | +| `source_url` | No | Origin URL for external sources | +| `source_language` | No | ISO 639-1 code of the raw material's language, e.g. `de`, `en`, `fr` | +| `date` | Yes | Publication or creation date (YYYY-MM-DD) | +| `tags` | No | Navigation tags for categorization | +| `entities` | No | Titles of the entities mentioned in this source | +| `concepts` | No | Titles of the concepts mentioned in this source | +| `summary` | Yes | One-liner for `kb/index.md` | -## Autorenanweisungen +## Authoring guidance -- Der Titel beginnt mit "Source - ", gefolgt vom Namen der Quelle -- `source_type` hat **keinen Default** - `wikitool new source` verweigert ohne einen expliziten Wert. Ist die Kategorie unklar, `unclassified` setzen statt zu raten; das ist ein sichtbares Katalogfach mit beratendem `lint`-Befund, kein Sammelbecken. Was `analysis` von `document` trennt und welche Area welchen Wert hält: [kb/sources/COLLECTION.md](../kb/sources/COLLECTION.md) -- `raw_files` listet jede Raw-Datei, die diese Quelle abdeckt (eine Source-Seite pro logischer Quelle, nicht pro Datei) -- `fidelity` und `authority` sind **Capture-Felder** (`capture_fields:` oben): am Drop-Punkt erhoben, danach nicht mehr frei änderbar. `wikitool raw accept --fidelity <wert> --authority <wert>` verweigert ohne beide; ohne `--page` druckt es stattdessen die fertige `new source --set fidelity=... --set authority=...`-Zeile, die `new source` seinerseits ohne beide Werte verweigert. `wikitool touch --set fidelity=<wert>` schreibt nur, solange das Feld fehlt - steht bereits ein Wert, verweigert es und verweist auf `raw accept --replaces` als einzigen Korrekturweg (eine korrigierte Erfassung ist eine neue Edition, keine Bearbeitung). `unknown` ist backfill-only: nur `wikitool touch` darf es schreiben, nie `raw accept` oder `new source` - dieselbe Konstruktion wie bei `source_language` für Seiten, die vor dieser Regel entstanden sind. Was die Werte bedeuten und wie sie sich unterscheiden: [raw/CONTRACT.md](../raw/CONTRACT.md#getting-a-file-in-incoming) -- Bei externen Artikeln immer `source_url` auf die Ursprungs-URL setzen -- `source_language` auf die Sprache des Rohmaterials setzen, nicht auf die der Seite -- Die Seite wird in der KB-Sprache geschrieben, unabhängig von der Sprache der Quelle; wörtliche Passagen werden im Original zitiert (`kb/CONVENTIONS.md` § "Language") -- Kernaussagen im Abschnitt Summary zusammenfassen -- Handlungsbedarf in den Abschnitt Action Items -- Bewusst Weggelassenes in den Abschnitt Not Extracted - siehe unten -- Erwähnte Entities und Concepts unter Related Entities/Concepts verlinken +- The title starts with "Source - ", followed by the name of the source +- `source_type` has **no default** - `wikitool new source` refuses without an explicit value. Where the category is unclear, set `unclassified` rather than guessing; that is a visible catalog slot with an advisory `lint` finding, not a dumping ground. What separates `analysis` from `document`, and which area holds which value: [kb/sources/COLLECTION.md](../kb/sources/COLLECTION.md) +- `raw_files` lists every raw file this source covers (one source page per logical source, not per file) +- `fidelity` and `authority` are **capture fields** (`capture_fields:` above): recorded at the drop point and not freely changeable afterwards. `wikitool raw accept --fidelity <value> --authority <value>` refuses without both; without `--page` it instead prints the ready-made `new source --set fidelity=... --set authority=...` line, which `new source` in turn refuses without both values. `wikitool touch --set fidelity=<value>` only writes while the field is absent - where a value already stands, it refuses and points at `raw accept --replaces` as the one correction path (a corrected capture is a new edition, not an edit). `unknown` is backfill-only: only `wikitool touch` may write it, never `raw accept` or `new source` - the same construction as `source_language` uses for pages that predate this rule. What the values mean and how they differ: [raw/CONTRACT.md](../raw/CONTRACT.md#getting-a-file-in-incoming) +- For external articles, always set `source_url` to the origin URL +- Set `source_language` to the raw material's language, not the page's +- The page is written in the KB language, whatever language the source is in; verbatim passages are quoted in the original (`kb/CONVENTIONS.md` § "Language") +- Summarize the key claims in the summary section +- Put anything actionable in the action items section +- Put deliberate omissions in the not-extracted section - see below +- Link the entities and concepts mentioned under related entities/concepts ## Not Extracted -Die Entscheidung, dass Material *nicht* übernommen werden soll, ist nicht rekonstruierbar: nichts -im Repository kann sie neu herleiten, und `sources coverage` weiß nur, ob eine Raw-Datei von -irgendeiner Source-Seite beansprucht wird - nie, ob jemand über ihren Inhalt entschieden hat. -Bleibt das unaufgeschrieben, wird dieselbe Quelle bei jedem späteren Durchgang neu verhandelt. +The decision that material should *not* be taken over cannot be reconstructed: nothing in the +repository can re-derive it, and `sources coverage` only knows whether a raw file is claimed by +some source page - never whether anyone decided about its contents. Left unwritten, the same +source is renegotiated on every later pass. -- Jede bewusste Auslassung mit **Begründung** festhalten, nicht nur mit Dateinamen. -- Pflicht, wenn der Ingest über `instructions/ingest-large-tree.md` lief - auf beiden Achsen: - beim Tree-Ingest hält der Abschnitt fest, was aus dem Baum nicht übernommen wurde, bei einer - thematisch breiten Einzelquelle, welche genannten Gegenstände keine eigene Seite bekommen - haben und warum. Optional bei einer einzelnen kleinen Datei - aber ein leerer Abschnitt ist - immer noch besser als ein fehlender. -- Gehört auf die Source-Seite, nicht in `kb/log.md`: es ist eine Aussage über *diese* Quelle, - und das Log ist chronologisch, nicht quellenbezogen. +- Record every deliberate omission with a **reason**, not just a filename. +- Mandatory where the ingest ran through `instructions/ingest-large-tree.md` - on both axes: for + a tree ingest the section records what was not taken from the tree; for a thematically broad + single source, which named subjects got no page of their own, and why. Optional for a single + small file - but an empty section still beats a missing one. +- Belongs on the source page, not in `kb/log.md`: it is a statement about *this* source, and the + log is chronological rather than per-source. ## Template +The block below is page material, so it is written in this instance's KB language +(`kb/CONVENTIONS.md` `language:`) rather than in the control plane's English - its headings +become the headings of every page `wikitool new source` scaffolds. + ```markdown # Source: {name} @@ -121,24 +135,18 @@ TODO: 2-3 Absätze zu den Kernaussagen des Quellmaterials. {concepts|bullets} ``` -`# Source:` bleibt als Präfix stehen - es spiegelt den `title_prefix` und damit den Titel, unter -dem die Seite verlinkt und zitiert wird. Der Wert hinter `**Typ:**` bleibt der englische -Enum-Wert. Fügt `wikitool cite` ein Zitat hinzu, entsteht am Seitenende der toolgeführte -Fußnoten-Block; wie er heißt, entscheidet die Instanz in `kb/CONVENTIONS.md` (`sections:`). +`# Source:` stays as a prefix - it mirrors the `title_prefix` and with it the title the page is +linked and cited under. The value behind `**Typ:**` stays the English enum value. When +`wikitool cite` adds a citation, the tool-managed footnote block appears at the end of the page; +what it is called is the instance's decision in `kb/CONVENTIONS.md` (`sections:`). --- -Ergänzende Hinweise: +Additional notes: -- Source-Seiten sind der maßgebliche Katalog dessen, was an Rohmaterial eingelesen wurde -- **Eine Raw-Datei, ein Besitzer.** Eine Raw-Datei steht in genau einem `raw_files:` - diese Seite ist dafür verantwortlich, sie zusammengefasst zu halten. Beliebig viele Seiten dürfen sie per `[^cite-id]` **zitieren**; ein Zitat ist Wiederverwendung, `raw_files:` ist eine Wartungszuständigkeit. Bei zwei Anspruchstellern ist undefiniert, welche Seite bei einer Änderung der Raw-Datei nachgezogen werden muss - dann verrotten beide still -- Source-Seiten machen Wissen bis zum ursprünglichen Rohmaterial rückverfolgbar -- `raw_files:` enthält konkrete existierende Dateipfade, nie Verzeichnisse -- Eine `raw_files:`-Liste jenseits von etwa 15 Einträgen zeigt an, dass der Schnitt zu grob war - - die Quelle hätte per `instructions/ingest-large-tree.md` in mehrere Source-Seiten geteilt - werden müssen -- `entities:` plus `concepts:` jenseits von etwa 20 Einträgen ist das Gegenstück auf der anderen - Achse: nicht zu wenig geschnitten, sondern zu viel auf einmal kompiliert. Geteilt wird eine - solche Quelle nicht - eine Raw-Datei hat einen Besitzer -, sie hätte den Extract-Pass aus - `instructions/ingest-large-tree.md` § "A broad source is not cut" gebraucht, damit nicht jeder - genannte Gegenstand eine Seite bekommt +- Source pages are the authoritative catalogue of what raw material has been ingested +- **One raw file, one owner.** A raw file appears in exactly one `raw_files:` - that page is responsible for keeping it summarized. Any number of pages may **cite** it via `[^cite-id]`; a citation is reuse, `raw_files:` is a maintenance responsibility. With two claimants it is undefined which page has to be brought up to date when the raw file changes - and then both rot quietly +- Source pages make knowledge traceable back to the original raw material +- `raw_files:` holds concrete existing file paths, never directories +- A `raw_files:` list beyond roughly 15 entries indicates the cut was too coarse - the source should have been split into several source pages via `instructions/ingest-large-tree.md` +- `entities:` plus `concepts:` beyond roughly 20 entries is the counterpart on the other axis: not cut too little, but compiled too much at once. Such a source is not split - a raw file has one owner - it needed the extract pass from `instructions/ingest-large-tree.md` § "A broad source is not cut", so that not every named subject gets a page