types/: Seiten-Type-Spec-Anleitungsprosa in stackeigene guidance-Datei ausgelagert (schliesst #104)
CI / verify (push) Failing after 43s
Release / release (push) Successful in 34s

Files changed:
- AGENTS.md
- CHANGES.md
- VERSION
- docs/ownership-and-templates.md
- instructions/migrations/6.0.0-type-guidance-split.md
- instructions/setup-instance.md
- tools/CONTRACT.md
- tools/chemenu/commands/dist_cmd.py
- tools/chemenu/commands/types_cmd.py
- tools/chemenu/tests/test_dist_cmd.py
- tools/chemenu/tests/test_dist_upgrade.py
- tools/chemenu/tests/test_type_resolver.py
- tools/chemenu/tests/test_types_cmd.py
- tools/chemenu/type_resolver.py
- tools/chemenu/types_core.py
- types/comparison.guidance.md
- types/comparison.md
- types/concept.guidance.md
- types/concept.md
- types/entity.guidance.md
- types/entity.md
- types/source.guidance.md
- types/source.md
- types/type-guidance.md
- types/type-guidance.schema.yaml
- types/type-spec.md
This commit is contained in:
2026-09-15 18:23:38 +02:00
parent 90ce41964f
commit d49513bda6
26 changed files with 789 additions and 238 deletions
+1
View File
@@ -99,6 +99,7 @@ What a file is called says who it is for and how it is loaded. This is a rule, n
| `instructions/<name>.md` | Agents | By link, or on explicit request | | `instructions/<name>.md` | Agents | By link, or on explicit request |
| `instructions/<name>/SKILL.md` | Agents | By the harness, once published | | `instructions/<name>/SKILL.md` | Agents | By the harness, once published |
| `types/<name>.md` | Agents + validator | Via `tools/wikitool types describe`. Split by `root:`: a page type-spec (`root: kb`) belongs to the instance and ships as `.template`; one describing a stack artifact ships verbatim | | `types/<name>.md` | Agents + validator | Via `tools/wikitool types describe`. Split by `root:`: a page type-spec (`root: kb`) belongs to the instance and ships as `.template`; one describing a stack artifact ships verbatim |
| `types/<name>.guidance.md` | Agents + validator | Via `tools/wikitool types describe`, composed with the `types/<name>.md` it documents. Stack-owned regardless of the type-spec's own `root:` - it ships verbatim and is optional, present only where the type-spec declares `guidance:` |
| `docs/<name>.md` | Agents and humans | By link, or on explicit request - never automatically, and never as instruction | | `docs/<name>.md` | Agents and humans | By link, or on explicit request - never automatically, and never as instruction |
| `INDEX.md` | Both | Generated - never hand-edited | | `INDEX.md` | Both | Generated - never hand-edited |
+44 -1
View File
@@ -59,7 +59,7 @@ concern - readable here, never shipped as something to parse.
--- ---
## 6.0.0-beta.5 - 2026-09-15 - Control-Plane-Sprache universell: Achse ist das Publikum, kein Instanz-Schalter ## 6.0.0-beta.6 - 2026-09-15 - types/: Seiten-Type-Spec-Anleitungsprosa in stackeigene guidance-Datei ausgelagert
**Author:** Torben Nehmer **Author:** Torben Nehmer
@@ -74,6 +74,7 @@ concern - readable here, never shipped as something to parse.
- SKILL.md: relative Links durch repo-root-relative Pfade ersetzt, docs verify/instructions verify pruefen Linkziele - SKILL.md: relative Links durch repo-root-relative Pfade ersetzt, docs verify/instructions verify pruefen Linkziele
- docs verify: der Linkziel-Check erreicht auch die instanz-eigenen kb/CONVENTIONS.md und COLLECTION.md - daher Grenzuebertritt - docs verify: der Linkziel-Check erreicht auch die instanz-eigenen kb/CONVENTIONS.md und COLLECTION.md - daher Grenzuebertritt
- TOC-Scope auf types/ und docs/ erweitert, Sprachregeln zentralisiert, --breaking akkumuliert - TOC-Scope auf types/ und docs/ erweitert, Sprachregeln zentralisiert, --breaking akkumuliert
- types/: Seiten-Type-Spec-Anleitungsprosa in stackeigene guidance-Datei ausgelagert
**Medium impact** **Medium impact**
- docs verify: ein nur als .template ausgeliefertes Linkziel gilt als aufgeloest - docs verify: ein nur als .template ausgeliefertes Linkziel gilt als aufgeloest
@@ -264,6 +265,48 @@ Verzeichnis fuenf trug: `docs/model-and-effort-selection.md` fehlte, und zwar ab
ein Link dorthin die Claude-Code-eigene Entscheidung in die anderen drei Harnesses laden wuerde. ein Link dorthin die Claude-Code-eigene Entscheidung in die anderen drei Harnesses laden wuerde.
Der Satz zaehlt jetzt, was von hier aus verlinkt ist, und benennt die sechste Seite samt Grund. Der Satz zaehlt jetzt, was von hier aus verlinkt ist, und benennt die sechste Seite samt Grund.
### types/: Seiten-Type-Spec-Anleitungsprosa in stackeigene guidance-Datei ausgelagert
Ein `root: kb` Type-Spec (`entity`, `concept`, `source`, `comparison`) hatte zwei Publika in
einer Datei: Anleitungsprosa fuer den Agenten (When to use/When NOT to use/Authoring guidance),
und Seitenmaterial (`## Template`-Block, `layout:`-Titel). Ownership gilt pro Datei, also wurde
die ganze Datei beim Setup als `.template` adoptiert und danach nie wieder angefasst - eine
Instanz, die ihre Type-Specs frueh adoptiert hat, las bis in alle Zukunft die Anleitung vom Tag
ihrer Erzeugung, weil `dist upgrade` das `.template` neben die adoptierte Datei schrieb, nie die
Datei selbst (`docs/ownership-and-templates.md` § "Where the file boundary strains").
Der urspruengliche Vorschlag drehte den Schnitt um (Type-Spec stackeigen, Seitenmaterial heraus)
und wurde beim Pruefen gegen `setup-instance.md` und `evolve-subtypes.md` verworfen: die
Frontmatter-Konfiguration (`layout:`, Enum-Werte, `base_dir`) ist instanzeigener Inhalt, keine
Stack-Maschinerie - beide Instructions weisen die Instanz an, Enum und `layout:`-Eintrag in
derselben Aenderung zu setzen. Stattdessen bleibt der Type-Spec instanzeigen, und nur die
maschinenabgeleitete Anleitungsprosa zieht in eine neue, stackeigene `types/<name>.guidance.md`,
verknuepft ueber ein optionales `guidance:`-Frontmatterfeld (neuer, nicht instanziierbarer Typ
`type-guidance`, wie `lint-report` ohne `base_dir:`). `tools/wikitool types describe <name>`
komponiert beide Haelften weiterhin zu einer Antwort - ein Agent muss nie wissen, dass ein Typ aus
zwei Dateien besteht. `type_resolver.extract_template()` liest das Template unveraendert allein
aus `types/<name>.md`; kein zweiter Ladepfad fuer `wikitool new`.
`dist_cmd._plan_types()`/`find_leaks()` teilten sich vorher `name.split(".", 1)[0]` als
Stamm-Berechnung - beides haette `entity.guidance.md` faelschlich als instanzeigenen Stamm
"entity" erkannt (die eine haette sie zum `.template` gemacht, die andere sie als Leak gemeldet).
Neuer gemeinsamer Prädikat `_owned_type_stem()` prueft die exakte Endung (`<stem>.md` oder
`<stem>.schema.yaml`), nicht den ersten Punkt.
Grenzuebertritt-Frage bewusst geprueft und verneint: Drop-in in beide Richtungen (ein Type-Spec
ohne `guidance:` verhaelt sich unveraendert, eine alte Maschinerie liest `types/<name>.md` wie
zuvor und die Guidance-Datei ist fuer sie inert), also `--minor` statt `--major`. Die einmalige
Adoption in einer bestehenden Instanz ist als `instructions/migrations/6.0.0-type-guidance-split.md`
dokumentiert - `obligation: offered`, der erste Gebrauch dieses seit 4.0.0 existierenden, bis jetzt
unbenutzten Mechanismus fuer ein instanzeigenes, upgradebares Machinery-File.
Verifiziert: `tools/wikitool docs verify`/`instructions verify` gruen, 1261 Tests gruen (8 neu:
`get_guidance`, das Template bleibt auf `types/<name>.md` allein geladen, die Guidance-Datei
schifft verbatim neben einem `.template`-adoptierten Type-Spec statt als weiteres `.template`,
ein `dist upgrade` schreibt verbesserte Guidance-Prosa in eine adoptierte Instanz obwohl deren
Type-Spec selbst nie im Stamp stand, `types describe` komponiert beide Haelften in JSON und
Textausgabe getrennt nachweisbar).
## 5.1.0 - 2026-09-12 - changelog: Kandidaten-Eintrag nach Impact gruppiert, version regrade zur Nachkorrektur, version release verlangt eine Zusammenfassung ## 5.1.0 - 2026-09-12 - changelog: Kandidaten-Eintrag nach Impact gruppiert, version regrade zur Nachkorrektur, version release verlangt eine Zusammenfassung
**Author:** Torben Nehmer **Author:** Torben Nehmer
+1 -1
View File
@@ -1 +1 @@
6.0.0-beta.5 6.0.0-beta.6
+41 -25
View File
@@ -16,7 +16,7 @@ overwriting them would silently erase a choice someone made on purpose.
- [Why silent overwrite is the failure being designed against](#why-silent-overwrite-is-the-failure-being-designed-against) - [Why silent overwrite is the failure being designed against](#why-silent-overwrite-is-the-failure-being-designed-against)
- [Why the boundary is a predicate rather than a list](#why-the-boundary-is-a-predicate-rather-than-a-list) - [Why the boundary is a predicate rather than a list](#why-the-boundary-is-a-predicate-rather-than-a-list)
- [Why a `.template`, not just an absent file](#why-a-template-not-just-an-absent-file) - [Why a `.template`, not just an absent file](#why-a-template-not-just-an-absent-file)
- [Where the file boundary strains](#where-the-file-boundary-strains) - [Where the file boundary used to strain](#where-the-file-boundary-used-to-strain)
- [The consequence in practice](#the-consequence-in-practice) - [The consequence in practice](#the-consequence-in-practice)
<!-- /wikitool:toc --> <!-- /wikitool:toc -->
@@ -92,36 +92,50 @@ also why `ENVIRONMENT.md` only warrants a WARN rather than a FAIL when absent -
checkout among possibly several and is gitignored for that reason, so its absence is a normal checkout among possibly several and is gitignored for that reason, so its absence is a normal
state rather than a sign setup was skipped. state rather than a sign setup was skipped.
## Where the file boundary strains ## Where the file boundary used to strain
"The file itself already answers that" holds for every file above except one shape, and it is "The file itself already answers that" held for every file above except one shape: a `root: kb`
worth naming rather than discovering: a `root: kb` type-spec has two audiences inside one file. type-spec used to carry two audiences inside one file.
Its authoring guidance - when to use this type, what each frontmatter field means, how to cite - Its authoring guidance - when to use this type, what each frontmatter field means, how to cite -
is instruction to an agent. It reads like the stack's own prose because it *is* the stack's own was instruction to an agent. It read like the stack's own prose because it *was* the stack's own
prose: a later release that learns something about writing entity pages would want to improve it prose: a later release that learned something about writing entity pages would want to improve it
everywhere. Its `## Template` block and its `layout:` titles are the opposite: they become the everywhere. Its `## Template` block and its `layout:` titles were the opposite: they became the
literal headings of pages this instance writes, in the language this instance chose, and no literal headings of pages this instance writes, in the language this instance chose, and no
release has any business touching them. release had any business touching them.
The same file is where the language question comes apart from this one, and for the same reason: The same file is where the language question comes apart from the ownership one, and for the same
ownership decides who may change a line, its reader decides what language it is in - which is why reason: ownership decides who may change a line, its reader decides what language it is in -
an instance's own type-spec keeps English guidance around a template block in its own language. which is why a `root: kb` type-spec still keeps English prose around a template block written in
[language-boundaries.md](language-boundaries.md) has that argument; the rest of this page is about its own language. [language-boundaries.md](language-boundaries.md) has that argument; this page is
ownership alone. about ownership alone.
Ownership is per file, so the instance keeps both halves. The template half is correct that way. Ownership is per file, so a file carrying both audiences had to give both halves to whoever owned
The guidance half pays for it: an instance that adopted its type-specs at setup will never it. The template half was correct that way. The guidance half paid for it: an instance that
receive an improvement to them again, because `dist upgrade` writes the `.template` beside the adopted its type-specs at setup never received an improvement to the guidance again, because
adopted file and never the file itself. Nothing breaks, and nothing reports it - the instance `dist upgrade` wrote the `.template` beside the adopted file and never the file itself. Nothing
simply keeps reading the guidance it was handed the day it was created. broke, and nothing reported it - the instance simply kept reading the guidance it was handed the
day it was created.
This is not an argument against the per-file boundary; the boundary is what makes an upgrade That was not an argument against the per-file boundary; the boundary is what makes an upgrade
safe at all, and merging inside a shared file is the failure the whole section above is about. safe at all, and merging inside a shared file is the failure the whole section above is about. It
It is an argument that this particular file is cut in the wrong place. Splitting it - the was an argument that this particular file was cut in the wrong place - so it was cut again. A
template block into its own instance-owned file, the guidance staying stack-owned and shipping `root: kb` type-spec may now declare `guidance:`, a repo-relative path to a second,
verbatim - would put the boundary back where the audiences actually divide, and would cost a stack-owned file (`types/<name>.guidance.md`) holding exactly the half that used to be stranded:
migration for every instance that already adopted one. when to use the type, when not to, and mechanism-level advice that holds for every instance. That
file ships verbatim and upgrades like any other machinery file, whether or not the type-spec that
links it has ever been adopted. `types/type-spec.md` §§ "Who owns a type-spec" and "Anatomy of a
type" hold the current shape; `tools/wikitool types describe <name>` composes both files into one
answer, so an agent asking for a type's contract never needs to know it comes from more than one
file. An instance that adopted its type-specs before this split existed takes it as an *offered*
migration rather than something an upgrade applies on its own - the same reasoning as any other
instance-owned file in the middle category below, spelled out for this one case because it is the
case that motivated the category existing at all.
A type-spec that declares no `guidance:` - one an instance writes entirely for itself - is
unaffected: it is still described from its own body alone, the way every type-spec worked before
`guidance:` existed. The split is optional exactly where there is no stack-owned improvement to
receive.
## The consequence in practice ## The consequence in practice
@@ -129,7 +143,9 @@ An upgrade sorts every shipped path into three categories, not two - and the thi
becomes visible once an upgrade is a command rather than a hand-run copy: becomes visible once an upgrade is a command rather than a hand-run copy:
- **Verbatim files** - `AGENTS.md`, `kb/CONTRACT.md`, the per-stage contracts, everything under - **Verbatim files** - `AGENTS.md`, `kb/CONTRACT.md`, the per-stage contracts, everything under
`tools/`, `types/` and `instructions/` - are the release's to replace. `tools/`, `types/` and `instructions/` - are the release's to replace. This is where a `root:
kb` type-spec's optional `types/<name>.guidance.md` sits: verbatim, even though the type-spec
it documents (below) is not.
- **`.template`-sourced files** - `USER.md`, `SOUL.md`, `kb/CONVENTIONS.md`, each - **`.template`-sourced files** - `USER.md`, `SOUL.md`, `kb/CONVENTIONS.md`, each
`kb/<name>/COLLECTION.md`, `ENVIRONMENT.md`, the `root: kb` type-specs - are never written by `kb/<name>/COLLECTION.md`, `ENVIRONMENT.md`, the `root: kb` type-specs - are never written by
an upgrade at all. The distribution ships only the `.template` beside them, so the filled file an upgrade at all. The distribution ships only the `.template` beside them, so the filled file
@@ -0,0 +1,116 @@
---
type: types/instruction.md
name: 6.0.0-type-guidance-split
description: "Add a guidance: field to an adopted root:kb type-spec so it starts receiving the stack's authoring-prose improvements again, without touching the type-spec's own frontmatter or template."
manual: true
migrates_to: 6.0.0
migration_kind: assisted
obligation: offered
---
# Link an adopted type-spec to its stack-owned guidance file (6.0.0)
Before 6.0.0, a `root: kb` type-spec (`entity`, `concept`, `source`, `comparison`, or one this
instance added itself) carried its generic authoring prose - when to use the type, when not to,
mechanism-level advice such as citation and provenance rules - in the same file as its frontmatter
configuration and its `## Template` block. Adopting the type-spec at setup meant adopting all of
it at once, and an upgrade never touched the adopted file again: the prose an instance received
was frozen at the day it ran `setup-instance.md`, while every later improvement shipped only in
the `.template` beside it (`docs/ownership-and-templates.md` § "Where the file boundary used to
strain").
6.0.0 splits that prose into a separate, stack-owned `types/<name>.guidance.md`, linked from the
type-spec via an optional `guidance:` frontmatter field. The new file ships verbatim and upgrades
like any other machinery file from here on - but only once a type-spec actually points at it.
Taking this offer is exactly that: adding one frontmatter line per adopted type-spec. It is
`assisted`, not `mechanical`, because whether this instance's own copy of the prose has diverged
from the shipped default is a judgment call a script cannot make.
This migration is **offered, not required**. A type-spec with no `guidance:` keeps working
exactly as it did before 6.0.0 - it is described from its own body alone. Declining costs nothing
except future improvements to the prose half; nothing about the machinery stops fitting.
<!-- wikitool:toc -->
## Contents
- [When to run](#when-to-run)
- [Steps](#steps)
- [How to tell a migrated type-spec from an unmigrated one](#how-to-tell-a-migrated-type-spec-from-an-unmigrated-one)
- [Decision points](#decision-points)
- [Scope](#scope)
<!-- /wikitool:toc -->
## When to run
Any time after installing 6.0.0 machinery over an instance that adopted at least one `root: kb`
type-spec before this migration existed. `tools/wikitool migrate status` lists it under "optional
upgrade(s) available"; taking it is not gated on anything else being current.
## Steps
1. **Confirm the new guidance files actually arrived.** `dist upgrade` writes `types/<name>.guidance.md`
as an ordinary new/unchanged file - it does not depend on this migration at all. If
`ls types/*.guidance.md` shows nothing, the machinery upgrade has not landed yet; run that
first.
2. **For each adopted `root: kb` type-spec, decide whether its authoring prose still matches the
shipped default.** Compare the type-spec's current prose (everything outside `## Frontmatter`
and `## Template`) against the corresponding `types/<name>.guidance.md`:
- **Unchanged, or changed only in ways this instance is happy to lose:** proceed to step 3
directly - the new guidance file already carries the improved version.
- **Locally edited in a way worth keeping** (a house style note, an extra rule specific to
this corpus): that edit has to move somewhere before the old prose is dropped. Either fold
it into a local copy of the guidance file this instance then owns for itself (any path is
valid for `guidance:`, not only the shipped one), or keep it in the type-spec's own body
instead of adding `guidance:` at all - both are legitimate; declining the stack default for
one type is not an error.
3. **Add `guidance: types/<name>.guidance.md` to the type-spec's frontmatter** - by hand, the same
way any other type-spec frontmatter field is written (a type-spec is machinery, not a `kb/`
page, so this is not a `wikitool touch` call). Do not remove `## Frontmatter` or `## Template`;
only the generic prose around them is what the guidance file now carries.
4. **Delete the now-duplicated prose from the type-spec**, keeping the H1, a short pointer to the
guidance file (`types/entity.md`'s own current text is the worked example), `## Frontmatter`
and `## Template`. Where step 2 found a local edit worth keeping and it lives in the
type-spec's own body rather than a private guidance file, leave that part exactly where it is.
5. **Verify:**
```bash
tools/wikitool types describe <name>
```
The output must read the same as it did before this migration - the guidance prose composed
ahead of the type-spec's own body, in one answer. A diff against the pre-migration output of
the same command, restricted to wording, is expected only where step 2 found something to
drop or fold in; the structure (frontmatter fields, template block) must be byte-identical.
6. **Record it:**
```bash
tools/wikitool migrate done 6.0.0 --pages 0
```
`--pages 0` because no `kb/` page changes - this migration touches machinery under `types/`
only. This does **not** advance `kb_version`, per `obligation: offered` above; it only marks
the offer as taken so `migrate status` stops listing it.
## How to tell a migrated type-spec from an unmigrated one
`grep -L '^guidance:' types/*.md` (excluding `.guidance.md` files themselves, which never carry
the field) lists every `root: kb` type-spec that has not taken the offer yet.
## Decision points
- **A type this instance wrote entirely for itself?** No `types/<name>.guidance.md` exists for
it and none should be authored to match this migration artificially - `guidance:` is for
receiving a *stack* default, and a self-written type has none to receive. Leave it as it is.
- **Local prose worth keeping, but no interest in maintaining a private guidance file?** Skip
`guidance:` for that one type-spec. Nothing forces uniformity across an instance's own types.
## Scope
For `types/` machinery, not `kb/` content - the one migration document in this directory that
is. No page's frontmatter or body changes, `sources coverage`/`lint`/`kb_version` are all
unaffected, and `migrate done`'s `--pages` is `0` for exactly that reason.
+10 -3
View File
@@ -89,9 +89,16 @@ and ready for its first ingest.
Under `types/` this covers exactly the type-specs with `root: kb` - `entity`, `concept`, Under `types/` this covers exactly the type-specs with `root: kb` - `entity`, `concept`,
`source`, `comparison` - along with their `.schema.yaml`. They describe pages *this* `source`, `comparison` - along with their `.schema.yaml`. They describe pages *this*
instance writes, so they belong to it: prose, template and language may all be rewritten. instance writes, so they belong to it: frontmatter, template and language may all be
`instruction`, `lint-report` and `type-spec` describe stack artifacts and arrive rewritten. `instruction`, `lint-report`, `type-spec` and `type-guidance` describe stack
unchanged. artifacts and arrive unchanged - the glob above never matches them because none of them
ships as a `.template` in the first place.
A `root: kb` type-spec's generic authoring guidance (when to use the type, when not to)
is not part of this adoption at all: it lives in a sibling `types/<name>.guidance.md`
this instance never renames, the same as `instruction.md` - it ships verbatim and a later
`dist upgrade` improves it directly, without the type-spec that links it needing to be
touched. `types/type-spec.md` § "Anatomy of a type" has the shape.
2. Ask the user for the KB language. `kb/CONVENTIONS.md.template` defaults to **English**; 2. Ask the user for the KB language. `kb/CONVENTIONS.md.template` defaults to **English**;
[kb-profiles.md](kb-profiles.md) additionally holds a complete German profile, whose full [kb-profiles.md](kb-profiles.md) additionally holds a complete German profile, whose full
+1 -1
View File
@@ -166,7 +166,7 @@ tools/wikitool <command> --help
| Command | Purpose | | Command | Purpose |
|---------|---------| |---------|---------|
| `types list [--json]` | List every type-spec under `types/` (name, schema path, subtype field, description) - discover what page types exist without reading `types/*.md` directly | | `types list [--json]` | List every type-spec under `types/` (name, schema path, subtype field, description) - discover what page types exist without reading `types/*.md` directly |
| `types describe <name> [--json]` | Print one type's full contract: required/optional frontmatter fields with enums, its subtype field (if any), and its authoring body. A type-spec over the `docs toc` threshold carries a generated table-of-contents region; it is stripped from this output rather than echoed, since the whole body is being handed over and a navigation aid into it would be noise | | `types describe <name> [--json]` | Print one type's full contract: required/optional frontmatter fields with enums, its subtype field (if any), and its authoring body - composed with the stack-owned `types/<name>.guidance.md` where the type-spec declares `guidance:` (`--json` reports it separately as `guidance`/`guidance_path`, absent for a type with none), so a `root: kb` type's contract reads as one answer even though it may live in two files. A type-spec (or its guidance file) over the `docs toc` threshold carries a generated table-of-contents region; it is stripped from this output rather than echoed, since the whole body is being handed over and a navigation aid into it would be noise |
| `instructions sync [--force]` | Publish every `instructions/<name>/SKILL.md` into `.agents/skills/` and `.claude/skills/` as **copies**, and delete published skills whose source is gone. Both targets are gitignored, so a fresh clone runs this once - see `instructions/bootstrap.md`. Re-running is also how a drifted copy is repaired: the source always wins. `--force` is required only to replace a target directory that is not a published skill at all (no `SKILL.md` in it) | | `instructions sync [--force]` | Publish every `instructions/<name>/SKILL.md` into `.agents/skills/` and `.claude/skills/` as **copies**, and delete published skills whose source is gone. Both targets are gitignored, so a fresh clone runs this once - see `instructions/bootstrap.md`. Re-running is also how a drifted copy is repaired: the source always wins. `--force` is required only to replace a target directory that is not a published skill at all (no `SKILL.md` in it) |
| `instructions verify` | Check the instruction layer: flat instructions validate against `types/instruction.schema.yaml`, each `SKILL.md` carries the frontmatter its harness reads, no `SKILL.md` carries a relative markdown link (`sync` copies it to a different depth than the source, so a `SKILL.md` references a target as a repo-root-relative plain path instead - see [instructions/CONTRACT.md](../instructions/CONTRACT.md) § "A skill's outbound reference is a plain path, not a link"), every published copy is byte-identical to its source, no instruction is left that nothing references, and nothing under `instructions/dev/` is referenced from outside it (a `<!-- dist:strip-start/end -->` block is exempt - see [instructions/CONTRACT.md](../instructions/CONTRACT.md)). Missing *every* copy is reported as "run sync", not as drift - that is a clean checkout | | `instructions verify` | Check the instruction layer: flat instructions validate against `types/instruction.schema.yaml`, each `SKILL.md` carries the frontmatter its harness reads, no `SKILL.md` carries a relative markdown link (`sync` copies it to a different depth than the source, so a `SKILL.md` references a target as a repo-root-relative plain path instead - see [instructions/CONTRACT.md](../instructions/CONTRACT.md) § "A skill's outbound reference is a plain path, not a link"), every published copy is byte-identical to its source, no instruction is left that nothing references, and nothing under `instructions/dev/` is referenced from outside it (a `<!-- dist:strip-start/end -->` block is exempt - see [instructions/CONTRACT.md](../instructions/CONTRACT.md)). Missing *every* copy is reported as "run sync", not as drift - that is a clean checkout |
| `instructions list [--json]` | List the flat instructions with their descriptions. This is how the layer is discovered; `search` deliberately covers `kb/` only | | `instructions list [--json]` | List the flat instructions with their descriptions. This is how the layer is discovered; `search` deliberately covers `kb/` only |
+35 -4
View File
@@ -338,6 +338,32 @@ def instance_owned_type_stems() -> set[str]:
return stems return stems
# The suffix a type-spec's own two files carry - `<stem>.md` and
# `<stem>.schema.yaml` - as opposed to a sibling file that merely starts with
# the same stem, such as `<stem>.guidance.md` (Gitea #104). Checked as an
# exact suffix rather than by splitting on the first `.`, which is what let
# `entity.guidance.md` be mistaken for the `entity` type-spec's own file
# before this existed - a stack-owned file re-keyed as though it were the
# instance's `.template` to adopt, and flagged as a leak by the other call
# site for not being one.
_TYPE_SCHEMA_SUFFIX = ".schema.yaml"
def _owned_type_stem(relative: str) -> Optional[str]:
"""The type stem `relative` (a path under `types/`, no `.template`
suffix) names, if it is exactly that type-spec's own `<stem>.md` or
`<stem>.schema.yaml` - `None` for anything else under `types/`,
including a `<stem>.guidance.md` file. `_plan_types()` and `find_leaks()`
both ask this instead of computing their own stem, so the two answer the
same question about the same path (AGENTS.md invariant 8)."""
name = relative.rsplit("/", 1)[-1]
if name.endswith(_TYPE_SCHEMA_SUFFIX):
return name[: -len(_TYPE_SCHEMA_SUFFIX)]
if name.endswith(".md") and not name.endswith(".guidance.md"):
return name[: -len(".md")]
return None
def _plan_types() -> dict[str, PlannedFile]: def _plan_types() -> dict[str, PlannedFile]:
"""`types/`, with the page type-specs re-keyed as templates. """`types/`, with the page type-specs re-keyed as templates.
@@ -347,6 +373,11 @@ def _plan_types() -> dict[str, PlannedFile]:
to be adopted before it counts. A type-spec's `.schema.yaml` travels with to be adopted before it counts. A type-spec's `.schema.yaml` travels with
it, because the two are one type (see types/type-spec.md § Anatomy) and it, because the two are one type (see types/type-spec.md § Anatomy) and
adopting half of it would leave a spec validated by a file it does not own. adopting half of it would leave a spec validated by a file it does not own.
A type-spec's optional `<name>.guidance.md` (Gitea #104) is the opposite:
stack-owned even where the type-spec itself is instance-owned, and ships
verbatim beside the `.template` - `_owned_type_stem` is what keeps it out
of this re-keying despite sharing the type-spec's own stem.
""" """
plan = _copy_tree(config.TYPES_DIR, "types", frozenset()) plan = _copy_tree(config.TYPES_DIR, "types", frozenset())
stems = instance_owned_type_stems() stems = instance_owned_type_stems()
@@ -355,9 +386,8 @@ def _plan_types() -> dict[str, PlannedFile]:
rekeyed: dict[str, PlannedFile] = {} rekeyed: dict[str, PlannedFile] = {}
for relative, planned in plan.items(): for relative, planned in plan.items():
name = relative.rsplit("/", 1)[-1] stem = _owned_type_stem(relative)
stem = name.split(".", 1)[0] if stem is not None and stem in stems:
if stem in stems:
rekeyed[f"{relative}.template"] = planned rekeyed[f"{relative}.template"] = planned
else: else:
rekeyed[relative] = planned rekeyed[relative] = planned
@@ -512,7 +542,8 @@ def find_leaks(plan: dict[str, PlannedFile]) -> list[str]:
elif ( elif (
relative.startswith("types/") relative.startswith("types/")
and not relative.endswith(".template") and not relative.endswith(".template")
and name.split(".", 1)[0] in owned_types and (owned_stem := _owned_type_stem(relative)) is not None
and owned_stem in owned_types
): ):
leaks.append(f"{relative} (this instance's page type-spec; ship the .template)") leaks.append(f"{relative} (this instance's page type-spec; ship the .template)")
elif relative.startswith("instructions/dev/"): elif relative.startswith("instructions/dev/"):
+13 -6
View File
@@ -55,7 +55,10 @@ def describe_type_command(
"""Print one type's full contract: frontmatter fields (required/optional, """Print one type's full contract: frontmatter fields (required/optional,
with enums where declared), its subtype field if any, and its authoring with enums where declared), its subtype field if any, and its authoring
body - the same information an LLM would otherwise gather by reading the body - the same information an LLM would otherwise gather by reading the
raw type-spec and `.schema.yaml` files directly.""" raw type-spec and `.schema.yaml` files directly. Where the type-spec
declares `guidance:`, that stack-owned file's prose is composed in ahead
of the type-spec's own body, so a `root: kb` type's contract still reads
as one answer even though it lives in two files (Gitea #104)."""
try: try:
described = describe_type(name) described = describe_type(name)
except UnknownType as exc: except UnknownType as exc:
@@ -89,9 +92,13 @@ def describe_type_command(
typer.echo("") typer.echo("")
typer.echo("## Authoring guidance") typer.echo("## Authoring guidance")
# A type-spec over 100 lines carries a generated table-of-contents region # A type-spec (and its guidance file) over 100 lines carries a generated
# (`chemenu/toc.py`), which serves whoever opens the file. Here it would be # table-of-contents region (`chemenu/toc.py`), which serves whoever opens
# noise: this command already hands over the whole body, so there is # the file directly. Here it would be noise: this command already hands
# nothing left for a navigation aid to navigate - only markers and a list # over the whole body, so there is nothing left for a navigation aid to
# of headings the reader is about to see anyway. # navigate - only markers and a list of headings the reader is about to
# see anyway.
if described["guidance"]:
typer.echo(toc.strip_region(described["guidance"]))
typer.echo("")
typer.echo(toc.strip_region(described["body"])) typer.echo(toc.strip_region(described["body"]))
+24 -1
View File
@@ -81,7 +81,16 @@ def repo(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
# the instance's; `instruction` writes into the repo and is the stack's. # the instance's; `instruction` writes into the repo and is the stack's.
(types_dir / "entity.md").write_text( (types_dir / "entity.md").write_text(
"---\ntype: types/type-spec.md\nname: entity\ndescription: d\n" "---\ntype: types/type-spec.md\nname: entity\ndescription: d\n"
"schema: types/entity.schema.yaml\nbase_dir: entities\n---\n\n# Entity\n", "schema: types/entity.schema.yaml\nbase_dir: entities\n"
"guidance: types/entity.guidance.md\n---\n\n# Entity\n",
encoding="utf-8",
)
# entity's guidance: stack-owned even though entity.md itself is
# instance-owned - the file `_owned_type_stem` must not mistake for
# entity's own `.md`/`.schema.yaml` despite sharing its stem (Gitea #104).
(types_dir / "entity.guidance.md").write_text(
"---\ntype: types/type-guidance.md\nname: entity\ndescription: When to use entity.\n"
"---\n\n# Entity Guidance\n",
encoding="utf-8", encoding="utf-8",
) )
(types_dir / "instruction.md").write_text( (types_dir / "instruction.md").write_text(
@@ -394,6 +403,20 @@ def test_page_type_specs_ship_as_templates_and_stack_types_do_not(repo, monkeypa
assert "types/instruction.md.template" not in plan assert "types/instruction.md.template" not in plan
def test_guidance_file_ships_verbatim_beside_a_templated_type_spec(repo, monkeypatch):
"""Gitea #104: `types/entity.guidance.md` is stack-owned even though
`types/entity.md` (same stem) is instance-owned - it must cross like
`types/instruction.md` above, never re-keyed as though it were the
type-spec's own `.template`."""
from chemenu.type_resolver import resolver
monkeypatch.setattr(resolver, "_repo_root", config.ROOT)
plan = dist_cmd.build_plan()
assert "types/entity.guidance.md" in plan
assert "types/entity.guidance.md.template" not in plan
def test_plan_creates_empty_raw_and_incoming_not_real_content(repo): def test_plan_creates_empty_raw_and_incoming_not_real_content(repo):
"""Both flat since Gitea #67: `raw/` addresses a file by its accept date, """Both flat since Gitea #67: `raw/` addresses a file by its accept date,
never by a hand-picked type, so there is nothing left to seed per type.""" never by a hand-picked type, so there is nothing left to seed per type."""
+50
View File
@@ -223,6 +223,56 @@ def test_seeded_once_paths_are_never_written_even_if_the_release_stamp_lists_the
assert not (instance / preserved).exists() assert not (instance / preserved).exists()
# --- a root:kb type-spec's guidance half upgrades like any other file -------
#
# Gitea #104: before the split, `types/<name>.md` carried both the
# instance-owned template and the stack-owned authoring prose in one file, so
# an instance that had adopted it (renamed the `.template`) never received a
# prose improvement again - `dist upgrade` only ever wrote the `.template`
# beside the adopted file, never the file itself. Splitting the prose into a
# sibling `.guidance.md` that ships verbatim (never `.template`-sourced) means
# it upgrades through the ordinary unchanged/new path below, even though the
# type-spec it documents is never in the stamp at all and therefore never
# touched.
def test_upgrade_writes_improved_guidance_prose_over_an_adopted_type_spec(instance, tmp_path):
(instance / "types").mkdir()
(instance / "types" / "entity.md").write_text(
# Adopted from `types/entity.md.template` at some earlier setup - this
# file was never part of any release stamp and `dist upgrade` must
# never touch it.
"---\ntype: types/type-spec.md\nname: entity\ndescription: d\n"
"schema: types/entity.schema.yaml\nbase_dir: entities\n"
"guidance: types/entity.guidance.md\n---\n\n# Entity\n",
encoding="utf-8",
)
(instance / "types" / "entity.guidance.md").write_text("old guidance\n", encoding="utf-8")
stamp = json.loads((instance / version_mod.RELEASE_STAMP_FILENAME).read_text())
stamp["files"]["types/entity.guidance.md"] = _digest("old guidance\n")
(instance / version_mod.RELEASE_STAMP_FILENAME).write_text(json.dumps(stamp), encoding="utf-8")
release = _release(
tmp_path, "release", "1.1.0",
{
"AGENTS.md": "core\n",
"tools/wikitool": "#!/bin/sh\n",
"types/entity.guidance.md": "improved guidance\n",
},
)
dist_cmd.run_upgrade(release)
assert (instance / "types" / "entity.guidance.md").read_text(encoding="utf-8") == (
"improved guidance\n"
)
# The adopted type-spec itself was never in either stamp, so it is
# completely untouched by the upgrade.
assert "guidance: types/entity.guidance.md" in (
instance / "types" / "entity.md"
).read_text(encoding="utf-8")
# --- migration chain: reported, never run ----------------------------------- # --- migration chain: reported, never run -----------------------------------
+49
View File
@@ -64,6 +64,54 @@ def test_get_page_ref_fields_defaults_to_empty():
assert resolver.get_page_ref_fields("types/type-spec.md") == [] assert resolver.get_page_ref_fields("types/type-spec.md") == []
def test_get_guidance_reads_the_linked_type_guidance_file():
"""Gitea #104: a root:kb type-spec's generic authoring prose lives in a
separate, stack-owned `<name>.guidance.md`, linked via `guidance:` -
unlike `schema:`, this one's absence is the common case (an instance
typed for itself), not an error."""
guidance = resolver.get_guidance("types/entity.md")
assert guidance is not None
assert "When to use" in guidance or "When NOT to use" in guidance
def test_get_guidance_is_none_when_the_type_spec_declares_none():
"""`instruction` and `lint-report` describe stack artifacts and have
never carried a `guidance:` field - this is the type with no linked
guidance at all, not a broken link."""
assert resolver.get_guidance("types/instruction.md") is None
assert resolver.get_guidance("types/lint-report.md") is None
def test_extract_template_reads_only_the_type_spec_never_the_guidance_file(tmp_path):
"""`wikitool new` must keep exactly one load path for its scaffold - the
first ```markdown block of `types/<name>.md` itself - even though the
type-spec now optionally links a second file. A ```markdown block placed
in the guidance file instead must never be picked up."""
from chemenu.type_resolver import TypeResolver
types_dir = tmp_path / "types"
types_dir.mkdir()
(types_dir / "widget.md").write_text(
# Self-referential `type:` (like the badtype fixture above), purely so
# this narrow fixture needs no real `types/type-spec.md` on disk -
# `extract_template` never reads the `type:` field at all.
"---\ntype: types/widget.md\nname: widget\ndescription: A widget type.\n"
"schema: null\nbase_dir: widgets\nguidance: types/widget.guidance.md\n---\n\n"
"# Widget\n\n## Template\n\n```markdown\n# {name}\n```\n",
encoding="utf-8",
)
(types_dir / "widget.guidance.md").write_text(
"---\ntype: types/type-guidance.md\nname: widget\ndescription: Guidance for widget.\n"
"---\n\n# Widget Guidance\n\n```markdown\nTHIS MUST NEVER BE THE SCAFFOLD\n```\n",
encoding="utf-8",
)
bad_resolver = TypeResolver(repo_root=tmp_path)
type_spec = bad_resolver.load_type_spec("types/widget.md")
template = bad_resolver.extract_template(type_spec)
assert template == "# {name}"
assert "THIS MUST NEVER BE THE SCAFFOLD" not in template
def test_get_capture_fields_reads_the_type_spec(): def test_get_capture_fields_reads_the_type_spec():
"""`fidelity`/`authority` are fixed once, at capture time (Gitea #67) - """`fidelity`/`authority` are fixed once, at capture time (Gitea #67) -
`raw accept`, `new source` and `touch` all read the field list from here `raw accept`, `new source` and `touch` all read the field list from here
@@ -221,6 +269,7 @@ def test_list_type_specs_finds_every_type_spec():
names = {fm.get("name") for _, fm in resolver.list_type_specs()} names = {fm.get("name") for _, fm in resolver.list_type_specs()}
assert names == { assert names == {
"type-spec", "entity", "concept", "source", "comparison", "lint-report", "instruction", "type-spec", "entity", "concept", "source", "comparison", "lint-report", "instruction",
"type-guidance",
} }
+37
View File
@@ -16,6 +16,7 @@ def test_types_list_finds_all_current_type_specs():
names = {row["name"] for row in rows} names = {row["name"] for row in rows}
assert names == { assert names == {
"type-spec", "entity", "concept", "source", "comparison", "lint-report", "instruction", "type-spec", "entity", "concept", "source", "comparison", "lint-report", "instruction",
"type-guidance",
} }
@@ -52,6 +53,42 @@ def test_types_describe_entity_reports_schema_and_body():
assert "wikitool:footnotes" not in data["body"] assert "wikitool:footnotes" not in data["body"]
def test_types_describe_entity_composes_guidance_and_body_separately():
"""Gitea #104: the generic authoring prose (When to use / When NOT to
use) now lives in the stack-owned `entity.guidance.md`, reported under
its own JSON keys, while `body` stays exactly what it was - the
instance-owned type-spec's own text (frontmatter table + template)."""
result = runner.invoke(app, ["types", "describe", "entity", "--json"])
assert result.exit_code == 0, result.output
import json
data = json.loads(result.output)
assert data["guidance_path"] == "types/entity.guidance.md"
assert data["guidance"] is not None
assert "## When to use" in data["guidance"]
assert "## When to use" not in data["body"]
assert "## Kerndaten" in data["body"]
def test_types_describe_composes_guidance_ahead_of_body_in_text_output():
result = runner.invoke(app, ["types", "describe", "entity"])
assert result.exit_code == 0, result.output
guidance_at = result.output.index("## When to use")
template_at = result.output.index("## Kerndaten")
assert guidance_at < template_at
def test_types_describe_a_type_with_no_guidance_omits_it_cleanly():
"""`instruction` describes a stack artifact and has never carried a
`guidance:` field - this must read exactly as it did before the split
existed, not print an empty section."""
result = runner.invoke(app, ["types", "describe", "instruction", "--json"])
assert result.exit_code == 0, result.output
import json
data = json.loads(result.output)
assert data["guidance"] is None
assert data["guidance_path"] is None
def test_types_describe_unknown_name_fails_cleanly(): def test_types_describe_unknown_name_fails_cleanly():
result = runner.invoke(app, ["types", "describe", "bogus"]) result = runner.invoke(app, ["types", "describe", "bogus"])
assert result.exit_code != 0 assert result.exit_code != 0
+27
View File
@@ -436,6 +436,33 @@ class TypeResolver:
type_spec = self.load_type_spec(type_path, source_file) type_spec = self.load_type_spec(type_path, source_file)
return type_spec['frontmatter'].get('title_prefix') or "" return type_spec['frontmatter'].get('title_prefix') or ""
def get_guidance(self, type_path: str, source_file: Path = None) -> Optional[str]:
"""Return the stack-owned guidance body a type-spec's `guidance:`
field points to, or None if it declares none - the common case for a
type an instance writes entirely for itself (Gitea #104).
The linked file is loaded as a type-spec-shaped document (`type:
types/type-guidance.md`, `name:`, `description:`) the same way
`schema:` is resolved, so a broken link or a malformed guidance file
fails the same way a broken `schema:` would rather than silently
returning nothing.
Args:
type_path: The type path to resolve, e.g. 'types/entity.md'
source_file: The source file path (for relative type resolution)
Raises:
ValueError: If the type path cannot be resolved, or `guidance:`
names a path that cannot be resolved or does not validate as
a type-guidance document.
"""
type_spec = self.load_type_spec(type_path, source_file)
guidance_path = type_spec['frontmatter'].get('guidance')
if not guidance_path:
return None
guidance_spec = self.load_type_spec(guidance_path, type_spec['path'])
return guidance_spec['body']
def get_page_ref_fields(self, type_path: str, source_file: Path = None) -> list: def get_page_ref_fields(self, type_path: str, source_file: Path = None) -> list:
"""Return the frontmatter fields whose entries are wiki page titles """Return the frontmatter fields whose entries are wiki page titles
(e.g. `['related', 'sources']` for an entity), as declared by the (e.g. `['related', 'sources']` for an entity), as declared by the
+7
View File
@@ -59,6 +59,8 @@ def describe_type(name: str) -> Dict[str, Any]:
type_spec = resolver.load_type_spec(type_path) type_spec = resolver.load_type_spec(type_path)
frontmatter = type_spec["frontmatter"] frontmatter = type_spec["frontmatter"]
schema = resolver.get_schema(type_path) schema = resolver.get_schema(type_path)
guidance_path = frontmatter.get("guidance")
guidance_body = resolver.get_guidance(type_path) if guidance_path else None
fields: list[Dict[str, Any]] = [] fields: list[Dict[str, Any]] = []
if schema is not None: if schema is not None:
@@ -86,5 +88,10 @@ def describe_type(name: str) -> Dict[str, Any]:
"base_dir": frontmatter.get("base_dir"), "base_dir": frontmatter.get("base_dir"),
"title_prefix": frontmatter.get("title_prefix"), "title_prefix": frontmatter.get("title_prefix"),
"fields": fields, "fields": fields,
# `body` stays the type-spec's own body, unchanged - additive fields
# below it keep the MCP wire contract readable for an older client
# that has never heard of the guidance split (Gitea #104).
"body": type_spec["body"].strip(), "body": type_spec["body"].strip(),
"guidance_path": guidance_path,
"guidance": guidance_body.strip() if guidance_body is not None else None,
} }
+41
View File
@@ -0,0 +1,41 @@
---
type: types/type-guidance.md
name: comparison
description: When to write a comparison page instead of a neighboring type, and how to write a conforming one - structure and analysis rules that hold regardless of what this instance is comparing
---
# Comparison Guidance
`comparison` is the type for pages that set several entities, tools, technologies or approaches
systematically against one another along fixed criteria. Comparison pages support decisions and
make the trade-offs between alternatives visible.
## When to use
- Comparing several technologies or frameworks for a concrete purpose
- Analysing the advantages and disadvantages of different tools or libraries
- Evaluating alternative ways of solving a problem
- Documenting a decision process with several options examined
## When NOT to use
- For documenting a single entity or concept (use `entity` or `concept` respectively)
- For summaries of source material (use `source`)
- For pages covering a single subject rather than comparing several
## Authoring guidance
- A title that names the comparison (e.g. "Go vs Rust", "Kubernetes vs Docker Swarm"); it follows the established names of the subjects compared, not the KB language (`kb/CONTRACT.md` § "Titles are identifiers", `kb/CONVENTIONS.md` §§ "Naming" and "Language")
- State clearly what is being compared and why
- Use a comparison table with the criteria as rows
- An analysis that evaluates the table rather than repeating it
- A recommendation wherever one can be justified
---
Relevant Notes:
- Comparison pages support technology selection and architecture decisions
- The entities compared should all exist as wiki pages
- The criteria should fit the decision at hand
- A comparison stays focused on one concrete purpose or decision context
+5 -31
View File
@@ -5,24 +5,15 @@ description: Structured type for comparison pages that set several entities or a
schema: types/comparison.schema.yaml schema: types/comparison.schema.yaml
base_dir: comparisons base_dir: comparisons
page_ref_fields: [entities, related] page_ref_fields: [entities, related]
guidance: types/comparison.guidance.md
--- ---
# Comparison # Comparison
`comparison` is the type for pages that set several entities, tools, technologies or approaches systematically against one another along fixed criteria. Comparison pages support decisions and make the trade-offs between alternatives visible. This instance's configuration for the `comparison` type: its frontmatter fields as this schema
requires them, and the page skeleton `wikitool new comparison` scaffolds. When to use this type
## When to use and how to write a conforming page is [types/comparison.guidance.md](comparison.guidance.md) -
`tools/wikitool types describe comparison` composes both into one answer.
- Comparing several technologies or frameworks for a concrete purpose
- Analysing the advantages and disadvantages of different tools or libraries
- Evaluating alternative ways of solving a problem
- Documenting a decision process with several options examined
## When NOT to use
- For documenting a single entity or concept (use `entity` or `concept` respectively)
- For summaries of source material (use `source`)
- For pages covering a single subject rather than comparing several
## Frontmatter ## Frontmatter
@@ -35,14 +26,6 @@ page_ref_fields: [entities, related]
| `related` | No | Declared outbound edges - one `compares-with` edge per subject, written by `wikitool xref add` | | `related` | No | Declared outbound edges - one `compares-with` edge per subject, written by `wikitool xref add` |
| `summary` | Yes | One-liner for `kb/index.md` | | `summary` | Yes | One-liner for `kb/index.md` |
## Authoring guidance
- A title that names the comparison (e.g. "Go vs Rust", "Kubernetes vs Docker Swarm"); it follows the established names of the subjects compared, not the KB language (`kb/CONTRACT.md` § "Titles are identifiers", `kb/CONVENTIONS.md` §§ "Naming" and "Language")
- State clearly what is being compared and why
- Use a comparison table with the criteria as rows
- An analysis that evaluates the table rather than repeating it
- A recommendation wherever one can be justified
## Template ## Template
The block below is page material, so it is written in this instance's KB language The block below is page material, so it is written in this instance's KB language
@@ -75,12 +58,3 @@ TODO: Falls möglich - was wann und für wen zu verwenden ist. Unter welchen Ums
convention, and the page title itself (`Go vs Rust`) does not carry it. When `wikitool xref` adds convention, and the page title itself (`Go vs Rust`) does not carry it. When `wikitool xref` adds
a relationship, the tool-managed cross-reference section appears; what it is called is the a relationship, the tool-managed cross-reference section appears; what it is called is the
instance's decision in `kb/CONVENTIONS.md` (`sections:`). instance's decision in `kb/CONVENTIONS.md` (`sections:`).
---
Additional notes:
- Comparison pages support technology selection and architecture decisions
- The entities compared should all exist as wiki pages
- The criteria should fit the decision at hand
- A comparison stays focused on one concrete purpose or decision context
+43
View File
@@ -0,0 +1,43 @@
---
type: types/type-guidance.md
name: concept
description: When to write a concept page instead of a neighboring type, and how to write a conforming one - citation, linking and register rules that hold regardless of which concept_type values this instance chose
---
# Concept Guidance
`concept` is the type for abstract ideas, methodologies, patterns and architectural decisions.
Concepts provide the theoretical and methodological ground that entities implement or follow.
## When to use
- Documenting an architectural pattern or design approach
- Explaining a design pattern or a programming methodology
- Describing a communication protocol or standard
- Recording a workflow or process that recurs across projects
- Documenting an architectural decision (ADR) or design decision
- Analysing a recurring problem and its solutions
## When NOT to use
- For concrete things such as projects, systems or tools (use `entity`)
- For source documents or ingested material (use `source`)
- For comparative analyses of concrete subjects (use `comparison`)
## Authoring guidance
- The title is the concept's canonical name and follows the established term of art, not the KB language (`kb/CONTRACT.md` § "Titles are identifiers", `kb/CONVENTIONS.md` §§ "Naming" and "Language")
- Open with a clear definition: what the concept is
- Give examples wherever they carry the understanding
- Link to the entities that implement or use the concept
- Under `provenance: sourced` or `mixed`, back hard facts inline with a `[^cite-id]` footnote -
`tools/wikitool cite add --page "<Title>" --source "Source - X"` mints the id and its definition
- Wikipedia register: factual, neutral, no marketing language
---
Relevant Notes:
- Concept pages carry the wiki's theoretical underpinning
- Each concept type stands for a different category of abstract knowledge
- Concepts should be linked to from the entities that implement or use them
+5 -44
View File
@@ -6,6 +6,7 @@ schema: types/concept.schema.yaml
subtype_field: concept_type subtype_field: concept_type
base_dir: concepts base_dir: concepts
page_ref_fields: [related, sources] page_ref_fields: [related, sources]
guidance: types/concept.guidance.md
layout: layout:
architecture: {dir: architectures, title: Architekturen} architecture: {dir: architectures, title: Architekturen}
pattern: {dir: patterns, title: Muster} pattern: {dir: patterns, title: Muster}
@@ -17,32 +18,10 @@ layout:
# Concept # Concept
`concept` is the type for abstract ideas, methodologies, patterns and architectural decisions. Concepts provide the theoretical and methodological ground that entities implement or follow. This instance's configuration for the `concept` type: its frontmatter fields as this schema
requires them, and the page skeleton `wikitool new concept` scaffolds. When to use this type and
<!-- wikitool:toc --> how to write a conforming page is [types/concept.guidance.md](concept.guidance.md) -
## Contents `tools/wikitool types describe concept` composes both into one answer.
- [When to use](#when-to-use)
- [When NOT to use](#when-not-to-use)
- [Frontmatter](#frontmatter)
- [Authoring guidance](#authoring-guidance)
- [Template](#template)
<!-- /wikitool:toc -->
## When to use
- Documenting an architectural pattern or design approach
- Explaining a design pattern or a programming methodology
- Describing a communication protocol or standard
- Recording a workflow or process that recurs across projects
- Documenting an architectural decision (ADR) or design decision
- Analysing a recurring problem and its solutions
## When NOT to use
- For concrete things such as projects, systems or tools (use `entity`)
- For source documents or ingested material (use `source`)
- For comparative analyses of concrete subjects (use `comparison`)
## Frontmatter ## Frontmatter
@@ -58,16 +37,6 @@ layout:
| `provenance` | Yes | sourced, general or mixed | | `provenance` | Yes | sourced, general or mixed |
| `summary` | Yes | One-liner for `kb/index.md` | | `summary` | Yes | One-liner for `kb/index.md` |
## Authoring guidance
- The title is the concept's canonical name and follows the established term of art, not the KB language (`kb/CONTRACT.md` § "Titles are identifiers", `kb/CONVENTIONS.md` §§ "Naming" and "Language")
- Open with a clear definition: what the concept is
- Give examples wherever they carry the understanding
- Link to the entities that implement or use the concept
- Under `provenance: sourced` or `mixed`, back hard facts inline with a `[^cite-id]` footnote -
`tools/wikitool cite add --page "<Title>" --source "Source - X"` mints the id and its definition
- Wikipedia register: factual, neutral, no marketing language
## Template ## Template
The block below is page material, so it is written in this instance's KB language The block below is page material, so it is written in this instance's KB language
@@ -112,11 +81,3 @@ The value behind `**Typ:**` stays the English enum value - that is what `search
on. When `wikitool xref` adds a relationship, the two tool-managed sections for relationships and on. When `wikitool xref` adds a relationship, the two tool-managed sections for relationships and
cross-references appear as well; what they are called is the instance's decision in cross-references appear as well; what they are called is the instance's decision in
`kb/CONVENTIONS.md` (`sections:`). `kb/CONVENTIONS.md` (`sections:`).
---
Additional notes:
- Concept pages carry the wiki's theoretical underpinning
- Each concept type stands for a different category of abstract knowledge
- Concepts should be linked to from the entities that implement or use them
+41
View File
@@ -0,0 +1,41 @@
---
type: types/type-guidance.md
name: entity
description: When to write an entity page instead of a neighboring type, and how to write a conforming one - citation, linking and register rules that hold regardless of which entity_type values this instance chose
---
# Entity Guidance
`entity` is the type for concrete things: projects, systems, tools, technologies or people.
Entities are the primary building blocks of the knowledge graph.
## When to use
- Representing a software project, an initiative or a piece of work
- Documenting a running system, service or infrastructure component
- Describing a CLI tool, utility or software library
- Recording information about a language, a framework or a protocol
- Documenting a person, a team or an organization
## When NOT to use
- For abstract concepts, patterns or methodologies (use `concept`)
- For source documents or ingested material (use `source`)
- For comparative analyses (use `comparison`)
## Authoring guidance
- The title is the entity's canonical name and follows the subject's established designation, not the KB language (`kb/CONTRACT.md` § "Titles are identifiers", `kb/CONVENTIONS.md` §§ "Naming" and "Language")
- The main description goes near the top
- Link to related entities and concepts wherever relationships exist
- Under `provenance: sourced` or `mixed`, back hard facts inline with a `[^cite-id]` footnote -
`tools/wikitool cite add --page "<Title>" --source "Source - X"` mints the id and its definition
- Wikipedia register: factual, neutral, no marketing language
---
Relevant Notes:
- Entity pages form the concrete inventory of the knowledge base
- Every entity type (project, system, tool and so on) is a variant of this base type
- The relationships between entities carry the knowledge graph's interconnection
+5 -42
View File
@@ -6,6 +6,7 @@ schema: types/entity.schema.yaml
subtype_field: entity_type subtype_field: entity_type
base_dir: entities base_dir: entities
page_ref_fields: [related, sources] page_ref_fields: [related, sources]
guidance: types/entity.guidance.md
layout: layout:
project: {dir: projects, title: Projekte} project: {dir: projects, title: Projekte}
system: {dir: systems, title: Systeme} system: {dir: systems, title: Systeme}
@@ -16,31 +17,10 @@ layout:
# Entity # Entity
`entity` is the type for concrete things: projects, systems, tools, technologies or people. Entities are the primary building blocks of the knowledge graph. This instance's configuration for the `entity` type: its frontmatter fields as this schema
requires them, and the page skeleton `wikitool new entity` scaffolds. When to use this type and
<!-- wikitool:toc --> how to write a conforming page is [types/entity.guidance.md](entity.guidance.md) -
## Contents `tools/wikitool types describe entity` composes both into one answer.
- [When to use](#when-to-use)
- [When NOT to use](#when-not-to-use)
- [Frontmatter](#frontmatter)
- [Authoring guidance](#authoring-guidance)
- [Template](#template)
<!-- /wikitool:toc -->
## When to use
- Representing a software project, an initiative or a piece of work
- Documenting a running system, service or infrastructure component
- Describing a CLI tool, utility or software library
- Recording information about a language, a framework or a protocol
- Documenting a person, a team or an organization
## When NOT to use
- For abstract concepts, patterns or methodologies (use `concept`)
- For source documents or ingested material (use `source`)
- For comparative analyses (use `comparison`)
## Frontmatter ## Frontmatter
@@ -56,15 +36,6 @@ layout:
| `provenance` | Yes | sourced, general or mixed | | `provenance` | Yes | sourced, general or mixed |
| `summary` | Yes | One-liner for `kb/index.md` | | `summary` | Yes | One-liner for `kb/index.md` |
## Authoring guidance
- The title is the entity's canonical name and follows the subject's established designation, not the KB language (`kb/CONTRACT.md` § "Titles are identifiers", `kb/CONVENTIONS.md` §§ "Naming" and "Language")
- The main description goes near the top
- Link to related entities and concepts wherever relationships exist
- Under `provenance: sourced` or `mixed`, back hard facts inline with a `[^cite-id]` footnote -
`tools/wikitool cite add --page "<Title>" --source "Source - X"` mints the id and its definition
- Wikipedia register: factual, neutral, no marketing language
## Template ## Template
The block below is page material, so it is written in this instance's KB language The block below is page material, so it is written in this instance's KB language
@@ -102,11 +73,3 @@ The relationships section is deliberately **not** in the template: it is a gener
`wikitool xref` creates between markers on the first edge entry and re-renders from `related:`. `wikitool xref` creates between markers on the first edge entry and re-renders from `related:`.
An author never writes into it. The value behind `**Typ:**` stays the English enum value - that is An author never writes into it. The value behind `**Typ:**` stays the English enum value - that is
what `search --field` filters on. what `search --field` filters on.
---
Additional notes:
- Entity pages form the concrete inventory of the knowledge base
- Every entity type (project, system, tool and so on) is a variant of this base type
- The relationships between entities carry the knowledge graph's interconnection
+63
View File
@@ -0,0 +1,63 @@
---
type: types/type-guidance.md
name: source
description: When to write a source page instead of a neighboring type, and how to write a conforming one - capture-field, provenance and not-extracted rules that hold regardless of which source_type values this instance chose
---
# Source Guidance
`source` is the type for pages that summarize and catalogue ingested raw material. Source pages
are the bridge between the `raw/` layer (immutable source files) and the `kb/` layer (compiled
knowledge). One source page stands for **one logical source**, which may span several raw files.
## When to use
- Summarizing a single external article, document or specification
- Recording several related notes or meeting records as one source
- Documenting an ingested PDF, manual or other document
- Capturing information about an image or a diagram
## When NOT to use
- For compiled knowledge (use `entity` or `concept`)
- For comparative analyses (use `comparison`)
- For original wiki content not derived from raw material
## Authoring guidance
- `source_type` has **no default** - `wikitool new source` refuses without an explicit value. Where the category is unclear, set `unclassified` rather than guessing; that is a visible catalog slot with an advisory `lint` finding, not a dumping ground. What separates `analysis` from `document`, and which area holds which value, is a call this instance's own collection contract makes - see `kb/sources/COLLECTION.md`
- `raw_files` lists every raw file this source covers (one source page per logical source, not per file)
- `fidelity` and `authority` are **capture fields** (`capture_fields:` on this type): recorded at the drop point and not freely changeable afterwards. `wikitool raw accept --fidelity <value> --authority <value>` refuses without both; without `--page` it instead prints the ready-made `new source --set fidelity=... --set authority=...` line, which `new source` in turn refuses without both values. `wikitool touch --set fidelity=<value>` only writes while the field is absent - where a value already stands, it refuses and points at `raw accept --replaces` as the one correction path (a corrected capture is a new edition, not an edit). `unknown` is backfill-only: only `wikitool touch` may write it, never `raw accept` or `new source` - the same construction as `source_language` uses for pages that predate this rule. What the values mean and how they differ: [raw/CONTRACT.md](../raw/CONTRACT.md#getting-a-file-in-incoming)
- For external articles, always set `source_url` to the origin URL
- Set `source_language` to the raw material's language, not the page's
- The page is written in the KB language, whatever language the source is in; verbatim passages are quoted in the original (`kb/CONVENTIONS.md` § "Language")
- Summarize the key claims in the summary section
- Put anything actionable in the action items section
- Put deliberate omissions in the not-extracted section - see below
- Link the entities and concepts mentioned under related entities/concepts
## Not Extracted
The decision that material should *not* be taken over cannot be reconstructed: nothing in the
repository can re-derive it, and `sources coverage` only knows whether a raw file is claimed by
some source page - never whether anyone decided about its contents. Left unwritten, the same
source is renegotiated on every later pass.
- Record every deliberate omission with a **reason**, not just a filename.
- Mandatory where the ingest ran through `instructions/ingest-large-tree.md` - on both axes: for
a tree ingest the section records what was not taken from the tree; for a thematically broad
single source, which named subjects got no page of their own, and why. Optional for a single
small file - but an empty section still beats a missing one.
- Belongs on the source page, not in `kb/log.md`: it is a statement about *this* source, and the
log is chronological rather than per-source.
---
Relevant Notes:
- Source pages are the authoritative catalogue of what raw material has been ingested
- **One raw file, one owner.** A raw file appears in exactly one `raw_files:` - that page is responsible for keeping it summarized. Any number of pages may **cite** it via `[^cite-id]`; a citation is reuse, `raw_files:` is a maintenance responsibility. With two claimants it is undefined which page has to be brought up to date when the raw file changes - and then both rot quietly
- Source pages make knowledge traceable back to the original raw material
- `raw_files:` holds concrete existing file paths, never directories
- A `raw_files:` list beyond roughly 15 entries indicates the cut was too coarse - the source should have been split into several source pages via `instructions/ingest-large-tree.md`
- `entities:` plus `concepts:` beyond roughly 20 entries is the counterpart on the other axis: not cut too little, but compiled too much at once. Such a source is not split - a raw file has one owner - it needed the extract pass from `instructions/ingest-large-tree.md` § "A broad source is not cut", so that not every named subject gets a page
+5 -61
View File
@@ -8,6 +8,7 @@ base_dir: sources
title_prefix: "Source - " title_prefix: "Source - "
page_ref_fields: [entities, concepts] page_ref_fields: [entities, concepts]
capture_fields: [fidelity, authority] capture_fields: [fidelity, authority]
guidance: types/source.guidance.md
layout: layout:
transcript: {dir: transcripts, title: Transkripte} transcript: {dir: transcripts, title: Transkripte}
analysis: {dir: analyses, title: Analysen} analysis: {dir: analyses, title: Analysen}
@@ -20,31 +21,10 @@ layout:
# Source # Source
`source` is the type for pages that summarize and catalogue ingested raw material. Source pages are the bridge between the `raw/` layer (immutable source files) and the `kb/` layer (compiled knowledge). One source page stands for **one logical source**, which may span several raw files. This instance's configuration for the `source` type: its frontmatter fields as this schema
requires them, and the page skeleton `wikitool new source` scaffolds. When to use this type and
<!-- wikitool:toc --> how to write a conforming page is [types/source.guidance.md](source.guidance.md) -
## Contents `tools/wikitool types describe source` composes both into one answer.
- [When to use](#when-to-use)
- [When NOT to use](#when-not-to-use)
- [Frontmatter](#frontmatter)
- [Authoring guidance](#authoring-guidance)
- [Not Extracted](#not-extracted)
- [Template](#template)
<!-- /wikitool:toc -->
## When to use
- Summarizing a single external article, document or specification
- Recording several related notes or meeting records as one source
- Documenting an ingested PDF, manual or other document
- Capturing information about an image or a diagram
## When NOT to use
- For compiled knowledge (use `entity` or `concept`)
- For comparative analyses (use `comparison`)
- For original wiki content not derived from raw material
## Frontmatter ## Frontmatter
@@ -67,31 +47,6 @@ layout:
## Authoring guidance ## Authoring guidance
- The title starts with "Source - ", followed by the name of the source - The title starts with "Source - ", followed by the name of the source
- `source_type` has **no default** - `wikitool new source` refuses without an explicit value. Where the category is unclear, set `unclassified` rather than guessing; that is a visible catalog slot with an advisory `lint` finding, not a dumping ground. What separates `analysis` from `document`, and which area holds which value: [kb/sources/COLLECTION.md](../kb/sources/COLLECTION.md)
- `raw_files` lists every raw file this source covers (one source page per logical source, not per file)
- `fidelity` and `authority` are **capture fields** (`capture_fields:` above): recorded at the drop point and not freely changeable afterwards. `wikitool raw accept --fidelity <value> --authority <value>` refuses without both; without `--page` it instead prints the ready-made `new source --set fidelity=... --set authority=...` line, which `new source` in turn refuses without both values. `wikitool touch --set fidelity=<value>` only writes while the field is absent - where a value already stands, it refuses and points at `raw accept --replaces` as the one correction path (a corrected capture is a new edition, not an edit). `unknown` is backfill-only: only `wikitool touch` may write it, never `raw accept` or `new source` - the same construction as `source_language` uses for pages that predate this rule. What the values mean and how they differ: [raw/CONTRACT.md](../raw/CONTRACT.md#getting-a-file-in-incoming)
- For external articles, always set `source_url` to the origin URL
- Set `source_language` to the raw material's language, not the page's
- The page is written in the KB language, whatever language the source is in; verbatim passages are quoted in the original (`kb/CONVENTIONS.md` § "Language")
- Summarize the key claims in the summary section
- Put anything actionable in the action items section
- Put deliberate omissions in the not-extracted section - see below
- Link the entities and concepts mentioned under related entities/concepts
## Not Extracted
The decision that material should *not* be taken over cannot be reconstructed: nothing in the
repository can re-derive it, and `sources coverage` only knows whether a raw file is claimed by
some source page - never whether anyone decided about its contents. Left unwritten, the same
source is renegotiated on every later pass.
- Record every deliberate omission with a **reason**, not just a filename.
- Mandatory where the ingest ran through `instructions/ingest-large-tree.md` - on both axes: for
a tree ingest the section records what was not taken from the tree; for a thematically broad
single source, which named subjects got no page of their own, and why. Optional for a single
small file - but an empty section still beats a missing one.
- Belongs on the source page, not in `kb/log.md`: it is a statement about *this* source, and the
log is chronological rather than per-source.
## Template ## Template
@@ -139,14 +94,3 @@ TODO: 2-3 Absätze zu den Kernaussagen des Quellmaterials.
linked and cited under. The value behind `**Typ:**` stays the English enum value. When linked and cited under. The value behind `**Typ:**` stays the English enum value. When
`wikitool cite` adds a citation, the tool-managed footnote block appears at the end of the page; `wikitool cite` adds a citation, the tool-managed footnote block appears at the end of the page;
what it is called is the instance's decision in `kb/CONVENTIONS.md` (`sections:`). what it is called is the instance's decision in `kb/CONVENTIONS.md` (`sections:`).
---
Additional notes:
- Source pages are the authoritative catalogue of what raw material has been ingested
- **One raw file, one owner.** A raw file appears in exactly one `raw_files:` - that page is responsible for keeping it summarized. Any number of pages may **cite** it via `[^cite-id]`; a citation is reuse, `raw_files:` is a maintenance responsibility. With two claimants it is undefined which page has to be brought up to date when the raw file changes - and then both rot quietly
- Source pages make knowledge traceable back to the original raw material
- `raw_files:` holds concrete existing file paths, never directories
- A `raw_files:` list beyond roughly 15 entries indicates the cut was too coarse - the source should have been split into several source pages via `instructions/ingest-large-tree.md`
- `entities:` plus `concepts:` beyond roughly 20 entries is the counterpart on the other axis: not cut too little, but compiled too much at once. Such a source is not split - a raw file has one owner - it needed the extract pass from `instructions/ingest-large-tree.md` § "A broad source is not cut", so that not every named subject gets a page
+65
View File
@@ -0,0 +1,65 @@
---
type: types/type-spec.md
name: type-guidance
description: "Stack-owned authoring prose for a root:kb page type - the machine-independent half a type-spec's optional guidance: field links to"
schema: types/type-guidance.schema.yaml
---
# Type Guidance
`type-guidance` describes the shape of one `types/<name>.guidance.md` file: the stack-owned half
of a `root: kb` page type's contract. A page type-spec ([types/type-spec.md](type-spec.md) §
"Who owns a type-spec") splits at the audience line - what an agent needs to know to write a
conforming page, versus what this instance's schema and template concretely are - and a guidance
file is the machine-independent half of that split, linked from the type-spec it documents via
that file's own `guidance:` frontmatter field.
## When to Use
- Writing or improving the generic authoring rules for a `root: kb` type: when to choose it, when
not to, and mechanism-level advice that holds regardless of which enum values, directory names
or template text this instance chose - citation mechanics, provenance rules, what a conforming
page does or does not claim
- Anything a later stack release would want every adopting instance to receive automatically,
the way a fix to `tools/` already does
## When NOT to Use
- Enum values, `layout:` titles, directory names or template text. Those name *this* instance's
choices and stay in the type-spec itself - the file `guidance:` points *from*, never the one it
points *to*
- A type an instance writes entirely for itself. `guidance:` is optional; a type-spec that omits
it is described from its own body alone, exactly as every type-spec worked before this type
existed
## Frontmatter
| Field | Required | Use |
|---|---:|---|
| `type` | Yes | `types/type-guidance.md` |
| `name` | Yes | The page type this documents, e.g. `entity` - matches that type-spec's own `name:` |
| `description` | Yes | Retrieval description for this guidance document |
## Conventions
- Linked from the type-spec it documents via that file's `guidance:` frontmatter field - a
repo-relative path, resolved the same way `schema:` is
- `tools/wikitool types describe <name>` composes both halves into one answer; an agent asking
for a type's contract never needs to know it comes from two files
- Written in the control plane's English ([AGENTS.md](../AGENTS.md) § File naming), like a
type-spec's own authoring prose - only the type-spec's `## Template` block and its `layout:`
titles are page material
---
Relevant Notes:
- Ships verbatim with every distribution; the type-spec it documents ships only as a `.template`
where `root: kb` - see `docs/ownership-and-templates.md` § "Where the file boundary used to
strain"
- No `## Template` section here: a guidance file's body is prose an author reads, not a body
structure `wikitool new` would scaffold. `type-guidance` declares no `base_dir:` for the same
reason `lint-report` does, and `wikitool new type-guidance` refuses for the same reason
- An instance that adopted its type-specs before this type existed sees nothing change until it
takes the offered migration that adds `guidance:` to each one - see
`instructions/migrations/` for the document
+23
View File
@@ -0,0 +1,23 @@
# YAML Schema for type-guidance documents
type: object
properties:
type:
type: string
const: "types/type-guidance.md"
description: Must reference the type-guidance type-spec
name:
type: string
description: >-
The page type this guidance documents, e.g. 'entity' - matches that
type-spec's own `name:` field
minLength: 1
pattern: "^[a-z][a-z0-9-]*$"
description:
type: string
description: Retrieval description for this guidance document
minLength: 10
required:
- type
- name
- description
additionalProperties: false
+37 -18
View File
@@ -33,6 +33,7 @@ A type-spec doc is the authoring and validation contract for one Chemenu artifac
- `description`: retrieval description for the type contract. - `description`: retrieval description for the type contract.
- `schema`: repo-relative path to the `.schema.yaml` file that validates artifacts of this type, or `null` when the type has no schema. - `schema`: repo-relative path to the `.schema.yaml` file that validates artifacts of this type, or `null` when the type has no schema.
- `subtype_field` (optional): the frontmatter field name on instances of this type that carries a subtype/category (e.g. `entity_type`), or omitted if this type has no such field. - `subtype_field` (optional): the frontmatter field name on instances of this type that carries a subtype/category (e.g. `entity_type`), or omitted if this type has no such field.
- `guidance` (optional): repo-relative path to a `types/type-guidance.md`-shaped document holding this type's stack-owned authoring prose - see § "Anatomy of a type" below. Meaningful only on a `root: kb` type-spec; omit for a type an instance writes entirely for itself.
## Validation Contract ## Validation Contract
@@ -65,28 +66,41 @@ frontmatter before anyone drew it:
| Type-spec | Describes | Owned by | Ships as | | Type-spec | Describes | Owned by | Ships as |
|---|---|---|---| |---|---|---|---|
| `root: kb` (`entity`, `concept`, `source`, `comparison`) | A page **this instance** writes | The instance | `types/<name>.md.template` plus its `.schema.yaml.template`, adopted by a rename | | `root: kb` (`entity`, `concept`, `source`, `comparison`) | A page **this instance** writes | The instance | `types/<name>.md.template` plus its `.schema.yaml.template`, adopted by a rename |
| `root: repo` (`instruction`), no `base_dir` (`lint-report`), and `type-spec` itself | A stack artifact | The stack | Verbatim | | `root: repo` (`instruction`), no `base_dir` (`lint-report`), and `type-spec`/`type-guidance` themselves | A stack artifact | The stack | Verbatim |
A page type-spec's prose and its `## Template` body are therefore the instance's to rewrite, and A page type-spec's frontmatter configuration and its `## Template` body are therefore the
an upgrade does not take that back. Improvements to a shipped default reach it as an *offered* instance's to rewrite, and an upgrade does not take that back. Its generic authoring prose is the
migration ([instructions/CONTRACT.md](../instructions/CONTRACT.md#instructionsmigrations)), opposite: where the type-spec declares `guidance:`, that prose lives in a separate, stack-owned
never by overwriting. `types/<name>.guidance.md` (§ "Anatomy of a type" below) that ships verbatim and is overwritten
by an upgrade like any other machinery file - the type-spec it documents does not have to be
re-adopted, or even touched, for that improvement to arrive. A type-spec that declares no
`guidance:` (a type an instance writes entirely for itself) has no stack half to receive; it is
described from its own body alone, exactly as every type-spec worked before this split existed.
An *existing* instance that adopted its type-specs before `guidance:` existed takes this as an
*offered* migration ([instructions/CONTRACT.md](../instructions/CONTRACT.md#instructionsmigrations))
rather than something an upgrade applies on its own - see `instructions/migrations/` for the
document.
**Ownership and language are not the same question here**, and this is the one file in the tree **Ownership and language still do not line up with each other**, and that stays true even though
where they come apart. Owning the file means the instance may rewrite any of it; the language it the authoring prose now has its own file rather than sharing one with the template. Owning the
writes in still follows what the line is *for*, per type-spec means the instance may rewrite its frontmatter and its `## Template` body; a guidance
[AGENTS.md § File naming](../AGENTS.md#file-naming): file it links is never the instance's to rewrite at all - it is the stack's, the same way
`tools/CONTRACT.md` is. What each part is *written in* still follows what the line is *for*, per
[AGENTS.md § File naming](../AGENTS.md#file-naming), independent of which file it lives in or who
owns that file:
| Part of a page type-spec | Audience | Language | | Part of a `root: kb` type's contract | Lives in | Audience | Language |
|---|---|---| |---|---|---|---|
| Authoring guidance, frontmatter table, when to use / not to use | An agent writing a page | The control plane's — English | | When to use / not to use, authoring guidance | The `guidance:` file, where declared | An agent writing a page | The control plane's — English |
| The `## Template` body, and the `layout:` titles that head a catalog section | The page itself | The instance's KB language (`kb/CONVENTIONS.md` `language:`) | | The frontmatter table documenting this instance's own fields | The type-spec itself | An agent writing a page | The control plane's — English |
| Field names, enum values, `dir:` values, `type:` paths | The machine | Neither — identifiers, never translated | | The `## Template` body, and the `layout:` titles that head a catalog section | The type-spec itself | The page itself | The instance's KB language (`kb/CONVENTIONS.md` `language:`) |
| Field names, enum values, `dir:` values, `type:` paths | Either file's frontmatter | The machine | Neither — identifiers, never translated |
That is the same prose/identifier cut [kb/CONTRACT.md](../kb/CONTRACT.md#language-and-identifiers) That is the same prose/identifier cut [kb/CONTRACT.md](../kb/CONTRACT.md#language-and-identifiers)
makes *inside* a page, applied one level up. It binds a type-spec an instance writes for itself makes *inside* a page, applied one level up. It binds a type-spec an instance writes for itself
exactly as it binds the four shipped ones: a new page type is instance-owned end to end, which exactly as it binds the four shipped ones: a new page type is instance-owned end to end, which
settles who may change it, not which language each half is written in. settles who may change it, not which language each half is written in - and it may declare its
own `guidance:` file if it wants the same shape, though nothing requires it to.
**What the stack still requires of the type layer is one line.** There must be a type-spec **What the stack still requires of the type layer is one line.** There must be a type-spec
declaring `name: source` whose schema requires `raw_files:` — the whole `raw/``kb/` declaring `name: source` whose schema requires `raw_files:` — the whole `raw/``kb/`
@@ -102,14 +116,19 @@ Python. Adding a type must require no code change.
### Anatomy of a type ### Anatomy of a type
Each type is two files: Each type is at least two files, and a `root: kb` type may be three:
| File | Owns | | File | Owns |
|------|------| |------|------|
| `types/<name>.md` | The authoring contract: when to use the type, when not to, guidance, and the `## Template` block used to scaffold new pages | | `types/<name>.md` | This instance's configuration: its frontmatter fields as this schema requires them, and the `## Template` block used to scaffold new pages. For a type with no `guidance:` (below), its own prose also carries the authoring contract - when to use the type, when not to |
| `types/<name>.schema.yaml` | The machine-checkable half: fields, types, enums, defaults, required-ness, `additionalProperties: false` | | `types/<name>.schema.yaml` | The machine-checkable half: fields, types, enums, defaults, required-ness, `additionalProperties: false` |
| `types/<name>.guidance.md` (optional, `root: kb` only) | The stack-owned authoring contract: when to use the type, when not to, and mechanism-level advice that holds regardless of this instance's own enum values or template text - linked from the type-spec's own `guidance:` field. `types/type-guidance.md` is its contract |
This file is the self-referential root contract that both are validated against. This file is the self-referential root contract every type-spec is validated against, and
`type-guidance.md` is validated against it the same way `lint-report.md` is - itself a
non-instantiable, contract-only type. `tools/wikitool types describe <name>` composes all of a
type's files into one answer regardless of how many there are; an agent asking for a type's
contract never needs to know it came from more than one file.
### Placement frontmatter ### Placement frontmatter