Files
aitbc/scripts/monitor-prs.py
aitbc dc547a506a
Some checks failed
AITBC CI/CD Pipeline / lint-and-test (3.11) (pull_request) Has been cancelled
AITBC CI/CD Pipeline / lint-and-test (3.12) (pull_request) Has been cancelled
AITBC CI/CD Pipeline / lint-and-test (3.13) (pull_request) Has been cancelled
AITBC CLI Level 1 Commands Test / test-cli-level1 (3.11) (pull_request) Has been cancelled
AITBC CLI Level 1 Commands Test / test-cli-level1 (3.12) (pull_request) Has been cancelled
AITBC CLI Level 1 Commands Test / test-cli-level1 (3.13) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (apps/coordinator-api/src) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (cli/aitbc_cli) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-core/src) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-crypto/src) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-sdk/src) (pull_request) Has been cancelled
Security Scanning / Bandit Security Scan (tests) (pull_request) Has been cancelled
Security Scanning / CodeQL Security Analysis (javascript) (pull_request) Has been cancelled
Security Scanning / CodeQL Security Analysis (python) (pull_request) Has been cancelled
Security Scanning / Dependency Security Scan (pull_request) Has been cancelled
Security Scanning / Container Security Scan (pull_request) Has been cancelled
Security Scanning / OSSF Scorecard (pull_request) Has been cancelled
AITBC CI/CD Pipeline / test-cli (pull_request) Has been cancelled
AITBC CI/CD Pipeline / test-services (pull_request) Has been cancelled
AITBC CI/CD Pipeline / test-production-services (pull_request) Has been cancelled
AITBC CI/CD Pipeline / security-scan (pull_request) Has been cancelled
AITBC CI/CD Pipeline / build (pull_request) Has been cancelled
AITBC CI/CD Pipeline / deploy-staging (pull_request) Has been cancelled
AITBC CI/CD Pipeline / deploy-production (pull_request) Has been cancelled
AITBC CI/CD Pipeline / performance-test (pull_request) Has been cancelled
AITBC CI/CD Pipeline / docs (pull_request) Has been cancelled
AITBC CI/CD Pipeline / release (pull_request) Has been cancelled
AITBC CI/CD Pipeline / notify (pull_request) Has been cancelled
AITBC CLI Level 1 Commands Test / test-summary (pull_request) Has been cancelled
Security Scanning / Security Summary Report (pull_request) Has been cancelled
chore: replace print() with proper logging; add jitter to automation
- CLI commands: replace print with click.echo (ensures proper stdout handling)
- Coordinator API services: add logging import and logger; replace print with logger.info
- Automation scripts: claim-task.py, monitor-prs.py, qa-cycle.py now use logging and have random jitter at startup
- Also includes fix for name shadowing in regulatory.py (aliased service imports) which was pending

This addresses issue #23 (print statements) and improves error handling.

