blob: 4c3a1641078b0de624cddaeeff8f7f083e91940d [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Tests for ``check-doc-sync.py``.
Every test builds a miniature repository in ``tmp_path`` and runs one check
against it. The point of each is the **red** case: a gate that cannot fail is
worse than no gate, because a green run reads as evidence while measuring
nothing. So each check is exercised in both directions — drift is reported, and
the corrected tree is silent.
The script's filename is hyphenated, which is not an importable module name, so
it is loaded through ``importlib.util``.
"""
from __future__ import annotations
import importlib.util
import os
from collections.abc import Iterator
from pathlib import Path
from types import ModuleType
import pytest
_SCRIPT = Path(__file__).resolve().parents[1] / "check-doc-sync.py"
def _load() -> ModuleType:
spec = importlib.util.spec_from_file_location("check_doc_sync", _SCRIPT)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
mod = _load()
@pytest.fixture
def repo(tmp_path: Path) -> Iterator[Path]:
"""A miniature repo, cd'd into — the script resolves paths relative to cwd."""
(tmp_path / "skills").mkdir()
(tmp_path / "tools" / "spec-loop" / "specs").mkdir(parents=True)
(tmp_path / "tools" / "dev").mkdir(parents=True)
(tmp_path / "docs" / "setup").mkdir(parents=True)
prev = Path.cwd()
os.chdir(tmp_path)
try:
yield tmp_path
finally:
os.chdir(prev)
def _skill(repo: Path, name: str, family: str, mode: str) -> None:
d = repo / "skills" / name
d.mkdir()
(d / "SKILL.md").write_text(
f"---\nname: magpie-{name}\nfamily: {family}\nmode: {mode}\n---\n\n# {name}\n",
encoding="utf-8",
)
def _errors(fn, *args) -> list[str]:
errs: list[str] = []
fn(errs, *args)
return errs
# ---------------------------------------------------------------------------
# 1. Spec-index completeness
# ---------------------------------------------------------------------------
def _specs(repo: Path, names: list[str], listed_in_overview: list[str], listed_in_readme: list[str]) -> None:
specs = repo / "tools" / "spec-loop" / "specs"
for n in names:
(specs / n).write_text(f"# {n}\n", encoding="utf-8")
(specs / "overview.md").write_text(
"\n".join(f"| Area | [{n}]({n}) |" for n in listed_in_overview), encoding="utf-8"
)
(specs / "README.md").write_text(
"\n".join(f"- [`{n}`]({n})," for n in listed_in_readme), encoding="utf-8"
)
def test_spec_listed_in_both_indexes_is_silent(repo: Path) -> None:
_specs(repo, ["adapters.md"], ["adapters.md"], ["adapters.md"])
assert _errors(mod.check_spec_index) == []
def test_spec_missing_from_overview_is_reported(repo: Path) -> None:
_specs(repo, ["adapters.md"], [], ["adapters.md"])
errs = _errors(mod.check_spec_index)
assert len(errs) == 1
assert "overview.md" in errs[0] and "adapters.md" in errs[0]
def test_spec_missing_from_readme_is_reported(repo: Path) -> None:
"""Listed in one index is not listed. This is the real-world shape: ten
specs were in neither, and a spec in only one reads as indexed."""
_specs(repo, ["adapters.md"], ["adapters.md"], [])
errs = _errors(mod.check_spec_index)
assert len(errs) == 1
assert "README.md" in errs[0]
def test_index_files_are_not_themselves_specs(repo: Path) -> None:
_specs(repo, [], [], [])
assert _errors(mod.check_spec_index) == []
# ---------------------------------------------------------------------------
# 2. Per-family counts in README.md
# ---------------------------------------------------------------------------
def _readme_family(repo: Path, family: str, declared: int) -> None:
(repo / "README.md").write_text(
"| Family | Type | Modes | Purpose | Detail |\n|---|---|---|---|---|\n"
f"| [**{family}**](docs/{family}/README.md) | opt-in | Triage | Does things. "
f"| {declared} skills, [`docs/{family}/`](docs/{family}/) |\n",
encoding="utf-8",
)
def test_matching_family_count_is_silent(repo: Path) -> None:
_skill(repo, "a", "security", "Triage")
_skill(repo, "b", "security", "Triage")
_readme_family(repo, "security", 2)
assert _errors(mod.check_readme_family_counts) == []
def test_stale_family_count_is_reported_with_both_numbers(repo: Path) -> None:
_skill(repo, "a", "security", "Triage")
_skill(repo, "b", "security", "Triage")
_skill(repo, "c", "security", "Drafting")
_readme_family(repo, "security", 2)
errs = _errors(mod.check_readme_family_counts)
assert len(errs) == 1
assert "says 2 skills" in errs[0] and "has 3" in errs[0]
def test_table_row_that_is_not_a_family_is_ignored(repo: Path) -> None:
"""The table carries rows whose bolded cell names no skill family. Those
must not be read as a family with a wrong count."""
_skill(repo, "a", "security", "Triage")
_readme_family(repo, "not-a-family", 99)
assert _errors(mod.check_readme_family_counts) == []
# ---------------------------------------------------------------------------
# 3. Per-mode counts in docs/modes.md
# ---------------------------------------------------------------------------
def _modes(repo: Path, rows: list[tuple[str, int]]) -> None:
body = "\n".join(f"| **{m}** | *(Agentic {m})* Does things. | stable | {n} |" for m, n in rows)
(repo / "docs" / "modes.md").write_text(
"# Modes\n\n## Modes at a glance\n\n"
"| Mode | Purpose | Status | Skill count |\n|---|---|---|---|\n" + body + "\n\n## Triage\n",
encoding="utf-8",
)
def test_matching_mode_count_is_silent(repo: Path) -> None:
_skill(repo, "a", "security", "Triage")
_modes(repo, [("Triage", 1)])
assert _errors(mod.check_modes_glance_counts) == []
def test_stale_mode_count_is_reported(repo: Path) -> None:
_skill(repo, "a", "security", "Triage")
_skill(repo, "b", "security", "Triage")
_modes(repo, [("Triage", 1)])
errs = _errors(mod.check_modes_glance_counts)
assert len(errs) == 1
assert "says 1 skills" in errs[0] and "has 2" in errs[0]
def test_mode_with_no_skills_must_declare_zero(repo: Path) -> None:
"""A deliberately-off mode declares 0. A non-zero count for a mode nothing
uses is drift, not an exemption."""
_skill(repo, "a", "security", "Triage")
_modes(repo, [("Triage", 1), ("Agentic Autonomous", 0)])
assert _errors(mod.check_modes_glance_counts) == []
_modes(repo, [("Triage", 1), ("Agentic Autonomous", 4)])
errs = _errors(mod.check_modes_glance_counts)
assert len(errs) == 1
assert "no skill declares that mode" in errs[0]
def test_missing_glance_table_is_not_an_error(repo: Path) -> None:
(repo / "docs" / "modes.md").write_text("# Modes\n\nNo glance table here.\n", encoding="utf-8")
assert _errors(mod.check_modes_glance_counts) == []
# ---------------------------------------------------------------------------
# 4. Catalogue totals in prose
# ---------------------------------------------------------------------------
def test_matching_total_is_silent(repo: Path) -> None:
(repo / "docs" / "setup" / "marketplace.md").write_text("Installs 2 skills.\n", encoding="utf-8")
assert _errors(mod.check_total_counts, 2) == []
def test_every_stale_total_is_reported_not_just_the_first(repo: Path) -> None:
(repo / "docs" / "setup" / "marketplace.md").write_text(
"Installs 71 skills.\nAll 71 skills load.\nThe 71 skills are namespaced.\n", encoding="utf-8"
)
errs = _errors(mod.check_total_counts, 74)
assert len(errs) == 3
assert all("says 71 skills" in e and "has 74" in e for e in errs)
def test_totals_check_reads_only_the_allowlist(repo: Path) -> None:
"""A doc outside the allowlist may legitimately count a subset — 'Nine
skills cover the staged path' — so a greedy scan would flag it forever."""
(repo / "docs" / "elsewhere.md").write_text("Nine skills cover the staged path.\n", encoding="utf-8")
assert _errors(mod.check_total_counts, 74) == []
# ---------------------------------------------------------------------------
# 5. Per-family plugin counts in the marketplace tables
# ---------------------------------------------------------------------------
def _plugin_table(repo: Path, rows: list[str], *, path: str = "docs/setup/marketplace.md") -> None:
target = repo / path
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(
"| Family plugin | Skills | ~Always-on tokens |\n|---|---|---|\n" + "\n".join(rows) + "\n",
encoding="utf-8",
)
def test_matching_plugin_counts_are_silent(repo: Path) -> None:
_skill(repo, "a", "security", "Triage")
_skill(repo, "b", "security", "Triage")
_skill(repo, "c", "pairing", "Pairing")
_plugin_table(repo, ["| `magpie-security` | 2 | ~0.6k |", "| `magpie-pairing` | 1 | ~0.3k |"])
assert _errors(mod.check_family_plugin_counts) == []
def test_stale_plugin_count_is_reported_with_both_numbers(repo: Path) -> None:
"""The drift this check was written for: `magpie-security` sat at 12 while
three more security skills had landed."""
_skill(repo, "a", "security", "Triage")
_skill(repo, "b", "security", "Triage")
_plugin_table(repo, ["| `magpie-security` | 12 | ~3.9k |"])
errs = _errors(mod.check_family_plugin_counts)
assert len(errs) == 1
assert "says 12 skills" in errs[0] and "has 2" in errs[0]
def test_plugin_naming_no_live_family_is_reported(repo: Path) -> None:
"""A renamed or deleted family leaves a row pointing at nothing — silence
there would let the table advertise an uninstallable plugin."""
_skill(repo, "a", "security", "Triage")
_plugin_table(repo, ["| `magpie-ghost` | 3 | ~0.9k |"])
errs = _errors(mod.check_family_plugin_counts)
assert len(errs) == 1
assert "names no live family" in errs[0]
def test_plugin_counts_are_checked_on_the_families_page_too(repo: Path) -> None:
"""The families table moved out of the walkthrough into its own page; the
count check followed it, and this is what proves it still bites there."""
_skill(repo, "a", "setup", "Triage")
_plugin_table(
repo,
["| `magpie-setup` | 9 | Sandbox, install |"],
path="docs/quick-start/families.md",
)
errs = _errors(mod.check_family_plugin_counts)
assert len(errs) == 1
assert "docs/quick-start/families.md" in errs[0]
def test_prose_mentioning_a_plugin_is_not_a_table_row(repo: Path) -> None:
"""Only a leading table cell counts — `magpie-security` named mid-sentence,
or in a bulleted trade-off list, carries no count to check."""
_skill(repo, "a", "security", "Triage")
(repo / "docs" / "setup" / "marketplace.md").write_text(
"Install `magpie-security` for 12 reasons.\n"
"- \u2705 `magpie-security` \u2248 3.9k always-on tokens.\n",
encoding="utf-8",
)
assert _errors(mod.check_family_plugin_counts) == []
# ---------------------------------------------------------------------------
# 6. Per-family skill counts on the family install pages
# ---------------------------------------------------------------------------
def _family_readme(repo: Path, directory: str, plugin_family: str, declared: int) -> None:
target = repo / "docs" / directory / "install.md"
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(
f"# Install & first runs\n\n"
f"Install just this family — one plugin, {declared} skills.\n\n"
f"```text\n/plugin install magpie-{plugin_family}@apache-magpie\n```\n",
encoding="utf-8",
)
def test_matching_family_readme_count_is_silent(repo: Path) -> None:
_skill(repo, "a", "pairing", "Pairing")
_skill(repo, "b", "pairing", "Pairing")
_family_readme(repo, "pairing", "pairing", 2)
assert _errors(mod.check_family_readme_counts) == []
def test_stale_family_readme_count_is_reported(repo: Path) -> None:
_skill(repo, "a", "pairing", "Pairing")
_family_readme(repo, "pairing", "pairing", 9)
errs = _errors(mod.check_family_readme_counts)
assert len(errs) == 1
assert "says 9 skills" in errs[0] and "has 1" in errs[0]
def test_family_is_read_from_the_install_command_not_the_directory(repo: Path) -> None:
"""`issue` documents itself under docs/issue-management/, so keying on the
directory name would report every one of its skills as missing."""
_skill(repo, "a", "issue", "Triage")
_family_readme(repo, "issue-management", "issue", 1)
assert _errors(mod.check_family_readme_counts) == []
def test_readme_without_an_install_section_is_skipped(repo: Path) -> None:
(repo / "docs" / "education").mkdir(parents=True)
(repo / "docs" / "education" / "README.md").write_text(
"# education\n\nNine skills cover the staged path.\n", encoding="utf-8"
)
assert _errors(mod.check_family_readme_counts) == []
def test_install_section_naming_a_dead_family_is_reported(repo: Path) -> None:
_skill(repo, "a", "pairing", "Pairing")
_family_readme(repo, "ghost", "ghost", 3)
errs = _errors(mod.check_family_readme_counts)
assert len(errs) == 1
assert "no live family" in errs[0]
# ---------------------------------------------------------------------------
# 7. No plugin-name stutter in the docs
# ---------------------------------------------------------------------------
def test_stuttering_invocation_is_reported(repo: Path) -> None:
"""`/magpie-security:security-issue-triage` is a command nobody can run —
the family plugin advertises the alias `issue-triage`."""
_skill(repo, "security-issue-triage", "security", "Triage")
(repo / "docs" / "guide.md").write_text(
"Run `/magpie-security:security-issue-triage` to triage.\n", encoding="utf-8"
)
errs = _errors(mod.check_no_plugin_name_stutter)
assert len(errs) == 1
assert "repeats the family name" in errs[0]
def test_first_segment_stutter_is_caught_too(repo: Path) -> None:
"""`release-*` skills sit in `release-management`, so the repeated token is
the family's first segment."""
_skill(repo, "release-vote-tally", "release-management", "Drafting")
(repo / "docs" / "guide.md").write_text(
"Run `/magpie-release-management:release-vote-tally`.\n", encoding="utf-8"
)
assert len(_errors(mod.check_no_plugin_name_stutter)) == 1
def test_the_dealiased_form_is_silent(repo: Path) -> None:
_skill(repo, "security-issue-triage", "security", "Triage")
(repo / "docs" / "guide.md").write_text(
"Run `/magpie-security:issue-triage` to triage.\n", encoding="utf-8"
)
assert _errors(mod.check_no_plugin_name_stutter) == []
def test_a_live_alias_starting_with_the_family_word_is_silent(repo: Path) -> None:
"""`magpie-pr-management` really ships `pr-triage` (a bare `triage` would
repeat `magpie-issue`'s), so naming it is not a stutter."""
_skill(repo, "pr-management-triage", "pr-management", "Triage")
(repo / "plugins" / "magpie-pr-management" / "skills" / "pr-triage").mkdir(parents=True)
(repo / "docs" / "guide.md").write_text("Run `/magpie-pr-management:pr-triage`.\n", encoding="utf-8")
assert _errors(mod.check_no_plugin_name_stutter) == []
def test_the_portable_single_token_form_is_not_a_stutter(repo: Path) -> None:
"""Snapshot installs really do invoke `/magpie-security-issue-triage`; the
guard must not chase the form it is documenting as correct elsewhere."""
_skill(repo, "security-issue-triage", "security", "Triage")
(repo / "docs" / "guide.md").write_text(
"Snapshot installs use `/magpie-security-issue-triage`.\n", encoding="utf-8"
)
assert _errors(mod.check_no_plugin_name_stutter) == []
def test_eval_fixtures_are_not_scanned(repo: Path) -> None:
"""Fixtures are expected outputs, not documentation."""
_skill(repo, "security-issue-triage", "security", "Triage")
fixture = repo / "tools" / "skill-evals" / "evals" / "x"
fixture.mkdir(parents=True)
(fixture / "expected.md").write_text("`/magpie-security:security-issue-triage`\n", encoding="utf-8")
assert _errors(mod.check_no_plugin_name_stutter) == []
def test_a_marked_line_may_show_the_stutter(repo: Path) -> None:
"""marketplace.md documents the anti-pattern, so it has to print one."""
_skill(repo, "security-issue-triage", "security", "Triage")
(repo / "docs" / "guide.md").write_text(
f"`/magpie-security:security-issue-triage` says security twice. {mod.STUTTER_ALLOW}\n",
encoding="utf-8",
)
assert _errors(mod.check_no_plugin_name_stutter) == []
def test_the_marker_only_exempts_its_own_line(repo: Path) -> None:
_skill(repo, "security-issue-triage", "security", "Triage")
(repo / "docs" / "guide.md").write_text(
f"bad example {mod.STUTTER_ALLOW}\n`/magpie-security:security-issue-triage`\n",
encoding="utf-8",
)
assert len(_errors(mod.check_no_plugin_name_stutter)) == 1
# ---------------------------------------------------------------------------
# 8. The portable single-token form must say which install it means
# ---------------------------------------------------------------------------
def _doc(repo: Path, body: str, name: str = "guide.md") -> None:
(repo / "docs" / name).write_text(body, encoding="utf-8")
def test_portable_form_without_a_note_is_reported(repo: Path) -> None:
"""The marketplace install is the default, so a bare `/magpie-issue-triage`
reads as a command the reader can run — and it is not."""
_skill(repo, "issue-triage", "issue", "Triage")
_doc(repo, "Run `/magpie-issue-triage` to triage.\n")
errs = _errors(mod.check_portable_form_is_flagged)
assert len(errs) == 1
assert "snapshot-install form" in errs[0]
def test_a_page_that_states_the_install_is_silent(repo: Path) -> None:
_skill(repo, "issue-triage", "issue", "Triage")
for note in mod.PORTABLE_FORM_NOTES:
_doc(repo, f"{note}. Run `/magpie-issue-triage`.\n")
assert _errors(mod.check_portable_form_is_flagged) == []
def test_the_setup_mechanism_is_never_flagged(repo: Path) -> None:
"""`/magpie-setup` names the install mechanism, not a skill a marketplace
user invokes, so it stays legal on every page."""
_skill(repo, "setup", "setup", "Triage")
_doc(repo, "Run `/magpie-setup update` to refresh the snapshot.\n")
assert _errors(mod.check_portable_form_is_flagged) == []
def test_a_filesystem_path_is_not_an_invocation(repo: Path) -> None:
"""`.agents/skills/magpie-<skill>/` appears in `test -f` assertions in the
spec files; flagging those would demand breaking them."""
_skill(repo, "issue-triage", "issue", "Triage")
_doc(repo, "test -f .agents/skills/magpie-issue-triage/SKILL.md\n")
assert _errors(mod.check_portable_form_is_flagged) == []
def test_allowlisted_pages_may_show_both_forms(repo: Path) -> None:
_skill(repo, "issue-triage", "issue", "Triage")
(repo / "docs" / "setup").mkdir(parents=True, exist_ok=True)
(repo / "docs" / "setup" / "marketplace.md").write_text(
"Portable: `/magpie-issue-triage`.\n", encoding="utf-8"
)
assert _errors(mod.check_portable_form_is_flagged) == []
# ---------------------------------------------------------------------------
# 9. Dev scripts are documented
# ---------------------------------------------------------------------------
def _dev(repo: Path, scripts: list[str], readme_names: list[str]) -> None:
dev = repo / "tools" / "dev"
for s in scripts:
(dev / s).write_text("#!/bin/sh\n", encoding="utf-8")
(dev / "README.md").write_text(
"\n".join(f"| [`{n}`]({n}) | does a thing |" for n in readme_names), encoding="utf-8"
)
def test_documented_scripts_are_silent(repo: Path) -> None:
_dev(repo, ["check-a.py", "check-b.sh"], ["check-a.py", "check-b.sh"])
assert _errors(mod.check_dev_scripts_documented) == []
def test_undocumented_script_is_reported(repo: Path) -> None:
_dev(repo, ["check-a.py", "check-b.sh"], ["check-a.py"])
errs = _errors(mod.check_dev_scripts_documented)
assert len(errs) == 1
assert "check-b.sh" in errs[0]
def test_non_script_files_are_not_required_to_be_documented(repo: Path) -> None:
_dev(repo, [], [])
(repo / "tools" / "dev" / "notes.txt").write_text("scratch\n", encoding="utf-8")
(repo / "tools" / "dev" / "pyproject.toml").write_text("[project]\n", encoding="utf-8")
assert _errors(mod.check_dev_scripts_documented) == []
# ---------------------------------------------------------------------------
# main()
# ---------------------------------------------------------------------------
def test_main_exits_2_outside_a_repo_root(repo: Path, capsys: pytest.CaptureFixture[str]) -> None:
(repo / "skills").rmdir()
assert mod.main() == 2
assert "run from the repository root" in capsys.readouterr().err
def test_main_exits_1_and_names_every_problem(repo: Path, capsys: pytest.CaptureFixture[str]) -> None:
_skill(repo, "a", "security", "Triage")
_readme_family(repo, "security", 9)
_modes(repo, [("Triage", 9)])
_specs(repo, ["adapters.md"], [], [])
_dev(repo, ["check-x.sh"], [])
(repo / "docs" / "setup" / "marketplace.md").write_text("Installs 9 skills.\n", encoding="utf-8")
assert mod.main() == 1
err = capsys.readouterr().err
for fragment in ("family 'security'", "mode 'Triage'", "adapters.md", "check-x.sh", "says 9 skills"):
assert fragment in err
def test_main_exits_0_on_a_consistent_tree(repo: Path, capsys: pytest.CaptureFixture[str]) -> None:
_skill(repo, "a", "security", "Triage")
_readme_family(repo, "security", 1)
_family_docs(repo, "security", "security")
_modes(repo, [("Triage", 1)])
_specs(repo, ["adapters.md"], ["adapters.md"], ["adapters.md"])
_dev(repo, ["check-x.sh"], ["check-x.sh"])
(repo / "docs" / "setup" / "marketplace.md").write_text("Installs 1 skills.\n", encoding="utf-8")
assert mod.main() == 0
assert "OK" in capsys.readouterr().out
# ---------------------------------------------------------------------------
# 11. Eval-case counts
# ---------------------------------------------------------------------------
def _evals(repo: Path, tree: dict[str, dict[str, int]]) -> None:
"""Build `evals/<family>/<suite>/fixtures/case-N/` dirs matching *tree*."""
for family, suites in tree.items():
for suite, n in suites.items():
fixtures = repo / "tools" / "skill-evals" / "evals" / family / suite / "fixtures"
fixtures.mkdir(parents=True)
for i in range(n):
(fixtures / f"case-{i + 1}-x").mkdir()
def _eval_family_readme(repo: Path, family: str, text: str) -> Path:
path = repo / "tools" / "skill-evals" / "evals" / family / "README.md"
path.write_text(text, encoding="utf-8")
return path
def _eval_index(repo: Path, text: str) -> Path:
path = repo / "tools" / "skill-evals" / "README.md"
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
return path
def test_eval_counts_clean(repo: Path) -> None:
_evals(repo, {"setup": {"alpha": 2, "beta": 3}})
_eval_family_readme(repo, "setup", "## Suites (5 cases total)\n\n| alpha | x | 2 |\n| beta | x | 3 |\n")
_eval_index(repo, "- **setup** — 5 cases across 2 steps (alpha, beta)\n")
assert _errors(mod.check_eval_counts) == []
def test_eval_counts_flags_stale_total(repo: Path) -> None:
_evals(repo, {"setup": {"alpha": 2, "beta": 3}})
_eval_family_readme(repo, "setup", "## Suites (4 cases total)\n\n| alpha | x | 2 |\n| beta | x | 3 |\n")
_eval_index(repo, "- **setup** — 5 cases across 2 steps (alpha, beta)\n")
errs = _errors(mod.check_eval_counts)
assert any("heading says 4 cases, 5 on disk" in e for e in errs)
def test_eval_counts_flags_stale_suite_row(repo: Path) -> None:
_evals(repo, {"setup": {"alpha": 2, "beta": 3}})
_eval_family_readme(repo, "setup", "## Suites (5 cases total)\n\n| alpha | x | 9 |\n| beta | x | 3 |\n")
_eval_index(repo, "- **setup** — 5 cases across 2 steps (alpha, beta)\n")
errs = _errors(mod.check_eval_counts)
assert any("suite 'alpha' declares 9 cases, 2 on disk" in e for e in errs)
def test_eval_counts_flags_stale_index_line(repo: Path) -> None:
"""The exact drift this check was written for: a hand-maintained total."""
_evals(repo, {"setup": {"alpha": 2, "beta": 3}})
_eval_family_readme(repo, "setup", "## Suites (5 cases total)\n")
_eval_index(repo, "- **setup** — 49 cases across 2 steps (alpha, beta)\n")
errs = _errors(mod.check_eval_counts)
assert any("says 49 cases across 2 steps, disk has 5 across 2" in e for e in errs)
def test_eval_counts_flags_unlisted_family(repo: Path) -> None:
_evals(repo, {"setup": {"alpha": 1}, "issue-triage": {"beta": 2}})
_eval_family_readme(repo, "setup", "## Suites (1 cases total)\n")
_eval_family_readme(repo, "issue-triage", "## Suites (2 cases total)\n")
_eval_index(repo, "- **setup** — 1 cases across 1 steps (alpha)\n")
errs = _errors(mod.check_eval_counts)
assert any("'issue-triage'" in e and "no entry" in e for e in errs)
def test_eval_counts_flags_listed_family_with_no_suites(repo: Path) -> None:
_evals(repo, {"setup": {"alpha": 1}})
_eval_family_readme(repo, "setup", "## Suites (1 cases total)\n")
_eval_index(
repo, "- **setup** — 1 cases across 1 steps (alpha)\n- **ghost** — 3 cases across 1 steps (x)\n"
)
errs = _errors(mod.check_eval_counts)
assert any("'ghost' is listed but has no eval suites on disk" in e for e in errs)
def test_eval_counts_readme_without_a_total_is_not_drift(repo: Path) -> None:
"""A README that declares no total makes no claim that can go stale."""
_evals(repo, {"setup": {"alpha": 2}})
_eval_family_readme(repo, "setup", "# setup evals\n\nProse, no headline total.\n")
_eval_index(repo, "- **setup** — 2 cases across 1 steps (alpha)\n")
assert _errors(mod.check_eval_counts) == []
def test_eval_counts_fix_rewrites_numbers(repo: Path) -> None:
_evals(repo, {"setup": {"alpha": 2, "beta": 3}})
readme = _eval_family_readme(
repo, "setup", "## Suites (4 cases total)\n\n| alpha | x | 9 |\n| beta | x | 3 |\n"
)
index = _eval_index(repo, "- **setup** — 49 cases across 7 steps (alpha, beta)\n")
errs = _errors(mod.check_eval_counts, True)
assert errs == []
assert "## Suites (5 cases total)" in readme.read_text()
assert "| alpha | x | 2 |" in readme.read_text()
assert "- **setup** — 5 cases across 2 steps (alpha, beta)" in index.read_text()
assert _errors(mod.check_eval_counts) == []
def test_eval_counts_fix_generates_missing_entry(repo: Path) -> None:
_evals(repo, {"setup": {"alpha": 1}, "issue-triage": {"beta": 2, "gamma": 1}})
_eval_family_readme(repo, "setup", "## Suites (1 cases total)\n")
_eval_family_readme(repo, "issue-triage", "## Suites (3 cases total)\n")
index = _eval_index(repo, "- **setup** — 1 cases across 1 steps (alpha)\n")
assert _errors(mod.check_eval_counts, True) == []
assert "- **issue-triage** — 3 cases across 2 suites (beta, gamma)" in index.read_text()
assert _errors(mod.check_eval_counts) == []
def test_eval_counts_fix_preserves_colon_separator(repo: Path) -> None:
"""One line uses ':' and 'suites'; --fix re-derives numbers, not wording."""
_evals(repo, {"pr-management-code-review": {"alpha": 2}})
_eval_family_readme(repo, "pr-management-code-review", "## Suites (2 cases total)\n")
index = _eval_index(
repo, "- **pr-management-code-review**: 99 cases across 27 suites (step-4-* checks)\n"
)
assert _errors(mod.check_eval_counts, True) == []
assert "- **pr-management-code-review**: 2 cases across 1 suites (step-4-* checks)" in index.read_text()
def test_eval_counts_fix_keeps_the_blank_line_below_the_heading(repo: Path) -> None:
"""Rewriting the heading must not eat the blank line the table needs.
With ``re.M``, a trailing ``\\s*$`` in the heading pattern also matches the
newlines after it, so the substitution silently joined the heading to the
table and tripped markdownlint's MD058.
"""
_evals(repo, {"setup": {"alpha": 2}})
readme = _eval_family_readme(
repo, "setup", "# setup evals\n\n## Suites (9 cases total)\n\n| alpha | x | 2 |\n"
)
_eval_index(repo, "- **setup** — 2 cases across 1 steps (alpha)\n")
assert _errors(mod.check_eval_counts, True) == []
assert "## Suites (2 cases total)\n\n| alpha" in readme.read_text()
# ---------------------------------------------------------------------------
# 12. The marketplace add lives on one page
# ---------------------------------------------------------------------------
def test_marketplace_add_outside_the_allowed_pages_is_reported(repo: Path) -> None:
setup = repo / "docs" / "setup"
setup.mkdir(parents=True, exist_ok=True)
(setup / "marketplace-install.md").write_text("/plugin marketplace add apache/magpie\n", encoding="utf-8")
(repo / "docs" / "pairing").mkdir(parents=True)
(repo / "docs" / "pairing" / "README.md").write_text(
"```text\n/plugin marketplace add apache/magpie\n```\n", encoding="utf-8"
)
errs = _errors(mod.check_marketplace_add_is_not_repeated)
assert len(errs) == 1
assert "docs/pairing/README.md" in errs[0]
def test_marketplace_add_in_the_allowed_pages_is_silent(repo: Path) -> None:
setup = repo / "docs" / "setup"
setup.mkdir(parents=True, exist_ok=True)
for name in ("marketplace-install.md", "marketplace.md"):
(setup / name).write_text("/plugin marketplace add apache/magpie\n", encoding="utf-8")
assert _errors(mod.check_marketplace_add_is_not_repeated) == []
def test_the_other_harnesses_add_commands_are_caught_too(repo: Path) -> None:
"""Claude Code is not the only one that repeats: Codex, Antigravity and apm each
have their own one-time add, and each was carried on several pages."""
setup = repo / "docs" / "setup"
setup.mkdir(parents=True, exist_ok=True)
(repo / "docs" / "utilities").mkdir(parents=True)
(repo / "docs" / "utilities" / "README.md").write_text(
"```bash\ncodex plugin marketplace add apache/magpie\n"
"agy plugin install https://github.com/apache/magpie\n"
"apm install apache/magpie\n```\n",
encoding="utf-8",
)
errs = _errors(mod.check_marketplace_add_is_not_repeated)
assert len(errs) == 3
def test_a_design_document_may_quote_the_command(repo: Path) -> None:
"""docs/designs/ records what was decided, including the commands a plan
told an implementer to write. Rewriting history to satisfy a linter would
make the record wrong."""
designs = repo / "docs" / "designs"
designs.mkdir(parents=True)
(designs / "2026-01-01-something.md").write_text(
"/plugin marketplace add apache/magpie\n", encoding="utf-8"
)
assert _errors(mod.check_marketplace_add_is_not_repeated) == []
# ---------------------------------------------------------------------------
# 13. Every skill family has the same documentation pages
# ---------------------------------------------------------------------------
def _family_docs(
repo: Path, directory: str, family: str, *, readme: str | None = None, install: str | None = None
) -> None:
docs = repo / "docs" / directory
docs.mkdir(parents=True, exist_ok=True)
(docs / "README.md").write_text(
readme
if readme is not None
else f"# {directory}\n\n## Install & first runs\n\n[Install & first runs](install.md) has it.\n",
encoding="utf-8",
)
(docs / "install.md").write_text(
install
if install is not None
else "# Install & first runs\n\nInstall just this family — one plugin, 1 skills.\n\n"
f"```text\n/plugin install magpie-{family}@apache-magpie\n```\n\n"
"## Before the first run\n\nNothing.\n\n## Try these first\n\nA run.\n",
encoding="utf-8",
)
def test_family_with_both_pages_is_silent(repo: Path) -> None:
_skill(repo, "a", "pairing", "Pairing")
_family_docs(repo, "pairing", "pairing")
assert _errors(mod.check_family_docs_layout) == []
def test_family_docs_dir_override_is_honoured(repo: Path) -> None:
_skill(repo, "a", "issue", "Triage")
_family_docs(repo, "issue-management", "issue")
assert _errors(mod.check_family_docs_layout) == []
def test_single_page_family_is_reported(repo: Path) -> None:
_skill(repo, "a", "pairing", "Pairing")
_family_docs(repo, "pairing", "pairing")
(repo / "docs" / "pairing" / "install.md").unlink()
errs = _errors(mod.check_family_docs_layout)
assert len(errs) == 1
assert "docs/pairing/install.md: missing" in errs[0]
def test_readme_without_install_pointer_is_reported(repo: Path) -> None:
_skill(repo, "a", "pairing", "Pairing")
_family_docs(repo, "pairing", "pairing", readme="# pairing\n\n## Skills\n")
errs = _errors(mod.check_family_docs_layout)
assert len(errs) == 1
assert "Install & first runs" in errs[0]
def test_install_page_sections_are_required_in_order(repo: Path) -> None:
_skill(repo, "a", "pairing", "Pairing")
_family_docs(
repo,
"pairing",
"pairing",
install="# Install & first runs\n\n/plugin install magpie-pairing@apache-magpie\n\n"
"## Try these first\n\nA run.\n\n## Before the first run\n\nNothing.\n",
)
errs = _errors(mod.check_family_docs_layout)
assert len(errs) == 1
assert "out of order" in errs[0]
def test_install_page_for_the_wrong_family_is_reported(repo: Path) -> None:
_skill(repo, "a", "pairing", "Pairing")
_family_docs(repo, "pairing", "mentoring")
errs = _errors(mod.check_family_docs_layout)
assert any("/plugin install magpie-pairing@" in e for e in errs)