Compare commits

..

2 Commits

Author SHA1 Message Date
torben 72b2b4424f docs verify: DEVELOPMENT.md in STAGE_READMES; self-labelling release notes; prose corrections to 4.6.0 (#47 Block 3)
CI / verify (push) Successful in 54s
Release / release (push) Successful in 36s
Files changed:
- .gitea/workflows/release.yml
- CHANGES.md
- VERSION
- instructions/dev/stack-close/SKILL.md
- tools/CONTRACT.md
- tools/chemenu/commands/docs_verify.py
- tools/chemenu/commands/git_publish.py
- tools/chemenu/tests/test_docs_verify.py
2026-09-04 15:55:04 +02:00
torben 91bd430ac8 docs: DEVELOPMENT.md - stack-dev/stack-close split (stale since #47 Block 2)
CI / verify (push) Successful in 58s
Files changed:
- DEVELOPMENT.md
2026-09-04 15:31:24 +02:00
9 changed files with 141 additions and 13 deletions
+21
View File
@@ -102,11 +102,32 @@ jobs:
- name: Release notes from CHANGES.md - name: Release notes from CHANGES.md
# `version notes` fails when the changelog has no entry for this # `version notes` fails when the changelog has no entry for this
# version, which is the last place that mistake can still be caught. # version, which is the last place that mistake can still be caught.
#
# The footer below settles Gitea #47's second side-finding: a release
# note is written once, at tag time, and a later correction to
# CHANGES.md never reaches it - `gitea-mcp` has no release-edit method,
# and delete-and-recreate would destroy the attached tarball assets that
# INSTALL.md and `version check` point at. That happened for real to
# v4.4.0, whose note carried a fact that the corpus had already
# corrected. Rather than build a correction path for a text nobody can
# edit, the snapshot says it is one and names where the maintained
# version lives. A stale note then costs a reader one click instead of
# a wrong belief. Appended here rather than inside `version notes`,
# which is a general-purpose extractor whose other callers (a local
# preview, a pipe) should not inherit a release-page footer.
if: steps.version.outputs.skip != 'true' if: steps.version.outputs.skip != 'true'
run: | run: |
set -eu set -eu
tools/wikitool docs verify tools/wikitool docs verify
tools/wikitool version notes > /tmp/release-notes.md tools/wikitool version notes > /tmp/release-notes.md
cat >> /tmp/release-notes.md <<'EOF'
---
*This note is a snapshot of the `CHANGES.md` entry as it stood when the tag was cut, and
is never edited afterwards. The maintained version of this text - including any later
correction - is the entry for this version in `CHANGES.md` in the repository.*
EOF
cat /tmp/release-notes.md cat /tmp/release-notes.md
- name: Build the distribution tarball - name: Build the distribution tarball
+56
View File
@@ -35,6 +35,62 @@ dev-checkout concern - readable here, never shipped as something to parse.
--- ---
## 4.6.1 - 2026-09-04 - DEVELOPMENT.md im Kommandotabellen-Check, selbstbeschriftete Release-Notes, Prosa-Korrekturen (#47 Block 3)
**Author:** Torben Nehmer
<!-- wikitool:bumps -->
- DEVELOPMENT.md im Kommandotabellen-Check, selbstbeschriftete Release-Notes, Prosa-Korrekturen (#47 Block 3)
<!-- /wikitool:bumps -->
Block 3 aus #47 - die beiden Nebenbefunde entschieden und umgesetzt - plus drei Prosa-Korrekturen
an `4.6.0`, die eine Bewertung des eigenen Ergebnisses gefunden hat.
**`DEVELOPMENT.md` gehört in `docs_verify.STAGE_READMES`** (entschieden: ja). Das Gegenargument
bei der Aufnahme war, die Liste führe bisher nur ausgelieferte Dokumente, und `DEVELOPMENT.md`
wird von `dist_cmd.ROOT_FILES` bewusst nicht ausgeliefert. Beim Hinsehen löst es sich auf:
`check_readmes_have_no_command_table` überspringt eine Datei, die nicht existiert. In einer
ausgelieferten Instanz ist der Eintrag damit schlicht wirkungslos, im Entwicklungs-Checkout - dem
einzigen Ort, an dem die Datei existiert und also driften kann - greift er. Dafür spricht der
Anlass: genau diese Datei trug einmal eine Tabelle, die für jeden Verify-Befehl ein zweites Mal
beschrieb, was er prüft, und sie musste von Hand entfernt werden, weil nichts sie mit etwas
verglich. Zwei Tests: einer, der die Tabelle in `DEVELOPMENT.md` meldet, und einer, der
festhält, dass eine fehlende gelistete Datei übersprungen und nicht als Fund gemeldet wird - der
Instanz-Fall, an dem die Entscheidung hing. Der Konstantenname ist jetzt enger als sein Inhalt;
das steht als Kommentar daneben, statt eine Umbenennung durch zwei Aufrufstellen zu ziehen.
**Veröffentlichte Release-Notes veralten weiter - sie sagen es jetzt selbst** (entschieden:
Schnappschuss akzeptieren, statt einen Korrekturweg zu bauen). Eine nach dem Tag korrigierte
`CHANGES.md` erreicht die Release-Seite nicht: `gitea-mcp` kennt kein Release-Edit, und
Löschen-und-neu-Anlegen würde die angehängten Tarball-Assets vernichten, auf die `INSTALL.md` und
`version check` zeigen. Bei `v4.4.0` ist das real eingetreten. Statt eines Korrekturwegs für einen
Text, den niemand editieren kann, trägt der Schnappschuss jetzt eine Fußzeile, die sagt, dass er
einer ist und wo die gepflegte Fassung liegt - eine veraltete Notiz kostet einen Leser damit einen
Klick statt einer falschen Überzeugung. Angehängt in `release.yml` und nicht in `version notes`:
das Kommando ist ein allgemeiner Extraktor, dessen andere Aufrufer (lokale Vorschau, eine Pipe)
keine Release-Seiten-Fußzeile erben sollen. `.gitea/`-Änderung, also ohne eigenen Bump-Anspruch -
sie fährt hier mit.
**Drei Prosa-Korrekturen an `4.6.0`.** Der `4.6.0`-Eintrag und der Docstring von
`touches_stack_machinery` behaupteten, das Prädikat prüfe „denselben Pfad-Umfang, den der
CI-Versions-Gate selbst verwendet". Das stimmt nicht: CI matcht `[^/]+/CONTRACT\.md$`, also genau
eine Pfadebene, das Prädikat matcht `CONTRACT.md` in jeder Tiefe. Folgenlos im Verhalten - ein
Über-Match druckt eine Zeile zu viel, nie eine zu wenig -, aber es war eine behauptete Äquivalenz,
die keine ist, geschrieben in genau der ungeprüften Prosa-Phase, um die #47 sich dreht. Docstring
und `tools/CONTRACT.md` benennen die Differenz jetzt und begründen sie (bei einer Erinnerung ist
Über-Matchen die richtige Richtung). Drittens: `stack-close` beschrieb den eigenen Skill-Schnitt
zu stark („es gibt keinen nächsten Schritt mehr, an dem vorbei zu rutschen wäre"). Wahr für die
*Prozedur*, die nicht mehr im Kontext steht; nicht wahr für den *Auslöser* - `stack-dev`s „invoke
it now" ist weiterhin ein Satz, und die `publish`-Notiz nennt den Skill bewusst nicht beim Namen.
Zwei der drei Kettenglieder bleiben Selbstdisziplin. Der Skill sagt das jetzt selbst, statt sich
als Garantie zu verkaufen, die er nicht ist.
Verifiziert: `tools/wikitool docs verify`, `tools/wikitool instructions verify`,
`.venv/bin/python -m pytest -q` (969 passed, 2 davon neu), `release.yml` gegen den YAML-Parser
und das Heredoc als Trockenlauf gegen eine Beispiel-Notiz.
---
## 4.6.0 - 2026-09-04 - stack-dev/stack-close skill split, publish stack-machinery note, model-selection fix (#47 Block 2) ## 4.6.0 - 2026-09-04 - stack-dev/stack-close skill split, publish stack-machinery note, model-selection fix (#47 Block 2)
**Author:** Torben Nehmer **Author:** Torben Nehmer
+4 -1
View File
@@ -92,6 +92,9 @@ gegen einen Commit. `.gitea/workflows/release.yml` ist Schritt 6 oben.
Der `stack-dev`-Skill (`instructions/dev/`, nur in diesem Ursprungs-Repo vorhanden) fasst die Der `stack-dev`-Skill (`instructions/dev/`, nur in diesem Ursprungs-Repo vorhanden) fasst die
Regeln für eine Sitzung, die den Stack selbst statt Wiki-Inhalt bearbeitet: wann Regeln für eine Sitzung, die den Stack selbst statt Wiki-Inhalt bearbeitet: wann
Quellenbindung nicht gilt, wo Design endet und die mechanische Phase beginnt (mit dem Quellenbindung nicht gilt, wo Design endet und die mechanische Phase beginnt (mit dem
Modellwechsel-Hinweis), und dass Issue-Abschluss ein Body-Rewrite ist, kein Kommentar. Siehe Modellwechsel-Hinweis), und endet mit dem Publish. Die Schlussphase - Issue-Body als Rewrite
statt Kommentar, `docs/`-Veralterung, die Modell-Handover-Zeile über die ganze Sitzung - liegt
seit `4.6.0` in einem eigenen Folge-Skill, `stack-close`, den `stack-dev` an dieser Stelle
übergibt statt sie als weiteren eigenen Schritt zu führen. Siehe
[instructions/dev/issue-tracking.md](instructions/dev/issue-tracking.md) für den [instructions/dev/issue-tracking.md](instructions/dev/issue-tracking.md) für den
Issue-Tracker selbst. Issue-Tracker selbst.
+1 -1
View File
@@ -1 +1 @@
4.6.0 4.6.1
+11 -2
View File
@@ -32,8 +32,17 @@ the session knew the rule and skipped past it anyway, because nothing in the mom
question. Splitting the phase into its own skill does not add a check either - `wikitool` still question. Splitting the phase into its own skill does not add a check either - `wikitool` still
does not know this tracker exists and must not learn (see does not know this tracker exists and must not learn (see
[issue-tracking.md](../issue-tracking.md) § What no tool checks) - but it removes the thing that [issue-tracking.md](../issue-tracking.md) § What no tool checks) - but it removes the thing that
was actually failing: there is no longer a "next step" to skip past, only a skill nobody has was actually failing: the closing *procedure* is no longer sitting in the session's context as a
invoked yet. See Gitea #47 for the full incident history and the rejected alternative (a next step to run past - it exists only inside a skill someone has to invoke.
**Be precise about what that does and does not buy**, because the honest version is weaker than
"now it cannot be skipped". What did **not** change is the trigger: `stack-dev`'s "invoke it now"
is still a sentence, and `publish`'s stack-machinery note is deliberately generic enough not to
name this skill at all. Two of the three links in that chain remain self-discipline. The split
narrows the failure, it does not close it - treat a session that reaches this text as the
mechanism having worked *this time*, not as proof that it always will.
See Gitea #47 for the full incident history and the rejected alternative (a
model-switched subagent - not buildable in Claude Code, where a fork inherits the parent's model model-switched subagent - not buildable in Claude Code, where a fork inherits the parent's model
and a fresh subagent starts without the session's context). and a fresh subagent starts without the session's context).
+1 -1
View File
@@ -58,7 +58,7 @@ tools/wikitool <command> --help
| `sources trace --raw <path>` \| `--page "<Title>"` | Trace provenance in either direction: raw file -> source page(s) -> citing pages, or page -> its sources -> their raw files | | `sources trace --raw <path>` \| `--page "<Title>"` | Trace provenance in either direction: raw file -> source page(s) -> citing pages, or page -> its sources -> their raw files |
| `sources rebuild-index [--dry-run]` | Regenerate the `kb/provenance.md` reverse index (raw file -> source page -> citing pages) | | `sources rebuild-index [--dry-run]` | Regenerate the `kb/provenance.md` reverse index (raw file -> source page -> citing pages) |
| `sync [--remote origin] [--branch main] [--confirm-rebase TOKEN]` | Fetch `<remote>/<branch>` and bring the local branch up to date with it: fast-forward when the remote is simply ahead, rebase local commit(s) on top when both sides moved but touch disjoint files (a content conflict is then impossible by construction), and exit **42** for review when they touch the same file (the **rebase-review gate** - see `publish` below). Never commits, never pushes, never force-anything - no remote configured, or one that cannot be reached, is reported and skipped, not a failure. Meant to run once at the start of a writing session (`instructions/session-setup.md`) so the rest of it works against a current tree instead of discovering the drift at the final `publish` | | `sync [--remote origin] [--branch main] [--confirm-rebase TOKEN]` | Fetch `<remote>/<branch>` and bring the local branch up to date with it: fast-forward when the remote is simply ahead, rebase local commit(s) on top when both sides moved but touch disjoint files (a content conflict is then impossible by construction), and exit **42** for review when they touch the same file (the **rebase-review gate** - see `publish` below). Never commits, never pushes, never force-anything - no remote configured, or one that cannot be reached, is reported and skipped, not a failure. Meant to run once at the start of a writing session (`instructions/session-setup.md`) so the rest of it works against a current tree instead of discovering the drift at the final `publish` |
| `publish --message "<op>: <desc>" [--no-push] [--confirm TOKEN] [--confirm-rebase TOKEN] [--threshold N] [--remote origin] [--branch main] [--path P ...]` | Reconcile with `<remote>/<branch>` exactly like `sync` (skipped for `--no-push`), then stage all changes, commit, and push. If the reconcile step found a still-unpushed local commit and there is nothing new to stage, that commit is pushed anyway - a previous `publish` whose push failed no longer strands it. If the push is rejected despite the pre-check (a genuine race - something landed on the remote in between), one more reconcile-and-retry is attempted before giving up; never more than one. **Mass-Update Gate:** when >= `--threshold` (default 10) *counted* files would be committed, exits **42 (`EXIT_NEEDS_CLEARANCE`)** instead of publishing - a third outcome distinct from success (0) and a validation error (1) - and prints a review report: a scale line (file count, total lines added/removed, status breakdown), only-what-applies attention notes (deletions by name, control-plane and harness-config touches, published pages, the largest single change, binaries), and every counted path grouped by area with its status and churn, generated files split out as needing no review. The token digests each counted path **and its contents** plus the publish target, so a clearance carries neither to a different file list nor to edited contents; a wrong, invented or superseded token exits 42 again with the current state. Two kinds of path are committed but never counted and never shown for approval: anything under `work/`, and the files `wikitool` generates itself (`kb/index.md`, `kb/log.md`, `kb/provenance.md`, every `INDEX.md`) - each is recomputable from the tree, so approving it decides nothing, and a routine ingest rebuilds five or six of them. The refusal line accounts for both, by reason. The gate is evaluated *before* anything is staged, so a refused publish leaves the working tree untouched. **Publish-Remote Gate:** when this checkout carries a `.wikitool-remotes.json` and the resolved push URL of `--remote` is not listed in it, exits **42** before the reconcile step even fetches - the URL is read from `git remote get-url --push`, so a repointed remote does not pass on its name. Unlike the other two gates it has **no token and no flag**: the way past it is the user adding the URL to that file, and an agent editing it to get past a refusal is opening a gate on its own initiative. Absent file means unrestricted; a malformed one is an error, not permission. See [instructions/gates.md](../instructions/gates.md) `--yes`/`-y` are gone and now fail with an explicit error. `--path` (repeatable) scopes the whole operation - gate count, staging, and commit - to a subtree. **Stack-machinery note:** after a successful commit/push whose changed files include `tools/`, `types/`, `instructions/`, `AGENTS.md`, or any `<stage>/CONTRACT.md` - the same scope a stack version bump itself covers - prints one reminder line that the phase past this point (an issue-body rewrite, `docs/` staleness, a changelog entry's accuracy) is not covered by `docs verify`, `instructions verify` or `pytest`. Not a gate: no exit code change, nothing to clear, silent for an ordinary content publish | | `publish --message "<op>: <desc>" [--no-push] [--confirm TOKEN] [--confirm-rebase TOKEN] [--threshold N] [--remote origin] [--branch main] [--path P ...]` | Reconcile with `<remote>/<branch>` exactly like `sync` (skipped for `--no-push`), then stage all changes, commit, and push. If the reconcile step found a still-unpushed local commit and there is nothing new to stage, that commit is pushed anyway - a previous `publish` whose push failed no longer strands it. If the push is rejected despite the pre-check (a genuine race - something landed on the remote in between), one more reconcile-and-retry is attempted before giving up; never more than one. **Mass-Update Gate:** when >= `--threshold` (default 10) *counted* files would be committed, exits **42 (`EXIT_NEEDS_CLEARANCE`)** instead of publishing - a third outcome distinct from success (0) and a validation error (1) - and prints a review report: a scale line (file count, total lines added/removed, status breakdown), only-what-applies attention notes (deletions by name, control-plane and harness-config touches, published pages, the largest single change, binaries), and every counted path grouped by area with its status and churn, generated files split out as needing no review. The token digests each counted path **and its contents** plus the publish target, so a clearance carries neither to a different file list nor to edited contents; a wrong, invented or superseded token exits 42 again with the current state. Two kinds of path are committed but never counted and never shown for approval: anything under `work/`, and the files `wikitool` generates itself (`kb/index.md`, `kb/log.md`, `kb/provenance.md`, every `INDEX.md`) - each is recomputable from the tree, so approving it decides nothing, and a routine ingest rebuilds five or six of them. The refusal line accounts for both, by reason. The gate is evaluated *before* anything is staged, so a refused publish leaves the working tree untouched. **Publish-Remote Gate:** when this checkout carries a `.wikitool-remotes.json` and the resolved push URL of `--remote` is not listed in it, exits **42** before the reconcile step even fetches - the URL is read from `git remote get-url --push`, so a repointed remote does not pass on its name. Unlike the other two gates it has **no token and no flag**: the way past it is the user adding the URL to that file, and an agent editing it to get past a refusal is opening a gate on its own initiative. Absent file means unrestricted; a malformed one is an error, not permission. See [instructions/gates.md](../instructions/gates.md) `--yes`/`-y` are gone and now fail with an explicit error. `--path` (repeatable) scopes the whole operation - gate count, staging, and commit - to a subtree. **Stack-machinery note:** after a successful commit/push whose changed files include `tools/`, `types/`, `instructions/`, `AGENTS.md`, or a path ending in `CONTRACT.md` - roughly the scope a stack version bump covers, deliberately a shade broader than CI's version gate, which matches only a `CONTRACT.md` one segment deep - prints one reminder line that the phase past this point (an issue-body rewrite, `docs/` staleness, a changelog entry's accuracy) is not covered by `docs verify`, `instructions verify` or `pytest`. Not a gate: no exit code change, nothing to clear, silent for an ordinary content publish |
| `work new (--input <raw path> \| --key <run key>) [--again] [--dry-run]` | Scaffold `work/<runkey>/` for one workshop run: refuses a collision instead of suffixing it, and writes the required `README.md` + `plan.md`. `--input` derives the run key from the path below `raw/` (an ingest); `--key` names it outright for a run with no raw input - a migration or a sweep across `kb/` - and may not start with `ingest-`, which stays reserved for derived keys. Exactly one of the two. `--again` opens a dated second pass over a tree that has itself changed. See [work/CONTRACT.md](../work/CONTRACT.md) | | `work new (--input <raw path> \| --key <run key>) [--again] [--dry-run]` | Scaffold `work/<runkey>/` for one workshop run: refuses a collision instead of suffixing it, and writes the required `README.md` + `plan.md`. `--input` derives the run key from the path below `raw/` (an ingest); `--key` names it outright for a run with no raw input - a migration or a sweep across `kb/` - and may not start with `ingest-`, which stays reserved for derived keys. Exactly one of the two. `--again` opens a dated second pass over a tree that has itself changed. See [work/CONTRACT.md](../work/CONTRACT.md) |
| `work close --run-key <name> [--yes] [--dry-run]` | Delete a finished workshop. Lists what would be lost and requires `--yes`, because nothing in it is recoverable from the rest of the repo - the durable conclusions must already be in `kb/` | | `work close --run-key <name> [--yes] [--dry-run]` | Delete a finished workshop. Lists what would be lost and requires `--yes`, because nothing in it is recoverable from the rest of the repo - the durable conclusions must already be in `kb/` |
| `budget status` | Show the current session's `wikitool` call count and recent command history (never counted against the budget) | | `budget status` | Show the current session's `wikitool` call count and recent command history (never counted against the budget) |
+11 -1
View File
@@ -136,7 +136,17 @@ ROOT_README = config.ROOT / "README.md"
# tools/README.md is exactly the file it drifted in. INSTALL.md is here for the # tools/README.md is exactly the file it drifted in. INSTALL.md is here for the
# same reason: it is human-facing prose about installing an instance, and the # same reason: it is human-facing prose about installing an instance, and the
# command reference lives exactly once, in tools/CONTRACT.md. # command reference lives exactly once, in tools/CONTRACT.md.
STAGE_READMES = ("tools/README.md", "INSTALL.md") #
# DEVELOPMENT.md joined them after it drifted the same way (Gitea #47): it grew
# a table describing what each verify command checks, which had to be removed by
# hand because nothing compared it to anything. It is not shipped - dist_cmd
# .ROOT_FILES excludes it - and that is not an argument against listing it here:
# `check_readmes_have_no_command_table` skips a file that does not exist, so in
# a distributed instance this entry is simply inert, while in the dev checkout
# (the only place the file exists, and the only place it can drift) it is
# checked. The name is now narrower than the tuple - these are the human-facing
# prose docs that must not re-list commands, stage README or not.
STAGE_READMES = ("tools/README.md", "INSTALL.md", "DEVELOPMENT.md")
# Docs that must not re-introduce the pre-migration bare-enum `type:` form. # Docs that must not re-introduce the pre-migration bare-enum `type:` form.
# The per-collection contracts are appended at call time, since which ones exist # The per-collection contracts are appended at call time, since which ones exist
+12 -7
View File
@@ -441,13 +441,18 @@ STACK_MACHINERY_NOTE = (
def touches_stack_machinery(changed_files: list[str]) -> bool: def touches_stack_machinery(changed_files: list[str]) -> bool:
"""Whether `changed_files` includes a path under `version-parts.md`'s """Whether `changed_files` includes a path under the stack version's own
scope for the stack version - `tools/`, `types/`, `instructions/`, scope - `tools/`, `types/`, `instructions/`, `AGENTS.md`, or a path ending
`AGENTS.md`, or any `<stage>/CONTRACT.md`. A publish in this class is, in `CONTRACT.md` at any depth. A publish in this class is, by construction
by construction of the `stack-dev`/`stack-close` split, always followed of the `stack-dev`/`stack-close` split, always followed by the unchecked
by the unchecked closing phase - `STACK_MACHINERY_NOTE` times a reminder closing phase - `STACK_MACHINERY_NOTE` times a reminder to land exactly
to land exactly there, for any session, not only one that read the there, for any session, not only one that read the skill that names it.
skill that names it."""
Deliberately a shade broader than CI's version gate, which matches
`<one-segment>/CONTRACT.md` only: this decides whether to print a sentence,
so over-matching costs a reminder nobody needed, while under-matching costs
the reminder in the one case it was built for. The two are not the same
predicate and should not be described as one."""
for path in changed_files: for path in changed_files:
if path in STACK_MACHINERY_NAMES: if path in STACK_MACHINERY_NAMES:
return True return True
+24
View File
@@ -100,6 +100,30 @@ def test_install_md_is_checked_too(tmp_path, monkeypatch):
assert any("`doctor`" in issue for issue in issues) assert any("`doctor`" in issue for issue in issues)
def test_development_md_is_checked_too(tmp_path, monkeypatch):
"""DEVELOPMENT.md drifted exactly this way once (Gitea #47): a table
describing what each verify command checks, removed by hand because nothing
compared it to anything."""
root = tmp_path
(root / "DEVELOPMENT.md").write_text(
"| Command | Purpose |\n| `docs verify` | checks docs |\n", encoding="utf-8"
)
monkeypatch.setattr(docs_verify.config, "ROOT", root)
monkeypatch.setattr(docs_verify, "ROOT_README", root / "README.md") # doesn't exist here
issues = docs_verify.check_readmes_have_no_command_table()
assert any("`docs verify`" in issue for issue in issues)
def test_an_absent_listed_doc_is_skipped_not_reported(tmp_path, monkeypatch):
"""The distributed-instance case: DEVELOPMENT.md is not shipped, so listing
it must stay inert where the file does not exist rather than failing a tree
that is correct."""
root = tmp_path
monkeypatch.setattr(docs_verify.config, "ROOT", root)
monkeypatch.setattr(docs_verify, "ROOT_README", root / "README.md")
assert docs_verify.check_readmes_have_no_command_table() == []
def test_legacy_type_blocks_are_absent(): def test_legacy_type_blocks_are_absent():
assert docs_verify.check_legacy_type_blocks() == [] assert docs_verify.check_legacy_type_blocks() == []