stack: TOC-Pflicht fuer Referenzdateien ueber 100 Zeilen (docs toc); session-setup.md/gates.md nennen die tatsaechliche Budget-Ausnahmeliste (schliesst #73, #76)
Files changed: - AGENTS.md - CHANGES.md - VERSION - instructions/CONTRACT.md - instructions/capture-session.md - instructions/claude-code-model-selection.md - instructions/dev/issue-tracking.md - instructions/dev/testing-conventions.md - instructions/dev/version-parts.md - instructions/evolve-subtypes.md - instructions/gates.md - instructions/german-terminology.md - instructions/ingest-large-tree.md - instructions/kb-profiles.md - instructions/link-taxonomy.md - instructions/mcp-read-server.md - instructions/migrate-corpus.md - instructions/migrations/3.0.0-authoring-conventions.md - instructions/migrations/4.0.0-link-taxonomy.md - instructions/private-instance.md - instructions/session-setup.md - instructions/setup-instance.md - kb/CONTRACT.md - kb/CONVENTIONS.md - kb/concepts/COLLECTION.md - raw/CONTRACT.md - tools/CONTRACT.md - tools/chemenu/commands/docs_verify.py - tools/chemenu/commands/instructions_cmd.py - tools/chemenu/tests/test_docs_verify.py - tools/chemenu/tests/test_instructions_cmd.py - tools/chemenu/tests/test_toc.py - tools/chemenu/toc.py - types/type-spec.md
This commit is contained in:
1 parent
a51d7a322f
commit
2c4c2b1c7c
34 files changed
+929
-15
No files matched your search
@@ -41,7 +41,7 @@ from typing import Optional
|
||||
|
||||
import typer
|
||||
|
||||
from chemenu import config, conventions, kb_collections, version as version_mod
|
||||
from chemenu import config, conventions, kb_collections, toc, version as version_mod
|
||||
from chemenu.commands import dist_cmd
|
||||
from chemenu.commands._util import fail, rel_path, success
|
||||
|
||||
@@ -351,6 +351,26 @@ def check_legacy_type_blocks() -> list[str]:
|
||||
return issues
|
||||
|
||||
|
||||
def check_toc_regions() -> list[str]:
|
||||
"""Every reference file over the line threshold carries a current TOC.
|
||||
|
||||
`toc.upsert` is idempotent (`toc.py`'s own docstring), so comparing its
|
||||
output against the file on disk catches both a missing region and a
|
||||
stale one - a heading added, renamed or reordered without re-running
|
||||
`wikitool docs toc --apply` - in one check, the same way `docs verify`
|
||||
checks every other generated-from-code copy.
|
||||
"""
|
||||
issues = []
|
||||
for path in toc.target_files():
|
||||
text = path.read_text(encoding="utf-8")
|
||||
if toc.upsert(text) != text:
|
||||
issues.append(
|
||||
f"{rel_path(path)} needs a table-of-contents region refreshed - "
|
||||
"run `wikitool docs toc --apply`"
|
||||
)
|
||||
return issues
|
||||
|
||||
|
||||
def command_table_free_readmes() -> list[Path]:
|
||||
"""Every README that must not carry a copy of the command table.
|
||||
|
||||
@@ -400,9 +420,12 @@ def check_readmes_have_no_command_table() -> list[str]:
|
||||
# The pattern knows nothing about Gitea - no client, no URL, no issue state -
|
||||
# which is what keeps `instructions/dev/issue-tracking.md` § "What no tool
|
||||
# checks" intact. It is a character pattern over shipped text, and `wikitool`
|
||||
# stays as ignorant of the board as it was. Markdown anchors are word
|
||||
# characters (`](#gates)`), so a link never matches.
|
||||
ISSUE_REFERENCE_RE = re.compile(r"#\d+")
|
||||
# stays as ignorant of the board as it was. Markdown anchors are usually word
|
||||
# characters (`](#gates)`), but a numbered step's TOC entry is not
|
||||
# (`](#2-fix-the-fidelity-before-writing-a-word)`) - the lookbehind excludes
|
||||
# exactly the `](#...` link-fragment shape, not `#\d+` generally, so a real
|
||||
# citation immediately after other punctuation still matches.
|
||||
ISSUE_REFERENCE_RE = re.compile(r"(?<!\]\()#\d+")
|
||||
|
||||
# What counts as shipped prose: Markdown, plus the `.template` files an instance
|
||||
# renames into place during setup. `tools/**/*.py` is deliberately outside it.
|
||||
@@ -675,6 +698,7 @@ def verify():
|
||||
+ check_migration_for_boundary()
|
||||
+ check_breaking_change_for_boundary()
|
||||
+ check_no_issue_references()
|
||||
+ check_toc_regions()
|
||||
)
|
||||
|
||||
if issues:
|
||||
@@ -686,6 +710,36 @@ def verify():
|
||||
f"{len(STAGE_CONTRACTS)} stage contract(s) present, no legacy type blocks, "
|
||||
f"{len(IGNORE_CANARIES)} ignore canaries clear, "
|
||||
f"no issue references in {len(shipped_prose())} shipped document(s), "
|
||||
f"tables of contents current on {len(toc.target_files())} reference file(s), "
|
||||
f"{version_mod.CHANGES_FILENAME} documents version "
|
||||
f"{(config.ROOT / version_mod.VERSION_FILENAME).read_text(encoding='utf-8').strip()}."
|
||||
)
|
||||
|
||||
|
||||
@app.command("toc")
|
||||
def toc_command(
|
||||
apply: bool = typer.Option(False, "--apply", help="Write changes; default is dry-run (preview only)"),
|
||||
):
|
||||
"""Create, refresh or remove the generated table-of-contents region on
|
||||
every reference file `toc.target_files()` covers - AGENTS.md, the stage
|
||||
and collection contracts, and every flat `instructions/**.md` file."""
|
||||
changed = []
|
||||
for path in toc.target_files():
|
||||
before = path.read_text(encoding="utf-8")
|
||||
after = toc.upsert(before)
|
||||
if after != before:
|
||||
changed.append((path, after))
|
||||
|
||||
if not changed:
|
||||
success("Every table of contents is already current.")
|
||||
return
|
||||
|
||||
for path, after in changed:
|
||||
typer.echo(rel_path(path))
|
||||
if apply:
|
||||
path.write_text(after, encoding="utf-8")
|
||||
|
||||
if apply:
|
||||
success(f"Refreshed the table of contents on {len(changed)} file(s).")
|
||||
else:
|
||||
typer.echo(f"\n{len(changed)} file(s) would change. Re-run with --apply to write.")
|
||||
@@ -24,6 +24,7 @@ expected state of a clean checkout rather than a fault.
|
||||
from __future__ import annotations
|
||||
|
||||
import filecmp
|
||||
import re
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
@@ -300,7 +301,14 @@ def dev_only_forbidden_references(instructions_dir: Path | None = None) -> set[s
|
||||
except OSError: # pragma: no cover - unreadable file
|
||||
continue
|
||||
for name in dev_names:
|
||||
if name in text:
|
||||
# Word-bounded, not a bare substring test: a generated TOC anchor
|
||||
# like `#where-stack-development-happens` contains "stack-dev"
|
||||
# as a raw substring without mentioning the skill at all. `\b`
|
||||
# does not fire between "v" and "e" (both word characters), so
|
||||
# "stack-development" is correctly not a match while a real
|
||||
# mention (`` `stack-dev` ``, `instructions/dev/stack-dev/`) -
|
||||
# bounded by punctuation on both sides - still is.
|
||||
if re.search(rf"\b{re.escape(name)}\b", text):
|
||||
referenced.add(name)
|
||||
return referenced
|
||||
|
||||
|
||||
Reference in new issue
Block a user