diff --git a/AGENTS.md b/AGENTS.md index 0bb1f45..2b5dd3b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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/.md` | Agents | By link, or on explicit request | | `instructions//SKILL.md` | Agents | By the harness, once published | | `types/.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/.guidance.md` | Agents + validator | Via `tools/wikitool types describe`, composed with the `types/.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/.md` | Agents and humans | By link, or on explicit request - never automatically, and never as instruction | | `INDEX.md` | Both | Generated - never hand-edited | diff --git a/CHANGES.md b/CHANGES.md index 87ef603..babff48 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 @@ -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 - 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 +- types/: Seiten-Type-Spec-Anleitungsprosa in stackeigene guidance-Datei ausgelagert **Medium impact** - 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. 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/.guidance.md`, +verknuepft ueber ein optionales `guidance:`-Frontmatterfeld (neuer, nicht instanziierbarer Typ +`type-guidance`, wie `lint-report` ohne `base_dir:`). `tools/wikitool types describe ` +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/.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 (`.md` oder +`.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/.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/.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 **Author:** Torben Nehmer diff --git a/VERSION b/VERSION index 7e3b0e0..9ef549a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.0.0-beta.5 +6.0.0-beta.6 diff --git a/docs/ownership-and-templates.md b/docs/ownership-and-templates.md index dcff917..1c4257f 100644 --- a/docs/ownership-and-templates.md +++ b/docs/ownership-and-templates.md @@ -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 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) -- [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) @@ -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 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 -worth naming rather than discovering: a `root: kb` type-spec has two audiences inside one file. +"The file itself already answers that" held for every file above except one shape: a `root: kb` +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 - -is instruction to an agent. It reads like the stack's own prose because it *is* the stack's own -prose: a later release that learns something about writing entity pages would want to improve it -everywhere. Its `## Template` block and its `layout:` titles are the opposite: they become the +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 learned something about writing entity pages would want to improve it +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 -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: -ownership decides who may change a line, its reader decides what language it is in - which is why -an instance's own type-spec keeps English guidance around a template block in its own language. -[language-boundaries.md](language-boundaries.md) has that argument; the rest of this page is about -ownership alone. +The same file is where the language question comes apart from the ownership one, and for the same +reason: ownership decides who may change a line, its reader decides what language it is in - +which is why a `root: kb` type-spec still keeps English prose around a template block written in +its own language. [language-boundaries.md](language-boundaries.md) has that argument; this page is +about ownership alone. -Ownership is per file, so the instance keeps both halves. The template half is correct that way. -The guidance half pays for it: an instance that adopted its type-specs at setup will never -receive an improvement to them again, because `dist upgrade` writes the `.template` beside the -adopted file and never the file itself. Nothing breaks, and nothing reports it - the instance -simply keeps reading the guidance it was handed the day it was created. +Ownership is per file, so a file carrying both audiences had to give both halves to whoever owned +it. The template half was correct that way. The guidance half paid for it: an instance that +adopted its type-specs at setup never received an improvement to the guidance again, because +`dist upgrade` wrote the `.template` beside the adopted file and never the file itself. Nothing +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 -safe at all, and merging inside a shared file is the failure the whole section above is about. -It is an argument that this particular file is cut in the wrong place. Splitting it - the -template block into its own instance-owned file, the guidance staying stack-owned and shipping -verbatim - would put the boundary back where the audiences actually divide, and would cost a -migration for every instance that already adopted one. +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. It +was an argument that this particular file was cut in the wrong place - so it was cut again. A +`root: kb` type-spec may now declare `guidance:`, a repo-relative path to a second, +stack-owned file (`types/.guidance.md`) holding exactly the half that used to be stranded: +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 ` 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 @@ -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: - **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/.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 `kb//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 diff --git a/instructions/migrations/6.0.0-type-guidance-split.md b/instructions/migrations/6.0.0-type-guidance-split.md new file mode 100644 index 0000000..4f9f537 --- /dev/null +++ b/instructions/migrations/6.0.0-type-guidance-split.md @@ -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/.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. + + +## 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) + + +## 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/.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/.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/.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 + ``` + + 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/.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. diff --git a/instructions/setup-instance.md b/instructions/setup-instance.md index 78ffc83..98be40a 100644 --- a/instructions/setup-instance.md +++ b/instructions/setup-instance.md @@ -89,9 +89,16 @@ and ready for its first ingest. Under `types/` this covers exactly the type-specs with `root: kb` - `entity`, `concept`, `source`, `comparison` - along with their `.schema.yaml`. They describe pages *this* - instance writes, so they belong to it: prose, template and language may all be rewritten. - `instruction`, `lint-report` and `type-spec` describe stack artifacts and arrive - unchanged. + instance writes, so they belong to it: frontmatter, template and language may all be + rewritten. `instruction`, `lint-report`, `type-spec` and `type-guidance` describe stack + 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/.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**; [kb-profiles.md](kb-profiles.md) additionally holds a complete German profile, whose full diff --git a/tools/CONTRACT.md b/tools/CONTRACT.md index 0ee4c29..7589fb7 100644 --- a/tools/CONTRACT.md +++ b/tools/CONTRACT.md @@ -166,7 +166,7 @@ tools/wikitool --help | Command | Purpose | |---------|---------| | `types list [--json]` | List every type-spec under `types/` (name, schema path, subtype field, description) - discover what page types exist without reading `types/*.md` directly | -| `types describe [--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 [--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/.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//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 `` 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 | diff --git a/tools/chemenu/commands/dist_cmd.py b/tools/chemenu/commands/dist_cmd.py index f8f35f6..ee5f77f 100644 --- a/tools/chemenu/commands/dist_cmd.py +++ b/tools/chemenu/commands/dist_cmd.py @@ -338,6 +338,32 @@ def instance_owned_type_stems() -> set[str]: return stems +# The suffix a type-spec's own two files carry - `.md` and +# `.schema.yaml` - as opposed to a sibling file that merely starts with +# the same stem, such as `.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 `.md` or + `.schema.yaml` - `None` for anything else under `types/`, + including a `.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]: """`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 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. + + A type-spec's optional `.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()) stems = instance_owned_type_stems() @@ -355,9 +386,8 @@ def _plan_types() -> dict[str, PlannedFile]: rekeyed: dict[str, PlannedFile] = {} for relative, planned in plan.items(): - name = relative.rsplit("/", 1)[-1] - stem = name.split(".", 1)[0] - if stem in stems: + stem = _owned_type_stem(relative) + if stem is not None and stem in stems: rekeyed[f"{relative}.template"] = planned else: rekeyed[relative] = planned @@ -512,7 +542,8 @@ def find_leaks(plan: dict[str, PlannedFile]) -> list[str]: elif ( relative.startswith("types/") 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)") elif relative.startswith("instructions/dev/"): diff --git a/tools/chemenu/commands/types_cmd.py b/tools/chemenu/commands/types_cmd.py index 1d5f4e1..20f7a7d 100644 --- a/tools/chemenu/commands/types_cmd.py +++ b/tools/chemenu/commands/types_cmd.py @@ -55,7 +55,10 @@ def describe_type_command( """Print one type's full contract: frontmatter fields (required/optional, with enums where declared), its subtype field if any, and its authoring 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: described = describe_type(name) except UnknownType as exc: @@ -89,9 +92,13 @@ def describe_type_command( typer.echo("") typer.echo("## Authoring guidance") - # A type-spec over 100 lines carries a generated table-of-contents region - # (`chemenu/toc.py`), which serves whoever opens the file. Here it would be - # noise: this command already hands over the whole body, so there is - # nothing left for a navigation aid to navigate - only markers and a list - # of headings the reader is about to see anyway. + # A type-spec (and its guidance file) over 100 lines carries a generated + # table-of-contents region (`chemenu/toc.py`), which serves whoever opens + # the file directly. Here it would be noise: this command already hands + # over the whole body, so there is nothing left for a navigation aid to + # navigate - only markers and a list of headings the reader is about to + # see anyway. + if described["guidance"]: + typer.echo(toc.strip_region(described["guidance"])) + typer.echo("") typer.echo(toc.strip_region(described["body"])) diff --git a/tools/chemenu/tests/test_dist_cmd.py b/tools/chemenu/tests/test_dist_cmd.py index 1751633..03e3158 100644 --- a/tools/chemenu/tests/test_dist_cmd.py +++ b/tools/chemenu/tests/test_dist_cmd.py @@ -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. (types_dir / "entity.md").write_text( "---\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", ) (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 +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): """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.""" diff --git a/tools/chemenu/tests/test_dist_upgrade.py b/tools/chemenu/tests/test_dist_upgrade.py index 2c93bb9..c10ab06 100644 --- a/tools/chemenu/tests/test_dist_upgrade.py +++ b/tools/chemenu/tests/test_dist_upgrade.py @@ -223,6 +223,56 @@ def test_seeded_once_paths_are_never_written_even_if_the_release_stamp_lists_the assert not (instance / preserved).exists() +# --- a root:kb type-spec's guidance half upgrades like any other file ------- +# +# Gitea #104: before the split, `types/.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 ----------------------------------- diff --git a/tools/chemenu/tests/test_type_resolver.py b/tools/chemenu/tests/test_type_resolver.py index 0819ad4..f522a8c 100644 --- a/tools/chemenu/tests/test_type_resolver.py +++ b/tools/chemenu/tests/test_type_resolver.py @@ -64,6 +64,54 @@ def test_get_page_ref_fields_defaults_to_empty(): 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 `.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/.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(): """`fidelity`/`authority` are fixed once, at capture time (Gitea #67) - `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()} assert names == { "type-spec", "entity", "concept", "source", "comparison", "lint-report", "instruction", + "type-guidance", } diff --git a/tools/chemenu/tests/test_types_cmd.py b/tools/chemenu/tests/test_types_cmd.py index 699e845..eb0ac2e 100644 --- a/tools/chemenu/tests/test_types_cmd.py +++ b/tools/chemenu/tests/test_types_cmd.py @@ -16,6 +16,7 @@ def test_types_list_finds_all_current_type_specs(): names = {row["name"] for row in rows} assert names == { "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"] +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(): result = runner.invoke(app, ["types", "describe", "bogus"]) assert result.exit_code != 0 diff --git a/tools/chemenu/type_resolver.py b/tools/chemenu/type_resolver.py index 1b870b5..f90891d 100644 --- a/tools/chemenu/type_resolver.py +++ b/tools/chemenu/type_resolver.py @@ -436,6 +436,33 @@ class TypeResolver: type_spec = self.load_type_spec(type_path, source_file) 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: """Return the frontmatter fields whose entries are wiki page titles (e.g. `['related', 'sources']` for an entity), as declared by the diff --git a/tools/chemenu/types_core.py b/tools/chemenu/types_core.py index 68cf6f4..ad3a0a3 100644 --- a/tools/chemenu/types_core.py +++ b/tools/chemenu/types_core.py @@ -59,6 +59,8 @@ def describe_type(name: str) -> Dict[str, Any]: type_spec = resolver.load_type_spec(type_path) frontmatter = type_spec["frontmatter"] 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]] = [] if schema is not None: @@ -86,5 +88,10 @@ def describe_type(name: str) -> Dict[str, Any]: "base_dir": frontmatter.get("base_dir"), "title_prefix": frontmatter.get("title_prefix"), "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(), + "guidance_path": guidance_path, + "guidance": guidance_body.strip() if guidance_body is not None else None, } diff --git a/types/comparison.guidance.md b/types/comparison.guidance.md new file mode 100644 index 0000000..212cc13 --- /dev/null +++ b/types/comparison.guidance.md @@ -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 diff --git a/types/comparison.md b/types/comparison.md index c80560a..bbe09cf 100644 --- a/types/comparison.md +++ b/types/comparison.md @@ -5,24 +5,15 @@ description: Structured type for comparison pages that set several entities or a schema: types/comparison.schema.yaml base_dir: comparisons page_ref_fields: [entities, related] +guidance: types/comparison.guidance.md --- # 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. - -## 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 +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 +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. ## 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` | | `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 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 a relationship, the tool-managed cross-reference section appears; what it is called is the 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 diff --git a/types/concept.guidance.md b/types/concept.guidance.md new file mode 100644 index 0000000..1647582 --- /dev/null +++ b/types/concept.guidance.md @@ -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 "" --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 diff --git a/types/concept.md b/types/concept.md index 58a6c45..00774c5 100644 --- a/types/concept.md +++ b/types/concept.md @@ -6,6 +6,7 @@ schema: types/concept.schema.yaml subtype_field: concept_type base_dir: concepts page_ref_fields: [related, sources] +guidance: types/concept.guidance.md layout: architecture: {dir: architectures, title: Architekturen} pattern: {dir: patterns, title: Muster} @@ -17,32 +18,10 @@ layout: # 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. - -<!-- wikitool:toc --> -## Contents - -- [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`) +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 +how to write a conforming page is [types/concept.guidance.md](concept.guidance.md) - +`tools/wikitool types describe concept` composes both into one answer. ## Frontmatter @@ -58,16 +37,6 @@ layout: | `provenance` | Yes | sourced, general or mixed | | `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 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 cross-references appear as well; what they are called is the instance's decision in `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 diff --git a/types/entity.guidance.md b/types/entity.guidance.md new file mode 100644 index 0000000..88d539f --- /dev/null +++ b/types/entity.guidance.md @@ -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 diff --git a/types/entity.md b/types/entity.md index c7d17be..74cdaeb 100644 --- a/types/entity.md +++ b/types/entity.md @@ -6,6 +6,7 @@ schema: types/entity.schema.yaml subtype_field: entity_type base_dir: entities page_ref_fields: [related, sources] +guidance: types/entity.guidance.md layout: project: {dir: projects, title: Projekte} system: {dir: systems, title: Systeme} @@ -16,31 +17,10 @@ layout: # Entity -`entity` is the type for concrete things: projects, systems, tools, technologies or people. Entities are the primary building blocks of the knowledge graph. - -<!-- wikitool:toc --> -## Contents - -- [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`) +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 +how to write a conforming page is [types/entity.guidance.md](entity.guidance.md) - +`tools/wikitool types describe entity` composes both into one answer. ## Frontmatter @@ -56,15 +36,6 @@ layout: | `provenance` | Yes | sourced, general or mixed | | `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 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:`. An author never writes into it. The value behind `**Typ:**` stays the English enum value - that is 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 diff --git a/types/source.guidance.md b/types/source.guidance.md new file mode 100644 index 0000000..55604da --- /dev/null +++ b/types/source.guidance.md @@ -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 diff --git a/types/source.md b/types/source.md index 83c3df2..747a20d 100644 --- a/types/source.md +++ b/types/source.md @@ -8,6 +8,7 @@ base_dir: sources title_prefix: "Source - " page_ref_fields: [entities, concepts] capture_fields: [fidelity, authority] +guidance: types/source.guidance.md layout: transcript: {dir: transcripts, title: Transkripte} analysis: {dir: analyses, title: Analysen} @@ -20,31 +21,10 @@ layout: # 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. - -<!-- wikitool:toc --> -## Contents - -- [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 +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 +how to write a conforming page is [types/source.guidance.md](source.guidance.md) - +`tools/wikitool types describe source` composes both into one answer. ## Frontmatter @@ -67,31 +47,6 @@ layout: ## Authoring guidance - 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 @@ -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 `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:`). - ---- - -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 diff --git a/types/type-guidance.md b/types/type-guidance.md new file mode 100644 index 0000000..43f906a --- /dev/null +++ b/types/type-guidance.md @@ -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 diff --git a/types/type-guidance.schema.yaml b/types/type-guidance.schema.yaml new file mode 100644 index 0000000..3a1ed43 --- /dev/null +++ b/types/type-guidance.schema.yaml @@ -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 diff --git a/types/type-spec.md b/types/type-spec.md index a79168b..a8a347d 100644 --- a/types/type-spec.md +++ b/types/type-spec.md @@ -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. - `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. +- `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 @@ -65,28 +66,41 @@ frontmatter before anyone drew it: | 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: 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 -an upgrade does not take that back. Improvements to a shipped default reach it as an *offered* -migration ([instructions/CONTRACT.md](../instructions/CONTRACT.md#instructionsmigrations)), -never by overwriting. +A page type-spec's frontmatter configuration and its `## Template` body are therefore the +instance's to rewrite, and an upgrade does not take that back. Its generic authoring prose is the +opposite: where the type-spec declares `guidance:`, that prose lives in a separate, stack-owned +`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 -where they come apart. Owning the file means the instance may rewrite any of it; the language it -writes in still follows what the line is *for*, per -[AGENTS.md § File naming](../AGENTS.md#file-naming): +**Ownership and language still do not line up with each other**, and that stays true even though +the authoring prose now has its own file rather than sharing one with the template. Owning the +type-spec means the instance may rewrite its frontmatter and its `## Template` body; a guidance +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 | -|---|---|---| -| Authoring guidance, frontmatter table, when to use / not to use | 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:`) | -| Field names, enum values, `dir:` values, `type:` paths | The machine | Neither — identifiers, never translated | +| Part of a `root: kb` type's contract | Lives in | Audience | Language | +|---|---|---|---| +| When to use / not to use, authoring guidance | The `guidance:` file, where declared | An agent writing a page | The control plane's — English | +| The frontmatter table documenting this instance's own fields | The type-spec itself | An agent writing a page | The control plane's — English | +| 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) 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 -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 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 -Each type is two files: +Each type is at least two files, and a `root: kb` type may be three: | 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>.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