Document default tool-call and wall-time budgets for deterministic MCP work, forbid subagent commit delegation when gitea_commit_files is available, and pin the rules in runbooks, portable skill, operator guide, and doc-contract tests. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
89 lines
2.7 KiB
Python
89 lines
2.7 KiB
Python
"""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}" |