"""Doc-contract checks for subagent tool-budget guardrails (#259). Single-step MCP tasks must not expand into 100+ tool-call retry spirals with WebFetch/Playwright/manual-encoding fallbacks. These tests pin budget defaults, native-MCP-first rules, and forbidden detours in the runbooks, portable skill, and operator guide. """ from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent RUNBOOK = REPO_ROOT / "docs" / "llm-workflow-runbooks.md" SKILL = REPO_ROOT / "skills" / "llm-project-workflow" / "SKILL.md" def _normalize(text: str) -> str: """Collapse whitespace so phrase checks survive markdown line wrapping.""" return " ".join(text.split()) def _runbook_text() -> str: return _normalize(RUNBOOK.read_text(encoding="utf-8")) def _skill_text() -> str: return _normalize(SKILL.read_text(encoding="utf-8")) def test_runbook_has_subagent_tool_budget_section(): text = _runbook_text() assert "## Subagent Tool-Budget Guardrails" in text for phrase in ( "15", "5 minutes", "40", "15 minutes", "gitea_commit_files", "WebFetch", "Playwright", "no retry spirals", ): assert phrase.lower() in text.lower(), f"runbook missing phrase: {phrase!r}" def test_runbook_forbids_subagent_commit_delegation(): text = _runbook_text() for phrase in ( "do not delegate commit authority to a subagent", "main session first", "native MCP before fallback", ): assert phrase.lower() in text.lower(), f"runbook missing: {phrase!r}" def test_runbook_rejects_fallback_detours_when_mcp_commit_available(): lower = _runbook_text().lower() for forbidden in ("webfetch", "playwright", "manual llm-generated base64"): assert forbidden in lower, f"runbook must name forbidden detour: {forbidden!r}" assert "_encode_" in lower assert "gitea_commit_files" in lower def test_skill_doc_declares_subagent_tool_budget_guardrails(): text = _skill_text() assert "## Subagent Tool-Budget Guardrails" in text for phrase in ( "15 tool calls", "5 minutes", "gitea_commit_files", "WebFetch", "never resume a failed subagent", ): assert phrase.lower() in text.lower(), f"SKILL.md missing phrase: {phrase!r}" def test_operator_guide_rules_include_subagent_tool_budget(): import sys sys.path.insert(0, str(REPO_ROOT)) import gitea_mcp_server rule = gitea_mcp_server._GUIDE_RULES["subagent_tool_budget"] for phrase in ( "15 tool calls", "gitea_commit_files", "WebFetch", "retry loop", ): assert phrase in rule, f"operator guide rule missing: {phrase!r}"