feat: Autorenkonventionen nach Eigentum geschnitten - kb/CONVENTIONS.md, deklarierte Collections (3.0.0)
CI / verify (push) Successful in 53s
Release / release (push) Successful in 38s

Files changed:
- .gitea/workflows/ci.yml
- .wikitool-kb.json
- AGENTS.md
- CHANGES.md
- INSTALL.md
- README.md
- VERSION
- instructions/CONTRACT.md
- instructions/dev/testing-conventions.md
- instructions/german-terminology.md
- instructions/kb-profiles.md
- instructions/migrations/3.0.0-authoring-conventions.md
- instructions/private-instance.md
- instructions/setup-instance.md
- instructions/wiki-ingest/SKILL.md
- instructions/wiki-manage/SKILL.md
- kb/CONTRACT.md
- kb/CONVENTIONS.md
- kb/CONVENTIONS.md.template
- kb/comparisons/COLLECTION.md
- kb/concepts/COLLECTION.md
- kb/entities/COLLECTION.md
- kb/sources/COLLECTION.md
- tools/CONTRACT.md
- tools/README.md
- tools/chemenu/commands/dist_cmd.py
- tools/chemenu/commands/docs_verify.py
- tools/chemenu/commands/doctor.py
- tools/chemenu/commands/new_page.py
- tools/chemenu/conventions.py
- tools/chemenu/kb_collections.py
- tools/chemenu/kb_scan.py
- tools/chemenu/provenance.py
- tools/chemenu/sections.py
- tools/chemenu/tests/conftest.py
- tools/chemenu/tests/test_conventions.py
- tools/chemenu/tests/test_dist_cmd.py
- tools/chemenu/tests/test_doctor.py
- tools/chemenu/tests/test_new_page.py
- tools/chemenu/tests/test_types_cmd.py
- types/comparison.md
- types/concept.md
- types/entity.md
- types/source.md
- types/type-spec.md
This commit is contained in:
2026-09-02 15:02:10 +02:00
parent 9843df99d3
commit 502971d147
45 changed files with 1817 additions and 232 deletions
+48 -8
View File
@@ -2,9 +2,13 @@
repo's machinery.
`export` copies the pipeline's schema/compiler/control-plane layers (types/,
tools/, instructions/, the stage contracts, every kb/*/COLLECTION.md) into an
empty target, with no kb/ pages, no raw/ content, and no git history - see
instructions/setup-instance.md for what happens after. It never calls git.
tools/, instructions/, the stage contracts) into an empty target, with no kb/
pages, no raw/ content, and no git history - see instructions/setup-instance.md
for what happens after. It never calls git.
The two binding-but-instance-owned documents under kb/ - each collection's
COLLECTION.md and kb/CONVENTIONS.md - cross as `.template` and are adopted by a
rename, the same split USER.md/SOUL.md use at the repo root.
Three independent exclusion mechanisms feed the plan, for three different
shapes of "does not belong in someone else's instance":
@@ -35,7 +39,7 @@ from typing import Callable, NamedTuple, Optional, Union
import typer
from chemenu import config, kb_collections, kb_state, version as version_mod
from chemenu import config, conventions, kb_collections, kb_state, version as version_mod
from chemenu.commands._util import fail, rel_path, success, today_iso
app = typer.Typer(help="Build a distributable copy of the wiki machinery.")
@@ -289,12 +293,32 @@ def build_plan(origin: Optional[Origin] = None) -> dict[str, PlannedFile]:
for hook_dir in HOOK_DIRS:
plan.update(_copy_tree(config.ROOT / hook_dir, hook_dir, frozenset()))
# `kb/CONTRACT.md` is stack-owned and ships verbatim; everything beside it
# under `kb/` is the instance's own and ships only as a `.template`. That is
# the personalization split (`USER.md`/`SOUL.md`) one directory down, and
# the reason for it is the same: a distribution can say what the file
# decides, never what this instance decided.
kb_contract = config.KB_DIR / "CONTRACT.md"
if kb_contract.is_file():
plan["kb/CONTRACT.md"] = _read_planned_file(kb_contract, "kb/CONTRACT.md")
conventions_template = config.KB_DIR / conventions.CONVENTIONS_TEMPLATE
if conventions_template.is_file():
rel = f"kb/{conventions.CONVENTIONS_TEMPLATE}"
plan[rel] = _read_planned_file(conventions_template, rel)
# A collection contract is instance-owned too, but unlike `USER.md` the
# shipped content is not *wrong* for the receiver - it is the profile this
# repo's own collections adopted, and a fine starting point. So the file
# itself ships, under the template name: one source of truth here, and a
# receiving instance that has to rename it before it counts. Keeping a
# separate `.template` beside each contract would have meant maintaining two
# near-identical copies of the same text, which is the drift AGENTS.md
# invariant 8 exists to prevent.
for collection in kb_collections.iter_kb_collections():
rel = f"kb/{collection.name}/COLLECTION.md"
plan[rel] = _read_planned_file(collection / "COLLECTION.md", rel)
source = collection / kb_collections.CONTRACT_NAME
rel = f"kb/{collection.name}/{kb_collections.CONTRACT_NAME}.template"
plan[rel] = _read_planned_file(source, rel)
for relative in CONTRACT_ONLY_STAGES:
source = config.ROOT / relative
@@ -339,8 +363,21 @@ def build_plan(origin: Optional[Origin] = None) -> dict[str, PlannedFile]:
# IP literals) was considered and rejected - the project's own host legitimately
# appears in INSTALL.md and version.py, so such a scan would either whitelist
# the very string it is looking for or cry wolf on every export.
#
# `COLLECTION.md` and `CONVENTIONS.md` are deliberately *not* on the allowed
# list any more. Both bind, and both are the instance's to write, so they cross
# the boundary as `.template` and are adopted by a rename - a plan carrying the
# filled name would hand a new instance this one's authoring conventions as
# though they were the stack's.
_CONTENT_PREFIXES = ("kb/", "raw/")
_CONTENT_ALLOWED_NAMES = ("CONTRACT.md", "COLLECTION.md", "log.md", ".gitkeep")
_CONTENT_ALLOWED_NAMES = (
"CONTRACT.md",
f"{kb_collections.CONTRACT_NAME}.template",
conventions.CONVENTIONS_TEMPLATE,
"log.md",
".gitkeep",
)
_INSTANCE_OWNED_KB_FILES = (kb_collections.CONTRACT_NAME, conventions.CONVENTIONS_FILENAME)
def find_leaks(plan: dict[str, PlannedFile]) -> list[str]:
@@ -350,6 +387,8 @@ def find_leaks(plan: dict[str, PlannedFile]) -> list[str]:
name = relative.rsplit("/", 1)[-1]
if name in config.PERSONALIZATION_FILES or name == config.ENVIRONMENT_FILE:
leaks.append(f"{relative} (one instance's own personalization)")
elif relative.startswith("kb/") and name in _INSTANCE_OWNED_KB_FILES:
leaks.append(f"{relative} (this instance's authoring conventions; ship the .template)")
elif relative.startswith("instructions/dev/"):
leaks.append(f"{relative} (stack-development only)")
elif relative.startswith(_CONTENT_PREFIXES) and name not in _CONTENT_ALLOWED_NAMES:
@@ -394,7 +433,8 @@ def export_command(
AGENTS.md/README.md (dev-instance-only marker blocks removed),
instructions/ (no instructions/dev/), types/, tools/ (no venv/caches),
the .github/hooks/+.vibe session-tracing config plus .claude/settings.json,
every kb/*/COLLECTION.md (no pages, no areas), empty
kb/CONTRACT.md plus a COLLECTION.md.template per collection and
kb/CONVENTIONS.md.template (no pages, no areas), empty
raw/{articles,documents,notes,assets}/, VERSION, the USER.md/SOUL.md
personalization templates (never the filled files), and a
.wikitool-release.json stamp. The --source-*/--release-url/--update-url