-
-
Notifications
You must be signed in to change notification settings - Fork 3
Merge-gate zero-token layer: bot-anchoring, fake-green detection, red-first runner (Jay directive, bus 1850) #267
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| #!/usr/bin/env bash | ||
| set -uo pipefail | ||
|
|
||
| if [[ $# -lt 1 ]]; then | ||
| echo "FAILED: usage: check_bot_anchoring.sh <pr-number>" | ||
| exit 2 | ||
| fi | ||
|
|
||
| PR_NUMBER="$1" | ||
|
|
||
| PR_JSON=$(gh pr view "$PR_NUMBER" --json number,headRefOid,reviews 2>/dev/null) || { | ||
| echo "FAILED: gh/network error" | ||
| exit 3 | ||
| } | ||
|
|
||
| python3 - "$PR_JSON" << 'PYEOF' | ||
| import json, sys | ||
|
|
||
| try: | ||
| data = json.loads(sys.argv[1]) | ||
| except json.JSONDecodeError: | ||
| print('FAILED: could not parse PR data') | ||
| sys.exit(3) | ||
|
|
||
| head_oid = data.get('headRefOid', '') | ||
| reviews = data.get('reviews', []) | ||
| bot_authors = {'coderabbitai', 'qodo-code-review', 'kilo-code-bot'} | ||
|
|
||
| for r in reviews: | ||
| author = (r.get('author') or {}).get('login', '') | ||
| if author not in bot_authors: | ||
| continue | ||
| state = r.get('state', '') | ||
| if state not in ('COMMENTED', 'APPROVED', 'CHANGES_REQUESTED'): | ||
| continue | ||
| commit_oid = (r.get('commit') or {}).get('oid', '') | ||
| if commit_oid != head_oid: | ||
| continue | ||
| body = r.get('body') or '' | ||
| has_inline = r.get('includesCreatedEdit', False) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: The script relies on Reply with |
||
| if body.strip() or has_inline: | ||
| print('SUCCESS: anchored substantive bot review found') | ||
| sys.exit(0) | ||
|
|
||
| print('FAILED: no anchored substantive bot review found') | ||
| sys.exit(10) | ||
| PYEOF | ||
| exit $? | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| #!/usr/bin/env bash | ||
| set -uo pipefail | ||
|
|
||
| if [[ $# -lt 1 ]]; then | ||
| echo "FAILED: usage: check_fake_green.sh <pr-number>" | ||
| exit 2 | ||
| fi | ||
|
|
||
| PR_NUMBER="$1" | ||
|
|
||
| PR_JSON=$(gh pr view "$PR_NUMBER" --json number,headRefOid,reviews,comments 2>/dev/null) || { | ||
| echo "FAILED: gh/network error" | ||
| exit 3 | ||
| } | ||
|
|
||
| REPO_JSON=$(gh repo view --json owner,name 2>/dev/null) || { | ||
| echo "FAILED: gh/network error" | ||
| exit 3 | ||
| } | ||
|
|
||
| HEAD_OID=$(echo "$PR_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin)['headRefOid'])") || { | ||
| echo "FAILED: could not parse PR data" | ||
| exit 3 | ||
| } | ||
|
|
||
| OWNER=$(echo "$REPO_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin)['owner']['login'])") | ||
| REPO_NAME=$(echo "$REPO_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin)['name'])") | ||
|
|
||
| STATUS_JSON=$(gh api "repos/$OWNER/$REPO_NAME/commits/$HEAD_OID/status" 2>/dev/null) || { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: Deprecated The combined status endpoint ( Reply with |
||
| echo "FAILED: gh/network error" | ||
| exit 3 | ||
| } | ||
|
|
||
| CHECKRUNS_JSON=$(gh api "repos/$OWNER/$REPO_NAME/commits/$HEAD_OID/check-runs" 2>/dev/null) || { | ||
| echo "FAILED: gh/network error" | ||
| exit 3 | ||
| } | ||
|
|
||
| python3 - "$PR_JSON" "$STATUS_JSON" "$CHECKRUNS_JSON" << 'PYEOF' | ||
| import json, sys | ||
|
|
||
| try: | ||
| pr_data = json.loads(sys.argv[1]) | ||
| status_data = json.loads(sys.argv[2]) | ||
| checkruns_data = json.loads(sys.argv[3]) | ||
| except json.JSONDecodeError: | ||
| print('FAILED: could not parse gh response') | ||
| sys.exit(3) | ||
|
|
||
| reviews = pr_data.get('reviews', []) | ||
| comments = pr_data.get('comments', []) | ||
|
|
||
| # Check (a): success status/check with "Review rate limited" | ||
| for item in status_data.get('statuses', []): | ||
| if item.get('context') != 'CodeRabbit': | ||
| continue | ||
| if (item.get('state') or '').upper() != 'SUCCESS': | ||
| continue | ||
| desc = item.get('description') or '' | ||
| if 'Review rate limited' in desc: | ||
| print('FAILED: CodeRabbit commit status is SUCCESS but description contains "Review rate limited"') | ||
| print('Remediation: the plain "@coderabbitai review" command no-ops on a PR that was reviewed and then pushed to; only "@coderabbitai full review" forces a real pass.') | ||
| sys.exit(11) | ||
|
|
||
| for item in checkruns_data.get('check_runs', []): | ||
| if 'CodeRabbit' not in item.get('name', ''): | ||
| continue | ||
| conclusion = (item.get('conclusion') or '').upper() | ||
| if conclusion != 'SUCCESS': | ||
| continue | ||
| output = item.get('output') or {} | ||
| output_text = output.get('text') or output.get('title') or output.get('summary') or '' | ||
| if 'Review rate limited' in output_text: | ||
| print('FAILED: CodeRabbit check run is SUCCESS but output contains "Review rate limited"') | ||
| print('Remediation: the plain "@coderabbitai review" command no-ops on a PR that was reviewed and then pushed to; only "@coderabbitai full review" forces a real pass.') | ||
| sys.exit(11) | ||
|
|
||
| # Check (b): bare "Review finished" comment with no review object | ||
| cr_reviews = [ | ||
| r for r in reviews | ||
| if (r.get('author') or {}).get('login') == 'coderabbitai' | ||
| and r.get('state') in ('COMMENTED', 'APPROVED', 'CHANGES_REQUESTED') | ||
| ] | ||
|
|
||
| cr_comments = [ | ||
| c for c in comments | ||
| if (c.get('author') or {}).get('login') == 'coderabbitai' | ||
| ] | ||
|
|
||
| BARE_ACK_PATTERNS = ['Review finished', 'review finished'] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SUGGESTION: Redundant entries in
Reply with |
||
|
|
||
| def is_bare_ack(body): | ||
| body_lower = body.lower() | ||
| for pat in BARE_ACK_PATTERNS: | ||
| if pat.lower() in body_lower: | ||
| return True | ||
| return False | ||
|
|
||
| bare_acks = [c for c in cr_comments if is_bare_ack(c.get('body') or '')] | ||
|
|
||
| if bare_acks and not cr_reviews: | ||
| print('FAILED: only CodeRabbit artifact is a bare acknowledgement comment with no review object attached') | ||
| print('Remediation: the plain "@coderabbitai review" command no-ops on a PR that was reviewed and then pushed to; only "@coderabbitai full review" forces a real pass.') | ||
| sys.exit(11) | ||
|
|
||
| print('SUCCESS: no fake-green signals detected') | ||
| sys.exit(0) | ||
| PYEOF | ||
| exit $? | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| #!/usr/bin/env bash | ||
| set -uo pipefail | ||
|
|
||
| if [[ $# -lt 1 ]]; then | ||
| echo "FAILED: usage: red_first.sh <pr-number> --paths <src paths> --tests <pytest node ids>" | ||
| exit 2 | ||
| fi | ||
|
|
||
| PR_NUMBER="$1" | ||
| shift | ||
|
|
||
| SRC_PATHS=() | ||
| TEST_IDS=() | ||
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --paths) | ||
| shift | ||
| while [[ $# -gt 0 && "$1" != --* ]]; do | ||
| SRC_PATHS+=("$1") | ||
| shift | ||
| done | ||
| ;; | ||
| --tests) | ||
| shift | ||
| while [[ $# -gt 0 && "$1" != --* ]]; do | ||
| TEST_IDS+=("$1") | ||
| shift | ||
| done | ||
| ;; | ||
| *) | ||
| echo "FAILED: unknown argument: $1" | ||
| exit 2 | ||
| ;; | ||
| esac | ||
| done | ||
|
|
||
| if [[ ${#SRC_PATHS[@]} -eq 0 || ${#TEST_IDS[@]} -eq 0 ]]; then | ||
| echo "FAILED: both --paths and --tests are required" | ||
| exit 2 | ||
| fi | ||
|
|
||
| PR_JSON=$(gh pr view "$PR_NUMBER" --json headRefOid,headRefName,baseRefName 2>/dev/null) || { | ||
| echo "FAILED: gh/network error" | ||
| exit 3 | ||
| } | ||
|
|
||
| python3 - "$PR_JSON" -- "${SRC_PATHS[@]}" -- "${TEST_IDS[@]}" << 'PYEOF' | ||
| import json, sys, subprocess, os, tempfile | ||
|
|
||
| args = sys.argv[1:] | ||
| sep1 = args.index('--') | ||
| sep2 = args.index('--', sep1 + 1) | ||
|
|
||
| try: | ||
| data = json.loads(args[0]) | ||
| except json.JSONDecodeError: | ||
| print('FAILED: could not parse PR data') | ||
| sys.exit(3) | ||
|
|
||
| src_paths = args[sep1 + 1:sep2] | ||
| test_ids = args[sep2 + 1:] | ||
|
|
||
| head_ref = data.get('headRefName', '') | ||
| base_ref = data.get('baseRefName', '') | ||
|
|
||
| def run(cmd, **kwargs): | ||
| return subprocess.run(cmd, capture_output=True, text=True, **kwargs) | ||
|
|
||
| def eprint(*a, **kw): | ||
| print(*a, file=sys.stderr, **kw) | ||
|
|
||
| repo_root = run(['git', 'rev-parse', '--show-toplevel'], check=True).stdout.strip() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: Missing error handling for
Reply with |
||
| os.chdir(repo_root) | ||
|
|
||
| tmpdir = tempfile.mkdtemp(prefix='red-first-') | ||
| try: | ||
| result = run(['git', 'worktree', 'add', '-f', tmpdir, head_ref]) | ||
| if result.returncode != 0: | ||
| eprint('FAILED: could not check out PR branch into worktree') | ||
| eprint(result.stderr, end='') | ||
| print('FAILED: could not check out PR branch into worktree') | ||
| sys.exit(3) | ||
| except Exception: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: Dead The Reply with |
||
| eprint('FAILED: could not check out PR branch into worktree') | ||
| print('FAILED: could not check out PR branch into worktree') | ||
| sys.exit(3) | ||
|
|
||
| os.chdir(tmpdir) | ||
|
|
||
| # Stage 1: tests must PASS | ||
| eprint('Stage 1: running tests on PR branch...') | ||
| result = run(['uv', 'run', 'pytest'] + test_ids + ['-q']) | ||
| if result.returncode != 0: | ||
| eprint(result.stdout, end='') | ||
| eprint(result.stderr, end='') | ||
| print('FAILED: tests did not pass on PR branch') | ||
| os.chdir(repo_root) | ||
| run(['git', 'worktree', 'remove', tmpdir], capture_output=True) | ||
| sys.exit(13) | ||
|
|
||
| # Get merge base | ||
| try: | ||
| merge_base = run(['git', 'merge-base', head_ref, 'origin/' + base_ref], check=True).stdout.strip() | ||
| except subprocess.CalledProcessError: | ||
| try: | ||
| merge_base = run(['git', 'merge-base', head_ref, base_ref], check=True).stdout.strip() | ||
| except subprocess.CalledProcessError: | ||
| eprint('FAILED: could not find merge base') | ||
| print('FAILED: could not find merge base') | ||
| os.chdir(repo_root) | ||
| run(['git', 'worktree', 'remove', tmpdir], capture_output=True) | ||
| sys.exit(3) | ||
|
|
||
| # Stage 2: revert source paths to merge-base version | ||
| eprint('Stage 2: reverting source paths to merge-base...') | ||
| result = run(['git', 'checkout', merge_base, '--'] + src_paths) | ||
| if result.returncode != 0: | ||
| eprint('FAILED: could not revert source paths') | ||
| eprint(result.stderr, end='') | ||
| print('FAILED: could not revert source paths') | ||
| os.chdir(repo_root) | ||
| run(['git', 'worktree', 'remove', tmpdir], capture_output=True) | ||
| sys.exit(3) | ||
|
|
||
| # Stage 2 re-run: tests must FAIL | ||
| eprint('Stage 2: running tests after revert...') | ||
| result = run(['uv', 'run', 'pytest'] + test_ids + ['-q']) | ||
| if result.returncode == 0: | ||
| eprint(result.stdout, end='') | ||
| eprint(result.stderr, end='') | ||
| print('FAILED: tests passed without the fix in place') | ||
| os.chdir(repo_root) | ||
| run(['git', 'worktree', 'remove', tmpdir], capture_output=True) | ||
| sys.exit(12) | ||
|
|
||
| # Stage 3: restore | ||
| eprint('Stage 3: restoring source paths...') | ||
| result = run(['git', 'checkout', 'HEAD', '--'] + src_paths) | ||
| if result.returncode != 0: | ||
| eprint('FAILED: could not restore source paths') | ||
| eprint(result.stderr, end='') | ||
| print('FAILED: could not restore source paths') | ||
| os.chdir(repo_root) | ||
| run(['git', 'worktree', 'remove', tmpdir], capture_output=True) | ||
| sys.exit(3) | ||
|
|
||
| # Stage 3 re-run: tests must PASS | ||
| eprint('Stage 3: running tests after restore...') | ||
| result = run(['uv', 'run', 'pytest'] + test_ids + ['-q']) | ||
| if result.returncode != 0: | ||
| eprint(result.stdout, end='') | ||
| eprint(result.stderr, end='') | ||
| print('FAILED: tests did not pass after restore') | ||
| os.chdir(repo_root) | ||
| run(['git', 'worktree', 'remove', tmpdir], capture_output=True) | ||
| sys.exit(13) | ||
|
|
||
| # Cleanup | ||
| os.chdir(repo_root) | ||
| run(['git', 'worktree', 'remove', tmpdir], capture_output=True) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: No return-code check on final On the success path, Reply with |
||
|
|
||
| print('SUCCESS: red-first cycle completed') | ||
| sys.exit(0) | ||
| PYEOF | ||
| exit $? | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| { | ||
| "check_runs": [] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| { | ||
| "number": 218, | ||
| "headRefOid": "abc123def456", | ||
| "reviews": [ | ||
| { | ||
| "author": {"login": "coderabbitai"}, | ||
| "state": "COMMENTED", | ||
| "body": "<details><summary>Nitpick comments (1)</summary><blockquote>\n\n**Add coverage for the new import-tracking table.**\n</blockquote></details>", | ||
| "commit": {"oid": "abc123def456"}, | ||
| "includesCreatedEdit": false, | ||
| "submittedAt": "2026-07-28T12:21:45Z" | ||
| }, | ||
| { | ||
| "author": {"login": "qodo-code-review"}, | ||
| "state": "COMMENTED", | ||
| "body": "", | ||
| "commit": {"oid": "abc123def456"}, | ||
| "includesCreatedEdit": false, | ||
| "submittedAt": "2026-07-28T12:22:07Z" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "number": 218, | ||
| "headRefOid": "abc123def456", | ||
| "reviews": [ | ||
| { | ||
| "author": {"login": "coderabbitai"}, | ||
| "state": "COMMENTED", | ||
| "body": "Review content here", | ||
| "commit": {"oid": "oldsha123456"}, | ||
| "includesCreatedEdit": false, | ||
| "submittedAt": "2026-07-28T12:21:45Z" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| { | ||
| "number": 218, | ||
| "headRefOid": "abc123def456", | ||
| "reviews": [], | ||
| "comments": [ | ||
| { | ||
| "author": {"login": "coderabbitai"}, | ||
| "body": "Review finished", | ||
| "createdAt": "2026-07-28T12:21:48Z" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "number": 218, | ||
| "headRefOid": "abc123def456", | ||
| "reviews": [ | ||
| { | ||
| "author": {"login": "jaylfc"}, | ||
| "state": "APPROVED", | ||
| "body": "LGTM", | ||
| "commit": {"oid": "abc123def456"}, | ||
| "includesCreatedEdit": false, | ||
| "submittedAt": "2026-07-28T12:30:00Z" | ||
| } | ||
| ] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SUGGESTION: Hardcoded bot author names reduce flexibility
bot_authorsis a hardcoded set. If bot names change or new bots are added, this script must be edited. Consider reading from an environment variable (e.g.,BOT_AUTHORS) to make it configurable without code changes.Reply with
@kilocode-bot fix itto have Kilo Code address this issue.