diff --git a/review_proofs.py b/review_proofs.py index 7b08213..3b7295e 100644 --- a/review_proofs.py +++ b/review_proofs.py @@ -18,6 +18,54 @@ import re _FULL_SHA = re.compile(r"^[0-9a-f]{40}$") + +# Repo name disambiguation rules for blind PR queue review. +# User phrases referencing the "MCP Gitea tool" (or similar) must resolve to +# Gitea-Tools repo, not be confused with mcp-control-plane. +# If ambiguous (e.g. just "open PRs"), check both configured repos. +REPO_ALIASES = { + "gitea-tools": "Scaled-Tech-Consulting/Gitea-Tools", + "gitea tool": "Scaled-Tech-Consulting/Gitea-Tools", + "mcp gitea tool": "Scaled-Tech-Consulting/Gitea-Tools", + "gitea mcp tool": "Scaled-Tech-Consulting/Gitea-Tools", + "gitea-tools repo": "Scaled-Tech-Consulting/Gitea-Tools", + "mcp-control-plane": "Scaled-Tech-Consulting/mcp-control-plane", + "mcp control plane": "Scaled-Tech-Consulting/mcp-control-plane", +} + + +def resolve_repos_from_user_reference( + reference: str, configured: list[str] | None = None +) -> list[str]: + """Resolve a user reference string to the list of target repos to inventory. + + - Exact aliases for Gitea-Tools map only to Gitea-Tools. + - mcp-control-plane aliases map only to it. + - Empty, ambiguous, or general "open PRs" default to all configured repos + (both by default). + - Returns subset of configured; never invents new repos. + """ + if configured is None: + configured = [ + "Scaled-Tech-Consulting/Gitea-Tools", + "Scaled-Tech-Consulting/mcp-control-plane", + ] + if not reference or not reference.strip(): + return list(configured) + + ref_lower = reference.lower() + matched = [] + for alias, full_name in REPO_ALIASES.items(): + if alias in ref_lower: + if full_name not in matched: + matched.append(full_name) + if matched: + # return only the matched ones that are in configured, preserving order + return [r for r in configured if r in matched] + # no specific alias match → check all (complete inventory required) + return list(configured) + + SAFE_NEXT_ACTION_UNKNOWN_CONTAMINATION = ( "evidence missing: report contamination as unknown and " "choose another PR or stop" diff --git a/skills/llm-project-workflow/SKILL.md b/skills/llm-project-workflow/SKILL.md index db85a89..8c028b2 100644 --- a/skills/llm-project-workflow/SKILL.md +++ b/skills/llm-project-workflow/SKILL.md @@ -181,11 +181,21 @@ Worktree folder = branch with `/` replaced by `-` that the diff base is the PR base branch. If `HEAD` does not match the pinned head, **stop before review/merge** (`review_proofs.verify_pinned_head_checkout`). -6. **Inventory proof (#173):** a blind queue review must prove listing - completeness before claiming "only PRs found": both configured - repositories checked, open-PR filters stated, pagination handled or - explicitly not needed, and the total open PR count per repo reported - (`review_proofs.assess_inventory_completeness`). +6. **Inventory proof (#173 + repo disambiguation hardening):** a blind queue + review must prove listing completeness before claiming "only PRs found". + Use repo-name disambiguation: + - "Gitea-Tools" / "gitea tool" / "MCP Gitea tool" / "gitea MCP tool" / + "gitea-tools repo" resolve **only** to `Scaled-Tech-Consulting/Gitea-Tools`. + - "mcp-control-plane" resolves only to `Scaled-Tech-Consulting/mcp-control-plane`. + - Ambiguous ("open PRs", no explicit repo, "MCP Gitea tooling") → inventory + **both** configured repos. + Report must state exactly which repo(s) were checked. If only one checked: + "Only was checked. Other configured repos were not checked. This is + not a complete queue inventory." Never let a single-repo zero hide PRs in + the other. + Both configured repos must be reported with state filter, pagination proof, + and open-PR count (`review_proofs.assess_inventory_completeness` and + `resolve_repos_from_user_reference`). 7. Inspect the full diff; confirm scope matches the linked issue; flag unrelated files. 8. Run the tests. Validation reporting must include the exact command and exact results: pass/fail, counts of tests passed/skipped/failed, any diff --git a/skills/llm-project-workflow/templates/review-pr.md b/skills/llm-project-workflow/templates/review-pr.md index 3897a57..39af1f4 100644 --- a/skills/llm-project-workflow/templates/review-pr.md +++ b/skills/llm-project-workflow/templates/review-pr.md @@ -5,6 +5,19 @@ Copy, fill the `<...>` fields, and paste as the task prompt. ```text Task: review PR # for issue #. +Repo name disambiguation (Gitea-Tools blind review hardening): +- "Gitea-Tools", "gitea tool", "MCP Gitea tool", "gitea MCP tool", "gitea-tools repo" + → MUST resolve to `Scaled-Tech-Consulting/Gitea-Tools` (never treat as mcp-control-plane). +- "mcp-control-plane", "mcp control plane" → only `Scaled-Tech-Consulting/mcp-control-plane`. +- If user says "open PRs", "the queue", "MCP Gitea tooling" without explicit repo, + or reference is ambiguous: check BOTH configured repos: + `Scaled-Tech-Consulting/Gitea-Tools` and `Scaled-Tech-Consulting/mcp-control-plane`. +- In the final report, always state exactly which repo(s) were checked. + If only one was checked: explicitly say "Only was checked. Other + configured repos were not checked. This is not a complete queue inventory." +- A single-repo "no open PRs" result MUST NOT be reported as global "no open PRs" + if the other configured repo was not inventoried. + Rules (llm-project-workflow): - Review in a SEPARATE detached review worktree, never the author's folder. - You must NOT be the PR author. If the authenticated user == PR author, stop. diff --git a/tests/test_review_proofs.py b/tests/test_review_proofs.py index d981eb3..9876bba 100644 --- a/tests/test_review_proofs.py +++ b/tests/test_review_proofs.py @@ -25,6 +25,7 @@ from review_proofs import ( # noqa: E402 assess_self_review_contamination, assess_validation_report, build_final_report, + resolve_repos_from_user_reference, verify_pinned_head_checkout, ) @@ -489,5 +490,91 @@ class TestStdoutIsolation(unittest.TestCase): self.fail(f"stderr write failed: {exc}") +class TestRepoNameDisambiguation(unittest.TestCase): + """Harness assertions for repo name disambiguation (new blind-review hardening). + + "MCP Gitea tool" etc. must resolve to Gitea-Tools, not silently default to + mcp-control-plane. "open PRs" or ambiguous must check both. Single-repo + zero-result must not hide PRs in the other configured repo. + """ + + CONFIGURED = [ + "Scaled-Tech-Consulting/Gitea-Tools", + "Scaled-Tech-Consulting/mcp-control-plane", + ] + + def test_gitea_tools_aliases_resolve_to_gitea_tools_only(self): + for ref in [ + "MCP Gitea tool", + "gitea tool", + "Gitea-Tools", + "gitea mcp tool", + "gitea-tools repo", + ]: + result = resolve_repos_from_user_reference(ref, self.CONFIGURED) + self.assertEqual(result, ["Scaled-Tech-Consulting/Gitea-Tools"]) + + def test_mcp_control_plane_alias_resolves_only_to_it(self): + result = resolve_repos_from_user_reference( + "mcp-control-plane", self.CONFIGURED + ) + self.assertEqual(result, ["Scaled-Tech-Consulting/mcp-control-plane"]) + + def test_ambiguous_or_empty_defaults_to_both(self): + self.assertEqual( + resolve_repos_from_user_reference("open PRs", self.CONFIGURED), + self.CONFIGURED, + ) + self.assertEqual( + resolve_repos_from_user_reference("", self.CONFIGURED), + self.CONFIGURED, + ) + self.assertEqual( + resolve_repos_from_user_reference("review the queue", self.CONFIGURED), + self.CONFIGURED, + ) + + def test_single_repo_zero_result_does_not_hide_other(self): + # Simulate a run that only inventoried mcp because of bad alias resolution. + # The inventory must still require Gitea-Tools to claim "no open PRs". + mcp_only_reports = [ + { + "repo": "Scaled-Tech-Consulting/mcp-control-plane", + "state_filter": "open", + "pagination_complete": True, + "open_pr_count": 0, + } + ] + result = assess_inventory_completeness( + repo_reports=mcp_only_reports, + required_repos=self.CONFIGURED, + ) + self.assertFalse(result["complete"]) + self.assertTrue( + any("Gitea-Tools" in r for r in result["reasons"]) + ) + + def test_full_inventory_of_both_is_required_for_exhaustive_claim(self): + both_reports = [ + { + "repo": "Scaled-Tech-Consulting/Gitea-Tools", + "state_filter": "open", + "pagination_complete": True, + "open_pr_count": 1, + }, + { + "repo": "Scaled-Tech-Consulting/mcp-control-plane", + "state_filter": "open", + "pagination_complete": True, + "open_pr_count": 0, + }, + ] + result = assess_inventory_completeness( + repo_reports=both_reports, required_repos=self.CONFIGURED + ) + self.assertTrue(result["complete"]) + self.assertTrue(result["can_claim_exhaustive"]) + + if __name__ == "__main__": unittest.main()