#!/usr/bin/env python3 """Tier 2 required-files audit. Defense-in-depth check for the 2026-06-24 MCP regression: verifies that the 2 MCP-config files (opencode.json + mcp_paths.toml) are present in a tier-2 branch. If either is missing, the audit fails (exit 1) with a clear diagnostic. Context: setup_tier2_clone.ps1 modifies opencode.json and mcp_paths.toml IN the clone (C:\\projects\\manual_slop_tier2\\), and copies the tier-2 agent prompt + slash command from conductor/tier2/ into .opencode/. If a tier-2 commit accidentally captures any of these via `git add .`, they leak into the main repo. The pre-commit hook (conductor/tier2/githooks/pre-commit) auto-unstages them on commit but does not prevent the deletions from appearing in commit history. This audit is a defense-in-depth check: it can be run on any branch (typically a tier-2 branch) to verify the 2 required files are present. Run it in pre-merge, in a CI workflow, or manually before merging a tier-2 branch to master. Usage: # Audit the current HEAD uv run python scripts/audit_branch_required_files.py # Audit a specific ref (branch, commit, tag) uv run python scripts/audit_branch_required_files.py --ref origin/tier2/phase2_4_5_call_site_completion_20260621 # JSON output for CI integration uv run python scripts/audit_branch_required_files.py --json # Strict mode: exit 1 on any missing file (default; the script # is informational by default but `--strict` is the CI-gate mode) Exit codes: 0 - all required files present 1 - one or more required files missing (CI gate failure) 2 - usage error (bad args, git not available, ref not found) The 2 required files (the actual MCP regression target from 2026-06-24): 1. opencode.json - the OpenCode config that setup_tier2_clone.ps1 overrides 2. mcp_paths.toml - the MCP allowed paths that setup_tier2_clone.ps1 clears These are the 2 files that the 2026-06-24 MCP regression deleted from the tier-2 branch's index. The pre-commit hook strips them from tier-2 commits but does not prevent the deletion from being in the commit's diff (the hook only unstages ADDITIONS). The other 2 entries in conductor/tier2/githooks/forbidden-files.txt (.opencode/agents/tier2-autonomous.md and .opencode/commands/tier-2-auto-execute.md) are tier-2 sandbox-only working tree files that are NEVER tracked in any branch (per commit fab2e55b "undo sandbox file leaks"). They live only in the tier-2 clone's working tree, copied there by setup_tier2_clone.ps1 from conductor/tier2/{agents,commands}/. They are not REQUIRED for the audit. CI integration (when the project gets CI): Add to .github/workflows/ci.yml (or equivalent): - name: Verify tier-2 required files run: uv run python scripts/audit_branch_required_files.py --strict # The `--strict` flag is the default behavior; explicit for clarity. Or as a per-PR check on tier-2 branches: - name: Verify required files on tier-2 PR if: github.base_ref == 'master' && startsWith(github.head_ref, 'tier2/') run: uv run python scripts/audit_branch_required_files.py --strict Note: this script does NOT modify the working tree. It is read-only. """ from __future__ import annotations import argparse import json import subprocess import sys from pathlib import Path REQUIRED_FILES: tuple[str, ...] = ( "opencode.json", "mcp_paths.toml", ) def check_required_files(ref: str) -> list[str]: missing: list[str] = [] for required in REQUIRED_FILES: result = subprocess.run( ["git", "cat-file", "-e", f"{ref}:{required}"], capture_output=True, ) if result.returncode != 0: missing.append(required) return missing def main() -> int: parser = argparse.ArgumentParser( description="Verify tier-2 sandbox-required files are present on a branch.", ) parser.add_argument( "--ref", default="HEAD", help="Git ref to check (default: HEAD). E.g. origin/tier2/phase2_4_5_call_site_completion_20260621", ) parser.add_argument( "--json", action="store_true", help="Emit JSON output for CI integration.", ) parser.add_argument( "--strict", action="store_true", default=True, help="Exit 1 on any missing file (default; explicit for CI-gate clarity).", ) args = parser.parse_args() missing = check_required_files(args.ref) if args.json: result = { "ref": args.ref, "required": list(REQUIRED_FILES), "missing": missing, "ok": len(missing) == 0, } print(json.dumps(result, indent=2)) return 0 if result["ok"] else 1 if not missing: print(f"OK: {args.ref} has all {len(REQUIRED_FILES)} required tier-2 files.") for f in REQUIRED_FILES: print(f" + {f}") return 0 print(f"FAIL: {args.ref} is missing {len(missing)} required tier-2 file(s):", file=sys.stderr) for f in missing: print(f" - {f} (deleted or missing)", file=sys.stderr) print("", file=sys.stderr) print("This is a sandbox file leak. The 2026-06-24 MCP regression was caused", file=sys.stderr) print("by `setup_tier2_clone.ps1` modifications to opencode.json + mcp_paths.toml", file=sys.stderr) print("leaking into a tier-2 commit. To restore the missing files on this branch:", file=sys.stderr) print(" git checkout master -- ", file=sys.stderr) print(" git commit -m 'fix: restore (deleted by tier2 sandbox)'", file=sys.stderr) return 1 if __name__ == "__main__": sys.exit(main())