Add a second reviewer workflow prompt to the root MCP operator menu: "Skip already-reviewed stale REQUEST_CHANGES PR and hand off to author". show_reviewer_prompts becomes a small 2-option submenu (mirroring the existing show_author_prompts idiom): option 1 keeps the standard PR review prompt; option 2 is the new prompt. It instructs the reviewer to determine whether the current head already carries a non-stale REQUEST_CHANGES verdict and, if so, to submit no new terminal review mutation, run only handoff diagnostics, and return an author-ready fix prompt with the full 8-field handoff. Stacked on PR #479 (feat/issue-478-mcp-menu-shell); the menu is not yet on master. Diff limited to the new prompt: mcp-menu.sh, docs/mcp-menu.md, and a discoverability test in tests/test_mcp_menu_script.py. Closes #482. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
137 lines
5.0 KiB
Python
137 lines
5.0 KiB
Python
"""Hermetic tests for repository-root MCP operator menu (#478)."""
|
|
import os
|
|
import stat
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
SCRIPT = REPO_ROOT / "mcp-menu.sh"
|
|
DOCS = REPO_ROOT / "docs" / "mcp-menu.md"
|
|
|
|
REQUIRED_MENU_LABELS = (
|
|
"Project status / root checkout health",
|
|
"Author workflow prompts",
|
|
"Reviewer workflow prompts",
|
|
"Merger workflow prompts",
|
|
"Reconciler workflow prompts",
|
|
"Onboarding new project to this MCP workflow",
|
|
"Proxmox deployment menu placeholder",
|
|
"Create Proxmox LXC placeholder",
|
|
"Run tests",
|
|
"Exit",
|
|
)
|
|
|
|
DANGEROUS_PATTERNS = (
|
|
"git push --force",
|
|
"git push -f",
|
|
"--force-with-lease",
|
|
"delete-branch",
|
|
"gitea_delete_branch",
|
|
"issue-locks",
|
|
"issue_lock_store",
|
|
"curl ",
|
|
"wget ",
|
|
)
|
|
|
|
|
|
class TestMcpMenuScript(unittest.TestCase):
|
|
def setUp(self):
|
|
self.assertTrue(SCRIPT.is_file(), "mcp-menu.sh must exist at repo root")
|
|
self.content = SCRIPT.read_text(encoding="utf-8")
|
|
|
|
def test_script_exists_at_repo_root(self):
|
|
self.assertEqual(SCRIPT.name, "mcp-menu.sh")
|
|
self.assertEqual(SCRIPT.parent, REPO_ROOT)
|
|
|
|
def test_executable_bit_is_set(self):
|
|
mode = SCRIPT.stat().st_mode
|
|
self.assertTrue(mode & stat.S_IXUSR, "mcp-menu.sh must be executable by owner")
|
|
|
|
def test_shebang(self):
|
|
first_line = self.content.splitlines()[0]
|
|
self.assertEqual(first_line, "#!/usr/bin/env bash")
|
|
|
|
def test_uses_set_euo_pipefail(self):
|
|
self.assertIn("set -euo pipefail", self.content)
|
|
|
|
def test_no_dangerous_commands(self):
|
|
lowered = self.content.lower()
|
|
for pattern in DANGEROUS_PATTERNS:
|
|
with self.subTest(pattern=pattern):
|
|
self.assertNotIn(pattern.lower(), lowered)
|
|
|
|
def test_no_branch_deletion_verbs(self):
|
|
for token in ("git branch -D", "git branch -d", "push :refs"):
|
|
with self.subTest(token=token):
|
|
self.assertNotIn(token, self.content)
|
|
|
|
def test_contains_required_menu_labels(self):
|
|
for label in REQUIRED_MENU_LABELS:
|
|
with self.subTest(label=label):
|
|
self.assertIn(label, self.content)
|
|
|
|
def test_run_tests_prefers_run_tests_sh(self):
|
|
self.assertIn('run-tests.sh', self.content)
|
|
run_tests_idx = self.content.index("run_tests()")
|
|
run_tests_body = self.content[run_tests_idx : run_tests_idx + 800]
|
|
pytest_idx = run_tests_body.find("pytest")
|
|
run_tests_sh_idx = run_tests_body.find("run-tests.sh")
|
|
self.assertGreater(pytest_idx, 0)
|
|
self.assertGreater(run_tests_sh_idx, 0)
|
|
self.assertLess(run_tests_sh_idx, pytest_idx)
|
|
|
|
def test_run_tests_fail_closed_without_runner(self):
|
|
self.assertIn("fail closed", self.content.lower())
|
|
self.assertIn("exit 1", self.content)
|
|
|
|
def test_proxmox_entries_are_placeholders(self):
|
|
self.assertIn("TODO / issue-backed", self.content)
|
|
self.assertIn("NOT implemented", self.content)
|
|
|
|
def test_root_health_shows_required_fields(self):
|
|
health_fn = self._extract_function("show_root_checkout_health")
|
|
for snippet in (
|
|
"status --short --branch",
|
|
"rev-parse HEAD",
|
|
"prgs/master",
|
|
"WARNING: root checkout",
|
|
):
|
|
with self.subTest(snippet=snippet):
|
|
self.assertIn(snippet, health_fn)
|
|
|
|
def test_docs_mention_mcp_menu_sh(self):
|
|
self.assertTrue(DOCS.is_file(), "docs/mcp-menu.md must exist")
|
|
docs_text = DOCS.read_text(encoding="utf-8")
|
|
self.assertIn("./mcp-menu.sh", docs_text)
|
|
self.assertIn("placeholder", docs_text.lower())
|
|
|
|
def test_reviewer_skip_stale_request_changes_prompt_discoverable(self):
|
|
# #482: the skip-already-reviewed-stale-REQUEST_CHANGES reviewer prompt
|
|
# must be reachable from the reviewer menu and documented.
|
|
label = "Skip already-reviewed stale REQUEST_CHANGES PR and hand off to author"
|
|
reviewer_fn = self._extract_function("show_reviewer_prompts")
|
|
self.assertIn(label, reviewer_fn, "new reviewer prompt label must appear in the reviewer menu")
|
|
# The prompt must instruct: no duplicate terminal review mutation for a
|
|
# non-stale REQUEST_CHANGES head, and must carry the author handoff.
|
|
self.assertIn("do NOT submit another terminal review mutation", reviewer_fn)
|
|
self.assertIn("author-ready fix prompt", reviewer_fn)
|
|
docs_text = DOCS.read_text(encoding="utf-8")
|
|
self.assertIn("REQUEST_CHANGES", docs_text)
|
|
|
|
def _extract_function(self, name: str) -> str:
|
|
marker = f"{name}() {{"
|
|
start = self.content.index(marker)
|
|
depth = 0
|
|
for idx in range(start, len(self.content)):
|
|
char = self.content[idx]
|
|
if char == "{":
|
|
depth += 1
|
|
elif char == "}":
|
|
depth -= 1
|
|
if depth == 0:
|
|
return self.content[start : idx + 1]
|
|
self.fail(f"Could not parse function {name}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main() |