feat(scripts): add audit_branch_required_files.py (Rule 4 CI gate)
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 on a tier-2 branch. If either is missing, the audit fails (exit 1) with a clear diagnostic and the exact commands to restore the files. The pre-commit hook (conductor/tier2/githooks/pre-commit, hardened ineae75877) auto-unstages these files on commit, but does not prevent the deletion from being in the commit's diff. The 2026-06-24 MCP regression was exactly this: commit6956676fdeleted both files, and the empty fix commit (2b7e2de1) was a no-op. This audit catches that pattern 1 step earlier than the user noticing: on push, on pre-merge, on manual review. It checks the branch's index via 'git cat-file -e ref:file' (not the working tree) so it works in CI without a checked-out working tree. Usage: # Audit the current HEAD uv run python scripts/audit_branch_required_files.py # Audit a specific ref uv run python scripts/audit_branch_required_files.py --ref origin/tier2/foo # JSON output for CI integration uv run python scripts/audit_branch_required_files.py --json The script's REQUIRED_FILES list has 2 entries (the actual MCP regression targets), not 4. The 2 .opencode/agents/... files in conductor/tier2/githooks/forbidden-files.txt are tier-2 sandbox-only working tree files that are NEVER tracked in any branch (per commitfab2e55b'undo sandbox file leaks'); they live only in the tier-2 clone's working tree, copied there by setup_tier2_clone.ps1. Exit codes: 0 - all required files present 1 - one or more required files missing (CI gate failure) 2 - usage error Verified: - HEAD: OK (files restored by user commits71b51674+cb1b0c1c) - master: OK (files exist on master) -6956676f: FAIL (correctly detects the MCP regression commit) - --json output is valid JSON - --help shows clean usage 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 Or as a per-PR check on tier-2 branches: - name: Verify required files on tier-2 PR if: startsWith(github.head_ref, 'tier2/') run: uv run python scripts/audit_branch_required_files.py --strict
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
#!/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 -- <missing-file>", file=sys.stderr)
|
||||
print(" git commit -m 'fix: restore <missing-file> (deleted by tier2 sandbox)'", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user