mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 20:07:18 +01:00
New agent_ready/ package (dist and live-url modes, 100% branch coverage) checks robots, sitemap, llms.txt, ai-catalog, 404, no-JS text, markdown mirrors and JSON-LD. repo_contract gains web-repo detection and installs the agent-ready pre-commit hook, CI workflow and check_agent_ready.sh shim; scripts/check_agent_ready.sh is the shared entry point. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBScELfMUiieK9ManFbS3Y
202 lines
7.1 KiB
Python
202 lines
7.1 KiB
Python
"""The installer writes the shim, hook, workflow and Commands section once."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import stat
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from repo_contract import install as inst
|
|
from repo_contract import web
|
|
from repo_contract.check import violations
|
|
|
|
from .conftest import good_repo, write
|
|
from .test_web import vite_repo
|
|
|
|
|
|
def python_repo(root: Path) -> Path:
|
|
"""A python repo with tests and no contract pieces at all."""
|
|
write(root, "pyproject.toml", "[project]\nname='x'\n")
|
|
write(root, "tests/test_a.py", "def test_a():\n pass\n")
|
|
return root
|
|
|
|
|
|
def test_plan_lists_everything_for_a_bare_repo(repo: Path) -> None:
|
|
assert inst.plan(repo) == [
|
|
"scripts/check_repo_contract.sh",
|
|
".github/workflows/repo-contract.yml",
|
|
".pre-commit-config.yaml (repo-contract hook)",
|
|
]
|
|
|
|
|
|
def test_install_writes_pieces_and_is_idempotent(repo: Path) -> None:
|
|
python_repo(repo)
|
|
done = inst.install(repo)
|
|
assert len(done) == 3
|
|
shim = repo / inst.SHIM
|
|
assert shim.stat().st_mode & stat.S_IXUSR
|
|
assert "id: repo-contract" in (repo / inst.PRECOMMIT).read_text(encoding="utf-8")
|
|
assert inst.install(repo) == []
|
|
assert inst.plan(repo) == []
|
|
|
|
|
|
def test_marker_skips_workflow(repo: Path) -> None:
|
|
write(repo, ".dep-freshness-no-workflow")
|
|
inst.install(repo)
|
|
assert not (repo / inst.WORKFLOW).exists()
|
|
|
|
|
|
def test_utils_itself_is_not_given_a_shim(repo: Path) -> None:
|
|
write(repo, "repo_contract/check.py")
|
|
assert inst.install(repo) != []
|
|
assert not (repo / inst.SHIM).exists()
|
|
|
|
|
|
def test_hook_lands_in_the_last_local_block(repo: Path) -> None:
|
|
write(
|
|
repo,
|
|
inst.PRECOMMIT.as_posix(),
|
|
"repos:\n - repo: local\n hooks:\n - id: a\n"
|
|
" - repo: https://example.com/x\n hooks:\n - id: b",
|
|
)
|
|
inst.install(repo)
|
|
lines = (repo / inst.PRECOMMIT).read_text(encoding="utf-8").splitlines()
|
|
assert lines.index(" - id: repo-contract") < lines.index(
|
|
" - repo: https://example.com/x"
|
|
)
|
|
|
|
|
|
def test_hook_needs_a_local_block(repo: Path) -> None:
|
|
write(repo, inst.PRECOMMIT.as_posix(), "repos:\n - repo: https://example.com/x\n")
|
|
with pytest.raises(ValueError, match="no ` - repo: local` block"):
|
|
inst.install(repo)
|
|
|
|
|
|
def test_bootstrap_commands_creates_section_and_script(repo: Path) -> None:
|
|
python_repo(repo)
|
|
actions = inst.bootstrap_commands(repo)
|
|
assert any("test_changed.sh" in a for a in actions)
|
|
assert "## Commands" in (repo / "CLAUDE.md").read_text(encoding="utf-8")
|
|
assert inst.bootstrap_commands(repo) == []
|
|
|
|
|
|
def test_main_check_changes_nothing(
|
|
repo: Path, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
assert inst.main([str(repo), "--check"]) == 1
|
|
out = capsys.readouterr().out
|
|
assert "would write: scripts/check_repo_contract.sh" in out
|
|
assert not (repo / inst.SHIM).exists()
|
|
|
|
|
|
def test_main_installs_and_passes(
|
|
repo: Path, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
good_repo(repo)
|
|
assert inst.main([str(repo)]) == 0
|
|
assert "wrote: scripts/check_repo_contract.sh" in capsys.readouterr().out
|
|
assert violations(repo)[0] == []
|
|
|
|
|
|
def test_main_no_bootstrap_leaves_violation(repo: Path) -> None:
|
|
assert inst.main([str(repo), "--no-bootstrap"]) == 1
|
|
assert not (repo / "CLAUDE.md").exists()
|
|
|
|
|
|
def test_main_rejects_non_repo(tmp_path: Path) -> None:
|
|
assert inst.main([str(tmp_path)]) == 2
|
|
|
|
|
|
def test_main_reports_bootstrap_actions(
|
|
repo: Path, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
python_repo(repo)
|
|
inst.main([str(repo)])
|
|
assert "bootstrap: created scripts/test_changed.sh" in capsys.readouterr().out
|
|
|
|
|
|
WEB_PIECES = [
|
|
"repo-contract.toml ([agent_ready])",
|
|
"scripts/check_agent_ready.sh",
|
|
".github/workflows/agent-ready.yml",
|
|
".pre-commit-config.yaml (agent-ready hook)",
|
|
]
|
|
|
|
|
|
def test_vite_repo_gets_the_agent_ready_gate(repo: Path) -> None:
|
|
vite_repo(repo)
|
|
assert inst.plan(repo)[3:] == WEB_PIECES
|
|
assert inst.install(repo)[3:] == WEB_PIECES
|
|
toml = (repo / "repo-contract.toml").read_text(encoding="utf-8")
|
|
assert 'dist = "dist"' in toml and 'build = "pnpm run build"' in toml
|
|
hooks = (repo / inst.PRECOMMIT).read_text(encoding="utf-8")
|
|
assert "id: repo-contract" in hooks and "id: agent-ready" in hooks
|
|
assert (repo / "scripts/check_agent_ready.sh").stat().st_mode & stat.S_IXUSR
|
|
assert "check_agent_ready.sh --strict --install" in (
|
|
repo / ".github/workflows/agent-ready.yml"
|
|
).read_text(encoding="utf-8")
|
|
assert inst.install(repo) == []
|
|
assert inst.plan(repo) == []
|
|
|
|
|
|
def test_declared_url_site_gets_gate_but_config_is_kept(repo: Path) -> None:
|
|
body = '# mine\n[agent_ready]\nurl = "https://x.example"\n'
|
|
write(repo, "repo-contract.toml", body)
|
|
assert inst.install(repo)[3:] == WEB_PIECES[1:]
|
|
assert (repo / "repo-contract.toml").read_text(encoding="utf-8") == body
|
|
|
|
|
|
def test_existing_toml_without_table_gets_it_appended(repo: Path) -> None:
|
|
vite_repo(repo)
|
|
write(repo, "repo-contract.toml", "[other]\nx = 1")
|
|
inst.install(repo)
|
|
text = (repo / "repo-contract.toml").read_text(encoding="utf-8")
|
|
assert text.startswith("[other]\nx = 1\n") and "[agent_ready]" in text
|
|
|
|
|
|
def test_skipped_or_broken_config_installs_no_web_pieces(repo: Path) -> None:
|
|
write(repo, "repo-contract.toml", '[agent_ready]\nskip = "auth-gated"\n')
|
|
assert len(inst.plan(repo)) == 3
|
|
write(repo, "repo-contract.toml", "[agent_ready]\n")
|
|
assert len(inst.install(repo)) == 3
|
|
|
|
|
|
def test_marker_skips_agent_ready_workflow(repo: Path) -> None:
|
|
write(repo, ".dep-freshness-no-workflow")
|
|
write(repo, "repo-contract.toml", '[agent_ready]\nurl = "https://x.example"\n')
|
|
inst.install(repo)
|
|
assert not (repo / ".github/workflows/agent-ready.yml").exists()
|
|
|
|
|
|
def _agent_ready_files(repo: Path) -> list[str]:
|
|
lines = (repo / inst.PRECOMMIT).read_text(encoding="utf-8").splitlines()
|
|
start = lines.index(" - id: agent-ready")
|
|
return [ln for ln in lines[start:] if ln.startswith(" files: ")]
|
|
|
|
|
|
def test_hook_carries_the_files_filter_and_follows_config(repo: Path) -> None:
|
|
vite_repo(repo)
|
|
inst.install(repo)
|
|
assert _agent_ready_files(repo) == [" files: '" + web.DEFAULT_FILES + "'"]
|
|
with (repo / "repo-contract.toml").open("a", encoding="utf-8") as fh:
|
|
fh.write('files = "^(app/|it\'s\\\\.html$)"\n')
|
|
assert inst.plan(repo) == [".pre-commit-config.yaml (agent-ready hook)"]
|
|
assert inst.install(repo) == [".pre-commit-config.yaml (agent-ready hook)"]
|
|
assert _agent_ready_files(repo) == [" files: '^(app/|it''s\\.html$)'"]
|
|
assert inst.install(repo) == []
|
|
|
|
|
|
def test_hook_without_files_line_gets_one(repo: Path) -> None:
|
|
write(repo, "repo-contract.toml", '[agent_ready]\nurl = "https://x.example"\n')
|
|
write(
|
|
repo,
|
|
inst.PRECOMMIT.as_posix(),
|
|
"repos:\n - repo: local\n hooks:\n - id: agent-ready\n"
|
|
" entry: scripts/check_agent_ready.sh\n\n"
|
|
" - id: other\n entry: x\n",
|
|
)
|
|
inst.install(repo)
|
|
lines = (repo / inst.PRECOMMIT).read_text(encoding="utf-8").splitlines()
|
|
assert lines[5].startswith(" files: '") and lines[7] == " - id: other"
|