Files
chemenu/tools/chemenu/tests/test_instructions_cmd.py
T
torben 0fb8fd6122
CI / verify (push) Successful in 52s
Release / release (push) Successful in 36s
stack: SKILL.md-Links auf repo-root-relative Pfade umgestellt, docs verify/instructions verify pruefen Linkziele
Files changed:
- CHANGES.md
- VERSION
- instructions/CONTRACT.md
- instructions/dev/doc-pull-through.md
- instructions/dev/stack-close/SKILL.md
- instructions/dev/stack-dev/SKILL.md
- instructions/wiki-ingest/SKILL.md
- instructions/wiki-lint/SKILL.md
- instructions/wiki-manage/SKILL.md
- instructions/wiki-query/SKILL.md
- instructions/wiki-status/SKILL.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
2026-09-12 23:21:45 +02:00

656 lines
27 KiB
Python

"""Tests for the instruction layer: discovery, publication by copy, and verify.
Publication is the interesting half. `instructions/<name>/SKILL.md` is the
source; `.agents/skills/` and `.claude/skills/` are gitignored copies. A copy
can go stale where a symlink could not, so the drift check is what pays for
choosing copies - these tests hold it in place.
"""
from pathlib import Path
import pytest
import typer
from chemenu import config
from chemenu.tests.conftest import use_shipped_type_specs
from chemenu.commands import instructions_cmd
@pytest.fixture
def layer(tmp_path: Path, monkeypatch):
"""A self-contained instructions/ layer plus its two publish targets."""
root = tmp_path
instructions = root / "instructions"
(instructions / "wiki-demo").mkdir(parents=True)
(instructions / "wiki-demo" / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: Demo skill.\n---\n\n# Demo\n\nSee gates.md.\n",
encoding="utf-8",
)
(instructions / "CONTRACT.md").write_text("# instructions/ - Contract\n", encoding="utf-8")
(instructions / "gates.md").write_text(
"---\ntype: types/instruction.md\nname: gates\ndescription: What to do when a gate refuses.\n---\n\n# Gates\n",
encoding="utf-8",
)
(root / "AGENTS.md").write_text("# AGENTS\n", encoding="utf-8")
(root / "kb").mkdir()
monkeypatch.setattr(config, "ROOT", root)
monkeypatch.setattr(config, "KB_DIR", root / "kb")
monkeypatch.setattr(config, "INSTRUCTIONS_DIR", instructions)
monkeypatch.setattr(config, "AGENTS_SKILLS_DIR", root / ".agents" / "skills")
monkeypatch.setattr(config, "CLAUDE_SKILLS_DIR", root / ".claude" / "skills")
# `verify` resolves each instruction's `type: types/instruction.md`, and the
# resolver's root follows `ROOT` now - so the shipped specs have to be named
# rather than inherited. See `use_shipped_type_specs`.
use_shipped_type_specs(monkeypatch)
return root
def _skill_copy(root: Path, harness: str, name: str = "wiki-demo") -> Path:
return root / harness / "skills" / name
# --- discovery --------------------------------------------------------------
def test_a_directory_with_a_skill_md_is_a_skill(layer):
assert [p.name for p in instructions_cmd.skill_dirs()] == ["wiki-demo"]
def test_a_flat_file_is_an_instruction_and_the_contract_is_not(layer):
assert [p.name for p in instructions_cmd.instruction_files()] == ["gates.md"]
def test_the_real_repo_publishes_the_six_wiki_skills():
"""Guards the actual layout, not a fixture: these are the skills the
harness is expected to offer. `stack-dev` is nested under
instructions/dev/, discovered the same way as the five top-level ones."""
names = {p.name for p in instructions_cmd.skill_dirs()}
assert {
"wiki-ingest",
"wiki-query",
"wiki-lint",
"wiki-manage",
"wiki-status",
"stack-dev",
} <= names
def test_instructions_dev_flat_file_is_discovered(layer):
dev_dir = layer / "instructions" / "dev"
dev_dir.mkdir()
(dev_dir / "compiler-notes.md").write_text(
"---\ntype: types/instruction.md\nname: compiler-notes\ndescription: x\n---\n",
encoding="utf-8",
)
assert "compiler-notes.md" in {p.name for p in instructions_cmd.instruction_files()}
def test_instructions_dev_nested_skill_is_discovered(layer):
skill_dir = layer / "instructions" / "dev" / "stack-dev"
skill_dir.mkdir(parents=True)
(skill_dir / "SKILL.md").write_text("---\nname: stack-dev\ndescription: x\n---\n", encoding="utf-8")
assert "stack-dev" in {p.name for p in instructions_cmd.skill_dirs()}
def test_is_dev_only_distinguishes_the_boundary(layer):
dev_dir = layer / "instructions" / "dev"
dev_dir.mkdir()
dev_file = dev_dir / "compiler-notes.md"
dev_file.write_text(
"---\ntype: types/instruction.md\nname: compiler-notes\ndescription: x\n---\n",
encoding="utf-8",
)
assert instructions_cmd.is_dev_only(dev_file)
assert not instructions_cmd.is_dev_only(layer / "instructions" / "gates.md")
# --- sync -------------------------------------------------------------------
def test_sync_publishes_copies_not_symlinks(layer):
instructions_cmd.sync(force=False)
for harness in (".agents", ".claude"):
published = _skill_copy(layer, harness)
assert (published / "SKILL.md").is_file()
assert not published.is_symlink()
assert "Demo skill." in (published / "SKILL.md").read_text(encoding="utf-8")
def test_sync_replaces_a_leftover_symlink_mirror(layer):
"""The previous design symlinked; an existing checkout still has those."""
target = _skill_copy(layer, ".claude")
target.parent.mkdir(parents=True)
target.symlink_to(layer / "instructions" / "wiki-demo", target_is_directory=True)
instructions_cmd.sync(force=False)
assert not target.is_symlink()
assert (target / "SKILL.md").is_file()
def test_sync_is_idempotent(layer):
instructions_cmd.sync(force=False)
instructions_cmd.sync(force=False)
assert instructions_cmd.drift(
layer / "instructions" / "wiki-demo", _skill_copy(layer, ".agents")
) is None
def test_sync_removes_a_published_skill_whose_source_is_gone(layer):
instructions_cmd.sync(force=False)
orphan = _skill_copy(layer, ".agents", "wiki-gone")
orphan.mkdir(parents=True)
(orphan / "SKILL.md").write_text("---\nname: wiki-gone\n---\n", encoding="utf-8")
instructions_cmd.sync(force=False)
assert not orphan.exists()
def test_sync_refuses_to_delete_content_it_did_not_generate(layer):
target = _skill_copy(layer, ".agents")
target.mkdir(parents=True)
(target / "hand-written.md").write_text("keep me\n", encoding="utf-8")
with pytest.raises(typer.Exit):
instructions_cmd.sync(force=False)
assert (target / "hand-written.md").exists()
instructions_cmd.sync(force=True)
assert not (target / "hand-written.md").exists()
def test_sync_fails_when_there_is_nothing_to_publish(layer):
import shutil
shutil.rmtree(layer / "instructions" / "wiki-demo")
with pytest.raises(typer.Exit):
instructions_cmd.sync(force=False)
# --- drift ------------------------------------------------------------------
def test_drift_detects_missing_edited_and_extra(layer):
source = layer / "instructions" / "wiki-demo"
target = _skill_copy(layer, ".agents")
assert instructions_cmd.drift(source, target) == "missing"
instructions_cmd.sync(force=False)
assert instructions_cmd.drift(source, target) is None
(target / "SKILL.md").write_text("---\nname: wiki-demo\n---\n# edited\n", encoding="utf-8")
assert "differs" in instructions_cmd.drift(source, target)
instructions_cmd.sync(force=False)
(target / "extra.md").write_text("x\n", encoding="utf-8")
assert "extra" in instructions_cmd.drift(source, target)
# --- verify -----------------------------------------------------------------
def _verify_error(layer) -> str:
with pytest.raises(typer.Exit):
instructions_cmd.verify()
return ""
def test_verify_passes_on_a_healthy_layer(layer, capsys):
instructions_cmd.sync(force=False)
instructions_cmd.verify()
assert "valid" in capsys.readouterr().out
def test_verify_treats_a_clean_checkout_as_bootstrap_not_drift(layer, capsys):
"""Nothing published at all is the expected state after `git clone`, so it
must point at the bootstrap procedure rather than report five failures."""
instructions_cmd.verify()
out = capsys.readouterr().out
assert "instructions sync" in out
assert "bootstrap" in out
def test_verify_reports_a_drifted_copy(layer):
instructions_cmd.sync(force=False)
(_skill_copy(layer, ".claude") / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: tampered\n---\n", encoding="utf-8"
)
with pytest.raises(typer.Exit):
instructions_cmd.verify()
def test_verify_reports_a_partially_published_layer(layer):
"""One copy missing is drift; all copies missing is a fresh clone."""
instructions_cmd.sync(force=False)
import shutil
shutil.rmtree(_skill_copy(layer, ".claude"))
with pytest.raises(typer.Exit):
instructions_cmd.verify()
def test_verify_rejects_an_instruction_that_fails_its_schema(layer):
(layer / "instructions" / "gates.md").write_text(
"---\ntype: types/instruction.md\nname: gates\n---\n\n# Gates\n", encoding="utf-8"
)
instructions_cmd.sync(force=False)
with pytest.raises(typer.Exit):
instructions_cmd.verify()
def test_verify_rejects_a_name_that_does_not_match_the_filename(layer):
(layer / "instructions" / "gates.md").write_text(
"---\ntype: types/instruction.md\nname: not-gates\ndescription: x\n---\n", encoding="utf-8"
)
instructions_cmd.sync(force=False)
with pytest.raises(typer.Exit):
instructions_cmd.verify()
def test_verify_rejects_a_skill_whose_name_does_not_match_its_folder(layer):
(layer / "instructions" / "wiki-demo" / "SKILL.md").write_text(
"---\nname: something-else\ndescription: Demo.\n---\n", encoding="utf-8"
)
instructions_cmd.sync(force=False)
with pytest.raises(typer.Exit):
instructions_cmd.verify()
def test_verify_reports_an_instruction_nothing_references(layer):
"""An instruction nothing loads is inert - it deploys to no one, and
nothing else in the stack would ever say so."""
(layer / "instructions" / "wiki-demo" / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: Demo skill.\n---\n\n# Demo\n", encoding="utf-8"
)
instructions_cmd.sync(force=False)
with pytest.raises(typer.Exit):
instructions_cmd.verify()
def test_manual_instruction_passes_verify_without_any_reference(layer, capsys):
"""A `manual: true` instruction is exempt from the reference requirement -
the opposite of every other instruction, deliberately."""
(layer / "instructions" / "gates.md").write_text(
"---\ntype: types/instruction.md\nname: gates\ndescription: x\nmanual: true\n---\n\n# Gates\n",
encoding="utf-8",
)
# The fixture's own SKILL.md says "See gates.md." by default - that would
# make this a `referenced` case, the opposite of what this test checks.
(layer / "instructions" / "wiki-demo" / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: Demo skill.\n---\n\n# Demo\n", encoding="utf-8"
)
instructions_cmd.sync(force=False)
instructions_cmd.verify()
assert "valid" in capsys.readouterr().out
def test_manual_instruction_fails_verify_if_it_is_referenced(layer):
"""The inverse invariant: a `manual` instruction being loadable from
somewhere defeats the entire point of marking it manual."""
(layer / "instructions" / "gates.md").write_text(
"---\ntype: types/instruction.md\nname: gates\ndescription: x\nmanual: true\n---\n\n# Gates\n",
encoding="utf-8",
)
(layer / "instructions" / "wiki-demo" / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: Demo skill.\n---\n\n# Demo\n\nSee gates.md.\n",
encoding="utf-8",
)
(layer / "AGENTS.md").write_text("# AGENTS\n\nSee gates.md.\n", encoding="utf-8")
instructions_cmd.sync(force=False)
with pytest.raises(typer.Exit):
instructions_cmd.verify()
def test_a_reference_from_claude_md_satisfies_the_requirement(layer, capsys):
"""CLAUDE.md is Claude Code's own auto-loaded file - a Claude-Code-only
instruction is linked from there instead of AGENTS.md, and that must
count exactly like an AGENTS.md reference does."""
(layer / "instructions" / "wiki-demo" / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: Demo skill.\n---\n\n# Demo\n", encoding="utf-8"
)
(layer / "CLAUDE.md").write_text("# CLAUDE\n\nSee gates.md.\n", encoding="utf-8")
instructions_cmd.sync(force=False)
instructions_cmd.verify()
assert "valid" in capsys.readouterr().out
def test_manual_instruction_fails_verify_if_referenced_from_claude_md(layer):
"""The CLAUDE.md mirror of test_manual_instruction_fails_verify_if_it_is_referenced:
CLAUDE.md is loaded automatically too, just by a different harness."""
(layer / "instructions" / "gates.md").write_text(
"---\ntype: types/instruction.md\nname: gates\ndescription: x\nmanual: true\n---\n\n# Gates\n",
encoding="utf-8",
)
(layer / "instructions" / "wiki-demo" / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: Demo skill.\n---\n\n# Demo\n", encoding="utf-8"
)
(layer / "CLAUDE.md").write_text("# CLAUDE\n\nSee gates.md.\n", encoding="utf-8")
instructions_cmd.sync(force=False)
with pytest.raises(typer.Exit):
instructions_cmd.verify()
def test_a_self_mention_does_not_count_as_a_reference(layer):
(layer / "instructions" / "gates.md").write_text(
"---\ntype: types/instruction.md\nname: gates\ndescription: x\n---\n\n# gates.md\n",
encoding="utf-8",
)
(layer / "instructions" / "wiki-demo" / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: Demo skill.\n---\n\n# Demo\n", encoding="utf-8"
)
assert "gates.md" not in instructions_cmd.referenced_names()
# --- skill reference paths ---------------------------------------------------
def test_the_fixture_skill_carries_no_relative_link(layer):
"""Forward direction: the fixture's own `wiki-demo/SKILL.md` mentions
`gates.md` as a bare word (`referenced_names()` relies on exactly that
substring match), never as a markdown link - so it must not trip the ban."""
assert instructions_cmd.check_skill_reference_paths() == []
def test_a_relative_markdown_link_in_a_skill_is_reported(layer):
"""`sync` copies `wiki-demo/SKILL.md` to `.claude/skills/wiki-demo/SKILL.md`
and `.agents/skills/wiki-demo/SKILL.md` - a different depth than the
source - so a link written `../gates.md` here would resolve to a
different, usually nonexistent, file once published."""
(layer / "instructions" / "wiki-demo" / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: Demo skill.\n---\n\n"
"# Demo\n\nSee [gates.md](../gates.md).\n",
encoding="utf-8",
)
issues = instructions_cmd.check_skill_reference_paths()
assert len(issues) == 1
assert "wiki-demo" in issues[0] and "../gates.md" in issues[0]
def test_a_plain_root_relative_path_is_not_a_reported_link(layer):
"""The decided fix: a `SKILL.md` names its target as a repo-root-relative
plain path, not a link - even one written correctly with brackets and
parens. `instructions/gates.md` on its own, with no `[...]`, must pass."""
(layer / "instructions" / "wiki-demo" / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: Demo skill.\n---\n\n"
"# Demo\n\nSee `instructions/gates.md`.\n",
encoding="utf-8",
)
assert instructions_cmd.check_skill_reference_paths() == []
def test_an_absolute_url_in_a_skill_is_not_reported(layer):
"""The ban is about relative paths breaking under the copy - an external
link is not affected by where the file sits, so it stays a normal link."""
(layer / "instructions" / "wiki-demo" / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: Demo skill.\n---\n\n"
"# Demo\n\nSee [the spec](https://example.com/spec).\n",
encoding="utf-8",
)
assert instructions_cmd.check_skill_reference_paths() == []
def test_a_pure_anchor_link_in_a_skill_is_not_reported(layer):
"""A same-page `#anchor` link is not a filesystem reference and does not
move when the file is copied."""
(layer / "instructions" / "wiki-demo" / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: Demo skill.\n---\n\n"
"# Demo\n\n## Steps\n\nSee [Steps](#steps) above.\n",
encoding="utf-8",
)
assert instructions_cmd.check_skill_reference_paths() == []
def test_link_syntax_shown_as_an_example_in_a_fence_is_not_flagged(layer):
"""A skill documenting the banned syntax as an example (rather than using
it) must not trip its own ban - code fences are masked before scanning,
the same way `toc.py` masks them before hunting for headings."""
(layer / "instructions" / "wiki-demo" / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: Demo skill.\n---\n\n"
"# Demo\n\nDo not write it like this:\n\n"
"```markdown\n[gates.md](../gates.md)\n```\n",
encoding="utf-8",
)
assert instructions_cmd.check_skill_reference_paths() == []
def test_verify_rejects_a_skill_with_a_relative_markdown_link(layer):
(layer / "instructions" / "wiki-demo" / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: Demo skill.\n---\n\n"
"# Demo\n\nSee [gates.md](../gates.md).\n",
encoding="utf-8",
)
instructions_cmd.sync(force=False)
with pytest.raises(typer.Exit):
instructions_cmd.verify()
def test_a_plain_path_resolves_the_same_regardless_of_which_copy_reads_it(layer):
"""The property the whole fix rests on. A repo-root-relative plain path
(`instructions/gates.md`) names the same file whether it is read from the
source (`instructions/wiki-demo/SKILL.md`) or from either published copy
(`.claude/skills/wiki-demo/SKILL.md`, `.agents/skills/wiki-demo/SKILL.md`),
because it is resolved against the instance root, never against the
reading file's own directory.
A relative link has no such property, which is the defect this whole
check exists to prevent: the same `../gates.md` means
`instructions/gates.md` from the source but a nonexistent
`.claude/skills/gates.md` / `.agents/skills/gates.md` from either
published copy - one directory short of the real file, because `sync`
copies the skill one level shallower than `instructions/<name>/` sits."""
instructions_cmd.sync(force=False)
target = layer / "instructions" / "gates.md"
assert target.is_file()
source_dir = layer / "instructions" / "wiki-demo"
claude_copy_dir = _skill_copy(layer, ".claude")
agents_copy_dir = _skill_copy(layer, ".agents")
# The plain path resolves against the instance root, regardless of which
# of the three directories above is doing the reading.
for _ in (source_dir, claude_copy_dir, agents_copy_dir):
assert (layer / "instructions" / "gates.md").is_file()
# The equivalent relative link would not: correct from the source, wrong
# from both copies.
assert (source_dir / ".." / "gates.md").resolve() == target.resolve()
assert not (claude_copy_dir / ".." / "gates.md").resolve().is_file()
assert not (agents_copy_dir / ".." / "gates.md").resolve().is_file()
# --- instructions/dev/ boundary ----------------------------------------------
def test_dev_only_instruction_referenced_from_outside_is_reported(layer):
"""instructions/dev/ is a hard boundary: `dist export` prunes it whole,
so a reference from outside would dangle in a distributed instance."""
dev_dir = layer / "instructions" / "dev"
dev_dir.mkdir()
(dev_dir / "compiler-notes.md").write_text(
"---\ntype: types/instruction.md\nname: compiler-notes\ndescription: x\n---\n",
encoding="utf-8",
)
(layer / "instructions" / "gates.md").write_text(
"---\ntype: types/instruction.md\nname: gates\ndescription: x\n---\n\n"
"See compiler-notes.md.\n",
encoding="utf-8",
)
(layer / "AGENTS.md").write_text("# AGENTS\n\nSee gates.md.\n", encoding="utf-8")
instructions_cmd.sync(force=False)
with pytest.raises(typer.Exit):
instructions_cmd.verify()
def test_dev_only_instruction_referenced_from_claude_md_is_reported(layer):
"""The CLAUDE.md mirror of test_dev_only_instruction_referenced_from_outside_is_reported.
CLAUDE.md carries the dev-only mention itself here - routing it through
gates.md instead would make this pass with or without CLAUDE.md in the
haystack, since gates.md was always scanned."""
dev_dir = layer / "instructions" / "dev"
dev_dir.mkdir()
(dev_dir / "compiler-notes.md").write_text(
"---\ntype: types/instruction.md\nname: compiler-notes\ndescription: x\n---\n",
encoding="utf-8",
)
(layer / "CLAUDE.md").write_text(
"# CLAUDE\n\nSee gates.md, and compiler-notes.md.\n", encoding="utf-8"
)
instructions_cmd.sync(force=False)
assert "compiler-notes.md" in instructions_cmd.dev_only_forbidden_references()
with pytest.raises(typer.Exit):
instructions_cmd.verify()
def test_a_superstring_mention_is_not_a_reference(layer):
"""`stack-dev` is a real skill name, but a heading "Where stack
development happens" renders a TOC anchor `#where-stack-development-happens`
that contains "stack-dev" as a raw substring without mentioning the skill
at all. The check has to be word-bounded, not `name in text`, or every
coincidental superstring becomes a false boundary violation."""
dev_dir = layer / "instructions" / "dev"
dev_dir.mkdir()
dev_skill = dev_dir / "stack-dev"
dev_skill.mkdir()
(dev_skill / "SKILL.md").write_text(
"---\nname: stack-dev\ndescription: x\n---\n\n# Stack Dev\n", encoding="utf-8"
)
(layer / "AGENTS.md").write_text(
"# AGENTS\n\nSee gates.md.\n"
"- [Where stack development happens](#where-stack-development-happens)\n",
encoding="utf-8",
)
instructions_cmd.sync(force=False)
assert "stack-dev" not in instructions_cmd.dev_only_forbidden_references()
def test_a_readme_mention_alone_does_not_keep_an_instruction_alive(layer):
"""README.md is 'never by an agent as instruction' (AGENTS.md's file-naming
table), so a mention there documents an instruction without deploying it.
Counting it would let `verify` stay green over an unreachable instruction."""
(layer / "instructions" / "wiki-demo" / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: Demo skill.\n---\n\n# Demo\n", encoding="utf-8"
)
(layer / "README.md").write_text("# README\n\nSee gates.md.\n", encoding="utf-8")
instructions_cmd.sync(force=False)
assert "gates.md" not in instructions_cmd.referenced_names()
with pytest.raises(typer.Exit):
instructions_cmd.verify()
def test_a_changelog_mention_alone_does_not_keep_an_instruction_alive(layer):
"""Same for CHANGES.md - and it is not even shipped: `dist export` replaces
it wholesale, so the mention does not survive into a distributed instance."""
(layer / "instructions" / "wiki-demo" / "SKILL.md").write_text(
"---\nname: wiki-demo\ndescription: Demo skill.\n---\n\n# Demo\n", encoding="utf-8"
)
(layer / "CHANGES.md").write_text("# Changelog\n\nAdded gates.md.\n", encoding="utf-8")
instructions_cmd.sync(force=False)
assert "gates.md" not in instructions_cmd.referenced_names()
with pytest.raises(typer.Exit):
instructions_cmd.verify()
def test_dev_only_instruction_referenced_from_the_readme_is_reported(layer):
"""README.md does not count as a *reference*, but it is still scanned for
the dev boundary: `dist export` copies it verbatim, so a dev-only path
mentioned there would dangle in a distributed instance."""
dev_dir = layer / "instructions" / "dev"
dev_dir.mkdir()
(dev_dir / "compiler-notes.md").write_text(
"---\ntype: types/instruction.md\nname: compiler-notes\ndescription: x\n---\n",
encoding="utf-8",
)
(layer / "README.md").write_text("# README\n\nSee compiler-notes.md.\n", encoding="utf-8")
instructions_cmd.sync(force=False)
assert "compiler-notes.md" in instructions_cmd.dev_only_forbidden_references()
def test_dev_only_reference_inside_dist_strip_block_is_exempt(layer, capsys):
"""The one sanctioned crossing: AGENTS.md's routing line to the dev
skill lives inside a dist:strip block, so it is stripped from the scan
before the boundary check runs - `dist export` removes both together,
so nothing is left dangling."""
dev_dir = layer / "instructions" / "dev"
dev_dir.mkdir()
(dev_dir / "compiler-notes.md").write_text(
"---\ntype: types/instruction.md\nname: compiler-notes\ndescription: x\n---\n",
encoding="utf-8",
)
(layer / "AGENTS.md").write_text(
"# AGENTS\n\n<!-- dist:strip-start -->\nSee compiler-notes.md.\n<!-- dist:strip-end -->\n",
encoding="utf-8",
)
instructions_cmd.sync(force=False)
instructions_cmd.verify()
assert "valid" in capsys.readouterr().out
def test_dev_only_mention_in_changelog_is_exempt(layer, capsys):
"""CHANGES.md is exempt from the boundary check: `dist export` always
replaces it wholesale with a template regardless of its content, so a
historical mention of a dev-only name there never reaches a distributed
instance - unlike AGENTS.md/README.md, which are copied (marker-stripped)."""
dev_dir = layer / "instructions" / "dev"
dev_dir.mkdir()
(dev_dir / "compiler-notes.md").write_text(
"---\ntype: types/instruction.md\nname: compiler-notes\ndescription: x\n---\n",
encoding="utf-8",
)
# Its real reference lives inside dev/, where it is allowed: the changelog
# mention below must be exempt from the boundary check, not a substitute
# for a reference (CHANGES.md does not deploy an instruction to anyone).
dev_skill = dev_dir / "stack-dev"
dev_skill.mkdir()
(dev_skill / "SKILL.md").write_text(
"---\nname: stack-dev\ndescription: x\n---\n\nSee compiler-notes.md.\n", encoding="utf-8"
)
(layer / "AGENTS.md").write_text("# AGENTS\n\nSee gates.md.\n", encoding="utf-8")
(layer / "CHANGES.md").write_text(
"# Changelog\n\n## Entry\n\nMentions compiler-notes.md in passing.\n", encoding="utf-8"
)
instructions_cmd.sync(force=False)
instructions_cmd.verify()
assert "valid" in capsys.readouterr().out
def test_dev_instruction_referenced_only_from_within_dev_passes(layer, capsys):
dev_dir = layer / "instructions" / "dev"
dev_dir.mkdir()
(dev_dir / "compiler-notes.md").write_text(
"---\ntype: types/instruction.md\nname: compiler-notes\ndescription: x\n---\n",
encoding="utf-8",
)
skill_dir = dev_dir / "stack-dev"
skill_dir.mkdir()
(skill_dir / "SKILL.md").write_text(
"---\nname: stack-dev\ndescription: x\n---\n\nSee compiler-notes.md.\n", encoding="utf-8"
)
instructions_cmd.sync(force=False)
instructions_cmd.verify()
assert "valid" in capsys.readouterr().out
# --- list -------------------------------------------------------------------
def test_list_reports_each_instruction_with_its_description(layer, capsys):
instructions_cmd.list_instructions(json_out=False)
out = capsys.readouterr().out
assert "gates" in out
assert "What to do when a gate refuses." in out
assert "CONTRACT" not in out
def test_list_json_is_machine_readable(layer, capsys):
import json
instructions_cmd.list_instructions(json_out=True)
rows = json.loads(capsys.readouterr().out)
assert rows == [
{
"name": "gates",
"path": "instructions/gates.md",
"description": "What to do when a gate refuses.",
}
]