feat: a subtype gets its own page skeleton from types/<type>.<value>.md (#117)
CI / verify (push) Successful in 5m17s
CI / pwsh (push) Successful in 2m1s
Release / release (push) Successful in 34s

Files changed:
- AGENTS.md
- CHANGES.md
- README.md
- VERSION
- docs/ownership-and-templates.md
- instructions/evolve-subtypes.md
- instructions/setup-instance.md
- instructions/subtype-templates.md
- instructions/upgrade-instance.md
- tools/CONTRACT.md
- tools/chemenu/commands/dist_cmd.py
- tools/chemenu/commands/docs_verify.py
- tools/chemenu/commands/new_page.py
- tools/chemenu/tests/test_dist_cmd.py
- tools/chemenu/tests/test_docs_verify.py
- tools/chemenu/tests/test_new_page.py
- tools/chemenu/tests/test_toc.py
- tools/chemenu/tests/test_type_resolver.py
- tools/chemenu/toc.py
- tools/chemenu/type_resolver.py
- types/concept.decision.md
- types/entity.guidance.md
- types/entity.md
- types/entity.person.md
- types/type-spec.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2
This commit is contained in:
torbenandClaude Opus 5.5 committed 2026-10-03 15:58:15 +02:00
1 parent c261b8f4ca
commit d8cb494d58
25 files changed
+747 -51

No files matched your search

+22 -7
View File
@@ -67,7 +67,7 @@ frontmatter before anyone drew it:
| Type-spec | Describes | Owned by | Ships as |
|---|---|---|---|
| `root: kb` (`entity`, `concept`, `source`, `comparison`, `project`) | 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`, `project`) | A page **this instance** writes | The instance | `types/<name>.md.template` plus its `.schema.yaml.template` and any subtype template's `types/<name>.<value>.md.template`, adopted by a rename |
| `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 frontmatter configuration and its `## Template` body are therefore the
@@ -124,13 +124,23 @@ Python. Adding a type must require no code change.
### Anatomy of a type
Each type is at least two files, and a `root: kb` type may be three:
Each type is at least two files, and a `root: kb` type may have more:
| File | Owns |
|------|------|
| `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 |
| `types/<name>.<value>.md` (optional, any number, only for a type with `subtype_field:`) | A **subtype template**: the page skeleton for pages whose subtype field holds `<value>`, where that subtype needs a different page shape than the `## Template` block gives - a person is not described by a version and a repository. Plain markdown in the KB language, no frontmatter, no fence: the whole file is the skeleton. Its name is its only declaration - nothing in the type-spec lists it. Owned like the type-spec it sits beside, so a `root: kb` type's subtype templates ship as `.template` too |
`docs verify` holds a subtype template to three things: `types/<name>.md` beside it is a
type-spec declaring `subtype_field:`, `<value>` is one that field's schema enum allows (where it
has an enum), and the file carries no frontmatter. A misspelt name would otherwise be a template
nothing ever reads, and nobody would notice. **`guidance` is reserved** - `types/<name>.guidance.md`
is always the guidance file, so a subtype value `guidance` can have no template of its own. A
subtype template never gets a table-of-contents region, whatever its length: it is copied into
every page scaffolded from it. When an instance needs one is
[instructions/subtype-templates.md](../instructions/subtype-templates.md).
**A `default:` is materialized by `wikitool new` only for a field the schema also lists in
`required:`.** On an optional field, `default:` documents what a reader should assume when the
@@ -141,9 +151,9 @@ newly scaffolded instruction regardless.
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.
non-instantiable, contract-only type. `tools/wikitool types describe <name>` composes the type-spec, its schema and its guidance into
one answer; an agent asking for a type's contract never needs to know it came from more than one
file. Subtype templates are not part of that answer - only `wikitool new` reads them.
### Placement frontmatter
@@ -206,13 +216,18 @@ No Python change is needed at any step; `wikitool` discovers types by scanning t
### Template variables
The `## Template` block is filled from the page's own frontmatter, plus `{name}` and
`wikitool new` picks one skeleton per page: `types/<name>.<value>.md` when the page's subtype
field holds `<value>` and that file exists, otherwise the `## Template` block. A subtype template
replaces the block whole - nothing is merged - so every subtype without a file of its own, and
every type without a `subtype_field:`, scaffolds exactly what the block says.
Either skeleton is filled from the page's own frontmatter, plus `{name}` and
`{today}`. Filters render structured fields: `{entities|bullets}`, `{tags|join}`,
`{entity_type|capitalize}`, `{entities|table_header}`, `{entities|table_sep}`,
`{entities|table_cells}`. `{field|literal text}` falls back to the literal when the field is
absent.
**A template never contains a tool-owned region.** The links and footnotes regions are generated
**A template - block or subtype file - never contains a tool-owned region.** The links and footnotes regions are generated
between markers by `xref` and `cite`, rendered from frontmatter, and re-rendered on every write -
so scaffolding them would create a section an author is forbidden to edit and the tool would
replace anyway. See `tools/chemenu/blocks.py`.