Files
chemenu/tools/chemenu/tests/test_docs_verify.py
T
torben 95ab40827a
CI / verify (push) Successful in 52s
Release / release (push) Successful in 36s
docs: tools/CONTRACT.md als Nachschlage-Dokument strukturiert, AGENTS.md-Routing angepasst (schliesst #92)
Files changed:
- AGENTS.md
- CHANGES.md
- VERSION
- tools/CONTRACT.md
- tools/chemenu/tests/test_docs_verify.py
2026-09-11 12:51:04 +02:00

651 lines
27 KiB
Python

import pytest
import typer
from chemenu import config
from chemenu.commands import dist_cmd, docs_verify
def test_every_registered_command_is_documented():
"""Forward direction: a command added to the CLI without a README row is
exactly the drift this check exists to catch."""
assert docs_verify.check_cli_readme() == []
def test_registered_commands_include_groups_and_top_level():
commands = docs_verify.registered_commands()
assert "new" in commands
assert "touch" in commands
assert "xref add" in commands
assert "migrate verify" in commands
assert "docs verify" in commands
def test_undocumented_command_is_reported(monkeypatch):
monkeypatch.setattr(
docs_verify, "registered_commands", lambda: {"new", "frobnicate"}
)
monkeypatch.setattr(docs_verify, "top_level_names", lambda: {"new", "frobnicate"})
issues = docs_verify.check_cli_readme()
assert any("frobnicate" in issue for issue in issues)
def test_documented_but_nonexistent_command_is_reported(monkeypatch):
monkeypatch.setattr(docs_verify, "registered_commands", lambda: set())
monkeypatch.setattr(docs_verify, "top_level_names", lambda: set())
issues = docs_verify.check_cli_readme()
assert any("is not a wikitool command" in issue for issue in issues)
def test_invented_subcommand_under_a_real_group_is_caught(tmp_path, monkeypatch):
"""Regression guard: checking only the first token (`xref`) let a typo'd
or invented subcommand sit undetected forever next to a real command
group. The reverse check must match the full registered path, not just
the top-level word."""
fake = tmp_path / "README.md"
fake.write_text(
"## Commands\n\n"
"| `xref frobnicate --a X --b Y` | does not exist |\n\n"
"## Error contracts\n\n"
"| `xref frobnicate --a X --b Y` | does not exist |\n",
encoding="utf-8",
)
monkeypatch.setattr(docs_verify, "CLI_README", fake)
monkeypatch.setattr(docs_verify, "registered_commands", lambda: {"xref add", "xref remove"})
issues = docs_verify.check_cli_readme()
assert any("xref frobnicate" in issue for issue in issues)
# --- section-scoped § Commands vs. § Error contracts (Gitea #91) ------------
def test_section_text_extracts_between_headings():
text = "# T\n\n## A\n\nfoo\n\n## B\n\nbar\n"
assert docs_verify.section_text(text, "## A").strip() == "foo"
def test_section_text_extends_to_end_of_file_when_last():
text = "# T\n\n## A\n\nfoo\nbar\n"
assert docs_verify.section_text(text, "## A").strip() == "foo\nbar"
def test_section_text_raises_on_missing_heading():
with pytest.raises(ValueError):
docs_verify.section_text("# T\n\nno headings here\n", "## Commands")
def _fake_contract(tmp_path, commands_rows: str, error_rows: str):
fake = tmp_path / "CONTRACT.md"
fake.write_text(
f"## Commands\n\n{commands_rows}\n## Error contracts\n\n{error_rows}\n",
encoding="utf-8",
)
return fake
def test_a_row_deleted_from_commands_is_caught_even_if_error_contracts_still_has_it(
tmp_path, monkeypatch
):
"""Regression for the bug the section split fixes: before, a name
surviving in either table hid its own deletion from the other, so
§ Commands losing a row was invisible as long as § Error contracts still
named it."""
fake = _fake_contract(
tmp_path,
commands_rows="| Command | Purpose |\n", # `frobnicate`'s row was deleted here
error_rows="| `frobnicate` | never | yes | retry |\n",
)
monkeypatch.setattr(docs_verify, "CLI_README", fake)
monkeypatch.setattr(docs_verify, "registered_commands", lambda: {"frobnicate"})
issues = docs_verify.check_cli_readme()
assert any(
"frobnicate" in issue and "§ Commands" in issue and "not documented" in issue
for issue in issues
)
def test_error_contracts_is_enforced_against_registered_commands(tmp_path, monkeypatch):
"""Before the split, § Error contracts was never itself compared against
the registered commands - a row missing there was invisible."""
fake = _fake_contract(
tmp_path,
commands_rows="| `frobnicate` | does things |\n",
error_rows="| Command | Exit 1 means | Atomic? | Retry policy |\n",
)
monkeypatch.setattr(docs_verify, "CLI_README", fake)
monkeypatch.setattr(docs_verify, "registered_commands", lambda: {"frobnicate"})
issues = docs_verify.check_cli_readme()
assert any(
"frobnicate" in issue and "§ Error contracts" in issue and "not documented" in issue
for issue in issues
)
def test_a_phantom_error_contract_row_is_reported(tmp_path, monkeypatch):
"""The reverse direction inside § Error contracts: a row for a command
that does not exist must be reported there too, not only in § Commands."""
fake = _fake_contract(
tmp_path,
commands_rows="| `frobnicate` | does things |\n",
error_rows="| `frobnicate` | never | yes | retry |\n| `ghost command` | never happened | no | n/a |\n",
)
monkeypatch.setattr(docs_verify, "CLI_README", fake)
monkeypatch.setattr(docs_verify, "registered_commands", lambda: {"frobnicate"})
issues = docs_verify.check_cli_readme()
assert any(
"ghost command" in issue and "§ Error contracts" in issue and "not a wikitool command" in issue
for issue in issues
)
def test_a_renamed_commands_heading_is_reported_not_silently_scanned(tmp_path, monkeypatch):
"""A renamed or removed `## Commands` heading must fail loudly - falling
back to scanning the whole file would make the two tables indistinguishable
again, which is the exact bug this check exists to prevent."""
fake = tmp_path / "CONTRACT.md"
fake.write_text(
"## Kommandos\n\n"
"| `frobnicate` | does things |\n\n"
"## Error contracts\n\n"
"| `frobnicate` | never | yes | retry |\n",
encoding="utf-8",
)
monkeypatch.setattr(docs_verify, "CLI_README", fake)
monkeypatch.setattr(docs_verify, "registered_commands", lambda: {"frobnicate"})
issues = docs_verify.check_cli_readme()
assert any("no '## Commands' heading found" in issue for issue in issues)
assert not any("§ Commands" in issue and "not documented" in issue for issue in issues)
def test_a_renamed_error_contracts_heading_is_reported_not_silently_scanned(tmp_path, monkeypatch):
fake = tmp_path / "CONTRACT.md"
fake.write_text(
"## Commands\n\n"
"| `frobnicate` | does things |\n\n"
"## Fehlerkontrakte\n\n"
"| `frobnicate` | never | yes | retry |\n",
encoding="utf-8",
)
monkeypatch.setattr(docs_verify, "CLI_README", fake)
monkeypatch.setattr(docs_verify, "registered_commands", lambda: {"frobnicate"})
issues = docs_verify.check_cli_readme()
assert any("no '## Error contracts' heading found" in issue for issue in issues)
assert not any("§ Error contracts" in issue and "not documented" in issue for issue in issues)
def test_a_section_runs_on_past_its_own_subheadings():
"""`###` groups inside a section must not end it. Both tables are split
into per-group subsections, each with its own table header, so a lookahead
that stopped at any `#` would truncate § Commands at its first group and
report every later command as undocumented."""
text = (
"## Commands\n\n"
"### Pages\n\n| `alpha` | does things |\n\n"
"### Git\n\n| `beta` | does other things |\n\n"
"## Error contracts\n\n| `gamma` | never | yes | retry |\n"
)
section = docs_verify.section_text(text, "## Commands")
assert docs_verify.documented_commands(section) == ["alpha", "beta"]
assert "gamma" not in section
def test_grouped_tables_are_still_checked_in_both_directions(tmp_path, monkeypatch):
"""The section split and the `###` grouping compose: each table is still
read as one pot of rows within its own section, however many subsections
and table headers it is broken into."""
fake = _fake_contract(
tmp_path,
commands_rows=(
"### Pages\n\n| Command | Purpose |\n| `alpha` | does things |\n\n"
"### Git\n\n| Command | Purpose |\n" # `beta`'s row was deleted here
),
error_rows=(
"### Pages\n\n| `alpha` | never | yes | retry |\n\n"
"### Git\n\n| `beta` | never | yes | retry |\n"
),
)
monkeypatch.setattr(docs_verify, "CLI_README", fake)
monkeypatch.setattr(docs_verify, "registered_commands", lambda: {"alpha", "beta"})
issues = docs_verify.check_cli_readme()
assert any(
"beta" in issue and "§ Commands" in issue and "not documented" in issue
for issue in issues
)
assert not any("§ Error contracts" in issue for issue in issues)
def test_collection_contracts_exist():
assert docs_verify.check_collection_contracts() == []
def test_readmes_carry_no_command_table():
"""A derived copy is checked or absent: the command table is checked in
tools/CONTRACT.md, so no README may hold a second one."""
assert docs_verify.check_readmes_have_no_command_table() == []
def test_a_command_table_in_the_root_readme_is_reported(tmp_path, monkeypatch):
fake = tmp_path / "README.md"
fake.write_text("| Command | Purpose |\n| `lint` | does things |\n", encoding="utf-8")
monkeypatch.setattr(docs_verify, "ROOT_README", fake)
issues = docs_verify.check_readmes_have_no_command_table()
assert any("`lint`" in issue for issue in issues)
def test_non_command_tables_in_the_root_readme_are_allowed(tmp_path, monkeypatch):
fake = tmp_path / "README.md"
fake.write_text("| Skill | Purpose |\n| `wiki-ingest` | ingests |\n", encoding="utf-8")
monkeypatch.setattr(docs_verify, "ROOT_README", fake)
assert docs_verify.check_readmes_have_no_command_table() == []
def test_stage_readmes_are_checked_too(tmp_path, monkeypatch):
"""tools/README.md is the file the command table actually drifted in - a
stage README is allowed to exist, but not to hold a second copy."""
root = tmp_path
(root / "tools").mkdir()
(root / "tools" / "README.md").write_text(
"| Command | Purpose |\n| `publish` | pushes |\n", encoding="utf-8"
)
monkeypatch.setattr(docs_verify.config, "ROOT", root)
monkeypatch.setattr(docs_verify, "ROOT_README", root / "README.md")
issues = docs_verify.check_readmes_have_no_command_table()
assert any("`publish`" in issue for issue in issues)
def test_install_md_is_checked_too(tmp_path, monkeypatch):
"""INSTALL.md is human-facing prose about installing an instance - the
command reference lives exactly once, in tools/CONTRACT.md."""
root = tmp_path
(root / "INSTALL.md").write_text(
"| Command | Purpose |\n| `doctor` | checks things |\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("`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():
assert docs_verify.check_legacy_type_blocks() == []
def test_legacy_type_regex_matches_pre_migration_form():
assert docs_verify.LEGACY_TYPE_RE.search("---\ntype: comparison\ntags: []\n---")
assert not docs_verify.LEGACY_TYPE_RE.search("---\ntype: types/comparison.md\n---")
def test_no_content_is_gitignored():
"""The regression guard for the 2026-08-13 `.gitignore` rewrite: patterns
like `*temp*` and `bin/` were silently excluding files under raw/, so the
wiki reported them as covered while `publish` never committed them."""
assert docs_verify.check_ignored_content() == []
def test_ignore_canaries_are_clear():
assert docs_verify.ignored_canaries() == []
def test_a_swallowed_canary_is_reported():
"""`tools/.wikitool_session/` is legitimately ignored, so it stands in for
a content path that a bad pattern would swallow."""
swallowed = docs_verify.ignored_canaries(("tools/.wikitool_session/budget.json",))
assert swallowed == ["tools/.wikitool_session/budget.json"]
def test_incoming_inbox_is_ignored():
"""Unlike raw/, a file under incoming/ must never be committed - promotion
via `wikitool raw accept` is what makes it immutable, not the drop."""
assert docs_verify.ignored_canaries(("incoming/documents/probe.pdf",)) == [
"incoming/documents/probe.pdf"
]
def test_the_environment_note_is_ignored_but_its_template_is_not():
"""The pattern has to split a file from its own template. `ENVIRONMENT.md`
describes one checkout and must never be committed; `ENVIRONMENT.md.template`
is tracked machinery that `dist export` ships, and the careless pattern
(`ENVIRONMENT.md*`) would swallow both."""
assert docs_verify.ignored_canaries(("ENVIRONMENT.md",)) == ["ENVIRONMENT.md"]
assert docs_verify.ignored_canaries(("ENVIRONMENT.md.template",)) == []
def test_coverage_output_is_ignored():
"""`pytest --cov` writes into tools/, and `publish` runs `git add -A`."""
paths = ("tools/coverage.xml", "tools/htmlcov/index.html", "tools/.coverage")
assert docs_verify.ignored_canaries(paths) == list(paths)
def test_ignore_checks_degrade_when_git_is_unavailable(monkeypatch):
"""Without git the ignore rules are unknowable, not wrong - `docs verify`
must stay usable rather than reporting a false positive."""
monkeypatch.setattr(docs_verify, "_git", lambda *a, **k: None)
assert docs_verify.check_ignored_content() == []
def test_this_repos_version_and_changelog_agree():
assert docs_verify.check_version_changelog() == []
def _versioned_tree(tmp_path, monkeypatch, version: str, changes: str):
(tmp_path / "VERSION").write_text(version, encoding="utf-8")
(tmp_path / "CHANGES.md").write_text(changes, encoding="utf-8")
monkeypatch.setattr(docs_verify.config, "ROOT", tmp_path)
def test_a_bump_with_no_changelog_entry_is_reported(tmp_path, monkeypatch):
"""The check that gives `version bump` its teeth: a version raised with
nothing written about it would ship release notes describing the
previous release."""
_versioned_tree(tmp_path, monkeypatch, "0.2.0\n", "# Changelog\n\n## 0.1.0 - 2026-08-29 - Old\n")
issues = docs_verify.check_version_changelog()
assert any("0.2.0" in issue and "0.1.0" in issue for issue in issues)
def test_a_changelog_with_no_versioned_entry_is_accepted(tmp_path, monkeypatch):
"""A fresh distribution ships an empty changelog, and this repo's own
pre-versioning entries are dated rather than versioned. Neither claims to
describe the current version."""
_versioned_tree(
tmp_path, monkeypatch, "0.1.0\n", "# Changelog\n\n## 2026-08-01 - Before versioning\n"
)
assert docs_verify.check_version_changelog() == []
def test_a_missing_or_malformed_version_is_reported(tmp_path, monkeypatch):
monkeypatch.setattr(docs_verify.config, "ROOT", tmp_path)
assert any("VERSION" in issue for issue in docs_verify.check_version_changelog())
_versioned_tree(tmp_path, monkeypatch, "not-a-version\n", "# Changelog\n")
assert any("semantic version" in issue for issue in docs_verify.check_version_changelog())
def test_this_repos_boundary_is_accounted_for():
assert docs_verify.check_migration_for_boundary() == []
def _boundary_tree(tmp_path, monkeypatch, current: str, previous: str, marker: str = ""):
"""A changelog with `current` as the topmost entry and `previous` as the
last release beneath it. `current` is normally an open candidate
(`2.0.0-beta.1`) - the checks compare the newest entry against the **last
release** (`version_mod.last_release`), which skips right past a topmost
entry that is itself already a release (that one's crossing, if any, was
already checked while it was still the open candidate)."""
(tmp_path / "VERSION").write_text(f"{current}\n", encoding="utf-8")
(tmp_path / "CHANGES.md").write_text(
"# Changelog\n\n---\n\n"
f"## {current} - 2026-09-01 - New\n\n{marker}Body.\n\n---\n\n"
f"## {previous} - 2026-08-30 - Old\n\nBody.\n",
encoding="utf-8",
)
instructions = tmp_path / "instructions"
(instructions / "migrations").mkdir(parents=True, exist_ok=True)
monkeypatch.setattr(docs_verify.config, "ROOT", tmp_path)
monkeypatch.setattr(docs_verify.config, "INSTRUCTIONS_DIR", instructions)
return tmp_path
def test_a_breaking_release_without_a_migration_is_reported(tmp_path, monkeypatch):
"""`version check` tells an instance it must migrate; without this, that is
where the trail ends."""
_boundary_tree(tmp_path, monkeypatch, "2.0.0-beta.1", "1.4.0")
issues = docs_verify.check_migration_for_boundary()
assert any("2.0.0-beta.1" in issue and "must migrate" in issue for issue in issues)
def test_a_compatible_release_needs_no_migration(tmp_path, monkeypatch):
_boundary_tree(tmp_path, monkeypatch, "1.5.0-beta.1", "1.4.0")
assert docs_verify.check_migration_for_boundary() == []
def test_a_fixed_release_is_never_re_checked_against_its_own_crossing(tmp_path, monkeypatch):
"""Regression for finding #2: comparing against the entry *beneath* the
newest one (rather than the last release) would find no boundary between
two betas of the same candidate - and would also, wrongly, re-flag an
already-fixed release forever. Once `current` is itself a release,
`last_release` returns it directly, so there is nothing left to compare."""
_boundary_tree(tmp_path, monkeypatch, "2.0.0", "1.4.0")
assert docs_verify.check_migration_for_boundary() == []
assert docs_verify.check_breaking_change_for_boundary() == []
def test_an_explicit_none_required_marker_satisfies_the_check(tmp_path, monkeypatch):
from chemenu import version as version_mod
_boundary_tree(
tmp_path, monkeypatch, "2.0.0-beta.1", "1.4.0",
marker=f"{version_mod.MIGRATION_NONE_MARKER} - nothing to change.\n\n",
)
assert docs_verify.check_migration_for_boundary() == []
def test_a_migration_document_satisfies_the_check(tmp_path, monkeypatch):
"""The document targets the candidate's *base* (`2.0.0`), not its full
pre-release form - matching what `version bump` looks for."""
root = _boundary_tree(tmp_path, monkeypatch, "2.0.0-beta.1", "1.4.0")
(root / "instructions" / "migrations" / "2.0.0-retype.md").write_text(
"---\ntype: types/instruction.md\nname: 2.0.0-retype\n"
"description: Retype.\nmanual: true\nmigrates_to: 2.0.0\n---\n",
encoding="utf-8",
)
assert docs_verify.check_migration_for_boundary() == []
def test_a_breaking_release_without_a_breaking_note_is_reported(tmp_path, monkeypatch):
"""A crossing that migrates nothing still leaves hand-work behind, so the
migration check passing is not evidence that anyone was told."""
from chemenu import version as version_mod
_boundary_tree(
tmp_path, monkeypatch, "2.0.0-beta.1", "1.4.0",
marker=f"{version_mod.MIGRATION_NONE_MARKER} - nothing to change.\n\n",
)
assert docs_verify.check_migration_for_boundary() == []
issues = docs_verify.check_breaking_change_for_boundary()
assert any("2.0.0-beta.1" in issue and "drop-in" in issue for issue in issues)
def test_a_compatible_release_needs_no_breaking_note(tmp_path, monkeypatch):
_boundary_tree(tmp_path, monkeypatch, "1.5.0-beta.1", "1.4.0")
assert docs_verify.check_breaking_change_for_boundary() == []
def test_a_breaking_change_marker_satisfies_the_check(tmp_path, monkeypatch):
from chemenu import version as version_mod
_boundary_tree(
tmp_path, monkeypatch, "2.0.0-beta.1", "1.4.0",
marker=f"{version_mod.BREAKING_CHANGE_MARKER} the feed moved.\n\n",
)
assert docs_verify.check_breaking_change_for_boundary() == []
def test_verify_raises_when_a_boundary_has_no_breaking_note(monkeypatch):
monkeypatch.setattr(
docs_verify, "check_breaking_change_for_boundary", lambda: ["unannounced"]
)
with pytest.raises(typer.Exit):
docs_verify.verify()
def test_verify_raises_when_a_boundary_has_no_migration(monkeypatch):
monkeypatch.setattr(docs_verify, "check_migration_for_boundary", lambda: ["unbridged"])
with pytest.raises(typer.Exit):
docs_verify.verify()
def test_verify_raises_when_the_version_is_undocumented(monkeypatch):
monkeypatch.setattr(docs_verify, "check_version_changelog", lambda: ["undocumented"])
with pytest.raises(typer.Exit):
docs_verify.verify()
def test_verify_raises_when_issues_exist(monkeypatch):
monkeypatch.setattr(docs_verify, "check_cli_readme", lambda: ["boom"])
with pytest.raises(typer.Exit):
docs_verify.verify()
def test_verify_raises_when_content_is_ignored(monkeypatch):
monkeypatch.setattr(docs_verify, "check_ignored_content", lambda: ["swallowed"])
with pytest.raises(typer.Exit):
docs_verify.verify()
# --- issue references in shipped documents -----------------------------------
def test_every_reference_files_toc_is_current():
"""Forward direction, against the real tree: every file `toc.target_files()`
covers must already carry the region `wikitool docs toc --apply` would
write - this is what a session forgetting to re-run it after adding a
heading is caught by."""
assert docs_verify.check_toc_regions() == []
def test_no_shipped_document_cites_an_issue():
"""Forward direction, against the real tree: a `#42` in a file `dist export`
ships points at a board only the origin repo has, and the reader of a
distributed instance can neither resolve it nor tell that it is
unresolvable."""
assert docs_verify.check_no_issue_references() == []
def test_a_cited_issue_number_is_reported(monkeypatch):
monkeypatch.setattr(
docs_verify,
"shipped_prose",
lambda: {"instructions/example.md": "A rule.\nRemoved in Gitea #66.\n"},
)
issues = docs_verify.check_no_issue_references()
assert len(issues) == 1
assert "instructions/example.md:2" in issues[0]
assert "#66" in issues[0]
def test_a_markdown_anchor_is_not_an_issue_reference(monkeypatch):
"""An ordinary heading's slug is word characters, so it never collides
with `#<digits>` in the first place - this pins that the lookbehind
exclusion does not accidentally start matching it either."""
monkeypatch.setattr(
docs_verify,
"shipped_prose",
lambda: {
"AGENTS.md": (
"# Heading\n"
"See [Gates](#gates) and [Collections](kb/CONTRACT.md#collections).\n"
"Exit code 42 means a human must look.\n"
)
},
)
assert docs_verify.check_no_issue_references() == []
def test_a_numbered_heading_anchor_is_not_an_issue_reference(monkeypatch):
"""A table-of-contents entry for a numbered step (`toc.py`) anchors on
the number itself - `#2-fix-the-fidelity-before-writing-a-word` - unlike
an ordinary heading's slug, which starts with a letter. The bare
`#\\d+` pattern would flag that as citing issue #2; the lookbehind
excludes exactly the `](#...` link-fragment shape it appears in."""
monkeypatch.setattr(
docs_verify,
"shipped_prose",
lambda: {
"instructions/example.md": (
"# Heading\n"
"- [2. Fix the fidelity before writing a word](#2-fix-the-fidelity-before-writing-a-word)\n"
)
},
)
assert docs_verify.check_no_issue_references() == []
def test_a_parenthesized_issue_number_is_still_reported(monkeypatch):
"""The lookbehind excludes `](#...`, not bare `(#...` - a real citation
written as a plain parenthetical must still be caught."""
monkeypatch.setattr(
docs_verify,
"shipped_prose",
lambda: {"instructions/example.md": "A rule (#66).\n"},
)
issues = docs_verify.check_no_issue_references()
assert len(issues) == 1
assert "#66" in issues[0]
def test_python_source_is_out_of_scope():
"""The scope decision, pinned: `tools/` ships as runtime machinery, and a
code comment addresses whoever edits that line - which only ever happens in
the origin repo, because `dist export` prunes the `stack-dev` skill with the
rest of `instructions/dev/`. So a `.py` file is in the export plan and out
of the scanned set."""
plan = dist_cmd.build_plan()
scanned = docs_verify.shipped_prose()
assert "tools/chemenu/commands/raw_cmd.py" in plan
assert not [path for path in scanned if path.endswith(".py")]
# ...while the prose beside it is scanned, templates included.
assert "tools/CONTRACT.md" in scanned
assert "instructions/CONTRACT.md" in scanned
assert "types/source.schema.yaml.template" in scanned
def test_a_strip_marked_pointer_never_reaches_the_check():
"""The sanctioned way to keep a pointer that is worth having here and
meaningless anywhere else. `shipped_prose` reads the export plan, whose text
already has its marker regions removed, so a number inside a marker block is
present in the working tree and absent from what ships.
Asserted over whichever files carry a marker today rather than a named one,
so retiring any single passage does not fail this for an unrelated reason -
what is pinned is that the mechanism works, not where it is used.
"""
shipped = docs_verify.shipped_prose()
# Destination paths only coincide with source paths where nothing was
# re-keyed (`types/*.md` ships as `.template`), so the ones that do not
# exist in the tree are simply not this test's subject.
sources = {
path: source.read_text(encoding="utf-8")
for path in shipped
if (source := config.ROOT / path).is_file()
}
hidden = {
path: source
for path, source in sources.items()
if "dist:strip-start" in source and docs_verify.ISSUE_REFERENCE_RE.search(source)
}
assert hidden, "no shipped document keeps an issue number behind a strip marker"
for path, source in hidden.items():
assert docs_verify.ISSUE_REFERENCE_RE.search(source)
assert not docs_verify.ISSUE_REFERENCE_RE.search(shipped[path]), path
def test_verify_raises_when_a_shipped_document_cites_an_issue(monkeypatch):
monkeypatch.setattr(docs_verify, "check_no_issue_references", lambda: ["cited"])
with pytest.raises(typer.Exit):
docs_verify.verify()