Note: Many bare except clauses (issue #20) remain; will be addressed separately.
2026-03-15 19:01:56 +00:00

140 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
import os, sys, json, subprocess, tempfile, shutil, re, time, random
from datetime import datetime
TOKEN = os.getenv("GITEA_TOKEN", "ffce3b62d583b761238ae00839dce7718acaad85")
API_BASE = os.getenv("GITEA_API_BASE", "http://gitea.bubuit.net:3000/api/v1")
REPO = "oib/aitbc"
AGENT = os.getenv("AGENT_NAME", "aitbc")
OTHER = "aitbc1" if AGENT == "aitbc" else "aitbc"
RING_PREFIXES = [
(0, ["packages/py/aitbc-core", "packages/py/aitbc-sdk"]),
(1, ["apps/"]),
(2, ["cli/", "analytics/", "tools/"]),
]
RING_THRESHOLD = {0: 0.9, 1: 0.8, 2: 0.7, 3: 0.5}
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger(__name__)
def log(msg):
logger.info(msg)
def api_get(path):
cmd = ["curl", "-s", "-H", f"Authorization: token {TOKEN}", f"{API_BASE}/{path}"]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
return None
try:
return json.loads(result.stdout)
except:
return None
def api_post(path, payload):
cmd = ["curl", "-s", "-X", "POST", "-H", f"Authorization: token {TOKEN}", "-H", "Content-Type: application/json",
f"{API_BASE}/{path}", "-d", json.dumps(payload)]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
return None
try:
return json.loads(result.stdout)
except:
return None
def get_open_prs():
return api_get(f"repos/{REPO}/pulls?state=open") or []
def get_my_reviews(pr_number):
return api_get(f"repos/{REPO}/pulls/{pr_number}/reviews") or []
def is_test_file(path):
return '/tests/' in path or path.startswith('tests/') or path.endswith('_test.py')
def detect_ring(base_sha, head_sha):
try:
output = subprocess.run(
["git", "diff", "--name-only", base_sha, head_sha],
capture_output=True, text=True, check=True, cwd='/opt/aitbc'
).stdout
files = [f.strip() for f in output.splitlines() if f.strip()]
except subprocess.CalledProcessError:
files = []
if files and all(is_test_file(f) for f in files):
return 3
for ring, prefixes in sorted(RING_PREFIXES, key=lambda x: x[0]):
for p in files:
if any(p.startswith(prefix) for prefix in prefixes):
return ring
return 3
def syntax_check(worktree):
py_files = []
for root, dirs, files in os.walk(worktree):
for f in files:
if f.endswith('.py'):
py_files.append(os.path.join(root, f))
for f in py_files:
r = subprocess.run([sys.executable, '-m', 'py_compile', f], capture_output=True)
if r.returncode != 0:
return False, f"Syntax error in {os.path.relpath(f, worktree)}"
return True, ""
def post_review(pr_number, event, body):
payload = {"event": event, "body": body}
return api_post(f"repos/{REPO}/pulls/{pr_number}/reviews", payload) is not None
def request_review(pr_number, reviewer):
payload = {"reviewers": [reviewer]}
return api_post(f"repos/{REPO}/pulls/{pr_number}/requested-reviewers", payload) is not None
def main():
# Jitter 0-60s
time.sleep(random.randint(0, 60))
log("Fetching open PRs...")
prs = get_open_prs()
if not prs:
log("No open PRs")
return
# Process sibling PRs
for pr in prs:
if pr['user']['login'] != OTHER:
continue
number = pr['number']
title = pr['title'][:50]
log(f"Reviewing sibling PR #{number}: {title}")
# Checkout and validate
tmp = tempfile.mkdtemp(prefix="aitbc_monitor_")
try:
subprocess.run(["git", "clone", "--no-checkout", "origin", tmp], capture_output=True, check=True)
wt = os.path.join(tmp, "wt")
os.makedirs(wt)
subprocess.run(["git", "--git-dir", os.path.join(tmp, ".git"), "--work-tree", wt, "fetch", "origin", pr['head']['ref']], capture_output=True, check=True)
subprocess.run(["git", "--git-dir", os.path.join(tmp, ".git"), "--work-tree", wt, "checkout", "FETCH_HEAD"], capture_output=True, check=True)
ok, err = syntax_check(wt)
if not ok:
post_review(number, "REQUEST_CHANGES", f"❌ Syntax error: {err}")
log(f"PR #{number} failed syntax: {err}")
continue
ring = detect_ring(pr['base']['sha'], pr['head']['sha'])
threshold = RING_THRESHOLD.get(ring, 0.5)
if ring == 0:
post_review(number, "REQUEST_CHANGES", f"Ring 0 (core): needs manual review. Threshold: >{threshold}")
log(f"PR #{number} is Ring0; manual review required")
else:
post_review(number, "APPROVE", f"✅ Autoapproved (ring {ring}, threshold >{threshold})")
log(f"PR #{number} approved")
finally:
shutil.rmtree(tmp, ignore_errors=True)
# Our own PRs: request review from OTHER if not yet
our_prs = [pr for pr in prs if pr['user']['login'] == AGENT]
for pr in our_prs:
number = pr['number']
reviews = get_my_reviews(number)
if not any(r['user']['login'] == OTHER for r in reviews):
log(f"Requesting review from {OTHER} on our PR #{number}")
request_review(number, OTHER)
if __name__ == "__main__":
main()