Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 502971d147 | |||
| 9843df99d3 |
@@ -221,6 +221,16 @@ jobs:
|
||||
for personal in USER SOUL; do
|
||||
grep -v 'wikitool:template-unfilled' "$personal.md.template" > "$personal.md"
|
||||
done
|
||||
# The authoring conventions ride the same split one directory down,
|
||||
# and are stubbed the same way: what is under test is that the export
|
||||
# carries the templates and that `doctor`/`docs verify` accept an
|
||||
# adopted one, not what a person would write into them. The collection
|
||||
# contracts are adopted verbatim - the shipped text is a working
|
||||
# default, unlike a personalization file.
|
||||
grep -v 'wikitool:template-unfilled' kb/CONVENTIONS.md.template > kb/CONVENTIONS.md
|
||||
for template in kb/*/COLLECTION.md.template; do
|
||||
cp "$template" "${template%.template}"
|
||||
done
|
||||
python3 -m venv tools/.venv
|
||||
tools/.venv/bin/pip install --quiet -r tools/requirements.txt
|
||||
tools/wikitool instructions sync
|
||||
|
||||
+8
-2
@@ -1,5 +1,11 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"kb_version": "1.0.0",
|
||||
"applied": []
|
||||
"kb_version": "3.0.0",
|
||||
"applied": [
|
||||
{
|
||||
"migration": "3.0.0-authoring-conventions",
|
||||
"at": "2026-09-02",
|
||||
"pages": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -77,7 +77,8 @@ What a file is called says who it is for and how it is loaded. This is a rule, n
|
||||
| `SOUL.md` | Agents | Always, every session |
|
||||
| `ENVIRONMENT.md` | Agents | Every session, **if it exists** - the one optional file in this table. Not committed: it describes one checkout, not the repo |
|
||||
| `<stage>/CONTRACT.md` | Agents | When writing in that stage |
|
||||
| `kb/<collection>/COLLECTION.md` | Agents | When writing in that collection |
|
||||
| `kb/CONVENTIONS.md` | Agents | When writing any page - it holds what *this* instance decided about authoring (language, section headings, naming, tone, relationship labels, confidence rubric), where `kb/CONTRACT.md` holds what the stack enforces. Instance-owned: a distribution ships only the `.template` |
|
||||
| `kb/<collection>/COLLECTION.md` | Agents | When writing in that collection. Instance-owned in the same way, and declares in frontmatter which profile it adopted |
|
||||
| `instructions/<name>.md` | Agents | By link, or on explicit request |
|
||||
| `instructions/<name>/SKILL.md` | Agents | By the harness, once published |
|
||||
| `types/<name>.md` | Agents + validator | Via `tools/wikitool types describe` |
|
||||
@@ -104,6 +105,16 @@ and `SOUL.md.template`; the Personalization step of
|
||||
writes the real files. `tools/wikitool doctor` FAILs on a missing one, and on one still
|
||||
carrying the template's sentinel.
|
||||
|
||||
The same `.template` split runs one directory down, for authoring rather than for voice.
|
||||
`kb/CONVENTIONS.md` and each `kb/<name>/COLLECTION.md` bind every page and belong to the
|
||||
instance, so a distribution ships them as templates and the KB-language step of
|
||||
[instructions/setup-instance.md](instructions/setup-instance.md) fills them in, out of a
|
||||
catalogue of ready-made profiles it routes to; `doctor` FAILs on a missing or unfilled
|
||||
`kb/CONVENTIONS.md` the same way.
|
||||
|
||||
Unlike `USER.md`, these two *are* a source of rules: they are as binding as `kb/CONTRACT.md`.
|
||||
What differs is ownership, not authority.
|
||||
|
||||
## Environment
|
||||
|
||||
`ENVIRONMENT.md` records what *this checkout* works through - harness, published skills,
|
||||
@@ -142,15 +153,17 @@ Alongside it, not part of it: `instructions/` (what agents are told to do) and t
|
||||
|-------|----------|--------|
|
||||
| `raw/` | [raw/CONTRACT.md](raw/CONTRACT.md) | Immutability, directory routing, untrusted-content rule |
|
||||
| `types/` | [types/type-spec.md](types/type-spec.md) | Type-spec anatomy, placement, adding a type, template variables |
|
||||
| `kb/` | [kb/CONTRACT.md](kb/CONTRACT.md) | Collections, naming, tone, linking, provenance, confidence |
|
||||
| `kb/` | [kb/CONTRACT.md](kb/CONTRACT.md) + `kb/CONVENTIONS.md` | What the stack enforces about a page (collections, linking, provenance, confidence machinery), and beside it what this instance decided (language, naming, tone, labels, rubric) |
|
||||
| `reports/` | [reports/CONTRACT.md](reports/CONTRACT.md) | Why reports and traces are generated, gitignored, and carried into `kb/log.md` |
|
||||
| `work/` | [work/CONTRACT.md](work/CONTRACT.md) | Workshop runs: run keys, required files, why they are tracked, how a run closes |
|
||||
| `tools/` | [tools/CONTRACT.md](tools/CONTRACT.md) | Full command reference, per-command error contracts, maintenance schedule |
|
||||
| `instructions/` | [instructions/CONTRACT.md](instructions/CONTRACT.md) | Instruction vs. skill, publishing, writing standard |
|
||||
|
||||
**By collection** - then read the contract for the collection you are writing in.
|
||||
[kb/CONTRACT.md](kb/CONTRACT.md) routes between `kb/entities/`, `kb/concepts/`, `kb/sources/`
|
||||
and `kb/comparisons/`, and holds the rules they share.
|
||||
[kb/CONTRACT.md](kb/CONTRACT.md) routes between this instance's collections and holds the rules
|
||||
the stack enforces across all of them; `kb/CONVENTIONS.md` holds the ones this instance chose.
|
||||
Both bind. The difference is who may change the sentence - which is also why a distribution
|
||||
ships the first verbatim and the second only as a `.template`.
|
||||
|
||||
**By task** - skills hold the step-by-step procedures. Sources live in `instructions/<name>/`:
|
||||
|
||||
|
||||
+79
@@ -20,6 +20,85 @@ their date-only headings.
|
||||
|
||||
---
|
||||
|
||||
## 3.0.0 - 2026-09-02 - Autorenkonventionen nach Eigentum geschnitten: kb/CONVENTIONS.md, deklarierte Collections
|
||||
|
||||
**Author:** Torben Nehmer
|
||||
|
||||
**Breaking Change:** kb/CONTRACT.md ist um alles gekuerzt, was eine Instanz selbst entscheidet; das steht jetzt in einer neuen, instanzeigenen kb/CONVENTIONS.md, aus der der Compiler die drei toolgefuehrten Abschnittsnamen liest. Eine bestehende Instanz muss diese Datei anlegen, auf jedem kb/*/COLLECTION.md profile: und required_by_stack: deklarieren und kb/CONTRACT.md aus dem Release nachziehen - sonst FAILt doctor und docs verify bricht. Ablauf: instructions/migrations/3.0.0-authoring-conventions.md
|
||||
|
||||
`kb/CONTRACT.md` war eine Datei mit zwei Autoritäten. Der eine Teil ist code-erzwungen und in
|
||||
jeder Instanz gleich; der andere - **§ Language komplett**, das Beziehungslabel-Vokabular, die
|
||||
Tonfall-Beispiele samt deutscher Buzzword-Liste, die Confidence-Rubrik, das ADR-Präfix - ist
|
||||
Konvention, die jede Instanz für sich entscheidet, und wurde trotzdem als bindender Contract
|
||||
verbatim ausgeliefert. Wer bei Schritt 5 von `setup-instance.md` "Englisch" antwortete, hatte
|
||||
danach `kb/CONTRACT.md`, vier Type-Specs **und `tools/chemenu/sections.py`** lokal geändert -
|
||||
und `private-instance.md`s Decision Point sagt für so einen Merge-Konflikt: Upstream-Seite
|
||||
nehmen. Für diese Instanz hieß das: KB-Sprache zurück auf Deutsch.
|
||||
|
||||
**Der Schnitt läuft jetzt danach, wer den Satz ändern darf.** `kb/CONTRACT.md` behält, was
|
||||
`wikitool` erzwingt; neu daneben liegt `kb/CONVENTIONS.md`, die **genauso bindet** und der
|
||||
Instanz gehört. Unterschied ist Eigentum, nicht Autorität - deshalb liefert die Distribution nur
|
||||
`kb/CONVENTIONS.md.template`, exakt der `USER.md`/`SOUL.md`-Split ein Verzeichnis tiefer. Dazu
|
||||
`instructions/kb-profiles.md`: der Katalog erprobter Profile, ausdrücklich **Palette und kein
|
||||
Enum**. Übernommen wird der *Text* in die Instanzdatei, nie ein Verweis auf den Katalog - ein
|
||||
Verweis wäre wieder genau die Konstruktion, die dieser Release beendet.
|
||||
|
||||
**`sections.py` hält keine Überschrift mehr.** `RELATIONSHIPS = "Beziehungen"` war die Stelle,
|
||||
an der die Konvention in Code übergelaufen war: solange sie dort stand, konnte kein Template die
|
||||
Sprache umstellen. Neu ist `tools/chemenu/conventions.py`, das die drei Namen aus
|
||||
`kb/CONVENTIONS.md` liest; `sections.py` löst sie per PEP 562 bei jedem Zugriff auf, wie
|
||||
`config` seine Pfade - ein Modulkonstante hätte den Wert an den Baum gebunden, in dem der Prozess
|
||||
gestartet ist. Aus demselben Grund ist `provenance.CITE_BLOCK_HEADING` ein `__getattr__` und
|
||||
`render_cite_block(heading=None)` löst innerhalb des Aufrufs auf. Der Alias-Mechanismus, den das
|
||||
Modul schon hatte, **ist** der Migrationspfad: erkannt wird die kanonische Form plus die
|
||||
deklarierten `section_aliases:` plus das, was dieser Stack vor der Konventionsdatei geschrieben
|
||||
hat. Ohne Datei antwortet dieser Fallback - richtig für jeden Korpus, der ihn erreichen kann,
|
||||
denn der wurde unter genau diesen Namen geschrieben; `doctor` ist die laute Hälfte davon.
|
||||
|
||||
**Die vier Page-Type-Specs schreiben `## {section.relationships}`** statt einer Überschrift.
|
||||
Neue Template-Variablen `{section.relationships}` / `{section.see_also}` / `{section.footnotes}`,
|
||||
gefüllt aus der Instanzdeklaration. Damit ändert eine anderssprachige Instanz **keine Datei unter
|
||||
`tools/` oder `types/`** mehr - was Schritt 5 von `setup-instance.md` von fünf Editierstellen
|
||||
über drei Schichten auf eine Entscheidung reduziert.
|
||||
|
||||
**`COLLECTION.md` bekommt Frontmatter.** Bisher wurde eine Collection rein an der Dateipräsenz
|
||||
erkannt; die Deklaration brauchte einen Träger, sonst wäre der Ortsschnitt nur durch einen
|
||||
Prosaschnitt ersetzt worden. `profile:` nennt den übernommenen Katalogeintrag (Freitext - eine
|
||||
selbst angelegte Collection hat dort keinen), `required_by_stack:` sagt, ob `wikitool` die
|
||||
Collection *namentlich* auflöst. Das zweite ist **nicht** die Wahl der Instanz: `docs verify`
|
||||
prüft es beidseitig gegen `kb_collections.STACK_REQUIRED_COLLECTIONS`. Heute steht dort genau
|
||||
`sources` - `sources coverage`, die `[^cite-id]`-Auflösung und `kb/provenance.md` hängen an dem
|
||||
Namen, `entities` an keinem.
|
||||
|
||||
**Das zweite Leck der Merge-Prozedur ist zu.** `git checkout HEAD -- kb raw` holte *alles* unter
|
||||
beiden Stages auf den Vor-Merge-Stand - auch `kb/CONTRACT.md` und `raw/CONTRACT.md`. Änderte der
|
||||
Upstream einen davon, warf die Prozedur das Update still weg, und die Kontrollzeile meldete dabei
|
||||
*leer*, bestätigte den Fehler also, statt ihn zu fangen. `private-instance.md` nimmt die
|
||||
Upstream-Seite jetzt für die drei Maschinerie-Pfade unter den Content-Stages zurück
|
||||
(`kb/CONTRACT.md`, `kb/CONVENTIONS.md.template`, `raw/CONTRACT.md`) und schließt sie aus der
|
||||
Kontrollzeile aus. Dieselbe Altlast in der Tarball-Richtung: `INSTALL.md` Schritt 3 fasste `kb/`
|
||||
gar nicht an und zog `kb/CONTRACT.md` damit nie nach - jetzt ausdrücklich benannt.
|
||||
|
||||
**Verworfen, gemessen: `sources/` aus `kb/` herausziehen.** Der Graph ist einwurzelig
|
||||
(`kb_scan.iter_kb_pages` macht ein `rglob` über `kb/`, darauf sitzen Link-Graph, Orphan-Check,
|
||||
`index rebuild` und `search`), und Source-Seiten sind darin der dichteste Knotentyp. Ein Hoist
|
||||
machte jede Graph-Operation dauerhaft zweiwurzelig, um ein Verzeichnis umzubenennen. Vor allem
|
||||
aber kann der *Ort* Eigentum ohnehin nicht kodieren, sobald Collections offen sind: eine selbst
|
||||
angelegte liegt im selben `kb/` wie die Defaults. Eigentum ist eine deklarierte Eigenschaft -
|
||||
daher das Frontmatter oben. Gitea #39 trägt die Ablehnung im Volltext.
|
||||
|
||||
**Warum das MAJOR ist.** Die Rückwärtshälfte des Drop-in-Tests hält - 2.5.0 ignoriert beide neuen
|
||||
Deklarationen folgenlos. Die Vorwärtshälfte nicht: nach dem Kopieren der Maschinerie FAILt
|
||||
`doctor` auf der fehlenden `kb/CONVENTIONS.md`, `docs verify` bricht auf den undeklarierten
|
||||
Collections, und `kb/CONTRACT.md` muss aus dem Release nachgezogen werden. Ein Shim war die
|
||||
Alternative (`doctor` nur WARN, Pflichtfelder tolerant) und wurde verworfen: er hätte genau den
|
||||
Zustand normalisiert, in dem eine Instanz glaubt, sie habe entschieden, während in Wahrheit der
|
||||
Fallback antwortet - für eine englische Instanz hieße das `## Beziehungen` in englischen Seiten.
|
||||
Die Handarbeit ist eine Datei und zwei Frontmatter-Zeilen je Collection; keine einzige `kb/`-Seite
|
||||
ändert sich, weshalb `migrate done 3.0.0 --pages 0` ehrlich und kein Platzhalter ist.
|
||||
|
||||
---
|
||||
|
||||
## 2.5.0 - 2026-09-02 - Versionsstelle: Kompatibilitaet statt Inhaltsmigration, Breaking-Change-Vermerk erzwungen
|
||||
|
||||
**Author:** Torben Nehmer
|
||||
|
||||
+19
-12
@@ -68,11 +68,14 @@ Zwei Schritte, von denen nur der erste rein menschlich ist:
|
||||
Wiki-Seite (`$WIKI_AUTHOR` überschreibt dies bei Bedarf).
|
||||
- **Remote** (optional) - eine URL, wenn du das Repo auf einen Server pushen willst; sonst
|
||||
bleibt die Instanz lokal, und jedes `publish` läuft mit `--no-push`.
|
||||
- **KB-Sprache** - die exportierte Distribution bringt **Deutsch** mit: die Regel in
|
||||
`kb/CONTRACT.md`, das Vokabular in `instructions/german-terminology.md` und deutsche
|
||||
Abschnittsnamen in den Seitenvorlagen. Das ist eine Entscheidung dieser Ursprungsinstanz,
|
||||
keine Eigenschaft des Musters. Willst du eine andere Sprache, sag es **vor dem ersten
|
||||
Ingest** - danach ist es eine Migration jeder bereits angelegten Seite.
|
||||
- **Autorenkonventionen** - Sprache, Abschnittsnamen, Namensformen, Ton, Beziehungslabels
|
||||
und Confidence-Rubrik stehen in `kb/CONVENTIONS.md`, dazu je Collection die Regeln in
|
||||
`kb/<name>/COLLECTION.md`. Die Distribution bringt davon nur die `.template`-Dateien mit:
|
||||
das sind Entscheidungen *dieser* Instanz, keine Eigenschaft des Musters, und nichts davon
|
||||
liegt unter `tools/` oder `types/`. Fertige Profile - darunter ein vollständiges deutsches -
|
||||
hält `instructions/kb-profiles.md` bereit; es ist eine Palette, kein Enum. Sag die Sprache
|
||||
**vor dem ersten Ingest** - danach ist ein Wechsel der Abschnittsnamen eine Migration jeder
|
||||
bereits angelegten Seite.
|
||||
- **Personalization** - wer diese Instanz bedient (`USER.md`) und wie sie klingt
|
||||
(`SOUL.md`). Die Distribution bringt nur `USER.md.template` und `SOUL.md.template` mit:
|
||||
persönlicher Inhalt gehört nicht in jede exportierte Kopie, aber beide Dateien werden in
|
||||
@@ -177,13 +180,17 @@ dieser Unterschied ist der Zustand, in dem sich jede Instanz mitten im Upgrade b
|
||||
|
||||
2. Release-Tarball herunterladen und entpacken (Weg A), die Release-Notes lesen.
|
||||
3. Die **Maschinerie** aus dem Tarball über die Instanz kopieren: `tools/`, `types/`,
|
||||
`instructions/`, `AGENTS.md`, `VERSION`, `.wikitool-release.json`. Nicht anfassen: `kb/`,
|
||||
`raw/`, `work/`, `.wikitool-kb.json` und `.git/` - das ist die Instanz selbst.
|
||||
4. Achtung bei lokal angepassten Contract-Dateien: wer z. B. die KB-Sprache umgestellt hat
|
||||
(Schritt 5 in `setup-instance.md`), hat `kb/CONTRACT.md` und die Templates unter `types/`
|
||||
verändert. Diese Änderungen vorher sichern und danach wieder einspielen. Welche Dateien das
|
||||
sind, verrät ein Vergleich gegen die sha256-Summen im `files`-Block der alten
|
||||
`.wikitool-release.json`.
|
||||
`instructions/`, `AGENTS.md`, `VERSION`, `.wikitool-release.json` - **und `kb/CONTRACT.md`**.
|
||||
Die letzte Datei liegt unter einem Content-Verzeichnis, ist aber Stack-Eigentum: sie hält,
|
||||
was `wikitool` erzwingt, und ist in jeder Instanz gleich. Nicht anfassen: alles andere unter
|
||||
`kb/` und `raw/`, `work/`, `.wikitool-kb.json` und `.git/` - das ist die Instanz selbst,
|
||||
`kb/CONVENTIONS.md` und die `kb/*/COLLECTION.md` eingeschlossen.
|
||||
4. Achtung bei lokal angepassten Stack-Dateien. Die Autorenkonventionen gehören **nicht** dazu:
|
||||
`kb/CONVENTIONS.md` und die `kb/*/COLLECTION.md` liegen unter `kb/`, werden in Schritt 3
|
||||
also ohnehin nicht angefasst - genau dafür ist der Schnitt da. Wer darüber hinaus etwas
|
||||
unter `tools/`, `types/` oder `instructions/` verändert hat, sichert das vorher und spielt
|
||||
es danach wieder ein. Welche Dateien das sind, verrät ein Vergleich gegen die sha256-Summen
|
||||
im `files`-Block der alten `.wikitool-release.json`.
|
||||
5. **Die Migrationskette abarbeiten.** `tools/wikitool migrate status` listet jetzt alle
|
||||
offenen Migrationen in der Reihenfolge, in der sie laufen müssen - bei einem Sprung über
|
||||
mehrere Versionen sind das mehrere. Für jede: das genannte Dokument unter
|
||||
|
||||
@@ -14,12 +14,15 @@ and maintains a persistent wiki** that compounds over time.
|
||||
English; the compiled pages under `kb/` are not. What stays English inside them is everything that
|
||||
is an *identifier* rather than prose - page titles, section headings, wikilink targets, citation
|
||||
ids, schema enum values, tags, commands, paths and code - so `GitOps Ownership Model` and
|
||||
`## Beziehungen` sit in the same page without contradiction. The rule is
|
||||
[kb/CONTRACT.md § Language](kb/CONTRACT.md#language); the vocabulary behind it is
|
||||
`## Beziehungen` sit in the same page without contradiction. Which lines are identifiers is
|
||||
[kb/CONTRACT.md § Language and identifiers](kb/CONTRACT.md#language-and-identifiers); *which
|
||||
language* the prose is in, and what the tool-owned headings are called, is this instance's own
|
||||
[kb/CONVENTIONS.md](kb/CONVENTIONS.md), and the vocabulary behind it is
|
||||
[instructions/german-terminology.md](instructions/german-terminology.md).
|
||||
|
||||
This is a per-instance decision, not a property of the pattern. A new instance built with
|
||||
`dist export` starts empty and can pick any language by editing that one contract section before
|
||||
This is a per-instance decision, not a property of the pattern - which is why it lives in a file
|
||||
the instance owns rather than in one the stack ships. A new instance built with
|
||||
`dist export` starts empty and picks any language by filling in `kb/CONVENTIONS.md` before
|
||||
the first ingest.
|
||||
|
||||
## Getting started
|
||||
@@ -401,7 +404,7 @@ This wiki is tailored for IT work with:
|
||||
|
||||
- **Entity types** specific to software development and systems
|
||||
- **Relationship types** like `hängt ab von`, `verwendet`, `implementiert` - the vocabulary is in
|
||||
[kb/CONTRACT.md § Linking](kb/CONTRACT.md#linking)
|
||||
[kb/CONVENTIONS.md](kb/CONVENTIONS.md), because it is this instance's rather than the stack's
|
||||
- **Templates** for projects, systems, tools, technologies, ADRs
|
||||
- **Guidelines** for documenting technical decisions
|
||||
- **Cross-reference patterns** for code and architecture
|
||||
|
||||
@@ -58,8 +58,10 @@ whether an instruction is still reachable, which is exactly why the answer means
|
||||
`instructions/dev/` boundary check below asks the opposite question - what would *dangle* in a
|
||||
distributed instance - and does scan README.md, because `dist export` ships it verbatim.
|
||||
|
||||
Two kinds of file use the Manual tier today: [german-terminology.md](german-terminology.md), a
|
||||
vocabulary consulted on demand rather than a procedure, and every migration document (below).
|
||||
Three kinds of file use the Manual tier today: [german-terminology.md](german-terminology.md), a
|
||||
vocabulary consulted on demand rather than a procedure; [kb-profiles.md](kb-profiles.md), the
|
||||
catalogue of authoring profiles an instance may adopt into its own `kb/CONVENTIONS.md` and
|
||||
`COLLECTION.md` files; and every migration document (below).
|
||||
|
||||
## `instructions/migrations/`
|
||||
|
||||
@@ -153,7 +155,8 @@ What lives where:
|
||||
|-------|------|
|
||||
| [AGENTS.md](../AGENTS.md) | Invariants and routing - what must always hold |
|
||||
| `instructions/` | How the tooling is *operated* |
|
||||
| [kb/CONTRACT.md](../kb/CONTRACT.md) + each `COLLECTION.md` | How a page is *authored* |
|
||||
| [kb/CONTRACT.md](../kb/CONTRACT.md) | What the stack enforces about a page, in every instance |
|
||||
| `kb/CONVENTIONS.md` + each `COLLECTION.md` | What *this* instance decided about authoring - owned by the instance, shipped only as a `.template` |
|
||||
| [types/](../types/type-spec.md) | What a page structurally *is* |
|
||||
| [tools/CONTRACT.md](../tools/CONTRACT.md) | What each command does and how it fails |
|
||||
|
||||
|
||||
@@ -35,6 +35,11 @@ Do not re-do any of this per test; it is done for you, per test, via `monkeypatc
|
||||
fixture redirects it into `tmp_path`. Tracing is never disabled suite-wide, because two
|
||||
telemetry tests assert that a trace gets written.
|
||||
|
||||
Two in-process caches are cleared alongside the environment, for the same reason: `config`'s
|
||||
resolved paths and `conventions`' parsed `kb/CONVENTIONS.md`. A test that *rewrites* the
|
||||
conventions file mid-test calls `conventions.reset_cache()` itself - the fixture answers for the
|
||||
boundary between tests, not for one inside a test.
|
||||
|
||||
## When to run
|
||||
|
||||
Whenever you add or change a test under `tools/chemenu/tests/`.
|
||||
|
||||
@@ -7,9 +7,13 @@ manual: true
|
||||
|
||||
# German terminology for `kb/`
|
||||
|
||||
Reference vocabulary for [kb/CONTRACT.md](../kb/CONTRACT.md#language)'s rule that pages are
|
||||
written in German. The rule lives there; the word list lives here, because it is lookup material
|
||||
rather than a norm and would otherwise be loaded on every write.
|
||||
Reference vocabulary for [kb/CONVENTIONS.md](../kb/CONVENTIONS.md#language)'s rule that this
|
||||
instance's pages are written in German. The rule lives there; the word list lives here, because
|
||||
it is lookup material rather than a norm and would otherwise be loaded on every write.
|
||||
|
||||
**This file belongs to the `german` language profile, not to the stack.** An instance writing in
|
||||
another language deletes or replaces it - see
|
||||
[kb-profiles.md](kb-profiles.md).
|
||||
|
||||
Derived from translating all 248 pages on 2026-08-29. Every entry below is a decision that was
|
||||
made wrong at least once first - each cost a correction pass across published pages, which is why
|
||||
@@ -98,8 +102,8 @@ none of them structural, so no check found them. It is the one thing to watch fo
|
||||
instructional prose.
|
||||
|
||||
- **Quotations are never reworded**, neither translated nor moved into the impersonal register.
|
||||
- Buzzwords and AI filler are banned by [kb/CONTRACT.md](../kb/CONTRACT.md#tone); the German list
|
||||
is there.
|
||||
- Buzzwords and AI filler are banned by [kb/CONVENTIONS.md](../kb/CONVENTIONS.md#tone); the
|
||||
German list is there.
|
||||
- Dash as ` - `, not `—`.
|
||||
- German number formatting only in prose („10.000 Punkte"). Never inside code, version numbers or
|
||||
measurements (`75-85 px`, `10m`, `0.90`).
|
||||
@@ -108,4 +112,4 @@ instructional prose.
|
||||
|
||||
This is about prose in `kb/`. What is prose and what is an identifier - titles, headings, wikilink
|
||||
targets, cite-ids, enum values, tags, code - is decided by
|
||||
[kb/CONTRACT.md](../kb/CONTRACT.md#language), not here.
|
||||
[kb/CONTRACT.md](../kb/CONTRACT.md#language-and-identifiers), not here.
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
---
|
||||
type: types/instruction.md
|
||||
name: kb-profiles
|
||||
description: Ready-made answers for kb/CONVENTIONS.md and each COLLECTION.md - the proven collection contracts and language profiles this stack has shipped, offered as a palette to adopt or adapt, never as a binding source.
|
||||
manual: true
|
||||
---
|
||||
# Pick a profile for a collection or for this instance's conventions
|
||||
|
||||
**This page is a palette, not an enum.** Each `kb/<name>/COLLECTION.md` stays authoritative for
|
||||
its own collection and `kb/CONVENTIONS.md` for the instance as a whole; an entry here is a
|
||||
proven starting point, nothing more. Adopting one means *copying its text into* that file - not
|
||||
pointing at this page and inheriting whatever it says later. Nothing in the stack reads this
|
||||
document, and `profile:` in a contract's frontmatter records where the text came from, not where
|
||||
it lives.
|
||||
|
||||
That direction is deliberate and it is the opposite of how this repo used to work. Language,
|
||||
tone, naming and the relationship vocabulary sat in `kb/CONTRACT.md`, a file `dist export` ships
|
||||
verbatim - so every instance that wanted something else edited a stack file, and an upstream
|
||||
merge handed the stack's answer back. What binds is now the instance's; what ships is this
|
||||
catalogue, and it binds nothing.
|
||||
|
||||
## When to run
|
||||
|
||||
- Setting up a new instance: the KB-language step of
|
||||
[setup-instance.md](setup-instance.md) sends you here to fill `kb/CONVENTIONS.md`.
|
||||
- Adding a collection to an existing instance, and wanting a contract that already works rather
|
||||
than a blank one.
|
||||
- Rewriting an existing `COLLECTION.md` or `kb/CONVENTIONS.md` and wanting to see what the
|
||||
alternatives were.
|
||||
|
||||
Not for changing what the *stack* enforces. That is [kb/CONTRACT.md](../kb/CONTRACT.md), and it
|
||||
is not a profile.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Decide what you are filling.** Two different files, and they are not interchangeable:
|
||||
|
||||
| File | Holds | Profiles below |
|
||||
|---|---|---|
|
||||
| `kb/CONVENTIONS.md` | Language, section headings, naming forms, tone, relationship labels, confidence rubric - once per instance | [Language profiles](#language-profiles) |
|
||||
| `kb/<name>/COLLECTION.md` | What one collection holds, its quality goal, its local linking and naming rules | [Collection profiles](#collection-profiles) |
|
||||
|
||||
2. **Copy the entry's text into the file**, then edit it until it is true of this instance.
|
||||
A profile you adopted and then changed is still that profile's `profile:` value - the field
|
||||
records the starting point, not a promise of fidelity.
|
||||
|
||||
3. **Record it.** `profile: <name>` in the file's frontmatter, or `profile: none` for a
|
||||
collection written from scratch. `wikitool docs verify` checks the field is there; it does
|
||||
not check the value against this page, because a collection an instance invented has no
|
||||
entry here to name.
|
||||
|
||||
4. **Set `required_by_stack:` on a collection - and set it correctly.** This one is *not* a
|
||||
choice: it says whether `wikitool` resolves against the collection by name, and
|
||||
`docs verify` checks it against the stack's own list. `sources` is `true`, everything else
|
||||
is `false`. See [kb/CONTRACT.md § Collections](../kb/CONTRACT.md#collections).
|
||||
|
||||
## Language profiles
|
||||
|
||||
A language profile answers all of `kb/CONVENTIONS.md` at once. There is one today, because one
|
||||
is what this repo has actually run.
|
||||
|
||||
### `german`
|
||||
|
||||
The profile this repo's own instance uses, and the reason this catalogue exists: it was the
|
||||
stack's hardcoded behaviour until the conventions file existed.
|
||||
|
||||
| Decides | Value |
|
||||
|---|---|
|
||||
| `language:` | `de` |
|
||||
| `sections:` | `Beziehungen` / `Siehe auch` / `Fußnoten` |
|
||||
| Naming | Human-readable titles with spaces; singular for entities; `adr-NNN-` for decisions; `X vs Y` for comparisons |
|
||||
| Tone | Wikipedia register, with a German buzzword and filler list |
|
||||
| Relationship labels | `hängt ab von` · `verwendet` · `implementiert` · `erweitert` · `ersetzt` · `steht in Konflikt mit` · `benötigt` · `erzeugt` · `konsumiert` · `besitzt` · `pflegt` · `läuft auf` · `verwandt mit` |
|
||||
| Confidence rubric | 0.5 base, +0.2 per supporting source (max +0.6), recency and source-quality bonuses; hedge with "möglicherweise"/"kann" below 0.6, "unsicher"/"unbestätigt" below 0.4 |
|
||||
| Terminology | [german-terminology.md](german-terminology.md) - which English terms stay English, and which have a settled German form |
|
||||
|
||||
**The full text to copy** is this repo's own [kb/CONVENTIONS.md](../kb/CONVENTIONS.md). An
|
||||
instance adopting it takes that file, not this table; the table is what the profile *decides*,
|
||||
so you can tell at a glance whether it is the one you want.
|
||||
|
||||
Adopting it also means keeping `german-terminology.md`. An instance on any other language
|
||||
deletes or replaces that file - it is the profile's lookup material, not the stack's.
|
||||
|
||||
### `english`
|
||||
|
||||
What `kb/CONVENTIONS.md.template` ships as its default, so "adopt `english`" means "fill in the
|
||||
template and change nothing structural". `sections:` are `Relationships` / `See Also` /
|
||||
`Footnotes`, which are also the names this stack wrote before it had a conventions file - so a
|
||||
corpus that predates the split needs no translation pass to adopt this profile.
|
||||
|
||||
There is no worked text for the rest of it. The template's placeholders are the questions;
|
||||
`german` above is what a filled answer looks like.
|
||||
|
||||
### Writing a third one
|
||||
|
||||
A language profile is not a translation of `german`. Two of its sections are judgment about a
|
||||
language rather than vocabulary in it - which foreign technical terms stay untranslated, and how
|
||||
to hedge a low-confidence claim - and those are exactly the two that read as awkward when
|
||||
translated mechanically. Write them, do not convert them.
|
||||
|
||||
The one part that is mechanical: `section_aliases:`. Whatever the corpus used before goes in
|
||||
that list, and the pages then migrate one at a time instead of all at once.
|
||||
|
||||
## Collection profiles
|
||||
|
||||
The four collections this repo runs. Each is a whole `COLLECTION.md`, and **the text to copy is
|
||||
the file itself** - `dist export` ships each one as `kb/<name>/COLLECTION.md.template`, which a
|
||||
new instance adopts by renaming. What follows is what each decides, so you can tell whether you
|
||||
want it.
|
||||
|
||||
### `entities`
|
||||
|
||||
Concrete, pointable things: projects, deployed systems, tools, technologies, people.
|
||||
|
||||
- **Quality goal:** pointability plus currency - what the thing is, where it actually is, and
|
||||
whether that is still true.
|
||||
- **Areas** driven by the `entity_type:` field: `projects/`, `systems/`, `tools/`,
|
||||
`technologies/`, `people/`. Areas, not collections - they inherit the contract and carry no
|
||||
`COLLECTION.md`.
|
||||
- **Per-area emphasis** spelled out, so a system page is not written like a technology page.
|
||||
- `required_by_stack: false`.
|
||||
|
||||
Take it when the wiki is about things that exist. Adapt the area list first: it is the part most
|
||||
likely to be wrong for another domain.
|
||||
|
||||
### `concepts`
|
||||
|
||||
Ideas rather than things: architectures, patterns, protocols, workflows, recurring problems,
|
||||
and the decisions taken about them.
|
||||
|
||||
- **Quality goal:** explanatory sufficiency - the page answers *why it is done this way* without
|
||||
the reader opening the entity pages that use it.
|
||||
- Carries the **ADR shape**: context, decision, consequences, status, and the rule that a
|
||||
superseded decision is never rewritten.
|
||||
- Routes head-to-head arguments out to `comparisons/` rather than hosting them.
|
||||
- `required_by_stack: false`.
|
||||
|
||||
Take it whenever `entities` is taken - the split between the two is what keeps either from
|
||||
becoming an essay.
|
||||
|
||||
### `sources`
|
||||
|
||||
One page per ingested source, carrying the `raw_files:` provenance every citation resolves
|
||||
against.
|
||||
|
||||
- **Quality goal:** faithful compression - what *this source* said, not what was concluded from
|
||||
it. A source page improved beyond its source is no longer evidence.
|
||||
- Titles carry the `Source - ` prefix, applied by `wikitool new source`.
|
||||
- `required_by_stack: **true**`. `sources coverage`, `[^cite-id]` resolution and
|
||||
`kb/provenance.md` resolve against the name `sources`.
|
||||
|
||||
Not optional in the way the others are. An instance may rewrite its authoring rules and may not
|
||||
rename or drop it.
|
||||
|
||||
### `comparisons`
|
||||
|
||||
Structured head-to-head evaluations of two or more things that already have pages.
|
||||
|
||||
- **Quality goal:** decidability - named, checkable dimensions and a stated trade-off, so a
|
||||
reader with a concrete situation can choose.
|
||||
- Every subject must already have a page; a comparison is a view over existing knowledge.
|
||||
- **Exempt from the orphan check** - comparisons are reached through the catalog, not through
|
||||
inbound prose links.
|
||||
- `required_by_stack: false`.
|
||||
|
||||
Skip it in a wiki that records rather than decides. It is the one of the four that is genuinely
|
||||
optional.
|
||||
|
||||
## Decision points
|
||||
|
||||
- **A profile is almost right?** Copy and edit. There is no partial adoption and no override
|
||||
file - the copy *is* the mechanism, and `profile:` still records where it started.
|
||||
- **Two collections want the same profile?** Fine. `profile:` is not unique, and two
|
||||
collections holding different subject matter under the same authoring rules is an ordinary
|
||||
outcome.
|
||||
- **Changing `sections:` after pages exist?** That is a corpus migration, not an edit. Put the
|
||||
old names in `section_aliases:` first, then translate page by page - the tool keeps finding
|
||||
the old headings for as long as the alias stands. See
|
||||
[migrate-corpus.md](migrate-corpus.md).
|
||||
- **Tempted to make this page binding** - to have `COLLECTION.md` say `profile: entities` and
|
||||
nothing else? Do not. That is the arrangement this split was written to end: the instance
|
||||
would be bound by a file the stack ships and upgrades, which is how an upstream merge changes
|
||||
an instance's authoring rules without anyone deciding to.
|
||||
|
||||
## Scope
|
||||
|
||||
Covers what an instance authors under `kb/`. It says nothing about what the stack enforces
|
||||
([kb/CONTRACT.md](../kb/CONTRACT.md)), what a page structurally is
|
||||
([types/type-spec.md](../types/type-spec.md)), or how a command behaves
|
||||
([tools/CONTRACT.md](../tools/CONTRACT.md)). None of those are profiles, and none of them are
|
||||
the instance's to change.
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
type: types/instruction.md
|
||||
name: 3.0.0-authoring-conventions
|
||||
description: 'Adopt the instance-owned authoring conventions introduced in 3.0.0 - write kb/CONVENTIONS.md, declare profile:/required_by_stack: on every COLLECTION.md, and replace kb/CONTRACT.md with the shipped one.'
|
||||
manual: true
|
||||
migrates_to: 3.0.0
|
||||
migration_kind: mechanical
|
||||
---
|
||||
# Adopt this instance's own authoring conventions (3.0.0)
|
||||
|
||||
Until 3.0.0, the rules for writing a page were split by *location*: everything about `kb/` sat
|
||||
in `kb/CONTRACT.md`, a file every distribution ships verbatim. Half of it was never the stack's
|
||||
to decide - the language pages are written in, the three tool-owned section headings, the naming
|
||||
forms, the tone, the relationship labels, the confidence rubric - so an instance that wanted
|
||||
something else edited a file the stack also ships, and an upstream merge handed the stack's
|
||||
answer back.
|
||||
|
||||
3.0.0 splits it by *ownership* instead. `kb/CONTRACT.md` keeps only what `wikitool` enforces;
|
||||
everything else moves into a new `kb/CONVENTIONS.md` that belongs to this instance, and each
|
||||
`kb/<name>/COLLECTION.md` now declares what it is. The compiler reads its section headings from
|
||||
that file rather than from `tools/chemenu/sections.py`.
|
||||
|
||||
**No page changes.** Not one line under `kb/entities/`, `kb/concepts/`, `kb/sources/` or
|
||||
`kb/comparisons/` is touched. What changes are the contracts beside them, which is why this is
|
||||
`mechanical` and takes minutes rather than a workshop.
|
||||
|
||||
## When to run
|
||||
|
||||
After installing 3.0.0 machinery over an instance that was on 2.x, when `tools/wikitool doctor`
|
||||
reports `FAIL conventions` or `tools/wikitool docs verify` reports a `COLLECTION.md` with no
|
||||
frontmatter. `tools/wikitool migrate status` names this document.
|
||||
|
||||
**Until it has run, the compiler answers out of a fallback.** `xref add` and `cite add` write
|
||||
`## Beziehungen` / `## Siehe auch` / `## Fußnoten` - what this stack hardcoded before the
|
||||
conventions file existed. That is correct for a corpus written under them and wrong for any
|
||||
other, so run this before the next `wiki-ingest` or `wiki-manage`, not afterwards.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Replace `kb/CONTRACT.md` from the release.** It is machinery that happens to live under a
|
||||
content directory, and the tarball update path used to skip it (see `INSTALL.md`, which now
|
||||
names it explicitly). The 3.0.0 version is roughly half the length of the 2.x one - the
|
||||
removed half is what step 2 is about to write into a file of yours.
|
||||
|
||||
```bash
|
||||
cp <unpacked-release>/kb/CONTRACT.md kb/CONTRACT.md
|
||||
```
|
||||
|
||||
A private instance cloned from an upstream takes it with the merge instead - see
|
||||
[private-instance.md](../private-instance.md), whose update procedure now re-takes the
|
||||
upstream side for exactly this path.
|
||||
|
||||
2. **Write `kb/CONVENTIONS.md`.** Two ways in, and the first is almost always right:
|
||||
|
||||
- **This instance writes German pages** (it did, unless you changed it): copy the release's
|
||||
`kb/CONVENTIONS.md.template` and fill it from the `german` profile in
|
||||
[kb-profiles.md](../kb-profiles.md) - whose worked full text is the origin repo's own
|
||||
`kb/CONVENTIONS.md`. Everything in it was already true of your corpus; it was simply
|
||||
written down somewhere you did not own.
|
||||
- **You had changed the language**, and therefore hold local edits to `kb/CONTRACT.md`,
|
||||
`types/*.md` and `tools/chemenu/sections.py`: those edits are what this file replaces. Copy
|
||||
the canonical heading names out of your old `sections.py` into `sections:`, the labels and
|
||||
tone rules out of your old `kb/CONTRACT.md`, then **discard the local edits under `tools/`
|
||||
and `types/`** and take the shipped versions. That is the whole point of the change: there
|
||||
is nothing left to patch there.
|
||||
|
||||
The minimum the tool needs is the frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
language: de
|
||||
profile: german
|
||||
sections:
|
||||
relationships: Beziehungen
|
||||
see_also: Siehe auch
|
||||
footnotes: Fußnoten
|
||||
---
|
||||
```
|
||||
|
||||
Set `sections:` to the names **your existing pages already carry**, not to what you would
|
||||
prefer. Changing them is a separate, real corpus migration; `section_aliases:` is how it is
|
||||
done page by page ([migrate-corpus.md](../migrate-corpus.md)).
|
||||
|
||||
Drop the `wikitool:template-unfilled` sentinel line while filling it in - `doctor` FAILs on a
|
||||
renamed-but-unanswered template exactly as it does for `USER.md`.
|
||||
|
||||
3. **Declare each collection.** Two frontmatter lines at the top of every
|
||||
`kb/<name>/COLLECTION.md`:
|
||||
|
||||
```yaml
|
||||
---
|
||||
profile: <the entry in instructions/kb-profiles.md this contract came from, or none>
|
||||
required_by_stack: false
|
||||
---
|
||||
```
|
||||
|
||||
`required_by_stack: true` on `kb/sources/` and **nowhere else**. It is not a preference:
|
||||
`sources coverage`, `[^cite-id]` resolution and `kb/provenance.md` resolve against that name,
|
||||
and `docs verify` checks the field against the stack's own list in both directions.
|
||||
|
||||
For the four default collections, the shipped `kb/<name>/COLLECTION.md.template` files carry
|
||||
the right values already.
|
||||
|
||||
4. **Verify.** All three must pass:
|
||||
|
||||
```bash
|
||||
tools/wikitool doctor # `conventions` must be OK
|
||||
tools/wikitool docs verify
|
||||
tools/wikitool lint
|
||||
```
|
||||
|
||||
`migrate verify` is deliberately not in that list: it compares pages, and no page changed.
|
||||
Running it would report nothing and prove nothing.
|
||||
|
||||
5. **Record it.**
|
||||
|
||||
```bash
|
||||
tools/wikitool migrate done 3.0.0 --pages 0
|
||||
```
|
||||
|
||||
`--pages 0` is honest, not a placeholder - see the note under step 1.
|
||||
|
||||
## How to tell a migrated instance from an unmigrated one
|
||||
|
||||
`kb/CONVENTIONS.md` exists, carries no `wikitool:template-unfilled` line, and names all three
|
||||
slots under `sections:`; every `kb/*/COLLECTION.md` opens with a frontmatter block; and
|
||||
`kb/CONTRACT.md` has a `## Language and identifiers` heading rather than a `## Language` one.
|
||||
`doctor` answers all of that in one call.
|
||||
|
||||
## Decision points
|
||||
|
||||
- **`doctor` says `conventions: FAIL` after step 2?** It prints which slot is missing. The three
|
||||
keys are `relationships`, `see_also` and `footnotes` - the *slot* names are fixed, only their
|
||||
values are yours.
|
||||
- **A collection this instance invented, with no profile behind it?** `profile: none`. The field
|
||||
records where the text came from; it is free text and `docs verify` does not check it against
|
||||
the catalogue, because an invented collection has no entry there to name.
|
||||
- **Tempted to point `profile:` at the catalogue instead of copying the text?** Do not. An
|
||||
adopted profile is a copy; a reference would put your binding authoring rules in a file the
|
||||
stack ships and upgrades, which is the arrangement 3.0.0 exists to end.
|
||||
- **Your old `kb/CONTRACT.md` had local edits you still want?** They belong in
|
||||
`kb/CONVENTIONS.md` now. If something you edited has no home there, it was a stack rule you
|
||||
overrode - file it as an issue against the origin repo rather than re-applying it.
|
||||
|
||||
## Scope
|
||||
|
||||
One instance's contracts, once. It changes no page, no frontmatter on a page, and nothing under
|
||||
`raw/`. The machinery half of the 3.0.0 upgrade - copying `tools/`, `types/`, `instructions/`,
|
||||
`AGENTS.md`, `VERSION` and `.wikitool-release.json` - is `INSTALL.md`'s, and has to have
|
||||
happened before step 1.
|
||||
@@ -100,17 +100,36 @@ So the merge has to be scoped. That is the procedure below, and it is not option
|
||||
[setup-instance.md](setup-instance.md), then [bootstrap.md](bootstrap.md) for the venv and
|
||||
the skills.
|
||||
|
||||
A clone inherits the upstream's `kb/CONVENTIONS.md` and `kb/*/COLLECTION.md` rather than
|
||||
templates, because it inherits the upstream's whole tree. They are yours from this point on:
|
||||
rewrite them if this instance writes its pages differently - the update procedure below
|
||||
restores them on every merge, so the change sticks. [kb-profiles.md](kb-profiles.md) has the
|
||||
alternatives.
|
||||
|
||||
## Taking a stack update
|
||||
|
||||
Take the machinery, never the content. The merge is held open, the content stages are forced
|
||||
back to your own state, and only then does it close:
|
||||
back to your own state, and only then does it close.
|
||||
|
||||
**Three files under those stages are machinery, not content**, and forcing them back is how an
|
||||
upstream contract change gets silently discarded:
|
||||
|
||||
| Path | Why it must take the upstream side |
|
||||
|---|---|
|
||||
| `kb/CONTRACT.md` | The stack's own knowledge-layer contract. Every rule in it is enforced by `wikitool`; an instance never edits it |
|
||||
| `kb/CONVENTIONS.md.template` | The template your `kb/CONVENTIONS.md` was filled from. The filled file is yours; the template is the stack's |
|
||||
| `raw/CONTRACT.md` | The raw stage's contract, for the same reason as the first row |
|
||||
|
||||
Everything else under `kb/` and `raw/` is yours, `kb/CONVENTIONS.md` and each
|
||||
`kb/<name>/COLLECTION.md` included - they bind your corpus, and they are exactly what the
|
||||
restore below is protecting.
|
||||
|
||||
```bash
|
||||
BEFORE=$(git rev-parse HEAD)
|
||||
git fetch upstream
|
||||
|
||||
# --no-commit holds the merge open; it may report conflicts under kb/ or raw/,
|
||||
# which the next three lines are about to make irrelevant.
|
||||
# which the next four lines are about to make irrelevant.
|
||||
git merge --no-commit --no-ff upstream/main || true
|
||||
|
||||
# Whatever the merge did to the content stages, undo it. HEAD is still your
|
||||
@@ -119,18 +138,32 @@ git rm -rq --cached --ignore-unmatch kb raw
|
||||
rm -rf kb raw
|
||||
git checkout HEAD -- kb raw
|
||||
|
||||
# ...then take the upstream side back for the machinery that lives among it.
|
||||
# MERGE_HEAD is still resolvable while the merge is open.
|
||||
git checkout MERGE_HEAD -- kb/CONTRACT.md kb/CONVENTIONS.md.template raw/CONTRACT.md
|
||||
|
||||
git commit --no-edit
|
||||
```
|
||||
|
||||
Then **check that it worked**, rather than trusting that it did:
|
||||
Then **check that it worked**, rather than trusting that it did. The same three paths are
|
||||
excluded here, spelled out rather than held in a variable so that the check can be read on its
|
||||
own and copied on its own:
|
||||
|
||||
```bash
|
||||
git diff --name-only $BEFORE HEAD -- kb raw # must print nothing
|
||||
git diff --name-only "$BEFORE" HEAD -- kb raw \
|
||||
| grep -vE '^(kb/CONTRACT\.md|kb/CONVENTIONS\.md\.template|raw/CONTRACT\.md)$'
|
||||
```
|
||||
|
||||
Must print nothing.
|
||||
|
||||
An empty result is the proof that the update touched machinery only. A non-empty one means a
|
||||
path slipped through - inspect it before going further.
|
||||
|
||||
**The exclusion is not cosmetic.** Without it the check reports *empty* for an update that just
|
||||
ate a `kb/CONTRACT.md` change - it would be confirming the failure it exists to catch. If one of
|
||||
the three paths does not appear in the diff at all, that is fine: it means upstream did not
|
||||
touch it.
|
||||
|
||||
Then, as after any stack change: `doctor`, `docs verify`, `instructions verify`, `migrate status`,
|
||||
`lint`. A `migrate status` with outstanding links means the update crossed a compatibility
|
||||
boundary - follow [migrate-corpus.md](migrate-corpus.md) before doing anything else.
|
||||
@@ -157,11 +190,20 @@ merge above. Nothing is lost by the detour: the fix has to pass that CI either w
|
||||
above overwrites those stages with your own afterwards, so the conflict resolves itself.
|
||||
Never resolve one by hand with `git add -A` - that is exactly how the upstream version, which
|
||||
git left sitting in your working tree, gets committed into your instance.
|
||||
- **`git diff` after the merge shows something under `kb/` or `raw/`?** Stop. The scoping step
|
||||
did not take. Do not publish; find out which path came through and where from.
|
||||
- **`git diff` after the merge shows something under `kb/` or `raw/`?** Stop - unless it is one
|
||||
of the three machinery paths the check excludes, which is the update working as intended. For
|
||||
anything else the scoping step did not take: do not publish; find out which path came through
|
||||
and where from.
|
||||
- **Conflict in `tools/`, `types/` or `instructions/`?** You changed the stack locally, which
|
||||
step "Where stack development happens" says not to do. Take the upstream side and re-file the
|
||||
change as an issue there.
|
||||
- **...but you changed how *your pages* are written?** That is not a stack change and the rule
|
||||
above does not apply to it. Language, section headings, naming forms, tone, relationship
|
||||
labels and the confidence rubric live in `kb/CONVENTIONS.md`, and each collection's authoring
|
||||
rules in `kb/<name>/COLLECTION.md` - all under `kb/`, all yours, all restored by the merge
|
||||
procedure rather than overwritten by it. If you find yourself editing `tools/` or `types/` to
|
||||
change an authoring convention, that is a stack bug: file it, because the split exists
|
||||
precisely so you do not have to.
|
||||
|
||||
## Scope
|
||||
|
||||
|
||||
@@ -60,24 +60,58 @@ bereit für den ersten `Ingest`.
|
||||
- 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).
|
||||
|
||||
5. **Entscheidungspunkt - KB-Sprache.** Frage den Nutzer, in welcher Sprache die Seiten unter
|
||||
`kb/` geschrieben werden sollen. Diese Instanz erbt aus dem Quell-Repo **Deutsch** - sowohl die
|
||||
Regel in [kb/CONTRACT.md](../kb/CONTRACT.md#language) als auch das Vokabular in
|
||||
[german-terminology.md](german-terminology.md) und die deutschen Abschnittsnamen in
|
||||
`tools/chemenu/sections.py`. Das ist eine Entscheidung der Ursprungsinstanz, keine
|
||||
Eigenschaft des Musters, und sie wird hier nicht stillschweigend weitergereicht.
|
||||
5. **Entscheidungspunkt - Autorenkonventionen.** Die Distribution bringt keine ausgefüllten
|
||||
Konventionen mit, sondern `kb/CONVENTIONS.md.template` und je Collection ein
|
||||
`kb/<name>/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?**
|
||||
|
||||
- **Deutsch bestätigt:** nichts zu tun.
|
||||
- **Andere Sprache:** *vor dem ersten Ingest* umstellen, denn danach ist es eine Migration
|
||||
jeder vorhandenen Seite. Zu ändern sind der Abschnitt "Language" in `kb/CONTRACT.md`, die
|
||||
Tonfall-Beispiele und Hedge-Wörter darunter, die vier Page-Type-Templates in `types/`, die
|
||||
kanonischen Namen in `sections.py` (die bisherigen als Alias behalten) und die
|
||||
Beziehungslabels in `kb/CONTRACT.md` § Linking. `german-terminology.md` wird dann ersetzt
|
||||
oder gelöscht.
|
||||
Ablauf:
|
||||
|
||||
Unverändert bleibt in jedem Fall die eigentliche Regel: **jede Zeile einer Seite ist Prosa
|
||||
oder Identifier, und nur Prosa wird übersetzt.** Titel, Wikilink-Ziele, Cite-IDs, Enum-Werte,
|
||||
Tags, Befehle und Pfade folgen keiner KB-Sprache.
|
||||
1. Die Collection-Contracts übernehmen - vier Kopien, keine Frage an den Nutzer, denn was
|
||||
dort steht ist unabhängig von der Sprache brauchbar:
|
||||
|
||||
```bash
|
||||
for template in kb/*/COLLECTION.md.template; do
|
||||
cp "$template" "${template%.template}"
|
||||
done
|
||||
```
|
||||
|
||||
Die `.template`-Dateien bleiben liegen; sie sind die Vorlage für den nächsten Export.
|
||||
|
||||
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.
|
||||
|
||||
3. `kb/CONVENTIONS.md.template` nach `kb/CONVENTIONS.md` kopieren, entlang des gewählten
|
||||
Profils ausfüllen - Sprache, Abschnittsnamen, Namensformen, Ton, Beziehungslabels,
|
||||
Confidence-Rubrik - und dabei die Sentinel-Zeile (`wikitool:template-unfilled`) entfernen.
|
||||
Die Platzhalter in geschweiften Klammern **sind** der Fragenkatalog.
|
||||
|
||||
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.
|
||||
|
||||
**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
|
||||
[migrate-corpus.md](migrate-corpus.md)).
|
||||
|
||||
**Nichts davon liegt unter `tools/` oder `types/`.** Der Compiler liest die Abschnittsnamen
|
||||
aus `kb/CONVENTIONS.md`, und die vier Page-Type-Templates setzen sie über
|
||||
`{section.…}`-Variablen ein - eine anderssprachige Instanz ändert dort keine Datei.
|
||||
|
||||
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.
|
||||
|
||||
`tools/wikitool doctor` prüft das Ergebnis in Schritt 12 (`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
|
||||
`COLLECTION.md`.
|
||||
|
||||
6. **Entscheidungspunkt - Personalization.** Die Distribution bringt
|
||||
`USER.md.template` und `SOUL.md.template` mit, aber keine ausgefüllten Fassungen: wer diese
|
||||
|
||||
@@ -61,8 +61,9 @@ pages should never have cost the concept contract. Field-level requirements alwa
|
||||
article also pass `--set source_url=<upstream URL>`; `raw_files:` must still point at the
|
||||
local copy. Then write the Summary / Key Takeaways / Action Items prose from step 4 - in the
|
||||
KB language, whatever the source's own language is, quoting verbatim passages in the
|
||||
original. The rule and what is exempt from it:
|
||||
[kb/CONTRACT.md](../../kb/CONTRACT.md#language).
|
||||
original. Which language that is: [kb/CONVENTIONS.md](../../kb/CONVENTIONS.md#language).
|
||||
What is exempt from it, in any language:
|
||||
[kb/CONTRACT.md](../../kb/CONTRACT.md#language-and-identifiers).
|
||||
|
||||
Fill `## Not Extracted` in the same pass: what you read and deliberately did not promote,
|
||||
with the reason. Nothing in the repository can re-derive that judgment, and without it the
|
||||
@@ -70,8 +71,9 @@ pages should never have cost the concept contract. Field-level requirements alwa
|
||||
|
||||
6. **Create or update entity pages.** Read
|
||||
[kb/entities/COLLECTION.md](../../kb/entities/COLLECTION.md) and
|
||||
[kb/CONTRACT.md](../../kb/CONTRACT.md) first - the second is where tone, naming, provenance
|
||||
and citation are defined.
|
||||
[kb/CONTRACT.md](../../kb/CONTRACT.md) plus
|
||||
[kb/CONVENTIONS.md](../../kb/CONVENTIONS.md) first - the second is where provenance and
|
||||
citation are defined, the third where this instance's tone and naming forms are.
|
||||
|
||||
New:
|
||||
|
||||
|
||||
@@ -13,10 +13,12 @@ integrating into an existing one.
|
||||
|
||||
**Before the first `wikitool` call:** [session-setup.md](../session-setup.md).
|
||||
|
||||
**Read before drafting:** [kb/CONTRACT.md](../../kb/CONTRACT.md) - naming, tone, linking,
|
||||
provenance and confidence - together with the target collection's own `COLLECTION.md`, which
|
||||
carries its quality goal and what is local to that subtree. Field-level requirements come from
|
||||
`tools/wikitool types describe <type>`.
|
||||
**Read before drafting:** [kb/CONTRACT.md](../../kb/CONTRACT.md) - linking, provenance and the
|
||||
confidence machinery, all of which the tool enforces - and
|
||||
[kb/CONVENTIONS.md](../../kb/CONVENTIONS.md), which is where this instance's language, naming
|
||||
forms, tone and relationship labels are, together with the target collection's own
|
||||
`COLLECTION.md`, which carries its quality goal and what is local to that subtree. Field-level
|
||||
requirements come from `tools/wikitool types describe <type>`.
|
||||
|
||||
## Creating a page
|
||||
|
||||
@@ -46,7 +48,7 @@ carries its quality goal and what is local to that subtree. Field-level requirem
|
||||
subjects - so the prose connects to existing pages instead of restating them.
|
||||
|
||||
5. **Draft.** Fill in the generated skeleton's TODO sections, following the tone rules in
|
||||
[kb/CONTRACT.md](../../kb/CONTRACT.md#tone). If `provenance:` is `sourced` or `mixed`, cite
|
||||
[kb/CONVENTIONS.md](../../kb/CONVENTIONS.md#tone). If `provenance:` is `sourced` or `mixed`, cite
|
||||
hard facts as you write them with `tools/wikitool cite add --page "<Title>" --source
|
||||
"Source - X"`, which also adds `X` to `sources:` - paste the `[^cite-id]` marker it prints.
|
||||
|
||||
|
||||
+74
-72
@@ -7,12 +7,25 @@ material in `raw/`, and is expected to stay correct without being re-derived.
|
||||
**Quality goal:** a page should answer a future question *without* re-reading the source it
|
||||
came from. If answering still requires the raw file, the page is incomplete.
|
||||
|
||||
This file holds the rules that apply in **every** collection. Each `kb/<name>/COLLECTION.md`
|
||||
declares that it inherits them and adds only what is local to its own subtree - read this file
|
||||
together with the target collection's contract before writing or editing a page.
|
||||
This file holds the rules that apply in **every** collection **and in every instance**. That
|
||||
second half is the cut: what is written here is enforced by `tools/wikitool` or follows from
|
||||
how it works, so it is identical everywhere and `dist export` ships it verbatim.
|
||||
|
||||
**What an instance decides for itself is next door, in
|
||||
[kb/CONVENTIONS.md](CONVENTIONS.md)** - the language pages are written in and its three
|
||||
tool-owned section headings, the naming forms, the tone, the relationship-label vocabulary, the
|
||||
confidence rubric. That file binds exactly as this one does; it is simply owned by the instance
|
||||
rather than by the stack, so the distribution ships only its `.template` and the instance writes
|
||||
the real one. Read both, plus the target collection's `kb/<name>/COLLECTION.md` (also
|
||||
instance-owned), before writing or editing a page.
|
||||
|
||||
The split is by **who may change the sentence**, not by what it is about. Language, tone and
|
||||
naming used to sit here, which meant every instance that answered "not German" to
|
||||
`setup-instance.md` was locally editing a file the stack also ships - and a merge from upstream
|
||||
would quietly hand it back.
|
||||
|
||||
Structural facts (which frontmatter fields exist, which are required, what the body skeleton
|
||||
looks like) are *not* here - they belong to the type-specs and are printed by
|
||||
looks like) are in neither - they belong to the type-specs and are printed by
|
||||
`tools/wikitool types describe <type>`. Never hand-write frontmatter; scaffold with
|
||||
`tools/wikitool new <type> --name "<Name>" --set field=value ...`.
|
||||
|
||||
@@ -21,7 +34,15 @@ looks like) are *not* here - they belong to the type-specs and are printed by
|
||||
`kb/` is a **namespace, not a collection**. It carries no `COLLECTION.md` of its own.
|
||||
|
||||
A directory under `kb/` is a **collection** exactly when it contains a `COLLECTION.md`. That
|
||||
file is the local authoring contract for every page in the subtree.
|
||||
file is the local authoring contract for every page in the subtree, and it belongs to the
|
||||
instance: it declares in its frontmatter which profile from
|
||||
[instructions/kb-profiles.md](../instructions/kb-profiles.md) it adopted, and whether the stack
|
||||
resolves against it by name.
|
||||
|
||||
| Field | Means |
|
||||
|---|---|
|
||||
| `profile:` | Which catalogue entry this contract started from, or `none`. Free text - the catalogue is a palette, not an enum, and a collection an instance invented has no entry to name |
|
||||
| `required_by_stack:` | Whether `wikitool` itself depends on this collection *by name*. Not the instance's to choose: `docs verify` checks it against the stack's own list. `kb/sources/` is `true` - `sources coverage`, `[^cite-id]` resolution and `kb/provenance.md` all resolve against that name - and everything else is `false` |
|
||||
|
||||
- A subdirectory *inside* a collection is an **area**. It inherits the enclosing contract and
|
||||
must not carry a `COLLECTION.md` of its own - `kb/entities/systems/` is an area of
|
||||
@@ -40,9 +61,11 @@ file is the local authoring contract for every page in the subtree.
|
||||
| `kb/sources/` | One summary page per ingested source, carrying its `raw_files:` provenance | [sources/COLLECTION.md](sources/COLLECTION.md) |
|
||||
| `kb/comparisons/` | Structured comparisons of two or more existing pages | [comparisons/COLLECTION.md](comparisons/COLLECTION.md) |
|
||||
|
||||
**Adding a collection:** `mkdir kb/<name>` and write a `kb/<name>/COLLECTION.md`. Collections
|
||||
The four rows above are this instance's collections, not a fixed set. **Adding one:**
|
||||
`mkdir kb/<name>` and write a `kb/<name>/COLLECTION.md` with the two fields above. Collections
|
||||
are discovered by contract presence, so no code change is needed. A collection only becomes
|
||||
*writable* once some type-spec declares a matching `base_dir:`.
|
||||
*writable* once some type-spec declares a matching `base_dir:`. Renaming or dropping one is the
|
||||
instance's call too - except where `required_by_stack: true` says otherwise.
|
||||
|
||||
**Where a page goes** is decided by its type-spec, never by hand - see
|
||||
[types/type-spec.md](../types/type-spec.md).
|
||||
@@ -61,18 +84,16 @@ Never hand-edit these; they are produced by `tools/wikitool`:
|
||||
To *find* a page, search rather than read the catalog: `tools/wikitool search "<text>"`, or
|
||||
`tools/wikitool search --field <predicate>` for a structured query over frontmatter.
|
||||
|
||||
## Naming
|
||||
## Titles are identifiers
|
||||
|
||||
- Human-readable titles with spaces: `Hybrid Search.md`, `Gitea Actions.md` - not kebab-case.
|
||||
- Singular for entities: `ha-core.md`, not `ha-cores.md`.
|
||||
- Comparison pages read as a comparison: `Go vs Rust.md`.
|
||||
- ADRs are prefixed: `adr-001-use-go-modules.md`.
|
||||
- The filename stem *is* the page title, and `[[wikilinks]]` must match it exactly.
|
||||
- Prefer readability over convention when the two conflict.
|
||||
**The filename stem *is* the page title, and `[[wikilinks]]` must match it exactly.** That is
|
||||
not a naming preference; it is the wiki's only way to address a page. `wikitool lint` reports an
|
||||
H1 that stops matching its title, `rename`/`rm` rewrite every reference to a stem, and a
|
||||
`[^cite-id]` resolves through one.
|
||||
|
||||
What to name a thing: projects use their repository or common name; systems a descriptive
|
||||
name; tools the tool's own name; technologies their standard spelling and capitalization;
|
||||
people a full name or common handle.
|
||||
Which *form* those titles take - spaces or kebab-case, singular or plural, what prefixes a
|
||||
decision record - is the instance's, in
|
||||
[kb/CONVENTIONS.md § Naming](CONVENTIONS.md#naming).
|
||||
|
||||
## Every page should
|
||||
|
||||
@@ -83,31 +104,20 @@ people a full name or common handle.
|
||||
- [ ] Duplicate no existing page
|
||||
- [ ] Appear in the catalog (guaranteed by `wikitool index rebuild`)
|
||||
|
||||
## Tone
|
||||
## Quotation cap
|
||||
|
||||
Wikipedia style: factual, neutral, specific.
|
||||
At most 2 blockquoted lines per page. `wikitool lint` reports overages as advisory, since
|
||||
exceeding the cap can be a legitimate judgment call - but the page should carry the knowledge
|
||||
itself, not delegate it to quotations. The cap is about how much of the page you let quotes
|
||||
carry; it does not apply to text you are citing verbatim from a source.
|
||||
|
||||
- No buzzwords ("bahnbrechend", "hochmodern", "leistungsstark", "revolutioniert").
|
||||
- No AI filler ("es sei angemerkt", "es ist wichtig zu betonen", "in der heutigen Zeit").
|
||||
- No em-dash asides carrying parenthetical reasoning.
|
||||
- At most 2 blockquoted lines per page. `wikitool lint` reports overages as advisory, since
|
||||
exceeding the cap can be a legitimate judgment call - but the page should carry the
|
||||
knowledge itself, not delegate it to quotations. The cap is about how much of the page you
|
||||
let quotes carry; it does not apply to text you are citing verbatim from a source.
|
||||
The register those lines are written in - what counts as a buzzword, what filler is refused -
|
||||
is the instance's, in [kb/CONVENTIONS.md § Tone](CONVENTIONS.md#tone).
|
||||
|
||||
Good: "MQTT ist ein leichtgewichtiges Publish-Subscribe-Protokoll für Geräte mit knappen
|
||||
Ressourcen."
|
||||
## Language and identifiers
|
||||
|
||||
Bad: "MQTT ist ein bahnbrechendes, hochmodernes Protokoll, das die IoT-Kommunikation
|
||||
revolutioniert - und es sei angemerkt, dass es ein Publish-Subscribe-Muster verwendet."
|
||||
|
||||
## 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 including this one, `instructions/`,
|
||||
and the type-specs for non-page artifacts.
|
||||
*Which* language pages are written in is [kb/CONVENTIONS.md](CONVENTIONS.md)'s to say. What
|
||||
follows here is the part that is not a choice, because the tool resolves against it.
|
||||
|
||||
Every line of a page is either **prose** or an **identifier**. Only prose is translated.
|
||||
|
||||
@@ -118,20 +128,14 @@ source page's Summary / Key Takeaways / Action Items / Not Extracted, and `summa
|
||||
|
||||
| Identifier | Why |
|
||||
|---|---|
|
||||
| Page titles, and the H1 that repeats one | A title is the wiki's only identifier for a page and follows the subject's own established name - see [Naming](#naming). `wikitool lint` reports an H1 that stops matching its title |
|
||||
| Page titles, and the H1 that repeats one | A title is the wiki's only identifier for a page and follows the subject's own established name - see [Titles are identifiers](#titles-are-identifiers). `wikitool lint` reports an H1 that stops matching its title |
|
||||
| The subtype value on the generated `**Typ:**` line | It renders a schema enum value (`technology`, `workflow`), which `search --field` filters on. The label is prose; the value is not |
|
||||
| `tags:` | Search keys, not prose |
|
||||
| Commands, paths, config keys, hostnames, code | They are what they are |
|
||||
| Quotations | Quoted verbatim in the source's own language |
|
||||
|
||||
Established English technical terms stay English inside German prose - "GitOps", "Ownership
|
||||
Model", "Reverse Proxy", "Pull Request". Translate a term only where the German one is genuinely
|
||||
the more common usage. A coined German equivalent nobody else writes makes the page harder to
|
||||
find, not more idiomatic.
|
||||
|
||||
Which terms those are, which have a settled German form, and the register the prose is written in:
|
||||
[instructions/german-terminology.md](../instructions/german-terminology.md). It is lookup material,
|
||||
not a second rule - every entry in it is a decision that was made wrong once first.
|
||||
Which foreign technical terms stay untranslated inside that prose is a judgment call the
|
||||
instance records - see [kb/CONVENTIONS.md § Language](CONVENTIONS.md#language).
|
||||
|
||||
**A source in another language** is still summarized in the KB language: a source page is
|
||||
evidence *about* a source, not a substitute for it. Quote verbatim in the original language and
|
||||
@@ -141,14 +145,18 @@ record the raw file's language in `source_language:`.
|
||||
|
||||
Three headings are a vocabulary the tool owns rather than prose an author picks: `xref add`
|
||||
writes into Relationships and See Also, and `cite add` owns the trailing Footnotes block. They
|
||||
follow the KB language like everything else - `## Beziehungen`, `## Siehe auch`, `## Fußnoten` -
|
||||
and `tools/chemenu/sections.py` is the single place naming them.
|
||||
follow the KB language like everything else, so **the instance names them**, in
|
||||
`kb/CONVENTIONS.md`'s `sections:` frontmatter. `tools/chemenu/conventions.py` reads that
|
||||
declaration and `tools/chemenu/sections.py` is what the rest of the compiler asks - there is no
|
||||
heading text in the compiler itself.
|
||||
|
||||
Each has aliases the tool still *recognizes* but no longer writes, which is what lets the corpus
|
||||
be translated page by page: a page still carrying `## Relationships` is found and appended to
|
||||
correctly, and `cite sync` leaves an untranslated `## Footnotes` heading alone rather than
|
||||
retitling it. Renaming a heading is the translation pass's job, never a side effect of another
|
||||
command. Any *other* heading an author adds is ordinary prose and is translated with the rest.
|
||||
retitling it. The recognized set is the canonical name, any `section_aliases:` the instance
|
||||
declared, and the names this stack wrote before the declaration existed. Renaming a heading is
|
||||
the translation pass's job, never a side effect of another command. Any *other* heading an
|
||||
author adds is ordinary prose and is translated with the rest.
|
||||
|
||||
## Linking
|
||||
|
||||
@@ -156,14 +164,10 @@ Every page links to what it mentions, in both directions. Cross-references are c
|
||||
`tools/wikitool xref add --a "<A>" --b "<B>" --rel-a "<label>" --rel-b "<label>"`, never by
|
||||
hand-editing the `related:` array or the Relationships/See Also bullets.
|
||||
|
||||
Use a typed relationship label rather than a generic one:
|
||||
|
||||
`hängt ab von` · `verwendet` · `implementiert` · `erweitert` · `ersetzt` · `steht in Konflikt mit`
|
||||
· `benötigt` · `erzeugt` · `konsumiert` · `besitzt` · `pflegt` · `läuft auf` · `verwandt mit`
|
||||
(last resort)
|
||||
|
||||
The labels are prose written into a `- **label:** [[Title]]` bullet; no code matches on them, so
|
||||
an untranslated page's English label is stale wording, not a broken reference.
|
||||
Use a typed relationship label rather than a generic one. The label is free text as far as the
|
||||
tool is concerned - it is written into a `- **label:** [[Title]]` bullet and no code matches on
|
||||
it - so which vocabulary this instance uses is
|
||||
[kb/CONVENTIONS.md § Relationship labels](CONVENTIONS.md#relationship-labels)'s to list.
|
||||
|
||||
A page is expected to have at least one inbound link; `wikitool lint` reports orphans.
|
||||
Comparison pages are exempt - they are reached through the catalog.
|
||||
@@ -187,15 +191,16 @@ Every claim is either traceable to a raw file or explicitly marked as not.
|
||||
command, or config value. `tools/wikitool cite add --page "<Title>" --source "Source - X"
|
||||
[--file <qualifier>]` mints the id, upserts its `[[Source - X]]` (or
|
||||
`[[Source - X|storage-model.md]]` for a multi-file source) definition in the page's trailing
|
||||
`## Footnotes` block, and adds `Source - X` to `sources:` - it prints the marker to paste at
|
||||
Footnotes block (named per [Section headings](#section-headings)), and adds `Source - X` to
|
||||
`sources:` - it prints the marker to paste at
|
||||
the fact; placing it is still manual. Never hand-type a cite-id (AGENTS.md invariant 1). This
|
||||
differs from a plain `[[Source - X]]` link, which only means "related to".
|
||||
- **Notation inside code is notation, not a reference.** A `[^cite-id]` or a `[[wikilink]]`
|
||||
written in backticks or a fenced block is read as an example: the citation does not count and
|
||||
the link does not exist. That is what lets a page document this stack's own syntax. It also
|
||||
means a marker appended to a line *inside* a fence cites nothing - put it on a
|
||||
`Quelle: [^cite-id]` line under the block, where it renders as a footnote instead of
|
||||
travelling with the command when someone copies it.
|
||||
means a marker appended to a line *inside* a fence cites nothing - put it on a source line
|
||||
under the block (`<source-word>: [^cite-id]`, in the KB language), where it renders as a
|
||||
footnote instead of travelling with the command when someone copies it.
|
||||
- A source cited inline must also appear in the page's frontmatter `sources:` list;
|
||||
`wikitool lint` checks this in both directions, and hard-errors on a leftover pre-migration
|
||||
`^[[...]]` marker, an undefined `[^cite-id]` reference, or an orphaned Footnotes definition.
|
||||
@@ -215,23 +220,20 @@ one - and never file the synthesized version back into the wiki.
|
||||
`confidence` is *derived* from it by `tools/wikitool confidence decay` and must never be
|
||||
edited directly.
|
||||
|
||||
Base score for a single source is 0.5, adjusted by:
|
||||
|
||||
- **+0.2 per supporting source** (max +0.6)
|
||||
- **+0.2** if confirmed <30 days ago, **+0.1** if <90 days
|
||||
- **+0.1** for official documentation, **+0.05** for a reputable secondary source
|
||||
- **+0.1** if multiple independent sources agree
|
||||
|
||||
Re-assess a page with `tools/wikitool touch --page "<Title>" --confidence-base <value>`.
|
||||
|
||||
In prose, hedge according to the score: below 0.6 write "möglicherweise"/"kann"; below 0.4
|
||||
write "unsicher"/"unbestätigt".
|
||||
What the number *means* - the base score, what raises it and by how much, and how to hedge in
|
||||
prose below a threshold - is a rubric rather than a mechanism, so it is
|
||||
[kb/CONVENTIONS.md § Confidence rubric](CONVENTIONS.md#confidence-rubric)'s.
|
||||
|
||||
## What does not belong here
|
||||
|
||||
- Raw source material - it stays immutable under `raw/`.
|
||||
- Type definitions, frontmatter contracts, or templates - those live in `types/`.
|
||||
- Procedures for operating the tooling - those live in `instructions/`.
|
||||
- **Anything an instance would have to rewrite for itself** - language, naming forms, tone,
|
||||
relationship labels, the confidence rubric. Those are `kb/CONVENTIONS.md`'s, and a sentence
|
||||
of that kind here is a sentence the stack ships over the instance's own answer.
|
||||
- Rules that apply to only one collection - those belong in that collection's
|
||||
`COLLECTION.md`.
|
||||
- Hand-edited generated files - see [Generated files](#generated-files).
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
---
|
||||
language: de
|
||||
profile: german
|
||||
sections:
|
||||
relationships: Beziehungen
|
||||
see_also: Siehe auch
|
||||
footnotes: Fußnoten
|
||||
---
|
||||
|
||||
# kb/ - Authoring Conventions of This Instance
|
||||
|
||||
The decisions [kb/CONTRACT.md](CONTRACT.md) deliberately does not make. The contract holds what
|
||||
the code enforces and is identical in every instance; this file holds what *this* instance
|
||||
chose, and no other instance has to agree with a word of it.
|
||||
|
||||
**It binds all the same.** Everything below applies to every page under `kb/`, exactly as the
|
||||
contract does. The difference is ownership, not authority: a rule here is changed by editing
|
||||
this file, a rule there by changing the stack.
|
||||
|
||||
Adopted from the `german` profile in
|
||||
[instructions/kb-profiles.md](../instructions/kb-profiles.md). That catalogue is a palette, not
|
||||
an enum - what is written here is what holds, whether or not a profile says the same thing.
|
||||
|
||||
The frontmatter above is the one machine-read part. `sections:` names the three headings
|
||||
`wikitool xref` and `wikitool cite` write into; `tools/chemenu/conventions.py` reads them and
|
||||
`tools/chemenu/sections.py` is what the rest of the compiler asks. Renaming one here changes
|
||||
what the tool *writes*; what it still *recognizes* is the union of that name, any
|
||||
`section_aliases:` declared beside it, and the names this stack wrote before this file existed.
|
||||
That asymmetry is the translation path: a page keeps working under its old heading until it is
|
||||
itself translated.
|
||||
|
||||
## 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.
|
||||
|
||||
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
|
||||
[kb/CONTRACT.md § Language and identifiers](CONTRACT.md#language-and-identifiers).
|
||||
|
||||
Established English technical terms stay English inside German prose - "GitOps", "Ownership
|
||||
Model", "Reverse Proxy", "Pull Request". Translate a term only where the German one is genuinely
|
||||
the more common usage. A coined German equivalent nobody else writes makes the page harder to
|
||||
find, not more idiomatic.
|
||||
|
||||
Which terms those are, which have a settled German form, and the register the prose is written
|
||||
in: [instructions/german-terminology.md](../instructions/german-terminology.md). It is lookup
|
||||
material, not a second rule - every entry in it is a decision that was made wrong once first.
|
||||
|
||||
### Section headings
|
||||
|
||||
The canonical names are the frontmatter's: `## Beziehungen`, `## Siehe auch`, `## Fußnoten`.
|
||||
The English forms this stack wrote before the corpus was translated are still recognized, so a
|
||||
page carrying `## Relationships` is found and appended to correctly and `cite sync` leaves an
|
||||
untranslated `## Footnotes` alone. Renaming such a heading is the translation pass's job, never
|
||||
a side effect of another command. Any *other* heading an author adds is ordinary prose and is
|
||||
translated with the rest.
|
||||
|
||||
## Naming
|
||||
|
||||
- Human-readable titles with spaces: `Hybrid Search.md`, `Gitea Actions.md` - not kebab-case.
|
||||
- Singular for entities: `ha-core.md`, not `ha-cores.md`.
|
||||
- Comparison pages read as a comparison: `Go vs Rust.md`.
|
||||
- ADRs are prefixed: `adr-001-use-go-modules.md`.
|
||||
- Prefer readability over convention when the two conflict.
|
||||
|
||||
What to name a thing: projects use their repository or common name; systems a descriptive
|
||||
name; tools the tool's own name; technologies their standard spelling and capitalization;
|
||||
people a full name or common handle.
|
||||
|
||||
The one naming fact that is *not* a choice, and therefore lives in the contract: the filename
|
||||
stem is the page title, and `[[wikilinks]]` must match it exactly.
|
||||
|
||||
## Tone
|
||||
|
||||
Wikipedia style: factual, neutral, specific.
|
||||
|
||||
- No buzzwords ("bahnbrechend", "hochmodern", "leistungsstark", "revolutioniert").
|
||||
- No AI filler ("es sei angemerkt", "es ist wichtig zu betonen", "in der heutigen Zeit").
|
||||
- No em-dash asides carrying parenthetical reasoning.
|
||||
|
||||
Good: "MQTT ist ein leichtgewichtiges Publish-Subscribe-Protokoll für Geräte mit knappen
|
||||
Ressourcen."
|
||||
|
||||
Bad: "MQTT ist ein bahnbrechendes, hochmodernes Protokoll, das die IoT-Kommunikation
|
||||
revolutioniert - und es sei angemerkt, dass es ein Publish-Subscribe-Muster verwendet."
|
||||
|
||||
The blockquote cap is not here: `wikitool lint` reports it, so it is the contract's.
|
||||
|
||||
## Relationship labels
|
||||
|
||||
`tools/wikitool xref add --rel-a/--rel-b` takes a free-text label. This instance uses a typed
|
||||
one rather than a generic one:
|
||||
|
||||
`hängt ab von` · `verwendet` · `implementiert` · `erweitert` · `ersetzt` · `steht in Konflikt mit`
|
||||
· `benötigt` · `erzeugt` · `konsumiert` · `besitzt` · `pflegt` · `läuft auf` · `verwandt mit`
|
||||
(last resort)
|
||||
|
||||
The labels are prose written into a `- **label:** [[Title]]` bullet; no code matches on them, so
|
||||
an untranslated page's English label is stale wording, not a broken reference.
|
||||
|
||||
## Confidence rubric
|
||||
|
||||
`confidence_base` is set by hand and `confidence` is derived from it - that mechanism is the
|
||||
contract's. What the number *means* is this instance's:
|
||||
|
||||
Base score for a single source is 0.5, adjusted by:
|
||||
|
||||
- **+0.2 per supporting source** (max +0.6)
|
||||
- **+0.2** if confirmed <30 days ago, **+0.1** if <90 days
|
||||
- **+0.1** for official documentation, **+0.05** for a reputable secondary source
|
||||
- **+0.1** if multiple independent sources agree
|
||||
|
||||
In prose, hedge according to the score: below 0.6 write "möglicherweise"/"kann"; below 0.4
|
||||
write "unsicher"/"unbestätigt".
|
||||
|
||||
## Keeping this file honest
|
||||
|
||||
Change it when a convention actually changes, and treat a change to `sections:` as a corpus
|
||||
migration rather than an edit: existing pages keep their old headings until something translates
|
||||
them, and the alias list is what carries them in the meantime. `wikitool doctor` FAILs on a
|
||||
missing or unfilled file, and `wikitool docs verify` refuses a `sections:` block that does not
|
||||
name all three slots.
|
||||
@@ -0,0 +1,104 @@
|
||||
---
|
||||
# wikitool:template-unfilled - delete this line once the file is answered.
|
||||
language: en
|
||||
profile: none
|
||||
sections:
|
||||
relationships: Relationships
|
||||
see_also: See Also
|
||||
footnotes: Footnotes
|
||||
# Headings this instance no longer writes but still recognizes, so a corpus can
|
||||
# be translated page by page instead of all at once. Optional; the names this
|
||||
# stack wrote before this file existed are always recognized anyway.
|
||||
# section_aliases:
|
||||
# relationships: [Beziehungen]
|
||||
# see_also: [Siehe auch]
|
||||
# footnotes: [Fußnoten]
|
||||
---
|
||||
|
||||
# kb/ - Authoring Conventions of This Instance
|
||||
|
||||
The decisions [kb/CONTRACT.md](CONTRACT.md) deliberately does not make. The contract holds what
|
||||
the code enforces and is identical in every instance; this file holds what *this* instance
|
||||
chooses, and no other instance has to agree with a word of it.
|
||||
|
||||
**It binds all the same.** Everything below applies to every page under `kb/`, exactly as the
|
||||
contract does. The difference is ownership, not authority: a rule here is changed by editing
|
||||
this file, a rule there by changing the stack.
|
||||
|
||||
Ready-made answers to every section below - including a complete German profile - are in
|
||||
[instructions/kb-profiles.md](../instructions/kb-profiles.md). That catalogue is a palette, not
|
||||
an enum: adopt an entry, adapt it, or write your own. What is written *here* is what holds.
|
||||
|
||||
The frontmatter above is the one machine-read part. `sections:` names the three headings
|
||||
`wikitool xref` and `wikitool cite` write into. Set them before the first page is written:
|
||||
afterwards, changing one is a corpus migration rather than an edit.
|
||||
|
||||
## 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.
|
||||
|
||||
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
|
||||
[kb/CONTRACT.md § Language and identifiers](CONTRACT.md#language-and-identifiers).
|
||||
|
||||
{Which established foreign-language technical terms stay untranslated inside this instance's
|
||||
prose, and where the vocabulary for that is looked up. Delete this paragraph if the KB language
|
||||
is the one those terms are already in.}
|
||||
|
||||
### Section headings
|
||||
|
||||
The canonical names are the frontmatter's. Any name this instance previously wrote stays
|
||||
recognized through `section_aliases:`, which is what lets a corpus be translated page by page.
|
||||
Renaming such a heading is the translation pass's job, never a side effect of another command.
|
||||
Any *other* heading an author adds is ordinary prose.
|
||||
|
||||
## Naming
|
||||
|
||||
- {Title form - words and spaces, or kebab-case, or the subject's own spelling.}
|
||||
- {Singular or plural for entities.}
|
||||
- {How a comparison page's title reads.}
|
||||
- {The ADR prefix, if this instance files decisions as pages.}
|
||||
- {What to name a thing: projects, systems, tools, technologies, people.}
|
||||
|
||||
The one naming fact that is *not* a choice, and therefore lives in the contract: the filename
|
||||
stem is the page title, and `[[wikilinks]]` must match it exactly.
|
||||
|
||||
## Tone
|
||||
|
||||
{The register pages are written in, in one line.}
|
||||
|
||||
- {Words and constructions this instance refuses, with examples in the KB language.}
|
||||
|
||||
Good: {one sentence that is what this instance wants.}
|
||||
|
||||
Bad: {the same sentence written the way it must not be.}
|
||||
|
||||
## Relationship labels
|
||||
|
||||
`tools/wikitool xref add --rel-a/--rel-b` takes a free-text label. Listing the ones this
|
||||
instance uses is what keeps a graph typed rather than a wiki full of "related to":
|
||||
|
||||
{the label vocabulary, in the KB language}
|
||||
|
||||
No code matches on these, so an old label on an untranslated page is stale wording, not a
|
||||
broken reference.
|
||||
|
||||
## Confidence rubric
|
||||
|
||||
`confidence_base` is set by hand and `confidence` is derived from it - that mechanism is the
|
||||
contract's. What the number *means* is this instance's:
|
||||
|
||||
{the base score, what raises it, and by how much}
|
||||
|
||||
{How to hedge in prose at a low score, in the KB language.}
|
||||
|
||||
## Keeping this file honest
|
||||
|
||||
Change it when a convention actually changes, and treat a change to `sections:` as a corpus
|
||||
migration rather than an edit. `wikitool doctor` FAILs on a missing or unfilled file, and
|
||||
`wikitool docs verify` refuses a `sections:` block that does not name all three slots.
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
profile: comparisons
|
||||
required_by_stack: false
|
||||
---
|
||||
|
||||
# kb/comparisons/ - Collection Contract
|
||||
|
||||
Structured head-to-head evaluations of two or more things that already have pages here. A
|
||||
@@ -7,8 +12,10 @@ comparison exists so that neither subject's own page has to argue against the ot
|
||||
That needs named, checkable dimensions and a stated trade-off; a page that lists differences
|
||||
without saying what they cost has described, not compared.
|
||||
|
||||
Inherits [kb/CONTRACT.md](../CONTRACT.md) - naming, tone, linking, provenance and confidence
|
||||
are defined there and are not restated here.
|
||||
Inherits [kb/CONTRACT.md](../CONTRACT.md) for the rules the stack enforces - linking mechanics,
|
||||
provenance, citation, the confidence machinery - and
|
||||
[kb/CONVENTIONS.md](../CONVENTIONS.md) for what this instance decided: language, naming forms,
|
||||
tone, relationship labels, the confidence rubric. Neither is restated here.
|
||||
|
||||
## Types offered
|
||||
|
||||
@@ -16,8 +23,9 @@ are defined there and are not restated here.
|
||||
|
||||
## Naming
|
||||
|
||||
The title reads as a comparison: `Go vs Rust.md`, `Traefik vs nginx.md`. Order the subjects as
|
||||
they are most commonly spoken, not alphabetically.
|
||||
The title form is [kb/CONVENTIONS.md § Naming](../CONVENTIONS.md#naming)'s. What is local here
|
||||
is the ordering: name the subjects as they are most commonly spoken together, not
|
||||
alphabetically.
|
||||
|
||||
## Requirements
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
profile: concepts
|
||||
required_by_stack: false
|
||||
---
|
||||
|
||||
# kb/concepts/ - Collection Contract
|
||||
|
||||
Ideas rather than things: architectures, patterns, protocols, workflows, recurring problems,
|
||||
@@ -8,8 +13,10 @@ records *what*.
|
||||
without the reader having to open the entity pages that use it. If the explanation only makes
|
||||
sense once you already know the system, it is on the wrong page.
|
||||
|
||||
Inherits [kb/CONTRACT.md](../CONTRACT.md) - naming, tone, linking, provenance and confidence
|
||||
are defined there and are not restated here.
|
||||
Inherits [kb/CONTRACT.md](../CONTRACT.md) for the rules the stack enforces - linking mechanics,
|
||||
provenance, citation, the confidence machinery - and
|
||||
[kb/CONVENTIONS.md](../CONVENTIONS.md) for what this instance decided: language, naming forms,
|
||||
tone, relationship labels, the confidence rubric. Neither is restated here.
|
||||
|
||||
## Types offered
|
||||
|
||||
@@ -17,8 +24,8 @@ are defined there and are not restated here.
|
||||
|
||||
## Decisions and ADRs
|
||||
|
||||
An architectural decision is a concept page prefixed `adr-NNN-`, e.g.
|
||||
`adr-001-use-go-modules.md`. It records:
|
||||
An architectural decision is a concept page, prefixed as
|
||||
[kb/CONVENTIONS.md § Naming](../CONVENTIONS.md#naming) says. It records:
|
||||
|
||||
- **Context** - what forced a decision.
|
||||
- **Decision** - what was chosen.
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
| [[Issue Label Scheme]] | decision | Zweiachsiges Pflicht-Labelschema fuer das Gitea-Board: prio/1..3 und size/XS..L, bewusst keine dritte Achse; die Regel liegt in instructions/dev/, weil sie keine ausgelieferte Instanz erreichen darf | 2026-08-31 |
|
||||
| [[Iteration and Cost Limits]] | workflow | Im Code durchgesetzte Obergrenze von 60 wikitool-Aufrufen je Session, Loop-Breaker bei 3 identischen Wiederholungen, Slot-Erstattung, ein gemessenes Kalibrierungsband, und Retrieval sowie der MCP-Leseserver bleiben ausgenommen | 2026-09-02 |
|
||||
| [[KB Migration]] | workflow | Migration des KB-Inhalts entlang einer geordneten Versionskette; abgegrenzt gegen offene Instanz-Aktionen, die in den doctor-Check gehoeren statt in die Kette | 2026-08-31 |
|
||||
| [[KB Stack Versioning]] | decision | Semantische Versionierung des Wiki-Stacks: VERSION beschreibt die Maschinerie, Kompatibilitaet ist die linkeste Nicht-Null-Komponente, und drei getrennte Dateien trennen Maschinerie, Herkunft und Content-Form | 2026-08-30 |
|
||||
| [[KB Stack Versioning]] | decision | Semantische Versionierung des Wiki-Stacks: VERSION beschreibt die Maschinerie, Kompatibilitaet (Drop-in-Ersatz) und Inhaltsmigration sind seit 2.5.0 getrennte, unabhaengig geprueft Fragen | 2026-09-02 |
|
||||
| [[Knowledge Compounding]] | workflow | Effekt, bei dem Wissen im Wiki an Wert gewinnt, weil jede neue Quelle an bestehende, untereinander verwiesene Seiten anknüpft und sie ergänzt. | 2026-08-29 |
|
||||
| [[Knowledge Graph]] | architecture | Typisierte Schicht aus Entities und Beziehungen über den Wiki-Seiten, die eine reichere Wissensdarstellung und graphbasierte Abfragen ermöglicht. | 2026-08-29 |
|
||||
| [[Lint Workflow]] | workflow | Deterministischer Health-Check rund um wikitool lint; seit 1.7.2 maskiert es Code vor dem Notation-Match und zaehlt Zitat-Bloecke statt Zeilen | 2026-09-01 |
|
||||
|
||||
@@ -3,13 +3,13 @@ type: types/concept.md
|
||||
concept_type: decision
|
||||
tags: [versioning, semver, release, stack]
|
||||
created: 2026-08-30
|
||||
modified: 2026-08-30
|
||||
modified: 2026-09-02
|
||||
related: [wikitool, Issue Label Scheme]
|
||||
sources: [Source - Conversation - Versioning CI-CD and Content Migration Session 2026-08-30, Source - Conversation - Issue Triage Labels and TODO Retirement Session 2026-08-31]
|
||||
sources: [Source - Conversation - Versioning CI-CD and Content Migration Session 2026-08-30, Source - Conversation - Issue Triage Labels and TODO Retirement Session 2026-08-31, Source - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02]
|
||||
confidence: 0.70
|
||||
confidence_base: 0.70
|
||||
provenance: sourced
|
||||
summary: 'Semantische Versionierung des Wiki-Stacks: VERSION beschreibt die Maschinerie, Kompatibilitaet ist die linkeste Nicht-Null-Komponente, und drei getrennte Dateien trennen Maschinerie, Herkunft und Content-Form'
|
||||
summary: 'Semantische Versionierung des Wiki-Stacks: VERSION beschreibt die Maschinerie, Kompatibilitaet (Drop-in-Ersatz) und Inhaltsmigration sind seit 2.5.0 getrennte, unabhaengig geprueft Fragen'
|
||||
---
|
||||
# KB Stack Versioning
|
||||
|
||||
@@ -42,9 +42,18 @@ deshalb eine ausdrückliche Handlung.
|
||||
Caret-Ranges
|
||||
verwenden[^s-conversation-versioning-ci-cd-and-content-migration-session-2026-08-30]. Sie gilt
|
||||
einheitlich für `0.x` und `1.x`, sodass unter `0.x` der Schritt `0.1.x` -> `0.2.0` dasselbe
|
||||
Migrationssignal trägt wie `MAJOR` ab `1.0.0`. Der Code für den `compat_key` ist deshalb
|
||||
einheitlich formuliert und musste beim Wechsel auf `1.0.0` nicht angefasst
|
||||
Signal trägt wie `MAJOR` ab `1.0.0`. Der Code für den `compat_key` ist deshalb einheitlich
|
||||
formuliert und musste beim Wechsel auf `1.0.0` nicht angefasst
|
||||
werden[^s-conversation-versioning-ci-cd-and-content-migration-session-2026-08-30].
|
||||
- **Kompatibilität und Inhaltsmigration sind zwei unabhängige Fragen, seit `2.5.0` auch zwei
|
||||
getrennte Marker.** Kompatibilität fragt, ob die neue Version ein Drop-in-Ersatz ist -
|
||||
vorwärts ohne Handarbeit, rückwärts noch downgradebar; Inhaltsmigration fragt, ob `kb/` sich
|
||||
bewegen muss. Ein Grenzübertritt kann `kb/` unangetastet lassen und trotzdem MAJOR sein - der
|
||||
`2.0.0`-Rebranding-Bump ist das Beispiel: Update-Pfad, Release-Artefaktname und
|
||||
Paket-Import-Name brachen, keine Seite tat es. `version bump` verlangt deshalb bei jedem
|
||||
Grenzübertritt `--breaking "<was aufhört zu funktionieren>"`, unabhängig von
|
||||
`--no-migration`/einem Migrationsdokument; beide Zeilen landen getrennt im
|
||||
`CHANGES.md`-Eintrag[^s-version-part-nomenclature-and-breaking-change-gate-session-2026-09-02].
|
||||
- **`x.y.z` ist die maximale Granularität. Keine Pre-Release-Suffixe.** Eine zweite
|
||||
Ordnungsregel müsste vom Release-Feed, von der Migrationskette und von der
|
||||
Kompatibilitätsprüfung gleichermaßen befolgt
|
||||
@@ -64,7 +73,11 @@ deshalb eine ausdrückliche Handlung.
|
||||
Instanz[^s-conversation-versioning-ci-cd-and-content-migration-session-2026-08-30].
|
||||
- **Die Grenze wird an zwei Stellen erzwungen:** in `version bump` und in `docs verify`, ergänzt
|
||||
um einen `kb-version`-Check in
|
||||
`doctor`[^s-conversation-versioning-ci-cd-and-content-migration-session-2026-08-30].
|
||||
`doctor`[^s-conversation-versioning-ci-cd-and-content-migration-session-2026-08-30]. Seit
|
||||
`2.5.0` prüft `docs verify` dort zwei unabhängige Dinge - `check_migration_for_boundary` (hat
|
||||
der Korpus sich bewegt) und `check_breaking_change_for_boundary` (wurde der Bruch benannt) -,
|
||||
weil ein Grenzübertritt die eine Prüfung bestehen und an der anderen scheitern
|
||||
kann[^s-version-part-nomenclature-and-breaking-change-gate-session-2026-09-02].
|
||||
|
||||
## Beispiele
|
||||
|
||||
@@ -105,7 +118,9 @@ kann.
|
||||
- [[wikitool]]
|
||||
- [[Issue Label Scheme]]
|
||||
- [[Source - Conversation - Issue Triage Labels and TODO Retirement Session 2026-08-31]]
|
||||
- [[Source - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02]]
|
||||
|
||||
## Fußnoten
|
||||
|
||||
[^s-conversation-versioning-ci-cd-and-content-migration-session-2026-08-30]: [[Source - Conversation - Versioning CI-CD and Content Migration Session 2026-08-30]]
|
||||
[^s-version-part-nomenclature-and-breaking-change-gate-session-2026-09-02]: [[Source - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02]]
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
profile: entities
|
||||
required_by_stack: false
|
||||
---
|
||||
|
||||
# kb/entities/ - Collection Contract
|
||||
|
||||
Concrete things that exist: a project, a deployed system, a CLI tool, a technology, a person or
|
||||
@@ -7,8 +12,10 @@ an organization. If it can be pointed at, it is an entity.
|
||||
is, where it actually is, and whether that is still true. An entity page that describes a
|
||||
system correctly but names no host, path, version or status has not earned its keep.
|
||||
|
||||
Inherits [kb/CONTRACT.md](../CONTRACT.md) - naming, tone, linking, provenance and confidence
|
||||
are defined there and are not restated here.
|
||||
Inherits [kb/CONTRACT.md](../CONTRACT.md) for the rules the stack enforces - linking mechanics,
|
||||
provenance, citation, the confidence machinery - and
|
||||
[kb/CONVENTIONS.md](../CONVENTIONS.md) for what this instance decided: language, naming forms,
|
||||
tone, relationship labels, the confidence rubric. Neither is restated here.
|
||||
|
||||
## Types offered
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|------|------|---------|----------------|
|
||||
| [[andybalholm-edl]] | project | Go-basierte EDL-Bibliothek für die Kommunikation mit eingebetteten Geräten. | 2026-08-29 |
|
||||
| [[BCDModule]] | project | Go-Modul, das die Entscheidungslogik für die Batterieladung umsetzt. | 2026-08-29 |
|
||||
| [[Chemenu]] | project | Deterministischer Wissenskompiler (raw/ -> kb/); seit 2.0.0 unter dem Namen Chemenu, seit 2026-09-01 oeffentlich, seit 2.4.0 mit einem MCP-Leseserver als zweitem Konsumenten | 2026-09-02 |
|
||||
| [[Chemenu]] | project | Deterministischer Wissenskompiler (raw/ -> kb/); seit 2.0.0 unter dem Namen Chemenu; Issue 26 zur Versionsstellen-Nomenklatur in 2.5.0 geschlossen | 2026-09-02 |
|
||||
| [[goresponsiveness]] | project | Go-Werkzeug zur Messung von Anwendungsleistung und Responsiveness. | 2026-08-29 |
|
||||
| [[ha-core]] | project | Kern-Integrationsbibliothek für Home-Assistant-E3DC-Systeme; stellt die E3DC-Kommunikationsprotokolle und den Home-Assistant-Integrationscode bereit. | 2026-08-29 |
|
||||
| [[hacs-e3dc]] | project | Home Assistant Custom Component zur Überwachung von E3DC-Energiesystemen. | 2026-08-29 |
|
||||
@@ -98,6 +98,6 @@
|
||||
| [[Proton]] | tool | Wine-basierte Kompatibilitätsschicht von Valve; lässt Windows-Spiele über Steam unter Linux laufen, mit optimierter DirectX-Übersetzung. | 2026-08-29 |
|
||||
| [[qmd]] | tool | Lokale Suchmaschine fuer Markdown-Dateien: TypeScript/Node.js/Bun, SQLite-FTS5-BM25 plus sqlite-vec-Vektorsuche plus node-llama-cpp-LLM-Reranking. | 2026-09-02 |
|
||||
| [[Steam]] | tool | Valves Plattform für digitalen Spielevertrieb und Spielebibliothek auf dem PC. | 2026-08-29 |
|
||||
| [[wikitool]] | tool | Deterministisches CLI fuer alle mechanischen Wiki-Operationen; seit 2.0.0 im Paket chemenu, seit 2.4.0 zusaetzlich als MCP-Leseserver erreichbar | 2026-09-02 |
|
||||
| [[wikitool]] | tool | Deterministisches CLI fuer alle mechanischen Wiki-Operationen; seit 2.0.0 im Paket chemenu, seit 2.4.0 zusaetzlich als MCP-Leseserver erreichbar, seit 2.5.0 mit Breaking-Change-Pflichtmarker | 2026-09-02 |
|
||||
| [[Wine]] | tool | Kompatibilitätsschicht, die Windows-API-Aufrufe nach POSIX übersetzt und Windows-Anwendungen unter Linux, BSD und macOS ohne Virtualisierung oder Emulation ausführt. | 2026-08-29 |
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ tags: [wiki, llm, knowledge-base]
|
||||
created: 2026-08-04
|
||||
modified: 2026-09-02
|
||||
related: [Personalization Plane, Issue Label Scheme, Optional Instance Context File, Delete Rather Than Anonymize, Dual Licensing by File Plan, Publish-Remote Gate, MCP-Leseserver]
|
||||
sources: [Source - Copilot Skill Restructure Instructions, Source - Conversation - AGENTS.md Skill Restructuring Session 2026-08-04, Source - Conversation - Versioning CI-CD and Content Migration Session 2026-08-30, Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31, Source - Conversation - Issue Triage Labels and TODO Retirement Session 2026-08-31, Source - Conversation - Write-Once Frontmatter Fields and touch --set Session 2026-08-31, Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31, Source - Conversation - Two Round-Trip Defects Found by an Ingest Session 2026-08-31, Source - Conversation - Hardening the Test Suite Against Silent Environment Dependencies Session 2026-08-31, 'Source - Public Release, Corpus Purge and History Squash Session 2026-09-01', Source - Publish-Remote Gate and Issue Triage Session 2026-09-01, Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01, Source - MCP Read Server Implementation Session 2026-09-02]
|
||||
sources: [Source - Copilot Skill Restructure Instructions, Source - Conversation - AGENTS.md Skill Restructuring Session 2026-08-04, Source - Conversation - Versioning CI-CD and Content Migration Session 2026-08-30, Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31, Source - Conversation - Issue Triage Labels and TODO Retirement Session 2026-08-31, Source - Conversation - Write-Once Frontmatter Fields and touch --set Session 2026-08-31, Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31, Source - Conversation - Two Round-Trip Defects Found by an Ingest Session 2026-08-31, Source - Conversation - Hardening the Test Suite Against Silent Environment Dependencies Session 2026-08-31, 'Source - Public Release, Corpus Purge and History Squash Session 2026-09-01', Source - Publish-Remote Gate and Issue Triage Session 2026-09-01, Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01, Source - MCP Read Server Implementation Session 2026-09-02, Source - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02]
|
||||
confidence: 0.90
|
||||
confidence_base: 0.90
|
||||
provenance: mixed
|
||||
summary: Deterministischer Wissenskompiler (raw/ -> kb/); seit 2.0.0 unter dem Namen Chemenu, seit 2026-09-01 oeffentlich, seit 2.4.0 mit einem MCP-Leseserver als zweitem Konsumenten
|
||||
summary: Deterministischer Wissenskompiler (raw/ -> kb/); seit 2.0.0 unter dem Namen Chemenu; Issue 26 zur Versionsstellen-Nomenklatur in 2.5.0 geschlossen
|
||||
---
|
||||
# Chemenu
|
||||
|
||||
@@ -159,7 +159,8 @@ Repositorys selbst und keine Aussagen aus einer Rohdatenquelle; der Änderungsda
|
||||
auf den alten Repo-Pfad und lässt sich per Invariante 1 nicht von Hand reparieren. Die
|
||||
erste Einschätzung lautete `1.9.0` und wurde von Torben korrigiert; die Lücke in der Doku,
|
||||
die dazu führte - MAJOR ist dort als Inhaltsmigration statt als Kompatibilitätsbruch
|
||||
beschrieben - liegt als Issue #26. Verzeichnet in `CHANGES.md` (`2.0.0`).
|
||||
beschrieben - war Issue #26 und wurde in `2.5.0` geschlossen (siehe [[KB Stack Versioning]]).
|
||||
Verzeichnet in `CHANGES.md` (`2.0.0`).
|
||||
- 2026-08-31 - `1.8.1` (Commit `a243a4a`, Korrektur `2b7b3cb`): Test-Coverage wird in CI
|
||||
gemessen und als Artefakt ausgewiesen, ohne `--cov-fail-under` - siehe
|
||||
Messen vor Schwelle. Die Messung deckte einen `dist export`-Fehler auf: Coverage-Ausgabe
|
||||
@@ -228,6 +229,7 @@ Repositorys selbst und keine Aussagen aus einer Rohdatenquelle; der Änderungsda
|
||||
- [[Publish-Remote Gate]]
|
||||
- [[Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01]]
|
||||
- [[MCP-Leseserver]]
|
||||
- [[Source - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02]]
|
||||
|
||||
## Fußnoten
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ tags: [cli, automation, deterministic, wiki-management]
|
||||
created: 2026-08-03
|
||||
modified: 2026-09-02
|
||||
related: [Semantic Lint Automation, Session Orientation, Iteration and Cost Limits, KB Stack Versioning, KB Migration, Personalization Plane, Detect-Repair Asymmetry, Write-Once Frontmatter Fields, Denylist over Allowlist, Command Round-Trip Integrity, Green Suite Blind Spot, Ambient Environment Dependency, Structural Enforcement over Documented Rule, Optional Instance Context File, MCP-Leseserver]
|
||||
sources: [Source - LLM Improvements Codex Analysis, Source - LLM Improvements Sonnet Analysis, Source - Copilot Skill Restructure Instructions, Source - Conversation - AGENTS.md Skill Restructuring Session 2026-08-04, Source - LLM Improvements Production Agent Gaps 2026, Source - Conversation - Versioning CI-CD and Content Migration Session 2026-08-30, Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31, Source - Conversation - Issue Triage Labels and TODO Retirement Session 2026-08-31, Source - Conversation - Auto Mode and Tool Choice Session 2026-08-31, Source - Conversation - Write-Once Frontmatter Fields and touch --set Session 2026-08-31, Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31, Source - Conversation - Two Round-Trip Defects Found by an Ingest Session 2026-08-31, Source - Conversation - Hardening the Test Suite Against Silent Environment Dependencies Session 2026-08-31, Source - Conversation - ENVIRONMENT.md as an Optional Third Session-Level File Session 2026-08-31, 'Source - Public Release, Corpus Purge and History Squash Session 2026-09-01', Source - Publish-Remote Gate and Issue Triage Session 2026-09-01, Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01, Source - MCP Read Server Implementation Session 2026-09-02]
|
||||
sources: [Source - LLM Improvements Codex Analysis, Source - LLM Improvements Sonnet Analysis, Source - Copilot Skill Restructure Instructions, Source - Conversation - AGENTS.md Skill Restructuring Session 2026-08-04, Source - LLM Improvements Production Agent Gaps 2026, Source - Conversation - Versioning CI-CD and Content Migration Session 2026-08-30, Source - Conversation - Comma Bug Budget Refund and Lint Report Path Session 2026-08-31, Source - Conversation - Issue Triage Labels and TODO Retirement Session 2026-08-31, Source - Conversation - Auto Mode and Tool Choice Session 2026-08-31, Source - Conversation - Write-Once Frontmatter Fields and touch --set Session 2026-08-31, Source - Conversation - Gate Counting and Measured Calibration Session 2026-08-31, Source - Conversation - Two Round-Trip Defects Found by an Ingest Session 2026-08-31, Source - Conversation - Hardening the Test Suite Against Silent Environment Dependencies Session 2026-08-31, Source - Conversation - ENVIRONMENT.md as an Optional Third Session-Level File Session 2026-08-31, 'Source - Public Release, Corpus Purge and History Squash Session 2026-09-01', Source - Publish-Remote Gate and Issue Triage Session 2026-09-01, Source - Private-Instance Merge Correction and Issue 30 Session 2026-09-01, Source - MCP Read Server Implementation Session 2026-09-02, Source - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02]
|
||||
confidence: 0.90
|
||||
confidence_base: 0.90
|
||||
provenance: sourced
|
||||
summary: Deterministisches CLI fuer alle mechanischen Wiki-Operationen; seit 2.0.0 im Paket chemenu, seit 2.4.0 zusaetzlich als MCP-Leseserver erreichbar
|
||||
summary: Deterministisches CLI fuer alle mechanischen Wiki-Operationen; seit 2.0.0 im Paket chemenu, seit 2.4.0 zusaetzlich als MCP-Leseserver erreichbar, seit 2.5.0 mit Breaking-Change-Pflichtmarker
|
||||
---
|
||||
# wikitool
|
||||
|
||||
@@ -86,8 +86,15 @@ wikitool bietet die folgenden Befehlskategorien:
|
||||
- **Veröffentlichung:** `publish` - zählt seit `1.5.0` nur noch Dateien, die eine Entscheidung tragen: Pfade unter `work/` und generierte Dateien (`kb/index.md`, `kb/log.md`, `kb/provenance.md`, jede `INDEX.md`) werden committet und gepusht, aber nicht gegen die Schwelle gezählt; die Weigerungszeile weist beide Gründe getrennt aus[^s-conversation-gate-counting-and-measured-calibration-session-2026-08-31]. Siehe [[Mass-Update Gate]]
|
||||
- **Budget:** `budget status`, `budget reset` - Obergrenze seit `1.2.0` 60 Aufrufe je Sitzung; ein Aufruf, der über `_util.fail()` abgelehnt wurde, bekommt seinen Slot zurück und bleibt trotzdem in `recent`, damit der Loop-Breaker ihn sieht[^s-conversation-comma-bug-budget-refund-and-lint-report-path-session-2026-08-31]. Siehe [[Iteration and Cost Limits]]
|
||||
- **Versionierung:** `version bump`, `version check` - `bump` schreibt die Stack-Version in die
|
||||
Wurzeldatei `VERSION` und verweigert einen `MAJOR`-Sprung ohne Migrationsdokument, sofern er
|
||||
nicht ausdrücklich mit `--no-migration "<Begründung>"` gesetzt wird; `check` ist der einzige
|
||||
Wurzeldatei `VERSION`. Kompatibilität (ist die neue Version ein Drop-in-Ersatz - vorwärts ohne
|
||||
Handarbeit, rückwärts noch downgradebar) und Inhaltsmigration sind seit `2.5.0` zwei getrennte
|
||||
Fragen: ein Grenzübertritt verlangt zwingend `--breaking "<was aufhört zu funktionieren>"`,
|
||||
verweigert auf jedem anderen Bump, und *zusätzlich* entweder ein Migrationsdokument oder
|
||||
`--no-migration "<Begründung>"`, wenn `kb/` unangetastet bleibt. Beide Marker landen als
|
||||
eigene Zeile im `CHANGES.md`-Eintrag (`**Breaking Change:**` vor `**Migration:**`) und werden
|
||||
von `docs verify` unabhängig
|
||||
voneinander geprüft[^s-version-part-nomenclature-and-breaking-change-gate-session-2026-09-02].
|
||||
Siehe [[KB Stack Versioning]]. `check` ist der einzige
|
||||
Befehl, der einen Netzaufruf machen darf - ohne Schlüssel, mit Timeout und injizierbarem
|
||||
Fetch, damit Tests nie ein Netz
|
||||
berühren[^s-conversation-versioning-ci-cd-and-content-migration-session-2026-08-30]
|
||||
@@ -151,6 +158,19 @@ ist[^s-conversation-versioning-ci-cd-and-content-migration-session-2026-08-30].
|
||||
|
||||
## Historie
|
||||
|
||||
- 2026-09-02 - `2.5.0` (Commit `31662dc`, 806 Tests grün, 6 neu): Gitea-Issue #26 geschlossen.
|
||||
`instructions/dev/version-parts.md` (neu, `instructions/dev/` - kein Verweis aus einem
|
||||
ausgelieferten Artefakt, `instructions verify` hätte einen dangelnden Verweis nach
|
||||
`dist export` gemeldet) trennt die Kompatibilitäts- von der Migrationsfrage: Drop-in-Test in
|
||||
beiden Richtungen, Katalog der Brüche mit unangetastetem `kb/` (Update-Pfad, Artefaktname,
|
||||
Import-Name, Flag/Envvar, Shape einer maschinengelesenen Datei), `2.0.0` als Fallbeispiel.
|
||||
`version bump` bekommt `--breaking "<was aufhört zu funktionieren>"`, bei jedem
|
||||
Grenzübertritt Pflicht und auf jedem anderen Bump verweigert; `docs verify` prüft das über
|
||||
eine zweite, von der Migrationsprüfung unabhängige Regel. `stack-dev` bekommt einen
|
||||
Entscheidungspunkt: kein Grenzübertritt aus eigener Initiative, erst Bruch, Handarbeit je
|
||||
Instanz und Alternativen (Shim, aufschieben/bündeln, kompatibel/brechend mit
|
||||
Deprecation-Fenster aufspalten) vorlegen, dann
|
||||
Freigabe[^s-version-part-nomenclature-and-breaking-change-gate-session-2026-09-02].
|
||||
- 2026-09-02 - `2.2.3`-`2.4.1`: die vierstufige Sequenz aus Issue #36 (Publish-Remote-Gate
|
||||
scharf, Lesepfad gehärtet, Root-Auflösung und Bibliotheksgrenze, [[MCP-Leseserver]]) plus
|
||||
Menschendoku. `.wikitool-remotes.json` war trotz gegenteiliger Dokumentation nicht gesetzt -
|
||||
@@ -303,3 +323,4 @@ ist[^s-conversation-versioning-ci-cd-and-content-migration-session-2026-08-30].
|
||||
[^s-public-release-corpus-purge-and-history-squash-session-2026-09-01]: [[Source - Public Release, Corpus Purge and History Squash Session 2026-09-01]]
|
||||
[^s-publish-remote-gate-and-issue-triage-session-2026-09-01]: [[Source - Publish-Remote Gate and Issue Triage Session 2026-09-01]]
|
||||
[^s-mcp-read-server-implementation-session-2026-09-02]: [[Source - MCP Read Server Implementation Session 2026-09-02]]
|
||||
[^s-version-part-nomenclature-and-breaking-change-gate-session-2026-09-02]: [[Source - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02]]
|
||||
|
||||
+3
-3
@@ -13,11 +13,11 @@ The page tables live in a generated `INDEX.md` inside each collection, linked be
|
||||
|
||||
## Statistics
|
||||
|
||||
- **Total Pages:** 179
|
||||
- **Total Pages:** 180
|
||||
- **Comparisons:** 1
|
||||
- **Concepts:** 80
|
||||
- **Entities:** 72
|
||||
- **Sources:** 26
|
||||
- **Sources:** 27
|
||||
- **Last Updated:** 2026-09-02
|
||||
|
||||
---
|
||||
@@ -29,7 +29,7 @@ The page tables live in a generated `INDEX.md` inside each collection, linked be
|
||||
| `comparisons/` | 1 | [comparisons/INDEX.md](comparisons/INDEX.md) |
|
||||
| `concepts/` | 80 | [concepts/INDEX.md](concepts/INDEX.md) |
|
||||
| `entities/` | 72 | [entities/INDEX.md](entities/INDEX.md) |
|
||||
| `sources/` | 26 | [sources/INDEX.md](sources/INDEX.md) |
|
||||
| `sources/` | 27 | [sources/INDEX.md](sources/INDEX.md) |
|
||||
|
||||
### entities/
|
||||
|
||||
|
||||
@@ -91,3 +91,9 @@ Direkt gegen tobi/qmd auf GitHub geprueft: TypeScript statt der geratenen 'Go od
|
||||
Aufgaben-Checkbox fuer die qmd.md-Korrektur nachgezogen (war [ ], ist erledigt) und ein Korrektur-Hinweis ergaenzt: der Fidelity-Block des Rohtranskripts kuendigte ein zweites Transkript fuer die qmd-Korrektur an, das nie geschrieben wurde - die Korrektur lief stattdessen als eigene Quellen-Verifikation. raw/ ist unveraenderlich, die Korrektur steht deshalb auf der Source-Seite.
|
||||
|
||||
---
|
||||
|
||||
## [2026-09-02] ingest | raw/notes/Conversation Transcript - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02.md
|
||||
|
||||
Neue Source-Seite, Korrektur der veralteten MAJOR-als-Migration-Formulierung in wikitool.md und KB Stack Versioning.md, Nachtrag in Chemenu.md (Issue #26 geschlossen).
|
||||
|
||||
---
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
profile: sources
|
||||
required_by_stack: true
|
||||
---
|
||||
|
||||
# kb/sources/ - Collection Contract
|
||||
|
||||
One page per ingested source. A source page is the bridge between the untrusted material in
|
||||
@@ -9,8 +14,14 @@ concluded from it. Where the source is wrong, say what it claims and let the sub
|
||||
carry the correction. A source page that has been improved beyond its source is no longer
|
||||
evidence for anything.
|
||||
|
||||
Inherits [kb/CONTRACT.md](../CONTRACT.md) - naming, tone, linking, provenance and confidence
|
||||
are defined there and are not restated here.
|
||||
Inherits [kb/CONTRACT.md](../CONTRACT.md) for the rules the stack enforces - linking mechanics,
|
||||
provenance, citation, the confidence machinery - and
|
||||
[kb/CONVENTIONS.md](../CONVENTIONS.md) for what this instance decided: language, naming forms,
|
||||
tone, relationship labels, the confidence rubric. Neither is restated here.
|
||||
|
||||
**This collection is `required_by_stack`.** `sources coverage`, `[^cite-id]` resolution and
|
||||
`kb/provenance.md` resolve against it by name, so unlike every other collection it may not be
|
||||
renamed or dropped - its authoring rules below are the instance's, its existence is not.
|
||||
|
||||
## Types offered
|
||||
|
||||
|
||||
+2
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
# kb/sources/ - Index
|
||||
|
||||
26 page(s). Regenerated by `wikitool index rebuild`.
|
||||
27 page(s). Regenerated by `wikitool index rebuild`.
|
||||
|
||||
## All
|
||||
|
||||
@@ -33,5 +33,6 @@
|
||||
| [[Source - Public Release, Corpus Purge and History Squash Session 2026-09-01]] | notes | Sitzung, die den Chemenu-Stack von einer privaten Testinstanz in ein oeffentliches Repo ueberfuehrt: Korpus geloescht statt anonymisiert, Git-History auf einen Commit gesquashed, AGPL-3.0/CC-BY-4.0-Dual-Lizenz gewaehlt, dist export um einen Leak-Canary gehaertet. | 2026-09-01 |
|
||||
| [[Source - Publish-Remote Gate and Issue Triage Session 2026-09-01]] | notes | Sitzung, die ein drittes, Token-loses Gate fuer publish baut, instructions/private-instance.md schreibt, sechs Gitea-Issues auf den Rename und die neue Architektur nachzieht und die Actions-Run-Historie entfernen laesst. | 2026-09-01 |
|
||||
| [[Source - qmd - GitHub Repository]] | document | GitHub-API-Metadaten, package.json und README-Auszuege von tobi/qmd: TypeScript/Node/Bun statt Go oder Rust, BM25 (SQLite FTS5) plus Vektor-Suche (sqlite-vec) plus LLM-Reranking ueber node-llama-cpp. | 2026-09-02 |
|
||||
| [[Source - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02]] | notes | Sitzung, die die Versionsstelle als Kompatibilitaets- statt Migrationsfrage praezisiert und einen Freigabe-Ablauf fuer Breaking Changes in stack-dev einfuehrt | 2026-09-02 |
|
||||
| [[Source - Wine]] | notes | Wine-Konfiguration für Arch Linux: pacman-NoExtract-Einstellungen und Bottles-Runtime-Optionen einschließlich Proton- und Lutris-Varianten. | 2026-08-01 |
|
||||
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
---
|
||||
type: types/source.md
|
||||
source_type: notes
|
||||
author: Torben Nehmer
|
||||
raw_files: [raw/notes/Conversation Transcript - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02.md]
|
||||
source_language: de
|
||||
date: 2026-09-02
|
||||
tags: []
|
||||
entities: [wikitool, Chemenu]
|
||||
concepts: [KB Stack Versioning]
|
||||
summary: Sitzung, die die Versionsstelle als Kompatibilitaets- statt Migrationsfrage praezisiert und einen Freigabe-Ablauf fuer Breaking Changes in stack-dev einfuehrt
|
||||
---
|
||||
# Source: Version Part Nomenclature and Breaking Change Gate Session 2026-09-02
|
||||
|
||||
**Autor:** Torben Nehmer
|
||||
**Datum:** 2026-09-02
|
||||
**Raw-Dateien:** raw/notes/Conversation Transcript - Version Part Nomenclature and Breaking Change Gate Session 2026-09-02.md
|
||||
**Typ:** Notes
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
Umsetzung von Gitea-Issue #26: Die Doku des Stacks führte für die Wahl der Versionsstelle zwei
|
||||
Fragen zusammen, die nicht dieselbe sind - ob der Korpus migriert werden muss, und ob der
|
||||
Wechsel ein Drop-in-Ersatz ist. Nur `version bump --help` unterschied korrekt; die drei
|
||||
prosaischen Stellen (`stack-dev`, `version.py`-Docstring, `INSTALL.md`) beschrieben MAJOR als
|
||||
Migrationsfrage. Der `2.0.0`-Rebranding-Bump hatte genau daran zuerst `1.9.0` statt `--major`
|
||||
angesetzt.
|
||||
|
||||
Der Nutzer schärfte die Regel während der Sitzung zu einem konkreten zweiseitigen Test nach:
|
||||
"die neue version ist kein drop-in replacement. Sobald irgendwie Hand angelegt werden muss, sei
|
||||
es durch den user oder durch ein Migrationsscript, ist es ein major version change. selbiges
|
||||
gilt, wenn ein update nicht rückgängig gemacht werden kann [...] in allen Fällen muss bei einem
|
||||
Major version change ein 'Breaking Change' vermerkt werden. breaking changes sind damit teuer.
|
||||
passe stack-dev so an, dass in diesen Fällen zwingend der user informiert, Alternativen
|
||||
aufgezeigt und eine freigabe eingeholt wird." Zwei Auswahlentscheidungen davor: Durchsetzung im
|
||||
Code statt reiner Prosa (weil Prosa bereits einmal gedriftet war), und die Freigabe als
|
||||
"Decision point" statt als Gate-Sprache, um die drei echten code-erzwungenen Gates nicht zu
|
||||
verwässern.
|
||||
|
||||
## Kernaussagen
|
||||
|
||||
- Kompatibilität (Drop-in-Ersatz, vorwärts wie rückwärts) und Inhaltsmigration sind zwei
|
||||
unabhängige Fragen; MAJOR beantwortet die erste, `--no-migration`/ein Migrationsdokument die
|
||||
zweite.
|
||||
- Ein Grenzübertritt kann `kb/` völlig unangetastet lassen und trotzdem MAJOR sein - Katalog:
|
||||
Update-Pfad, Release-Artefaktname, Paket-Import-Name, ein umbenanntes Kommando/Flag/Envvar,
|
||||
die Shape einer maschinengelesenen Datei.
|
||||
- Ein Breaking Change ist teuer (jede bestehende Instanz zahlt einmal, von Hand) und deshalb
|
||||
genehmigungspflichtig: Bruch, Handarbeit je Instanz und Alternativen (Shim, aufschieben und
|
||||
bündeln, aufspalten mit Deprecation-Fenster) vorlegen, dann Freigabe abwarten.
|
||||
- Reine Prosa-Regeln drifted - deshalb wurde `--breaking` als Pflichtflag samt zweiter, von der
|
||||
Migrationsprüfung unabhängiger `docs verify`-Prüfung eingeführt, nicht nur eine Textänderung.
|
||||
|
||||
## Aufgaben
|
||||
|
||||
Keine offenen Aufgaben aus dieser Sitzung - Issue #26 wurde in derselben Sitzung geschlossen,
|
||||
mit Verweis auf Commit `31662dc` (`2.5.0`).
|
||||
|
||||
## Nicht übernommen
|
||||
|
||||
- Die vollständige Katalog-Tabelle und der `2.0.0`-Fallbeispiel-Text aus
|
||||
`instructions/dev/version-parts.md` werden hier nicht wiederholt - die Datei ist die
|
||||
autoritative Quelle (Instruktions-Layer, `manual`-artig durch die `instructions/dev/`-Grenze),
|
||||
diese Source-Seite fasst nur zusammen, was zur Entscheidung führte.
|
||||
- Der genaue Wortlaut der Tool-Fehlermeldungen (`version bump`-Refusals) steht im Transkript
|
||||
selbst; hier nur die Regel dahinter.
|
||||
|
||||
## Verwandte Entities
|
||||
|
||||
- [[wikitool]]
|
||||
- [[Chemenu]]
|
||||
|
||||
## Verwandte Concepts
|
||||
|
||||
- [[KB Stack Versioning]]
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
# Conversation Transcript - Version Part Nomenclature and Breaking Change Gate Session
|
||||
|
||||
> Source: Claude Code session (`claude-sonnet-5`), chemenu workspace
|
||||
> Collected: 2026-09-02
|
||||
> Participant: Torben Nehmer
|
||||
> Fidelity: **faithful summary transcript, not a verbatim log.** The user's instructions and
|
||||
> clarifications are quoted verbatim; the agent's own reasoning and file-reading steps are
|
||||
> paraphrased; tool output blocks (`docs verify`, `pytest`, `version bump --dry-run`, the
|
||||
> Mass-Update Gate refusal) are real, copied from the actual run, not reconstructed.
|
||||
> No second-hand material - no subagent was used.
|
||||
> No credentials appeared.
|
||||
> Single topic, not cut.
|
||||
|
||||
Closes Gitea issue #26 (already closed in-session) with `2.5.0`. Commit `31662dc` on `main`.
|
||||
Covers picking the version part for a stack change, and adding a user-approval gate for
|
||||
breaking changes to `stack-dev`.
|
||||
|
||||
## Turn 1 - `/stack-dev kümmere dich um #26`
|
||||
|
||||
The user invoked the `stack-dev` skill with the argument `kümmere dich um #26`. The agent read
|
||||
issue #26 via `gitea-mcp` (`torben/chemenu#26`, no comments yet).
|
||||
|
||||
**The issue's own account** (quoted from its body, since it is the source of the whole
|
||||
session): the stack's documentation conflated two questions when choosing a version bump part -
|
||||
"Muss der Korpus migriert werden?" and "Ist der Wechsel rückwärtskompatibel?" - and every place
|
||||
an agent would consult before a bump stated only the first:
|
||||
|
||||
| Ort | Wortlaut |
|
||||
|---|---|
|
||||
| `instructions/dev/stack-dev/SKILL.md`, Schritt 3 | `--major` ⇔ "Existing content must be migrated" |
|
||||
| `tools/chemenu/version.py`, module docstring | "from `1.0.0` on the same rule reads as the familiar 'MAJOR means migration'" |
|
||||
| `INSTALL.md` § Version und Updates | "ab `1.0.0` liest sich dieselbe Regel als das gewohnte 'MAJOR heißt Migration'" |
|
||||
|
||||
Only `version bump --help` had it right ("A bump that crosses the compatibility boundary
|
||||
additionally requires a migration document ... or `--no-migration`") - the one place read only
|
||||
after the decision is already made.
|
||||
|
||||
The issue's worked example was the `2.0.0` rebranding (`torben/llm-wiki-test1` →
|
||||
`torben/chemenu`): no page in `kb/` changed, so the first attempt at that bump was `1.9.0`. Three
|
||||
things broke anyway - the release-feed URL baked into every existing instance's
|
||||
`.wikitool-release.json` (unrepairable by hand, per invariant 1), the release artefact's
|
||||
filename, and the Python package's import name. The correct bump was `--major --no-migration`.
|
||||
The issue's "Was 'fertig' heißt" checklist asked for: rewording the three prose locations to
|
||||
lead with compatibility rather than migration, a decision aid enumerating which kinds of break
|
||||
force MAJOR even with `kb/` untouched, and a check that the three locations do not end up
|
||||
contradicting each other (invariant 8). It explicitly scoped out teaching `docs verify` to judge
|
||||
*whether* a bump chose the right part - "das ist eine Urteilsfrage und kein hartes Orakel."
|
||||
|
||||
The agent read the three prose locations plus `tools/chemenu/commands/version_cmd.py` (the
|
||||
existing `--no-migration` enforcement) and `docs_verify.py` (`check_migration_for_boundary`), to
|
||||
scope the fix before writing anything.
|
||||
|
||||
## Turn 2 - scope questions, then the user's clarification
|
||||
|
||||
Before writing, the agent asked two `AskUserQuestion` questions:
|
||||
|
||||
1. Whether the "Breaking Change" marker and approval requirement should be prose-only or also
|
||||
enforced in the tool. Options offered: "Prosa + Marker im Code (Empfehlung)" vs. "Nur Prosa".
|
||||
**User picked the enforced option** ("Prosa + Marker im Code (Empfehlung)") - a
|
||||
`--breaking` flag on `version bump`, checked by `docs verify`, on the reasoning that a
|
||||
pure-prose rule is exactly what drifted in the first place.
|
||||
2. Whether the pre-bump approval step should read like one of the codebase's actual gates
|
||||
(`instructions/gates.md` language: "anhalten ... auf ausdrückliche Freigabe warten") or as a
|
||||
softer "Decision points" entry. **User picked the softer framing** ("Als Entscheidungspunkt")
|
||||
- explicitly *not* gate language, to avoid diluting the weight of the three real,
|
||||
code-enforced gates (Mass-Update, Publish-Remote, Iteration Budget) by association.
|
||||
|
||||
The user then added the clarification that drove the rest of the session, quoted in full:
|
||||
|
||||
> "genaue nomenklatur für major version change: die neue version ist kein drop-in replacement.
|
||||
> Sobald irgendwie Hand angelegt werden muss, sei es durch den user oder durch ein
|
||||
> Migrationsscript, ist es ein major version change. selbiges gilt, wenn ein update nicht
|
||||
> rückgängig gemacht werden kann (Szenario: User aktualisiert den Stack von vA nach vB, vB hat
|
||||
> beim User ein Problem, kann jetzt vA nicht mehr einfach so als Downgrade installiert werden,
|
||||
> ist es ein breaking change)
|
||||
> in allen Fällen muss bei einem Major version change ein "Breaking Change" vermerkt werden.
|
||||
> breaking changes sind damit teuer. passe stack-dev so an, dass in diesen Fällen zwingend der
|
||||
> user informiert, Alternativen aufgezeigt und eine freigabe eingeholt wird."
|
||||
|
||||
This sharpened the issue's "compatibility" question into a concrete two-sided test - **forward**
|
||||
(can the new version be copied over with zero hand-work, by user or script) and **backward** (can
|
||||
the old version still be reinstalled afterwards) - and made explicit that either half failing,
|
||||
independent of `kb/`, is what MAJOR actually means.
|
||||
|
||||
## Turn 3 - what was written
|
||||
|
||||
**`instructions/dev/version-parts.md` (new).** The single source for the rule. Structure:
|
||||
the two-sided drop-in test (step 1); a catalogue of breaks that cross the boundary with `kb/`
|
||||
untouched - update path, release-artefact name, package import name, a renamed
|
||||
command/flag/envvar, the shape of a machine-read file like `.wikitool-kb.json` (step 2); the
|
||||
plain compatible-part table for patch/minor (step 3); the mandatory stop-and-inform-the-user
|
||||
step before any boundary-crossing bump, with three named alternatives (avoid via a
|
||||
compatibility shim, defer and batch with the next crossing, split into a compatible half now
|
||||
and a deprecated-then-removed half later) plus a recommendation (step 4); `--breaking` usage
|
||||
(step 5); the pre-existing migration-document-or-`--no-migration` step, now explicitly
|
||||
independent of compatibility (step 6); writing the entry body (step 7). Closes with the `2.0.0`
|
||||
case study as the worked example, matching the issue's own account.
|
||||
|
||||
Placed under `instructions/dev/` (not linked from any distributed artifact) because
|
||||
`tools/wikitool dist export` prunes that directory wholesale - the agent's first draft linked to
|
||||
it from `tools/CONTRACT.md` and the `version.py` docstring, which `tools/wikitool instructions
|
||||
verify` correctly rejected:
|
||||
|
||||
```
|
||||
ERROR Instruction layer issues:
|
||||
- version-parts.md: lives under instructions/dev/ but is referenced from
|
||||
outside it and outside a dist:strip block - `dist export` removes
|
||||
instructions/dev/ wholesale, so that reference would dangle in a distributed
|
||||
instance. Remove the reference, or wrap it in a <!-- dist:strip-start/end -->
|
||||
block if it belongs only to this dev instance.
|
||||
```
|
||||
|
||||
**Rejected approach:** wrapping the reference in `<!-- dist:strip-start/end -->` markers so it
|
||||
would still resolve in this repo. Not used - the agent instead rewrote the three shipped
|
||||
locations (`tools/CONTRACT.md`, `version.py` docstring, `version_cmd.py` docstring) to state the
|
||||
short form of the rule standalone, with no pointer to the dev-only file, since a shipped
|
||||
instance never has it to point to.
|
||||
|
||||
**`instructions/dev/stack-dev/SKILL.md`.** Step 3's table changed from "Existing content must
|
||||
be migrated → `--major`" to "Not a drop-in replacement ... → `--major`", with a pointer to
|
||||
`version-parts.md` for the full test and catalogue. A new "Decision points" entry: if a change
|
||||
turns out not to be a drop-in replacement, stop - do not bump across the boundary on the
|
||||
session's own initiative; show the user the concrete break, what each instance must do, and the
|
||||
three alternatives from `version-parts.md` step 4, then wait for a go-ahead. Written in the
|
||||
softer "Decision points" register per the user's second answer above, not gate language.
|
||||
|
||||
**`tools/chemenu/version.py`.** Module docstring reworded: "MAJOR means migration" → "MAJOR
|
||||
breaks", with a new paragraph stating the two questions are independent and naming both markers.
|
||||
New constant `BREAKING_CHANGE_MARKER = "**Breaking Change:**"`, alongside the existing
|
||||
`MIGRATION_NONE_MARKER`. `insert_changes_entry()` gained a `breaking_reason` parameter, writing
|
||||
the `**Breaking Change:**` line *before* the migration line - the break is what an operator acts
|
||||
on first.
|
||||
|
||||
**`tools/chemenu/commands/version_cmd.py`.** `bump` gained `--breaking TEXT`. Refused on a
|
||||
non-boundary-crossing bump; required on a boundary-crossing one, with a refusal message that
|
||||
also second-guesses the part choice itself: *"If that sentence is hard to write because nothing
|
||||
actually breaks - no hand-work on update, and the old version can still be put back - then the
|
||||
bump is probably not --major."* Verified live:
|
||||
|
||||
```
|
||||
$ tools/wikitool version bump --major --title "smoke" --dry-run
|
||||
ERROR 2.4.1 -> 3.0.0 crosses the compatibility boundary, so it is not a drop-in
|
||||
replacement - re-run with --breaking "<what stops working, and what an instance
|
||||
must do about it>".
|
||||
If that sentence is hard to write because nothing actually breaks - no
|
||||
hand-work on update, and the old version can still be put back - then the
|
||||
bump is probably not --major.
|
||||
```
|
||||
|
||||
```
|
||||
$ tools/wikitool version bump --minor --title "smoke" --breaking "x" --dry-run
|
||||
ERROR --breaking only applies to a bump that crosses the compatibility boundary;
|
||||
2.4.1 -> 2.5.0 does not.
|
||||
```
|
||||
|
||||
**`tools/chemenu/commands/docs_verify.py`.** New `check_breaking_change_for_boundary()`,
|
||||
independent of the existing `check_migration_for_boundary()` on purpose - the two check
|
||||
different things (did the corpus move vs. was the operator told the swap isn't drop-in), and a
|
||||
crossing with `kb/` untouched can satisfy the first while failing the second. Wired into
|
||||
`verify()` alongside the existing check.
|
||||
|
||||
**Tests.** `test_version_cmd.py`: nine existing calls updated for the new `breaking=` parameter,
|
||||
plus three new tests (`test_a_boundary_crossing_bump_without_breaking_is_refused`,
|
||||
`test_breaking_records_what_stops_working_in_the_changelog` - including the ordering assertion
|
||||
that `BREAKING_CHANGE_MARKER` precedes `MIGRATION_NONE_MARKER` in the rendered entry -
|
||||
`test_breaking_is_refused_on_a_compatible_bump`). `test_docs_verify.py`: four new tests mirroring
|
||||
the existing migration-boundary tests but for the breaking-change marker. Full suite: 806 passed.
|
||||
|
||||
**`tools/CONTRACT.md` and `INSTALL.md`.** Both reworded to lead with the drop-in question and
|
||||
treat migration as the second, independent one; `INSTALL.md` additionally tells the reader which
|
||||
two lines to look for in release notes (`Breaking Change:` and `Migration:`) before applying an
|
||||
update.
|
||||
|
||||
**`CHANGES.md`.** New `2.5.0` entry written after the bump, body filled in by the agent (the
|
||||
tool leaves it empty by design) - includes the "what deliberately did not change" note that
|
||||
`docs verify` still does not judge *whether* the chosen part was correct, matching the issue's
|
||||
explicit scope-out.
|
||||
|
||||
## Turn 4 - publish, twice
|
||||
|
||||
`tools/wikitool version bump --minor --title "..."` was run first (11 changed files, drop-in in
|
||||
both directions - the new requirement only binds the *next* boundary crossing, not
|
||||
retroactively). `tools/wikitool publish --message "..."` then hit the **Mass-Update Gate**
|
||||
(11 counted files ≥ threshold 10):
|
||||
|
||||
```
|
||||
NEEDS USER CLEARANCE Mass-Update Gate: this publish would commit and push 11
|
||||
counted files (>= threshold 10) to origin/main. ...
|
||||
```
|
||||
|
||||
Per the gate's own instructions, the agent reproduced the full file-by-area breakdown and the
|
||||
`--confirm <token>` line in its reply and ran nothing further that turn. The user replied
|
||||
"freigegeben" for both the earlier publish authorization ("publish ist freigegeben") and, in a
|
||||
separate turn, this specific token. The agent then ran `tools/wikitool publish --confirm
|
||||
95ae372d5677 --message '...'`, which pushed commit `31662dc` to `origin/main`, and verified
|
||||
`git rev-parse HEAD origin/main` matched afterward.
|
||||
|
||||
## Turn 5 - issue closeout and this capture
|
||||
|
||||
The user asked to update issue #26 "wie vorgeschlagen" (per the agent's own end-of-turn
|
||||
suggestion), run `instructions/capture-session.md`, and in the same pass correct
|
||||
`kb/entities/tools/wikitool.md`. The agent posted a comment on #26 summarizing what shipped
|
||||
(including the parts that went beyond the issue's own checklist - the `--breaking` flag and the
|
||||
second `docs verify` check, added because plain prose was judged likely to drift again) and
|
||||
closed the issue. This transcript and the `kb/` correction are the remaining two steps of that
|
||||
request.
|
||||
|
||||
## Outcome
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Version | `2.4.1` → `2.5.0` (`--minor`: new capability, still drop-in both directions) |
|
||||
| Commit | `31662dc` on `main`, pushed to `origin` |
|
||||
| Files changed | 11 (+432/-33): `instructions/dev/version-parts.md` (new), `instructions/dev/stack-dev/SKILL.md`, `tools/CONTRACT.md`, `tools/chemenu/version.py`, `tools/chemenu/commands/version_cmd.py`, `tools/chemenu/commands/docs_verify.py`, `tools/chemenu/tests/test_version_cmd.py`, `tools/chemenu/tests/test_docs_verify.py`, `CHANGES.md`, `INSTALL.md`, `VERSION` |
|
||||
| Tests | 806 passed (`tools/chemenu/tests/`), including 6 new |
|
||||
| Verification | `tools/wikitool docs verify` OK, `tools/wikitool instructions verify` OK (17 instructions, 6 skills, 12 published copies match), `tools/wikitool doctor` clean (only the expected `WIKITOOL_SESSION_ID` WARN) |
|
||||
| Issues | #26 closed, comment `torben/chemenu#26` (issuecomment-474) |
|
||||
| CI | Not yet observed in this session - a `VERSION` move on `main` triggers a tagged release per `.gitea/workflows/release.yml`; not polled |
|
||||
+3
-3
@@ -67,10 +67,10 @@ tools/wikitool <command> --help
|
||||
| `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, 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 here (and vice versa), every directory under `kb/` has a `COLLECTION.md` and no directory outside it does, every stage contract present, no pre-migration `type: entity` blocks left in the contracts, and the `.gitignore` canaries clear in both directions (nothing ignored under `raw/`/`kb/`, everything ignored under `reports/` and the published skill directories) |
|
||||
| `docs verify` | Check the docs that mirror the code: every CLI command documented here (and vice versa), 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, and the `.gitignore` canaries clear in both directions (nothing ignored under `raw/`/`kb/`, everything ignored under `reports/` and the published skill directories) |
|
||||
| `eval sessions [--json]` | List the sessions that have a trace under `reports/telemetry/`, most recent first. Read-only and exempt from the Iteration Budget Gate |
|
||||
| `eval score [--session <id>] [--json] [--markdown out.md] [--save] [--fail-on-error]` | Score one traced session: structural state from `lint`'s own checks (L1) plus trajectory rules over the trace (L2) - was a refused call repeated unchanged, was a gate flag passed without that gate having refused anything, did a publish of `kb/` pages go unlogged. Defaults to the current session. `--save` writes `reports/evals/<date>/<session>.{json,md}`. Read-only over `kb/` and exempt from the budget; see [../EVALS.md](../EVALS.md) |
|
||||
| `dist export <target> [--dry-run] [--source-repo U] [--source-commit SHA] [--release-url U] [--update-url U]` | Write a contentless, distributable copy of this repo's machinery into an empty `<target>` directory: `AGENTS.md`/`README.md`/`EVALS.md` with any `<!-- dist:strip-start -->...<!-- dist:strip-end -->` region removed, `instructions/` (minus `instructions/dev/`), `types/`, `tools/` (no venv/caches), the `.github/hooks/`+`.vibe/` session-tracing config plus `.claude/settings.json`, every `kb/*/COLLECTION.md` (no pages, no areas), empty `raw/{articles,documents,notes,assets}/`, `VERSION`, `USER.md.template`/`SOUL.md.template` (the templates ship; a filled `USER.md`/`SOUL.md` never does - the root allowlist is what makes that automatic), and a generated `.wikitool-release.json` stamp (version, export date, origin, and a sha256 per exported file - the base a later upgrade would compare against). The four origin options only fill stamp fields: `export` never calls git and cannot discover them. Refuses a non-empty target, and a tree with no `VERSION`. See `instructions/setup-instance.md`. One-way: there is no command that reconstructs a distributed instance into a dev instance - work on the stack in the origin repo (or a new dev instance exported from it) instead |
|
||||
| `dist export <target> [--dry-run] [--source-repo U] [--source-commit SHA] [--release-url U] [--update-url U]` | Write a contentless, distributable copy of this repo's machinery into an empty `<target>` directory: `AGENTS.md`/`README.md`/`EVALS.md` with any `<!-- dist:strip-start -->...<!-- dist:strip-end -->` region removed, `instructions/` (minus `instructions/dev/`), `types/`, `tools/` (no venv/caches), the `.github/hooks/`+`.vibe/` session-tracing config plus `.claude/settings.json`, `kb/CONTRACT.md` (no pages, no areas), empty `raw/{articles,documents,notes,assets}/`, `VERSION`, `USER.md.template`/`SOUL.md.template` plus `kb/CONVENTIONS.md.template` and each collection's contract re-keyed as `kb/<name>/COLLECTION.md.template` (the templates ship; the filled `USER.md`/`SOUL.md`/`kb/CONVENTIONS.md`/`kb/<name>/COLLECTION.md` never do - all four bind their instance and none of them are the stack's to decide, and `find_leaks` refuses a plan carrying one), and a generated `.wikitool-release.json` stamp (version, export date, origin, and a sha256 per exported file - the base a later upgrade would compare against). The four origin options only fill stamp fields: `export` never calls git and cannot discover them. Refuses a non-empty target, and a tree with no `VERSION`. See `instructions/setup-instance.md`. One-way: there is no command that reconstructs a distributed instance into a dev instance - work on the stack in the origin repo (or a new dev instance exported from it) instead |
|
||||
| `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 |
|
||||
@@ -80,7 +80,7 @@ tools/wikitool <command> --help
|
||||
| `migrate verify --from <rev> [--path P ...] [--expect-body-change] [--json] [--fail-on-error]` | Compare `kb/` against a git revision on the invariants a content migration must not change: wikilink and citation **counts** (not sets), footnote definitions, H1, and structural frontmatter. Reports added/removed pages without failing on them. `--expect-body-change` additionally flags a page whose body did not change at all. Not migration-specific - worth running after any bulk rewrite, and the one question `lint` cannot answer, since it reads a single revision and so cannot see that something went missing. Read-only and exempt from the budget gate |
|
||||
| `migrate done <version> [--pages N] [--dry-run]` | Record one migration as applied, advancing `kb_version` in `.wikitool-kb.json` to its target. **Refuses any version that is not the next link in the chain** - skipping one leaves the corpus in a shape no version describes, and an interrupted multi-step upgrade has to be resumable rather than guessable |
|
||||
| `migrate baseline <version> [--force]` | Declare `kb_version` once, for an instance predating `.wikitool-kb.json`. Refuses to overwrite an existing declaration without `--force`: advancing after a migration is `done`, which checks the chain, and this command must not become the quiet way around it |
|
||||
| `doctor [--json]` | Check that this instance is correctly configured: dependencies (Python, ripgrep), author resolution, stack version, git identity/branch/remote, published skills, kb/raw/reports/work/instructions structure, personalization (`USER.md`/`SOUL.md` present **and** filled - a file still carrying the template's sentinel is a `FAIL`, since a renamed template is not a filled one), the environment note (`ENVIRONMENT.md` - optional, so absent is `OK`; a still-templated one is a `WARN`), generated files, and `WIKITOOL_SESSION_ID`. Read-only, exit 1 only on a `FAIL` (a missing remote, session id, or `VERSION` is a `WARN`, not a fault). Exempt from the Iteration Budget Gate |
|
||||
| `doctor [--json]` | Check that this instance is correctly configured: dependencies (Python, ripgrep), author resolution, stack version, git identity/branch/remote, published skills, kb/raw/reports/work/instructions structure, personalization (`USER.md`/`SOUL.md` present **and** filled - a file still carrying the template's sentinel is a `FAIL`, since a renamed template is not a filled one), the KB conventions (`kb/CONVENTIONS.md` present, unsentinelled, and naming all three tool-owned section headings - a `FAIL` on any of the three, because `xref`/`cite` write out of it), the environment note (`ENVIRONMENT.md` - optional, so absent is `OK`; a still-templated one is a `WARN`), generated files, and `WIKITOOL_SESSION_ID`. Read-only, exit 1 only on a `FAIL` (a missing remote, session id, or `VERSION` is a `WARN`, not a fault). Exempt from the Iteration Budget Gate |
|
||||
|
||||
## Design notes
|
||||
|
||||
|
||||
+19
-10
@@ -39,7 +39,8 @@ tools/
|
||||
errors.py ChemenuError / ValidationError / BackendError
|
||||
corpus_cache.py one parsed corpus per commit, never cached while the tree is dirty
|
||||
kb_scan.py page iteration/loading over kb/
|
||||
kb_collections.py collection discovery (a directory with COLLECTION.md)
|
||||
kb_collections.py collection discovery (a directory with COLLECTION.md), and what one declares about itself
|
||||
conventions.py kb/CONVENTIONS.md: what this instance decided about authoring, as opposed to what the stack enforces
|
||||
type_resolver.py type-spec loading and schema resolution
|
||||
lint_core.py the lint checks and the report, with no CLI attached
|
||||
types_core.py type-spec listing/description, with no CLI attached
|
||||
@@ -110,15 +111,23 @@ procedure written down in advance is one an agent can complete alone. Whether
|
||||
a human *actually* saw it is not enforced here - that question is answered in
|
||||
the eval layer (`evals/trajectory.py`, `clearance-ended-the-turn`).
|
||||
|
||||
**Section names are a vocabulary, not literals.** `xref add` writes into Relationships and See
|
||||
Also, and `cite add` owns the trailing Footnotes block, so those three headings are structure the
|
||||
tool matches on. They are named once in `sections.py`, and each has one canonical spelling - what
|
||||
the tool writes - plus aliases it still recognizes. That asymmetry is what let the wiki be
|
||||
translated page by page instead of atomically: an untranslated `## Relationships` is still found
|
||||
and appended to. Dropping an alias is therefore a breaking change for any page not yet converted,
|
||||
not a cleanup. Renaming a heading is a migration's job; no other command may do it as a side
|
||||
effect (see `cite_block_heading` in `provenance.py`, which exists solely so `cite sync` stays a
|
||||
no-op on an untranslated page).
|
||||
**Section names are a vocabulary, not literals - and not the stack's.** `xref add` writes into
|
||||
Relationships and See Also, and `cite add` owns the trailing Footnotes block, so those three
|
||||
headings are structure the tool matches on. *Which words they are* is the corpus's own answer:
|
||||
`conventions.py` reads them from `kb/CONVENTIONS.md`, `sections.py` resolves them on access
|
||||
(PEP 562, the way `config` resolves its paths), and no heading text is written down in Python
|
||||
except the pre-conventions fallback for an instance that has not declared one yet.
|
||||
|
||||
Each slot has one canonical spelling - what the tool writes - plus aliases it still recognizes.
|
||||
That asymmetry is what let the wiki be translated page by page instead of atomically: an
|
||||
untranslated `## Relationships` is still found and appended to. Dropping an alias is therefore a
|
||||
breaking change for any page not yet converted, not a cleanup. Renaming a heading is a
|
||||
migration's job; no other command may do it as a side effect (see `cite_block_heading` in
|
||||
`provenance.py`, which exists solely so `cite sync` stays a no-op on an untranslated page).
|
||||
|
||||
Because the value is resolved rather than bound, nothing may capture it at import time - not a
|
||||
module constant, not an evaluated default argument. That is why `provenance.CITE_BLOCK_HEADING`
|
||||
is a module `__getattr__` and `render_cite_block(heading=None)` resolves inside the call.
|
||||
|
||||
**Generated output is never committed.** `reports/`, `.agents/skills/` and
|
||||
`.claude/skills/` are build output; `docs verify` carries canaries in both
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
repo's machinery.
|
||||
|
||||
`export` copies the pipeline's schema/compiler/control-plane layers (types/,
|
||||
tools/, instructions/, the stage contracts, every kb/*/COLLECTION.md) into an
|
||||
empty target, with no kb/ pages, no raw/ content, and no git history - see
|
||||
instructions/setup-instance.md for what happens after. It never calls git.
|
||||
tools/, instructions/, the stage contracts) into an empty target, with no kb/
|
||||
pages, no raw/ content, and no git history - see instructions/setup-instance.md
|
||||
for what happens after. It never calls git.
|
||||
|
||||
The two binding-but-instance-owned documents under kb/ - each collection's
|
||||
COLLECTION.md and kb/CONVENTIONS.md - cross as `.template` and are adopted by a
|
||||
rename, the same split USER.md/SOUL.md use at the repo root.
|
||||
|
||||
Three independent exclusion mechanisms feed the plan, for three different
|
||||
shapes of "does not belong in someone else's instance":
|
||||
@@ -35,7 +39,7 @@ from typing import Callable, NamedTuple, Optional, Union
|
||||
|
||||
import typer
|
||||
|
||||
from chemenu import config, kb_collections, kb_state, version as version_mod
|
||||
from chemenu import config, conventions, kb_collections, kb_state, version as version_mod
|
||||
from chemenu.commands._util import fail, rel_path, success, today_iso
|
||||
|
||||
app = typer.Typer(help="Build a distributable copy of the wiki machinery.")
|
||||
@@ -289,12 +293,32 @@ def build_plan(origin: Optional[Origin] = None) -> dict[str, PlannedFile]:
|
||||
for hook_dir in HOOK_DIRS:
|
||||
plan.update(_copy_tree(config.ROOT / hook_dir, hook_dir, frozenset()))
|
||||
|
||||
# `kb/CONTRACT.md` is stack-owned and ships verbatim; everything beside it
|
||||
# under `kb/` is the instance's own and ships only as a `.template`. That is
|
||||
# the personalization split (`USER.md`/`SOUL.md`) one directory down, and
|
||||
# the reason for it is the same: a distribution can say what the file
|
||||
# decides, never what this instance decided.
|
||||
kb_contract = config.KB_DIR / "CONTRACT.md"
|
||||
if kb_contract.is_file():
|
||||
plan["kb/CONTRACT.md"] = _read_planned_file(kb_contract, "kb/CONTRACT.md")
|
||||
|
||||
conventions_template = config.KB_DIR / conventions.CONVENTIONS_TEMPLATE
|
||||
if conventions_template.is_file():
|
||||
rel = f"kb/{conventions.CONVENTIONS_TEMPLATE}"
|
||||
plan[rel] = _read_planned_file(conventions_template, rel)
|
||||
|
||||
# A collection contract is instance-owned too, but unlike `USER.md` the
|
||||
# shipped content is not *wrong* for the receiver - it is the profile this
|
||||
# repo's own collections adopted, and a fine starting point. So the file
|
||||
# itself ships, under the template name: one source of truth here, and a
|
||||
# receiving instance that has to rename it before it counts. Keeping a
|
||||
# separate `.template` beside each contract would have meant maintaining two
|
||||
# near-identical copies of the same text, which is the drift AGENTS.md
|
||||
# invariant 8 exists to prevent.
|
||||
for collection in kb_collections.iter_kb_collections():
|
||||
rel = f"kb/{collection.name}/COLLECTION.md"
|
||||
plan[rel] = _read_planned_file(collection / "COLLECTION.md", rel)
|
||||
source = collection / kb_collections.CONTRACT_NAME
|
||||
rel = f"kb/{collection.name}/{kb_collections.CONTRACT_NAME}.template"
|
||||
plan[rel] = _read_planned_file(source, rel)
|
||||
|
||||
for relative in CONTRACT_ONLY_STAGES:
|
||||
source = config.ROOT / relative
|
||||
@@ -339,8 +363,21 @@ def build_plan(origin: Optional[Origin] = None) -> dict[str, PlannedFile]:
|
||||
# IP literals) was considered and rejected - the project's own host legitimately
|
||||
# appears in INSTALL.md and version.py, so such a scan would either whitelist
|
||||
# the very string it is looking for or cry wolf on every export.
|
||||
#
|
||||
# `COLLECTION.md` and `CONVENTIONS.md` are deliberately *not* on the allowed
|
||||
# list any more. Both bind, and both are the instance's to write, so they cross
|
||||
# the boundary as `.template` and are adopted by a rename - a plan carrying the
|
||||
# filled name would hand a new instance this one's authoring conventions as
|
||||
# though they were the stack's.
|
||||
_CONTENT_PREFIXES = ("kb/", "raw/")
|
||||
_CONTENT_ALLOWED_NAMES = ("CONTRACT.md", "COLLECTION.md", "log.md", ".gitkeep")
|
||||
_CONTENT_ALLOWED_NAMES = (
|
||||
"CONTRACT.md",
|
||||
f"{kb_collections.CONTRACT_NAME}.template",
|
||||
conventions.CONVENTIONS_TEMPLATE,
|
||||
"log.md",
|
||||
".gitkeep",
|
||||
)
|
||||
_INSTANCE_OWNED_KB_FILES = (kb_collections.CONTRACT_NAME, conventions.CONVENTIONS_FILENAME)
|
||||
|
||||
|
||||
def find_leaks(plan: dict[str, PlannedFile]) -> list[str]:
|
||||
@@ -350,6 +387,8 @@ def find_leaks(plan: dict[str, PlannedFile]) -> list[str]:
|
||||
name = relative.rsplit("/", 1)[-1]
|
||||
if name in config.PERSONALIZATION_FILES or name == config.ENVIRONMENT_FILE:
|
||||
leaks.append(f"{relative} (one instance's own personalization)")
|
||||
elif relative.startswith("kb/") and name in _INSTANCE_OWNED_KB_FILES:
|
||||
leaks.append(f"{relative} (this instance's authoring conventions; ship the .template)")
|
||||
elif relative.startswith("instructions/dev/"):
|
||||
leaks.append(f"{relative} (stack-development only)")
|
||||
elif relative.startswith(_CONTENT_PREFIXES) and name not in _CONTENT_ALLOWED_NAMES:
|
||||
@@ -394,7 +433,8 @@ def export_command(
|
||||
AGENTS.md/README.md (dev-instance-only marker blocks removed),
|
||||
instructions/ (no instructions/dev/), types/, tools/ (no venv/caches),
|
||||
the .github/hooks/+.vibe session-tracing config plus .claude/settings.json,
|
||||
every kb/*/COLLECTION.md (no pages, no areas), empty
|
||||
kb/CONTRACT.md plus a COLLECTION.md.template per collection and
|
||||
kb/CONVENTIONS.md.template (no pages, no areas), empty
|
||||
raw/{articles,documents,notes,assets}/, VERSION, the USER.md/SOUL.md
|
||||
personalization templates (never the filled files), and a
|
||||
.wikitool-release.json stamp. The --source-*/--release-url/--update-url
|
||||
|
||||
@@ -37,7 +37,7 @@ from typing import Optional
|
||||
|
||||
import typer
|
||||
|
||||
from chemenu import config, kb_collections, version as version_mod
|
||||
from chemenu import config, conventions, kb_collections, version as version_mod
|
||||
from chemenu.commands._util import fail, rel_path, success
|
||||
|
||||
app = typer.Typer(help="Verify documentation that mirrors the code or repo layout.")
|
||||
@@ -114,6 +114,12 @@ REQUIRED_TRACKED_PATHS = (
|
||||
"instructions/CONTRACT.md",
|
||||
"instructions/wiki-query/SKILL.md",
|
||||
"ENVIRONMENT.md.template",
|
||||
# The one `.template` that lives under a content directory. It is what a
|
||||
# distribution ships in place of this instance's own `kb/CONVENTIONS.md`, so
|
||||
# an ignore rule reaching it would produce exports whose receiving instance
|
||||
# has nothing to fill in - and `find_leaks` refuses to substitute the filled
|
||||
# file, correctly, so the export would simply be missing it.
|
||||
"kb/CONVENTIONS.md.template",
|
||||
)
|
||||
|
||||
CLI_README = config.ROOT / "tools" / "CONTRACT.md"
|
||||
@@ -207,13 +213,22 @@ def check_cli_readme() -> list[str]:
|
||||
|
||||
|
||||
def check_collection_contracts() -> list[str]:
|
||||
"""The three structural rules that define what a collection is.
|
||||
"""The structural rules that define what a collection is, plus what each one
|
||||
has to declare about itself.
|
||||
|
||||
Collections are discovered by contract presence rather than listed here, so
|
||||
`mkdir kb/<name>` + a COLLECTION.md is all it takes to add one. That only
|
||||
works if the inverse is also checked: a directory under kb/ *without* a
|
||||
contract is an unclaimed subtree whose pages obey no local rules, and a
|
||||
contract outside kb/ quietly widens "collection" back out to "any directory".
|
||||
|
||||
Presence alone stopped being enough once the contracts became
|
||||
instance-owned. A `COLLECTION.md` an instance wrote can be about anything,
|
||||
so the two facts the stack still needs from it - which profile it adopted,
|
||||
and whether the stack resolves against it by name - are declared in its
|
||||
frontmatter and checked here (`kb_collections.declaration_issues`), together
|
||||
with the shape of `kb/CONVENTIONS.md`, whose section names the compiler
|
||||
reads.
|
||||
"""
|
||||
issues = []
|
||||
|
||||
@@ -243,6 +258,9 @@ def check_collection_contracts() -> list[str]:
|
||||
if not (config.ROOT / relative_path).exists():
|
||||
issues.append(f"{relative_path} is missing - it is the authoring contract for its stage")
|
||||
|
||||
issues += kb_collections.declaration_issues()
|
||||
issues += conventions.declaration_issues()
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ from typing import Optional
|
||||
import typer
|
||||
from rich.console import Console
|
||||
|
||||
from chemenu import config, kb_collections, version as version_mod
|
||||
from chemenu import config, conventions, kb_collections, version as version_mod
|
||||
from chemenu.commands import git_publish, instructions_cmd
|
||||
from chemenu.commands._util import rel_path
|
||||
from chemenu.session import ENV_VAR as SESSION_ENV_VAR
|
||||
@@ -213,6 +213,47 @@ def check_personalization() -> Check:
|
||||
return Check("personalization", "OK", f"{', '.join(config.PERSONALIZATION_FILES)} present and filled")
|
||||
|
||||
|
||||
def check_conventions() -> Check:
|
||||
"""Whether this instance has said how its own pages are written.
|
||||
|
||||
`kb/CONVENTIONS.md` carries the decisions `kb/CONTRACT.md` deliberately no
|
||||
longer makes: the KB language and its three tool-owned section headings, the
|
||||
relationship-label vocabulary, the tone examples, the confidence rubric, the
|
||||
ADR prefix. The compiler reads the section names out of it, so an instance
|
||||
without one is not merely undocumented - `xref add` and `cite add` fall back
|
||||
to the names this stack hardcoded before the file existed, which is right
|
||||
only for a corpus that was written under them.
|
||||
|
||||
Hence `FAIL` rather than `WARN`, and hence the same two failure modes the
|
||||
personalization pair has: the distribution can ship the template but never
|
||||
the filled file, so a template renamed and left unanswered looks present and
|
||||
decides nothing.
|
||||
"""
|
||||
path = conventions.conventions_file()
|
||||
fix = (
|
||||
"Copy kb/CONVENTIONS.md.template to kb/CONVENTIONS.md and answer it - the KB-language "
|
||||
"step of instructions/setup-instance.md walks it, and instructions/kb-profiles.md has "
|
||||
"the ready-made profiles to adopt"
|
||||
)
|
||||
if not path.is_file():
|
||||
return Check(
|
||||
"conventions", "FAIL",
|
||||
f"kb/{conventions.CONVENTIONS_FILENAME} is missing - this instance has not "
|
||||
"declared how its pages are written",
|
||||
fix,
|
||||
)
|
||||
issues = conventions.declaration_issues()
|
||||
if issues:
|
||||
return Check("conventions", "FAIL", "; ".join(issues), fix)
|
||||
declared = conventions.language() or "unspecified"
|
||||
headings = ", ".join(conventions.canonical(slot) for slot in conventions.SLOTS)
|
||||
return Check(
|
||||
"conventions", "OK",
|
||||
f"kb/{conventions.CONVENTIONS_FILENAME} present, language {declared}, "
|
||||
f"sections {headings}",
|
||||
)
|
||||
|
||||
|
||||
def check_environment() -> Check:
|
||||
"""Whether this checkout records the environment it works through.
|
||||
|
||||
@@ -399,6 +440,7 @@ def run_doctor() -> list[Check]:
|
||||
check_skills(),
|
||||
check_structure(),
|
||||
check_personalization(),
|
||||
check_conventions(),
|
||||
check_environment(),
|
||||
check_publish_remotes(),
|
||||
check_generated_files(),
|
||||
@@ -411,9 +453,9 @@ def doctor_command(
|
||||
json_out: bool = typer.Option(False, "--json", help="Print the checks as JSON"),
|
||||
):
|
||||
"""Check that this instance is correctly configured: dependencies, author,
|
||||
git identity/remote, published skills, structure, personalization,
|
||||
generated files, and session scoping. Read-only. Exits 1 only if a check
|
||||
FAILs."""
|
||||
git identity/remote, published skills, structure, personalization, KB
|
||||
conventions, generated files, and session scoping. Read-only. Exits 1 only
|
||||
if a check FAILs."""
|
||||
checks = run_doctor()
|
||||
|
||||
if json_out:
|
||||
|
||||
@@ -24,7 +24,7 @@ import re
|
||||
|
||||
import typer
|
||||
|
||||
from chemenu import config
|
||||
from chemenu import config, conventions
|
||||
from chemenu.commands._util import (
|
||||
check_collision,
|
||||
check_raw_files_exist,
|
||||
@@ -166,7 +166,11 @@ def _apply_template_variables(template: str, variables: Dict[str, Any]) -> str:
|
||||
"""Apply variable substitutions to a template string.
|
||||
|
||||
Supports:
|
||||
- `{field}` - plain substitution from `variables[field]`
|
||||
- `{field}` - plain substitution from `variables[field]`, including the
|
||||
`{section.<slot>}` names this instance gave the three tool-owned
|
||||
headings (see chemenu.conventions). Those are what took the KB language
|
||||
out of `types/*.md`: a template writes `## {section.relationships}`, so
|
||||
scaffolding a page in another language needs no edit under `types/`
|
||||
- `{field|filter}` - apply a named filter (bullets, join, capitalize)
|
||||
to `variables[field]`'s value, so templates can render list/enum
|
||||
frontmatter fields directly instead of the caller precomputing a
|
||||
@@ -324,7 +328,13 @@ def new_page_command(
|
||||
|
||||
path = target_dir / f"{page_title}.md"
|
||||
body = _apply_template_variables(
|
||||
template, {**frontmatter, "name": name, "today": today.isoformat()}
|
||||
template,
|
||||
{
|
||||
**frontmatter,
|
||||
"name": name,
|
||||
"today": today.isoformat(),
|
||||
**conventions.section_variables(),
|
||||
},
|
||||
)
|
||||
|
||||
write_page(path, frontmatter, body)
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
"""What this instance decided, read from `kb/CONVENTIONS.md`.
|
||||
|
||||
`kb/CONTRACT.md` and this file answer two different questions. The contract
|
||||
holds what the code enforces - what a collection is, which files are generated,
|
||||
how `provenance:` and `confidence_base` work - and is identical in every
|
||||
instance, so `dist export` ships it verbatim. `kb/CONVENTIONS.md` holds what
|
||||
each instance decides for itself: the language its pages are written in, the
|
||||
relationship-label vocabulary, the tone examples, the confidence rubric, the
|
||||
ADR prefix. The distribution ships only `kb/CONVENTIONS.md.template`, exactly
|
||||
the split `USER.md`/`SOUL.md` already use one directory up.
|
||||
|
||||
Only one part of it is machine-read, and it is the part that used to be Python:
|
||||
the three section headings `xref add` and `cite add` write. While
|
||||
`RELATIONSHIPS = "Beziehungen"` sat in `sections.py`, an instance writing its
|
||||
pages in any other language had to edit the compiler to say so - which made the
|
||||
KB language a stack property in code while every document called it an instance
|
||||
decision.
|
||||
|
||||
**A missing conventions file is not an error here.** It is the state an
|
||||
instance is in between installing this machinery and running the migration that
|
||||
writes the file, and every command has to keep working through it. The fallback
|
||||
is `PRE_CONVENTIONS_NAMES` - not "the stack's language", but *what this stack
|
||||
hardcoded before the file existed*, which is by construction what any corpus
|
||||
reaching that state was written with. `wikitool doctor` is what says the file is
|
||||
missing; degrading loudly here would take out `doctor` itself.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
from chemenu import config
|
||||
from chemenu.frontmatter_io import read_page
|
||||
|
||||
CONVENTIONS_FILENAME = "CONVENTIONS.md"
|
||||
CONVENTIONS_TEMPLATE = f"{CONVENTIONS_FILENAME}.template"
|
||||
|
||||
# The three tool-owned headings, by slot name. The slot is the stable
|
||||
# identifier - it is what code, the type-spec templates and the conventions
|
||||
# file all key on - while the heading text itself is the instance's to choose.
|
||||
RELATIONSHIPS = "relationships"
|
||||
SEE_ALSO = "see_also"
|
||||
FOOTNOTES = "footnotes"
|
||||
SLOTS = (RELATIONSHIPS, SEE_ALSO, FOOTNOTES)
|
||||
|
||||
# Frontmatter keys read out of kb/CONVENTIONS.md.
|
||||
SECTIONS_KEY = "sections"
|
||||
SECTION_ALIASES_KEY = "section_aliases"
|
||||
LANGUAGE_KEY = "language"
|
||||
|
||||
# Every heading name this stack has ever written as canonical, newest first.
|
||||
# Two jobs, and they are separate: the first entry is the fallback for an
|
||||
# instance that has no conventions file yet, and the whole tuple is an implicit
|
||||
# alias set that every instance recognizes regardless of what it declares. The
|
||||
# second is what makes a corpus translatable page by page - a page still
|
||||
# carrying `## Footnotes` is untranslated, not broken, and `cite sync` has to
|
||||
# stay a no-op on it.
|
||||
PRE_CONVENTIONS_NAMES: dict[str, tuple[str, ...]] = {
|
||||
RELATIONSHIPS: ("Beziehungen", "Relationships"),
|
||||
SEE_ALSO: ("Siehe auch", "See Also"),
|
||||
FOOTNOTES: ("Fußnoten", "Footnotes"),
|
||||
}
|
||||
|
||||
|
||||
def conventions_file() -> Path:
|
||||
return config.KB_DIR / CONVENTIONS_FILENAME
|
||||
|
||||
|
||||
# (path, mtime_ns, size) -> frontmatter. `heading_re()` is called once per page
|
||||
# per lint run, so re-reading the file each time would put a stat+parse on a
|
||||
# per-page path for a document that changes about once per instance. Keyed on
|
||||
# the stat rather than on the path alone, so a test that rewrites the file
|
||||
# inside one process is not answered out of the cache.
|
||||
_CACHE: dict[tuple[str, int, int], dict[str, Any]] = {}
|
||||
|
||||
|
||||
def read_conventions() -> dict[str, Any]:
|
||||
"""`kb/CONVENTIONS.md`'s frontmatter, or `{}` if the file is absent.
|
||||
|
||||
Permissive on purpose, like `read_page` itself: a conventions file with
|
||||
broken YAML degrades to the pre-conventions defaults rather than taking
|
||||
every command down with it. `doctor` and `docs verify` are where that
|
||||
surfaces as a finding.
|
||||
"""
|
||||
path = conventions_file()
|
||||
if not path.is_file():
|
||||
return {}
|
||||
stat = path.stat()
|
||||
key = (str(path), stat.st_mtime_ns, stat.st_size)
|
||||
if key not in _CACHE:
|
||||
frontmatter, _ = read_page(path)
|
||||
_CACHE.clear()
|
||||
_CACHE[key] = frontmatter
|
||||
return _CACHE[key]
|
||||
|
||||
|
||||
def reset_cache() -> None:
|
||||
"""Drop the parsed conventions. For a caller that rewrote the file and has
|
||||
to see the new value within the same stat resolution."""
|
||||
_CACHE.clear()
|
||||
|
||||
|
||||
def _mapping(key: str) -> dict[str, Any]:
|
||||
value = read_conventions().get(key)
|
||||
return value if isinstance(value, dict) else {}
|
||||
|
||||
|
||||
def language() -> Optional[str]:
|
||||
"""The declared KB language tag (e.g. `de`), or None if undeclared.
|
||||
|
||||
Nothing in the compiler branches on it - the language is carried by the
|
||||
prose the instance writes, not by a switch. It is here because the
|
||||
conventions file is where a human and an agent look the answer up, and
|
||||
because `doctor` reports it.
|
||||
"""
|
||||
value = read_conventions().get(LANGUAGE_KEY)
|
||||
if value is None:
|
||||
return None
|
||||
return str(value).strip() or None
|
||||
|
||||
|
||||
def canonical(slot: str) -> str:
|
||||
"""The heading name this instance writes for `slot`."""
|
||||
declared = _mapping(SECTIONS_KEY).get(slot)
|
||||
if isinstance(declared, str) and declared.strip():
|
||||
return declared.strip()
|
||||
return PRE_CONVENTIONS_NAMES[slot][0]
|
||||
|
||||
|
||||
def names(slot: str) -> tuple[str, ...]:
|
||||
"""Every heading name `slot` is recognized under, canonical first.
|
||||
|
||||
The canonical name, then any `section_aliases:` the instance declared, then
|
||||
the names this stack wrote before the conventions file existed. Deduplicated
|
||||
while preserving that order, so an instance declaring English does not end
|
||||
up with `Relationships` listed twice.
|
||||
"""
|
||||
declared_aliases = _mapping(SECTION_ALIASES_KEY).get(slot)
|
||||
extra = declared_aliases if isinstance(declared_aliases, list) else []
|
||||
ordered = [
|
||||
canonical(slot),
|
||||
*(str(name).strip() for name in extra if str(name).strip()),
|
||||
*PRE_CONVENTIONS_NAMES[slot],
|
||||
]
|
||||
seen: dict[str, None] = {}
|
||||
for name in ordered:
|
||||
seen.setdefault(name, None)
|
||||
return tuple(seen)
|
||||
|
||||
|
||||
def section_variables() -> dict[str, str]:
|
||||
"""The `{section.<slot>}` substitutions a type-spec template can use.
|
||||
|
||||
This is what took the three German headings out of `types/*.md`: a template
|
||||
writes `## {section.relationships}` and the instance's own conventions fill
|
||||
it in, so scaffolding a page in another language needs no edit under
|
||||
`types/`.
|
||||
"""
|
||||
return {f"section.{slot}": canonical(slot) for slot in SLOTS}
|
||||
|
||||
|
||||
def declaration_issues() -> list[str]:
|
||||
"""What is wrong with this instance's conventions file, if anything.
|
||||
|
||||
Shared by `doctor` (which FAILs on it) and `docs verify` (which refuses a
|
||||
tree with it), so the two cannot disagree about what a valid declaration
|
||||
looks like. An absent file is *not* reported here - that is a separate
|
||||
finding with a separate fix, and only `doctor` makes it one.
|
||||
"""
|
||||
path = conventions_file()
|
||||
if not path.is_file():
|
||||
return []
|
||||
|
||||
issues: list[str] = []
|
||||
frontmatter, _ = read_page(path)
|
||||
if not frontmatter:
|
||||
return [
|
||||
f"kb/{CONVENTIONS_FILENAME} has no readable frontmatter - it must declare "
|
||||
f"`{SECTIONS_KEY}:` with the heading names this instance writes"
|
||||
]
|
||||
|
||||
declared = frontmatter.get(SECTIONS_KEY)
|
||||
if not isinstance(declared, dict):
|
||||
return [
|
||||
f"kb/{CONVENTIONS_FILENAME}: `{SECTIONS_KEY}:` must be a mapping of "
|
||||
f"{'/'.join(SLOTS)} to the heading text this instance writes"
|
||||
]
|
||||
for slot in SLOTS:
|
||||
value = declared.get(slot)
|
||||
if not isinstance(value, str) or not value.strip():
|
||||
issues.append(
|
||||
f"kb/{CONVENTIONS_FILENAME}: `{SECTIONS_KEY}.{slot}` is missing or empty - "
|
||||
"`xref add` and `cite add` write into a heading this instance has not named"
|
||||
)
|
||||
for slot in sorted(set(declared) - set(SLOTS)):
|
||||
issues.append(
|
||||
f"kb/{CONVENTIONS_FILENAME}: `{SECTIONS_KEY}.{slot}` is not a section the tool "
|
||||
f"owns; the slots are {', '.join(SLOTS)}"
|
||||
)
|
||||
|
||||
aliases = frontmatter.get(SECTION_ALIASES_KEY, {})
|
||||
if not isinstance(aliases, dict):
|
||||
issues.append(
|
||||
f"kb/{CONVENTIONS_FILENAME}: `{SECTION_ALIASES_KEY}:` must be a mapping of a "
|
||||
"slot to the list of headings still recognized under it"
|
||||
)
|
||||
else:
|
||||
for slot, value in sorted(aliases.items()):
|
||||
if slot not in SLOTS:
|
||||
issues.append(
|
||||
f"kb/{CONVENTIONS_FILENAME}: `{SECTION_ALIASES_KEY}.{slot}` is not a "
|
||||
f"section the tool owns; the slots are {', '.join(SLOTS)}"
|
||||
)
|
||||
elif not isinstance(value, list):
|
||||
issues.append(
|
||||
f"kb/{CONVENTIONS_FILENAME}: `{SECTION_ALIASES_KEY}.{slot}` must be a list"
|
||||
)
|
||||
|
||||
if config.TEMPLATE_SENTINEL in path.read_text(encoding="utf-8"):
|
||||
issues.append(
|
||||
f"kb/{CONVENTIONS_FILENAME} still carries the `{config.TEMPLATE_SENTINEL}` line - "
|
||||
"a renamed template is not a filled one"
|
||||
)
|
||||
return issues
|
||||
@@ -20,11 +20,32 @@ Two corollaries are enforced rather than documented:
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from chemenu import config
|
||||
|
||||
CONTRACT_NAME = "COLLECTION.md"
|
||||
|
||||
# What a collection declares about itself, in `COLLECTION.md`'s frontmatter.
|
||||
#
|
||||
# Presence on the filesystem says a collection *exists*; it cannot say who owns
|
||||
# the rules inside it. A `COLLECTION.md` is instance-owned - the distribution
|
||||
# ships a `.template` per default collection and the instance writes the real
|
||||
# one - so the two facts the stack still needs from it have to be declared
|
||||
# rather than inferred from the directory name, which an instance is free to
|
||||
# choose.
|
||||
PROFILE_FIELD = "profile"
|
||||
REQUIRED_BY_STACK_FIELD = "required_by_stack"
|
||||
|
||||
# Collections `wikitool` itself depends on by name, as opposed to ones that
|
||||
# merely hold pages. `sources` is here because three parts of the stack resolve
|
||||
# against it rather than against a page's type: `sources coverage` asks which
|
||||
# raw files no source page claims, every `[^cite-id]` footnote resolves to a
|
||||
# page in it, and `sources rebuild-index` writes `kb/provenance.md` from it. An
|
||||
# instance may add, rename or drop any collection that is not on this list;
|
||||
# renaming one that is leaves those three with nothing to resolve against.
|
||||
STACK_REQUIRED_COLLECTIONS = ("sources",)
|
||||
|
||||
|
||||
def iter_kb_collections(kb_dir: Path | None = None) -> list[Path]:
|
||||
"""Return every collection directory under kb/, sorted by name.
|
||||
@@ -82,6 +103,83 @@ def stray_collection_contracts(root: Path | None = None, kb_dir: Path | None = N
|
||||
return sorted(stray)
|
||||
|
||||
|
||||
def collection_declaration(collection: Path) -> dict[str, Any]:
|
||||
"""A collection's own `COLLECTION.md` frontmatter, or `{}` if it has none.
|
||||
|
||||
Permissive like every other frontmatter read in this package: an unreadable
|
||||
declaration degrades to empty here and is reported by `docs verify`, rather
|
||||
than taking down the discovery every command starts with.
|
||||
"""
|
||||
from chemenu.frontmatter_io import read_page
|
||||
|
||||
contract = collection / CONTRACT_NAME
|
||||
if not contract.is_file():
|
||||
return {}
|
||||
frontmatter, _ = read_page(contract)
|
||||
return frontmatter
|
||||
|
||||
|
||||
def declaration_issues(kb_dir: Path | None = None) -> list[str]:
|
||||
"""What each `COLLECTION.md` fails to declare about itself.
|
||||
|
||||
Two fields, for two questions the filesystem cannot answer. `profile:`
|
||||
names the entry in `instructions/kb-profiles.md` this collection adopted -
|
||||
free text, because the profile catalogue is a palette rather than an enum,
|
||||
and a collection an instance invented has no entry there to name.
|
||||
`required_by_stack:` is not the instance's to choose at all: it must agree
|
||||
with `STACK_REQUIRED_COLLECTIONS`, so a collection whose contract claims the
|
||||
stack depends on it - or one the stack does depend on and that says it does
|
||||
not - is a finding rather than a preference.
|
||||
"""
|
||||
root = kb_dir if kb_dir is not None else config.KB_DIR
|
||||
issues: list[str] = []
|
||||
|
||||
present = {path.name for path in iter_kb_collections(root)}
|
||||
for name in STACK_REQUIRED_COLLECTIONS:
|
||||
if name not in present:
|
||||
issues.append(
|
||||
f"kb/{name}/ is missing - `sources coverage`, `[^cite-id]` resolution and "
|
||||
f"`kb/provenance.md` all resolve against it by name"
|
||||
)
|
||||
|
||||
for collection in iter_kb_collections(root):
|
||||
relative = f"kb/{collection.name}/{CONTRACT_NAME}"
|
||||
declared = collection_declaration(collection)
|
||||
if not declared:
|
||||
issues.append(
|
||||
f"{relative} has no frontmatter - it must declare `{PROFILE_FIELD}:` and "
|
||||
f"`{REQUIRED_BY_STACK_FIELD}:` (see instructions/kb-profiles.md)"
|
||||
)
|
||||
continue
|
||||
|
||||
profile = declared.get(PROFILE_FIELD)
|
||||
if not isinstance(profile, str) or not profile.strip():
|
||||
issues.append(
|
||||
f"{relative}: `{PROFILE_FIELD}:` is missing or empty - name the profile from "
|
||||
f"instructions/kb-profiles.md this collection adopted, or `none`"
|
||||
)
|
||||
|
||||
required = declared.get(REQUIRED_BY_STACK_FIELD)
|
||||
expected = collection.name in STACK_REQUIRED_COLLECTIONS
|
||||
if not isinstance(required, bool):
|
||||
issues.append(
|
||||
f"{relative}: `{REQUIRED_BY_STACK_FIELD}:` is missing or not a boolean - "
|
||||
f"it must be {str(expected).lower()} for this collection"
|
||||
)
|
||||
elif required != expected:
|
||||
issues.append(
|
||||
f"{relative}: `{REQUIRED_BY_STACK_FIELD}: {str(required).lower()}` contradicts "
|
||||
f"the stack, which "
|
||||
+ (
|
||||
"does depend on this collection by name"
|
||||
if expected
|
||||
else "depends on no collection of this name"
|
||||
)
|
||||
+ f" - it must be {str(expected).lower()}"
|
||||
)
|
||||
return issues
|
||||
|
||||
|
||||
def _is_vendored(path: Path, repo_root: Path) -> bool:
|
||||
try:
|
||||
relative = path.relative_to(repo_root)
|
||||
|
||||
@@ -14,9 +14,9 @@ WIKILINK_RE = re.compile(r"\[\[([^\]|#]+)")
|
||||
|
||||
|
||||
# Root-level files under kb/ that are not pages: the generated catalog map, log
|
||||
# and provenance index, plus the contract that constrains the tree rather than
|
||||
# living in it.
|
||||
_KB_META_FILES = {"index.md", "log.md", "provenance.md", "CONTRACT.md"}
|
||||
# and provenance index, plus the two documents that constrain the tree rather
|
||||
# than living in it - the stack's contract and this instance's own conventions.
|
||||
_KB_META_FILES = {"index.md", "log.md", "provenance.md", "CONTRACT.md", "CONVENTIONS.md"}
|
||||
|
||||
# The per-collection authoring contract. Unlike the meta files above it is never
|
||||
# at the kb root - it sits one level down, in every collection - so it has to be
|
||||
|
||||
@@ -64,7 +64,22 @@ LEGACY_CITE_RE = re.compile(r"\^\[\[([^\]|#]+)(?:\|([^\]]+))?\]\]")
|
||||
# Written under the canonical name, but split_cite_block() matches the aliases
|
||||
# too - a page whose block still says "## Footnotes" keeps working until it is
|
||||
# translated. See chemenu/sections.py.
|
||||
CITE_BLOCK_HEADING = f"## {sections.FOOTNOTES}"
|
||||
#
|
||||
# Resolved on access rather than bound at import (PEP 562), because the
|
||||
# canonical name is now this instance's own - `kb/CONVENTIONS.md`, via
|
||||
# chemenu.conventions - and a module constant would freeze whichever corpus the
|
||||
# process started in. The functions below take it as a default the same way, via
|
||||
# None rather than an evaluated default argument.
|
||||
def __getattr__(name: str) -> str:
|
||||
if name == "CITE_BLOCK_HEADING":
|
||||
return f"## {sections.FOOTNOTES}"
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
|
||||
def cite_block_heading_default() -> str:
|
||||
"""The Footnotes heading this instance writes, `## ` included."""
|
||||
return f"## {sections.FOOTNOTES}"
|
||||
|
||||
|
||||
_SOURCE_TITLE_PREFIX = "Source - "
|
||||
|
||||
@@ -193,18 +208,23 @@ def cite_block_heading(body: str) -> str:
|
||||
alias is untranslated, not broken, and `cite sync` has to stay a no-op on
|
||||
it. Translating the heading is the migration's job, not the tool's."""
|
||||
match = sections.heading_re(sections.FOOTNOTES).search(body)
|
||||
return match.group(0).strip() if match else CITE_BLOCK_HEADING
|
||||
return match.group(0).strip() if match else cite_block_heading_default()
|
||||
|
||||
|
||||
def render_cite_block(
|
||||
definitions: dict[str, tuple[str, Optional[str]]], heading: str = CITE_BLOCK_HEADING
|
||||
definitions: dict[str, tuple[str, Optional[str]]], heading: Optional[str] = None
|
||||
) -> str:
|
||||
"""Render the Footnotes block for `definitions` (cite_id -> (title,
|
||||
qualifier)), preserving dict order. Empty dict renders "" - a page with
|
||||
no citations carries no block at all."""
|
||||
no citations carries no block at all.
|
||||
|
||||
`heading=None` means this instance's canonical Footnotes heading, resolved
|
||||
at call time. It cannot be an evaluated default: the name comes from
|
||||
`kb/CONVENTIONS.md`, so a default bound at import would answer for whichever
|
||||
corpus the process started in."""
|
||||
if not definitions:
|
||||
return ""
|
||||
lines = [heading, ""]
|
||||
lines = [heading or cite_block_heading_default(), ""]
|
||||
for cid, (title, qualifier) in definitions.items():
|
||||
target = f"{title}|{qualifier}" if qualifier else title
|
||||
lines.append(f"[^{cid}]: [[{target}]]")
|
||||
@@ -214,7 +234,7 @@ def render_cite_block(
|
||||
def render_page_body(
|
||||
head: str,
|
||||
definitions: dict[str, tuple[str, Optional[str]]],
|
||||
heading: str = CITE_BLOCK_HEADING,
|
||||
heading: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Reassemble a page body from its non-Footnotes content and citation
|
||||
definitions - the inverse of split_cite_block(). Pass the original body's
|
||||
|
||||
+50
-13
@@ -5,10 +5,12 @@ See Also by name, and `cite add` owns the trailing Footnotes block. An author
|
||||
may add any other heading they like - only the ones named here are matched by
|
||||
the tool, and only these have to stay predictable.
|
||||
|
||||
kb/CONTRACT.md's Language rule puts page prose in the KB language. That used to
|
||||
force these three to stay English, because a translated heading did not error -
|
||||
it made `xref add` append a *second* section, silently. This module removes that
|
||||
constraint by making the vocabulary explicit in one place.
|
||||
**Which words they are is the instance's decision, not the stack's.** They
|
||||
follow the KB language, and the KB language is declared in `kb/CONVENTIONS.md`
|
||||
(see `chemenu.conventions`). This module used to hold `RELATIONSHIPS =
|
||||
"Beziehungen"` as a Python constant, which made an instance writing its pages
|
||||
in any other language edit the compiler to say so - the one place a documented
|
||||
instance convention had leaked into code.
|
||||
|
||||
Each heading has one **canonical** name - what the tool writes - and any number
|
||||
of **aliases** it still recognizes. That asymmetry is what lets a corpus migrate
|
||||
@@ -16,24 +18,59 @@ page by page instead of all at once: a page still carrying `## Relationships` is
|
||||
found and appended to correctly, and only takes the canonical name when the page
|
||||
itself is translated. Removing an alias is therefore a breaking change for every
|
||||
page not yet converted, not a cleanup.
|
||||
|
||||
The three module attributes below resolve on access (PEP 562), the same way
|
||||
`config`'s paths do and for the same reason: a caller that repoints `KB_DIR`
|
||||
must not be answered out of a value bound at import time by whichever tree the
|
||||
process started in.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
RELATIONSHIPS = "Beziehungen"
|
||||
SEE_ALSO = "Siehe auch"
|
||||
FOOTNOTES = "Fußnoten"
|
||||
from chemenu import conventions
|
||||
|
||||
ALIASES: dict[str, tuple[str, ...]] = {
|
||||
RELATIONSHIPS: ("Relationships",),
|
||||
SEE_ALSO: ("See Also",),
|
||||
FOOTNOTES: ("Footnotes",),
|
||||
# The slots, re-exported so a caller keeps using `sections.RELATIONSHIPS` as an
|
||||
# opaque handle. The value it resolves to is the heading text; the name it is
|
||||
# looked up under is stable.
|
||||
_SLOT_ATTRS = {
|
||||
"RELATIONSHIPS": conventions.RELATIONSHIPS,
|
||||
"SEE_ALSO": conventions.SEE_ALSO,
|
||||
"FOOTNOTES": conventions.FOOTNOTES,
|
||||
}
|
||||
|
||||
|
||||
def __getattr__(name: str) -> str:
|
||||
slot = _SLOT_ATTRS.get(name)
|
||||
if slot is None:
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
return conventions.canonical(slot)
|
||||
|
||||
|
||||
def __dir__() -> list[str]:
|
||||
return sorted([*globals(), *_SLOT_ATTRS])
|
||||
|
||||
|
||||
def _slot_of(canonical: str) -> str:
|
||||
"""The slot whose current canonical name is `canonical`.
|
||||
|
||||
Callers hold on to the resolved heading text (`sections.FOOTNOTES`), not to
|
||||
the slot, so the lookup has to go back the other way. Falls back to matching
|
||||
against every name a slot is recognized under, so a caller that resolved the
|
||||
attribute before the conventions file changed still lands on the right slot.
|
||||
"""
|
||||
for slot in conventions.SLOTS:
|
||||
if canonical == conventions.canonical(slot):
|
||||
return slot
|
||||
for slot in conventions.SLOTS:
|
||||
if canonical in conventions.names(slot):
|
||||
return slot
|
||||
raise ValueError(f"{canonical!r} is not a tool-owned section heading")
|
||||
|
||||
|
||||
def names(canonical: str) -> tuple[str, ...]:
|
||||
"""Every name `canonical` is recognized under, canonical first."""
|
||||
return (canonical, *ALIASES.get(canonical, ()))
|
||||
return conventions.names(_slot_of(canonical))
|
||||
|
||||
|
||||
def heading_re(canonical: str) -> re.Pattern[str]:
|
||||
@@ -44,4 +81,4 @@ def heading_re(canonical: str) -> re.Pattern[str]:
|
||||
|
||||
def is_known(heading: str) -> bool:
|
||||
"""True if `heading` is a canonical name or an alias of one."""
|
||||
return any(heading in names(canonical) for canonical in ALIASES)
|
||||
return any(heading in conventions.names(slot) for slot in conventions.SLOTS)
|
||||
|
||||
@@ -3,7 +3,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from chemenu import config
|
||||
from chemenu import config, conventions
|
||||
from chemenu.frontmatter_io import write_page
|
||||
from chemenu.type_resolver import resolver
|
||||
|
||||
@@ -77,9 +77,17 @@ def hermetic_environment(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
||||
# attribute. That binding outlives the test and hands the next one a
|
||||
# corpus directory belonging to the previous tree. Cleared on both sides,
|
||||
# so neither a leak from before nor one from this test can be inherited.
|
||||
# One layer further in again: `conventions` parses `kb/CONVENTIONS.md` once
|
||||
# and keys the result on the file's own path and stat, so a repointed
|
||||
# `KB_DIR` cannot be answered out of it. Cleared here anyway, on both sides,
|
||||
# for the same reason `config.reset()` is - a fixture that leaves state
|
||||
# behind is the hole this file exists to close, and the cost of proving it
|
||||
# cannot leak is one function call per test.
|
||||
config.reset()
|
||||
conventions.reset_cache()
|
||||
yield home
|
||||
config.reset()
|
||||
conventions.reset_cache()
|
||||
|
||||
|
||||
def use_shipped_type_specs(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
"""Tests for `kb/CONVENTIONS.md` - the instance-owned half of the authoring rules.
|
||||
|
||||
Two things are under test here, and they are the two the split exists for: the
|
||||
compiler reads its section headings from the corpus rather than from Python, and
|
||||
a collection declares who owns its rules rather than having it inferred from the
|
||||
directory name.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from chemenu import config, conventions, kb_collections, sections
|
||||
|
||||
GERMAN = (
|
||||
"---\n"
|
||||
"language: de\n"
|
||||
"profile: german\n"
|
||||
"sections:\n"
|
||||
" relationships: Beziehungen\n"
|
||||
" see_also: Siehe auch\n"
|
||||
" footnotes: Fußnoten\n"
|
||||
"---\n\n# conventions\n"
|
||||
)
|
||||
|
||||
FRENCH = (
|
||||
"---\n"
|
||||
"language: fr\n"
|
||||
"profile: none\n"
|
||||
"sections:\n"
|
||||
" relationships: Relations\n"
|
||||
" see_also: Voir aussi\n"
|
||||
" footnotes: Notes\n"
|
||||
"section_aliases:\n"
|
||||
" relationships: [Beziehungen]\n"
|
||||
"---\n\n# conventions\n"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def kb_root(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
kb = tmp_path / "kb"
|
||||
kb.mkdir()
|
||||
monkeypatch.setattr(config, "ROOT", tmp_path)
|
||||
monkeypatch.setattr(config, "KB_DIR", kb)
|
||||
conventions.reset_cache()
|
||||
yield kb
|
||||
conventions.reset_cache()
|
||||
|
||||
|
||||
def _write(kb: Path, text: str) -> None:
|
||||
(kb / conventions.CONVENTIONS_FILENAME).write_text(text, encoding="utf-8")
|
||||
conventions.reset_cache()
|
||||
|
||||
|
||||
def _collection(kb: Path, name: str, profile: str = "none", required: bool = False) -> Path:
|
||||
directory = kb / name
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
(directory / kb_collections.CONTRACT_NAME).write_text(
|
||||
f"---\nprofile: {profile}\nrequired_by_stack: {str(required).lower()}\n---\n\n# {name}\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
return directory
|
||||
|
||||
|
||||
def test_missing_file_falls_back_to_what_the_stack_used_to_hardcode(kb_root):
|
||||
"""The state between installing this machinery and running the migration
|
||||
that writes the file. Every command has to keep working through it, and the
|
||||
only corpus that can be in it was written under these names."""
|
||||
assert conventions.canonical(conventions.FOOTNOTES) == "Fußnoten"
|
||||
assert sections.FOOTNOTES == "Fußnoten"
|
||||
|
||||
|
||||
def test_the_compiler_writes_the_headings_the_instance_declared(kb_root):
|
||||
_write(kb_root, FRENCH)
|
||||
assert sections.RELATIONSHIPS == "Relations"
|
||||
assert sections.SEE_ALSO == "Voir aussi"
|
||||
assert sections.FOOTNOTES == "Notes"
|
||||
|
||||
|
||||
def test_declared_aliases_and_the_pre_conventions_names_are_both_recognized(kb_root):
|
||||
"""The translation path. A page still carrying the old heading has to be
|
||||
found and appended to, or a language change would silently split every page
|
||||
into two Relationships sections."""
|
||||
_write(kb_root, FRENCH)
|
||||
pattern = sections.heading_re(sections.RELATIONSHIPS)
|
||||
for heading in ("## Relations", "## Beziehungen", "## Relationships"):
|
||||
assert pattern.search(f"# Page\n\n{heading}\n\n- x\n"), heading
|
||||
|
||||
|
||||
def test_the_canonical_name_is_not_duplicated_among_its_aliases(kb_root):
|
||||
"""An instance declaring the pre-conventions name gets it once, not twice -
|
||||
otherwise `heading_re`'s alternation carries a redundant branch and
|
||||
`names()` misreports what a page could be carrying."""
|
||||
_write(
|
||||
kb_root,
|
||||
"---\nsections:\n relationships: Relationships\n"
|
||||
" see_also: See Also\n footnotes: Footnotes\n---\n",
|
||||
)
|
||||
names = conventions.names(conventions.RELATIONSHIPS)
|
||||
assert names[0] == "Relationships"
|
||||
assert len(names) == len(set(names))
|
||||
|
||||
|
||||
def test_section_variables_are_what_a_type_spec_template_substitutes(kb_root):
|
||||
_write(kb_root, GERMAN)
|
||||
assert conventions.section_variables() == {
|
||||
"section.relationships": "Beziehungen",
|
||||
"section.see_also": "Siehe auch",
|
||||
"section.footnotes": "Fußnoten",
|
||||
}
|
||||
|
||||
|
||||
def test_a_rewritten_file_is_not_answered_out_of_the_cache(kb_root):
|
||||
_write(kb_root, GERMAN)
|
||||
assert sections.FOOTNOTES == "Fußnoten"
|
||||
_write(kb_root, FRENCH)
|
||||
assert sections.FOOTNOTES == "Notes"
|
||||
|
||||
|
||||
def test_an_incomplete_sections_block_is_reported(kb_root):
|
||||
_write(kb_root, "---\nlanguage: de\nsections:\n relationships: Beziehungen\n---\n")
|
||||
issues = conventions.declaration_issues()
|
||||
assert any("sections.see_also" in issue for issue in issues)
|
||||
assert any("sections.footnotes" in issue for issue in issues)
|
||||
|
||||
|
||||
def test_an_unfilled_template_is_reported_like_a_missing_one(kb_root):
|
||||
_write(kb_root, GERMAN.replace("language: de", f"# {config.TEMPLATE_SENTINEL}\nlanguage: de"))
|
||||
assert any(config.TEMPLATE_SENTINEL in issue for issue in conventions.declaration_issues())
|
||||
|
||||
|
||||
def test_an_absent_file_is_not_a_declaration_issue(kb_root):
|
||||
"""`doctor` FAILs on absence; `docs verify` must not, or a fresh export
|
||||
would be unverifiable before the setup step that writes the file."""
|
||||
assert conventions.declaration_issues() == []
|
||||
|
||||
|
||||
def test_a_collection_must_declare_its_profile_and_stack_dependence(kb_root):
|
||||
_collection(kb_root, "sources", profile="sources", required=True)
|
||||
(kb_root / "notes").mkdir()
|
||||
(kb_root / "notes" / kb_collections.CONTRACT_NAME).write_text("# notes\n", encoding="utf-8")
|
||||
issues = kb_collections.declaration_issues(kb_root)
|
||||
assert any("kb/notes/COLLECTION.md has no frontmatter" in issue for issue in issues)
|
||||
|
||||
|
||||
def test_required_by_stack_is_checked_against_the_stack_not_taken_on_trust(kb_root):
|
||||
"""The one field an instance may not choose. A collection claiming the stack
|
||||
depends on it would make a rename look unsafe when it is not - and, worse,
|
||||
`sources` claiming otherwise would make one look safe when it is not."""
|
||||
_collection(kb_root, "sources", required=False)
|
||||
_collection(kb_root, "entities", required=True)
|
||||
issues = kb_collections.declaration_issues(kb_root)
|
||||
assert any("kb/sources/COLLECTION.md" in issue and "must be true" in issue for issue in issues)
|
||||
assert any("kb/entities/COLLECTION.md" in issue and "must be false" in issue for issue in issues)
|
||||
|
||||
|
||||
def test_a_missing_stack_required_collection_is_reported(kb_root):
|
||||
_collection(kb_root, "entities")
|
||||
assert any("kb/sources/ is missing" in issue for issue in kb_collections.declaration_issues(kb_root))
|
||||
|
||||
|
||||
def test_a_correct_declaration_reports_nothing(kb_root):
|
||||
_collection(kb_root, "sources", profile="sources", required=True)
|
||||
_collection(kb_root, "entities", profile="entities")
|
||||
assert kb_collections.declaration_issues(kb_root) == []
|
||||
@@ -113,6 +113,14 @@ def repo(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
(kb / "entities" / "COLLECTION.md").write_text("# entities collection\n", encoding="utf-8")
|
||||
(kb / "entities" / "aurora.md").write_text("---\ntype: types/entity.md\n---\n", encoding="utf-8")
|
||||
(kb / "CONTRACT.md").write_text("# kb contract\n", encoding="utf-8")
|
||||
(kb / "CONVENTIONS.md").write_text(
|
||||
"---\nlanguage: de\nsections:\n relationships: Beziehungen\n"
|
||||
" see_also: Siehe auch\n footnotes: Fußnoten\n---\n\n# this instance\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(kb / "CONVENTIONS.md.template").write_text(
|
||||
f"<!-- {config.TEMPLATE_SENTINEL} -->\n# conventions template\n", encoding="utf-8"
|
||||
)
|
||||
|
||||
for relative in ("raw/CONTRACT.md", "reports/CONTRACT.md", "work/CONTRACT.md"):
|
||||
path = root / relative
|
||||
@@ -262,6 +270,10 @@ def test_find_leaks_is_silent_on_a_clean_plan(repo):
|
||||
"instructions/dev/commonplace-kb.md",
|
||||
"kb/entities/aurora.md",
|
||||
"raw/notes/personal-note.md",
|
||||
# Both bind every page and both are the instance's to write, so the
|
||||
# filled name must never cross - only the `.template` beside it does.
|
||||
"kb/CONVENTIONS.md",
|
||||
"kb/entities/COLLECTION.md",
|
||||
],
|
||||
)
|
||||
def test_find_leaks_catches_one_instance_own_data(repo, relative):
|
||||
@@ -283,12 +295,28 @@ def test_export_refuses_a_plan_that_leaks(repo, tmp_path, monkeypatch):
|
||||
assert not target.exists()
|
||||
|
||||
|
||||
def test_plan_copies_collection_contracts_not_pages(repo):
|
||||
def test_plan_ships_collection_contracts_as_templates_not_pages(repo):
|
||||
"""The stack's own contract crosses verbatim; the instance-owned ones cross
|
||||
under the template name and are adopted by a rename. Shipping
|
||||
`kb/entities/COLLECTION.md` would hand a new instance this one's authoring
|
||||
conventions as though the stack had decided them."""
|
||||
plan = dist_cmd.build_plan()
|
||||
assert "kb/entities/COLLECTION.md" in plan
|
||||
assert "kb/entities/COLLECTION.md.template" in plan
|
||||
assert "kb/entities/COLLECTION.md" not in plan
|
||||
assert "kb/CONTRACT.md" in plan
|
||||
assert not any(relative.endswith("aurora.md") for relative in plan)
|
||||
|
||||
# The shipped template is the contract's own text - one source of truth in
|
||||
# the origin repo, renamed across the boundary. A second file kept beside
|
||||
# each contract would be a near-identical copy, maintained by hand.
|
||||
assert plan["kb/entities/COLLECTION.md.template"].content == "# entities collection\n"
|
||||
|
||||
|
||||
def test_plan_ships_the_conventions_template_and_not_the_filled_file(repo):
|
||||
plan = dist_cmd.build_plan()
|
||||
assert "kb/CONVENTIONS.md.template" in plan
|
||||
assert "kb/CONVENTIONS.md" not in plan
|
||||
|
||||
|
||||
def test_plan_creates_empty_raw_subdirs_not_real_content(repo):
|
||||
plan = dist_cmd.build_plan()
|
||||
@@ -397,7 +425,7 @@ def test_export_into_a_fresh_directory_works(repo, tmp_path):
|
||||
target = tmp_path / "dist"
|
||||
dist_cmd.run_export(target, dry_run=False)
|
||||
assert (target / "AGENTS.md").is_file()
|
||||
assert (target / "kb" / "entities" / "COLLECTION.md").is_file()
|
||||
assert (target / "kb" / "entities" / "COLLECTION.md.template").is_file()
|
||||
assert (target / "raw" / "notes" / ".gitkeep").is_file()
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from chemenu import config
|
||||
from chemenu import config, conventions
|
||||
from chemenu.commands import doctor, instructions_cmd
|
||||
|
||||
|
||||
@@ -30,6 +30,12 @@ def instance(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
(kb / "log.md").write_text("# Log\n", encoding="utf-8")
|
||||
(kb / "provenance.md").write_text("# Provenance\n", encoding="utf-8")
|
||||
(kb / "CONTRACT.md").write_text("# kb contract\n", encoding="utf-8")
|
||||
(kb / "CONVENTIONS.md").write_text(
|
||||
"---\nlanguage: en\nprofile: none\nsections:\n"
|
||||
" relationships: Relationships\n see_also: See Also\n footnotes: Footnotes\n"
|
||||
"---\n\n# conventions\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(root / "VERSION").write_text("0.1.0\n", encoding="utf-8")
|
||||
(root / "USER.md").write_text("# USER.md - Fixture\n", encoding="utf-8")
|
||||
(root / "SOUL.md").write_text("# SOUL.md - Fixture\n", encoding="utf-8")
|
||||
@@ -194,6 +200,32 @@ def test_a_renamed_but_unfilled_template_fails(instance):
|
||||
assert "template" in next(c.detail for c in checks if c.name == "personalization")
|
||||
|
||||
|
||||
def test_conventions_are_ok_when_declared(instance):
|
||||
checks = doctor.run_doctor()
|
||||
assert _status(checks, "conventions") == "OK"
|
||||
assert "Relationships" in next(c.detail for c in checks if c.name == "conventions")
|
||||
|
||||
|
||||
def test_missing_conventions_fail(instance):
|
||||
"""Unlike `ENVIRONMENT.md`, this one is not optional: `xref add` and
|
||||
`cite add` write headings out of it, so an instance without it is being
|
||||
answered by whatever the stack hardcoded before the file existed."""
|
||||
(config.KB_DIR / conventions.CONVENTIONS_FILENAME).unlink()
|
||||
checks = doctor.run_doctor()
|
||||
assert _status(checks, "conventions") == "FAIL"
|
||||
assert "missing" in next(c.detail for c in checks if c.name == "conventions")
|
||||
|
||||
|
||||
def test_conventions_with_an_incomplete_sections_block_fail(instance):
|
||||
"""Present and deciding nothing - the same failure mode the personalization
|
||||
sentinel check exists for, one directory down."""
|
||||
(config.KB_DIR / conventions.CONVENTIONS_FILENAME).write_text(
|
||||
"---\nlanguage: en\nsections:\n relationships: Relationships\n---\n", encoding="utf-8"
|
||||
)
|
||||
conventions.reset_cache()
|
||||
assert _status(doctor.run_doctor(), "conventions") == "FAIL"
|
||||
|
||||
|
||||
def test_environment_is_ok_when_absent(instance):
|
||||
"""The file is optional, so absence is a healthy end state - a FAIL here
|
||||
would make it mandatory through the back door."""
|
||||
|
||||
@@ -75,6 +75,31 @@ def test_new_entity_creates_page_with_expected_frontmatter(monkeypatch, kb_dir):
|
||||
assert "# gateway.example.net" in body
|
||||
|
||||
|
||||
def test_scaffolded_body_carries_the_headings_this_instance_declared(monkeypatch, kb_dir):
|
||||
"""The type-spec writes `## {section.relationships}`, not a heading text, so
|
||||
an instance in another language scaffolds its own headings without editing
|
||||
anything under `types/`. This is that path end to end."""
|
||||
from chemenu import conventions
|
||||
|
||||
(kb_dir / conventions.CONVENTIONS_FILENAME).write_text(
|
||||
"---\nlanguage: fr\nprofile: none\nsections:\n relationships: Relations\n"
|
||||
" see_also: Voir aussi\n footnotes: Notes\n---\n\n# conventions\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
conventions.reset_cache()
|
||||
try:
|
||||
result = _invoke_new(monkeypatch, kb_dir, [
|
||||
"new", "entity", "--name", "passerelle", "--set", "entity_type=system",
|
||||
])
|
||||
assert result.exit_code == 0, result.output
|
||||
_fm, body = read_page(kb_dir / "entities/systems/passerelle.md")
|
||||
assert "## Relations" in body
|
||||
assert "## Voir aussi" in body
|
||||
assert "{section." not in body
|
||||
finally:
|
||||
conventions.reset_cache()
|
||||
|
||||
|
||||
def test_new_entity_applies_schema_declared_defaults(monkeypatch, kb_dir):
|
||||
"""provenance and confidence are no longer Typer flag defaults - they
|
||||
come from the schema's own `default:`, so omitting them still yields a
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from chemenu import sections
|
||||
from chemenu import conventions
|
||||
from chemenu.cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
@@ -45,9 +45,10 @@ def test_types_describe_entity_reports_schema_and_body():
|
||||
]
|
||||
assert fields_by_name["tags"]["required"] is False
|
||||
# The body must carry the page skeleton an authoring LLM works from. Anchored on the
|
||||
# tool-owned section vocabulary rather than a literal, so that translating the spec - or
|
||||
# the section names themselves - does not turn this into a tripwire.
|
||||
assert f"## {sections.RELATIONSHIPS}" in data["body"]
|
||||
# template *variable* rather than on any heading text: the spec no longer names the
|
||||
# tool-owned sections at all - `kb/CONVENTIONS.md` does, and `new` substitutes it - so a
|
||||
# literal here would assert the very coupling that was removed.
|
||||
assert f"## {{section.{conventions.RELATIONSHIPS}}}" in data["body"]
|
||||
|
||||
|
||||
def test_types_describe_unknown_name_fails_cleanly():
|
||||
|
||||
+3
-3
@@ -36,7 +36,7 @@ page_ref_fields: [entities]
|
||||
|
||||
## Autorenanweisungen
|
||||
|
||||
- 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`, Abschnitte "Naming" und "Language")
|
||||
- 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
|
||||
@@ -68,8 +68,8 @@ TODO: Falls möglich - was wann und für wen zu verwenden ist. Unter welchen Ums
|
||||
|
||||
`# 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 `## Siehe auch`; der Name steht in
|
||||
`tools/chemenu/sections.py`.
|
||||
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:`).
|
||||
|
||||
---
|
||||
|
||||
|
||||
+4
-3
@@ -45,7 +45,7 @@ page_ref_fields: [related, sources]
|
||||
|
||||
## Autorenanweisungen
|
||||
|
||||
- Der Titel ist der kanonische Name des Concepts und folgt der etablierten Fachbezeichnung, nicht der KB-Sprache (`kb/CONTRACT.md`, Abschnitte "Naming" und "Language")
|
||||
- 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
|
||||
@@ -90,8 +90,9 @@ TODO: Anti-Muster, Warnungen oder Situationen, in denen es fehl am Platz ist
|
||||
```
|
||||
|
||||
Der Wert hinter `**Typ:**` bleibt der englische Enum-Wert - danach filtert `search --field`.
|
||||
Fügt `wikitool xref` eine Beziehung hinzu, entstehen zusätzlich `## Beziehungen` und
|
||||
`## Siehe auch`; deren Namen stehen in `tools/chemenu/sections.py`.
|
||||
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
|
||||
`kb/CONVENTIONS.md` (`sections:`).
|
||||
|
||||
---
|
||||
|
||||
|
||||
+8
-6
@@ -50,7 +50,7 @@ layout:
|
||||
|
||||
## Autorenanweisungen
|
||||
|
||||
- Der Titel ist der kanonische Name der Entity und folgt der etablierten Bezeichnung des Gegenstands, nicht der KB-Sprache (`kb/CONTRACT.md`, Abschnitte "Naming" und "Language")
|
||||
- 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 -
|
||||
@@ -77,7 +77,7 @@ TODO: 1-2 Absätze dazu, was diese Entity ist und wozu sie dient.
|
||||
- **Verantwortlich:** TODO (falls zutreffend)
|
||||
- **Repository:** TODO (falls zutreffend)
|
||||
|
||||
## Beziehungen
|
||||
## {section.relationships}
|
||||
|
||||
- **Hängt ab von:** TODO
|
||||
- **Verwendet von:** TODO
|
||||
@@ -91,14 +91,16 @@ TODO: Ausführliche Informationen, nach sinnvollen Abschnitten gegliedert
|
||||
|
||||
- [{today}] - Page created via wikitool
|
||||
|
||||
## Siehe auch
|
||||
## {section.see_also}
|
||||
|
||||
- TODO: Verwandte Seiten
|
||||
```
|
||||
|
||||
`## Beziehungen` und `## Siehe auch` sind toolgeführt: `wikitool xref` schreibt in genau diese
|
||||
Abschnitte, benannt in `tools/chemenu/sections.py`. Der Wert hinter `**Typ:**` bleibt der
|
||||
englische Enum-Wert - danach filtert `search --field`.
|
||||
Die beiden `{section.…}`-Platzhalter sind toolgeführte Abschnitte: `wikitool xref` schreibt in
|
||||
genau sie hinein, und wie sie heißen, entscheidet die Instanz in `kb/CONVENTIONS.md`
|
||||
(`sections:`) - nicht dieser Type-Spec und nicht der Compiler. `wikitool new` setzt den
|
||||
aktuellen Namen ein. Der Wert hinter `**Typ:**` bleibt der englische Enum-Wert - danach filtert
|
||||
`search --field`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+3
-3
@@ -48,7 +48,7 @@ page_ref_fields: [entities, concepts]
|
||||
- `raw_files` listet jede Raw-Datei, die diese Quelle abdeckt (eine Source-Seite pro logischer Quelle, nicht pro Datei)
|
||||
- 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/CONTRACT.md`, Abschnitt "Language")
|
||||
- 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
|
||||
@@ -108,8 +108,8 @@ TODO: 2-3 Absätze zu den Kernaussagen des Quellmaterials.
|
||||
|
||||
`# 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 `## Fußnoten`; der Name
|
||||
steht in `tools/chemenu/sections.py`.
|
||||
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:`).
|
||||
|
||||
---
|
||||
|
||||
|
||||
+15
-8
@@ -98,17 +98,24 @@ The `## Template` block is filled from the page's own frontmatter, plus `{name}`
|
||||
`{entities|table_cells}`. `{field|literal text}` falls back to the literal when the field is
|
||||
absent.
|
||||
|
||||
Three further variables come from the instance rather than from the page:
|
||||
`{section.relationships}`, `{section.see_also}` and `{section.footnotes}`, filled from
|
||||
`kb/CONVENTIONS.md`'s `sections:` declaration. A template writes a tool-owned heading through
|
||||
one of those and never as literal text - that is what lets an instance change the KB language
|
||||
without editing anything under `types/`.
|
||||
|
||||
### Ownership boundary
|
||||
|
||||
| Owned here | Owned by `kb/CONTRACT.md` and the collection contracts |
|
||||
|------------|-----------------------------------------------------------|
|
||||
| Frontmatter fields, enums, defaults, required-ness | Quality goal and tone |
|
||||
| Directory placement and title prefix | Naming conventions |
|
||||
| Body skeleton (template) | Linking policy and relationship vocabulary |
|
||||
| When to use / not use this type | Provenance and confidence practice |
|
||||
| Owned here | Owned by `kb/CONTRACT.md` | Owned by `kb/CONVENTIONS.md` and the collection contracts |
|
||||
|------------|--------------------------|-----------------------------------------------------------|
|
||||
| Frontmatter fields, enums, defaults, required-ness | Provenance and citation mechanics | Quality goal and tone |
|
||||
| Directory placement and title prefix | The confidence machinery | Naming conventions and the confidence rubric |
|
||||
| Body skeleton (template) | Linking mechanics and the orphan check | Relationship vocabulary |
|
||||
| When to use / not use this type | The prose/identifier rule | The KB language and its section-heading names |
|
||||
|
||||
If a rule would be identical for every type, it belongs in `kb/CONTRACT.md`, not in a
|
||||
type-spec. If it is identical for every page in one collection, it belongs in that
|
||||
If a rule would be identical for every type *and* every instance, it belongs in
|
||||
`kb/CONTRACT.md`. If every instance would answer it differently, it belongs in
|
||||
`kb/CONVENTIONS.md`. If it is identical for every page in one collection, it belongs in that
|
||||
collection's `COLLECTION.md`.
|
||||
|
||||
### What does not belong here
|
||||
|
||||
Reference in New Issue
Block a user