feat: docs reference generates a skill catalogue and a page-type reference under docs/reference/ from the shipped defaults, checked and written in the origin repo only (#189)
CI / verify (push) Successful in 6m10s
CI / pwsh (push) Successful in 1m59s
Release / release (push) Successful in 34s

Files changed:
- CHANGES.md
- README.md
- VERSION
- docs/reference/README.md
- docs/reference/commands/README.md
- docs/reference/commands/types-instructions-and-docs.md
- docs/reference/page-types.md
- docs/reference/skills.md
- instructions/dev/doc-pull-through.md
- tools/CONTRACT.md
- tools/README.md
- tools/chemenu/commands/docs_verify.py
- tools/chemenu/reference_doc.py
- tools/chemenu/tests/test_reference_doc.py

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-06 22:10:09 +02:00
1 parent d644a1d4f7
commit 070cae6aad
14 files changed
+606 -65

No files matched your search

+175 -1
View File
@@ -1,11 +1,14 @@
import json
import re
import pytest
from typer.testing import CliRunner
from chemenu import blocks, cli, cli_contract, config, reference_doc, toc
from chemenu import version as version_mod
from chemenu.cli import app
from chemenu.commands import dist_cmd, docs_verify
from chemenu.commands import dist_cmd, docs_verify, instructions_cmd
from chemenu.type_resolver import resolver
runner = CliRunner()
@@ -94,6 +97,112 @@ def test_the_reference_pages_survive_dist_export_unchanged():
assert plan[region.path].content == (config.ROOT / region.path).read_text(encoding="utf-8")
def _type_sections(page: str) -> dict[str, str]:
region = blocks.find(page, reference_doc.PAGE_TYPES_REGION)
return {
match.group(1): match.group(2)
for match in re.finditer(r"^## `([^`]+)`\n(.*?)(?=^## |\Z)", region, re.M | re.S)
}
def test_the_skill_catalogue_has_one_row_per_shipped_skill():
shipped = {
d.name for d in instructions_cmd.skill_dirs() if not instructions_cmd.is_dev_only(d)
}
assert shipped
region = blocks.find(
(config.ROOT / reference_doc.SKILLS).read_text(encoding="utf-8"), reference_doc.SKILLS_REGION
)
rows = re.findall(r"^\| `([^`]+)` \|", region, re.M)
assert sorted(rows) == sorted(shipped)
for directory in instructions_cmd.skill_dirs():
if instructions_cmd.is_dev_only(directory):
assert f"`{directory.name}`" not in region
def test_page_types_has_one_section_per_page_type_matching_types_describe():
"""Required fields and subtypes are what `types describe --json` says, and
each subtype's directory is where `new` would write it."""
sections = _type_sections((config.ROOT / reference_doc.PAGE_TYPES).read_text(encoding="utf-8"))
names = {
fm["name"] for path, fm in resolver.list_type_specs()
if path.rsplit("/", 1)[-1][:-3] in dist_cmd.instance_owned_type_stems()
}
assert names and set(sections) == names
for name, section in sections.items():
result = runner.invoke(app, ["types", "describe", name, "--json"])
assert result.exit_code == 0, result.output
described = json.loads(result.output)
required = [f["field"] for f in described["fields"] if f["required"]]
listed = re.findall(r"^- `([^`]+)` - ", section.split("**Required fields**")[1], re.M)
assert listed == required, name
field = described["subtype_field"]
rows = re.findall(r"^\| `([^`]+)` \| `([^`]+)` \|", section, re.M)
if not field:
assert rows == [], name
continue
enum = next(f["enum"] for f in described["fields"] if f["field"] == field)
assert [value for value, _ in rows] == enum, name
for value, written_to in rows:
target = resolver.compute_target_dir(described["type_path"], {field: value})
assert written_to == target.relative_to(config.ROOT).as_posix() + "/", (name, value)
def test_a_description_is_split_at_its_use_when_sentence():
assert reference_doc.split_description("Does X. Use when Y happens.") == ("Does X.", "Y happens.")
assert reference_doc.split_description("Does X.") == ("Does X.", "only when asked for by name")
def test_a_table_cell_escapes_pipes_and_tags_but_not_inside_code():
cell = reference_doc._cell('says "ingest <file>" or `a <b> | c` | d')
assert cell == 'says "ingest &lt;file&gt;" or `a <b> \\| c` \\| d'
@pytest.fixture
def exported_instance(tmp_path, monkeypatch):
"""A `dist export` of this tree, adopted and then changed the way an
instance may: a type-spec of its own making, and a skill of its own."""
target = tmp_path / "instance"
dist_cmd.run_export(target)
monkeypatch.setattr(config, "ROOT", target)
monkeypatch.setattr(resolver, "_repo_root", target)
for template in sorted((target / "types").glob("*.template")):
template.rename(template.with_suffix(""))
entity = target / "types" / "entity.md"
entity.write_text(
entity.read_text(encoding="utf-8").replace(
"description: Base type for entity pages", "description: This instance's entities"
),
encoding="utf-8",
)
own = target / "instructions" / "own-skill"
own.mkdir()
(own / "SKILL.md").write_text(
"---\nname: own-skill\ndescription: Does this instance's thing. Use when asked.\n---\n# Own\n",
encoding="utf-8",
)
return target
def test_an_adapted_instance_leaves_both_pages_alone(exported_instance):
root = exported_instance
assert version_mod.stamp_file().is_file()
pages = {
relative: (root / relative).read_text(encoding="utf-8")
for relative in (reference_doc.SKILLS, reference_doc.PAGE_TYPES)
}
assert docs_verify.check_reference_regions() == []
result = runner.invoke(app, ["docs", "reference", "--apply"])
assert result.exit_code == 0, result.output
assert "Left alone in this instance" in result.output
for relative, before in pages.items():
assert (root / relative).read_text(encoding="utf-8") == before, relative
assert docs_verify.check_reference_regions() == []
# --- a fixture tree -------------------------------------------------------
GROUPS = (("Fixture things", ("frobnicate",)), ("Other", ("gate keep",)))
@@ -127,15 +236,34 @@ HEADS = {
"docs/reference/commands/other.md": "# Other\n\nThe other one.\n",
"docs/reference/commands/README.md": "# Command reference\n\nAll of it.\n",
"docs/reference/exit-codes.md": "# Exit codes and gates\n\nWhere it stops.\n",
"docs/reference/skills.md": "# Skill catalogue\n\nThe skills.\n",
"docs/reference/page-types.md": "# Page types\n\nThe types.\n",
}
REGION_OF = {
"docs/reference/commands/fixture-things.md": reference_doc.COMMANDS_REGION,
"docs/reference/commands/other.md": reference_doc.COMMANDS_REGION,
"docs/reference/commands/README.md": reference_doc.INDEX_REGION,
"docs/reference/exit-codes.md": reference_doc.EXIT_CODES_REGION,
"docs/reference/skills.md": reference_doc.SKILLS_REGION,
"docs/reference/page-types.md": reference_doc.PAGE_TYPES_REGION,
}
def _page_type(description: str = "A fixture type.") -> reference_doc.PageType:
return reference_doc.PageType(
described={
"name": "gadget", "description": description, "subtype_field": "gadget_type",
"root": "kb", "base_dir": "gadgets", "title_prefix": None,
"fields": [
{"field": "type", "required": True, "type": "string", "enum": None},
{"field": "gadget_type", "required": True, "type": "string", "enum": ["widget"]},
{"field": "tags", "required": False, "type": "array", "enum": None},
],
},
subtypes=(reference_doc.Subtype("widget", "widgets", "Widgets"),),
)
@pytest.fixture
def tree(tmp_path, monkeypatch):
monkeypatch.setattr(config, "ROOT", tmp_path)
@@ -143,6 +271,11 @@ def tree(tmp_path, monkeypatch):
monkeypatch.setattr(cli_contract, "all_records", lambda: dict(records))
monkeypatch.setattr(cli_contract, "GROUPS", GROUPS)
monkeypatch.setattr(cli, "options_texts", lambda paths: {p: " --flag A flag." for p in paths})
monkeypatch.setattr(
reference_doc, "shipped_skills",
lambda: [reference_doc.Skill("fixture-skill", "Does a thing. Use when asked.")],
)
monkeypatch.setattr(reference_doc, "page_types", lambda: [_page_type()])
for relative, head in HEADS.items():
path = tmp_path / relative
path.parent.mkdir(parents=True, exist_ok=True)
@@ -286,3 +419,44 @@ def test_the_index_links_every_command_to_its_section(tree):
index = (root / "docs/reference/commands/README.md").read_text(encoding="utf-8")
assert "- [`frobnicate`](fixture-things.md#frobnicate) - Does a thing." in index
assert "- [`gate keep`](other.md#gate-keep) - Does a thing." in index
def test_a_changed_skill_or_type_makes_verify_name_its_page(tree, monkeypatch):
runner.invoke(app, ["docs", "reference", "--apply"])
assert docs_verify.check_reference_regions() == []
monkeypatch.setattr(
reference_doc, "shipped_skills",
lambda: [reference_doc.Skill("fixture-skill", "Does another thing. Use when asked.")],
)
monkeypatch.setattr(reference_doc, "page_types", lambda: [_page_type("A changed type.")])
issues = docs_verify.check_reference_regions()
for page in ("docs/reference/skills.md", "docs/reference/page-types.md"):
assert any(page in issue and "docs reference --apply" in issue for issue in issues), page
def test_the_fixture_skill_and_type_render(tree):
root, _ = tree
runner.invoke(app, ["docs", "reference", "--apply"])
skills = (root / "docs/reference/skills.md").read_text(encoding="utf-8")
assert "| `fixture-skill` | Does a thing. | asked. |" in skills
types = (root / "docs/reference/page-types.md").read_text(encoding="utf-8")
assert "- Written to: `kb/gadgets/<subtype directory>/`" in types
assert "| `widget` | `kb/gadgets/widgets/` | Widgets |" in types
assert "- `gadget_type` - string, one of `widget`" in types
assert "`tags`" not in types
def test_an_instance_skips_a_missing_skill_or_type_page(tree):
"""In an instance the two pages are not the tree's to check - not even
whether they exist."""
root, _ = tree
runner.invoke(app, ["docs", "reference", "--apply"])
(root / version_mod.RELEASE_STAMP_FILENAME).write_text("{}", encoding="utf-8")
(root / "docs/reference/skills.md").unlink()
(root / "docs/reference/page-types.md").write_text("# Page types\n", encoding="utf-8")
assert docs_verify.check_reference_regions() == []
result = runner.invoke(app, ["docs", "reference", "--apply"])
assert result.exit_code == 0, result.output
assert "Left alone in this instance: docs/reference/skills.md, docs/reference/page-types.md" in result.output
assert not (root / "docs/reference/skills.md").exists()