chore: replace print() with proper logging; add jitter to automation
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
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
- 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.
This commit is contained in:
138
scripts/claim-task.py
Normal file
138
scripts/claim-task.py
Normal file
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
import os, sys, json, subprocess, random, time, logging
|
||||
from datetime import datetime
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
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")
|
||||
|
||||
def log(msg):
|
||||
logger.info(msg)
|
||||
|
||||
def die(msg):
|
||||
log(f"FATAL: {msg}")
|
||||
sys.exit(1)
|
||||
|
||||
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 compute_utility(issue):
|
||||
score = 0
|
||||
labels = [l['name'] for l in issue['labels']]
|
||||
if 'security' in labels:
|
||||
score += 100
|
||||
elif 'bug' in labels:
|
||||
score += 50
|
||||
elif 'feature' in labels:
|
||||
score += 30
|
||||
elif 'refactor' in labels:
|
||||
score += 10
|
||||
if 'good-first-task-for-agent' in labels:
|
||||
score += 20
|
||||
if any(tag in labels for tag in ['needs-design', 'blocked', 'needs-reproduction']):
|
||||
score -= 1000
|
||||
score += issue['comments'] * 5
|
||||
return score
|
||||
|
||||
def get_open_unassigned_issues():
|
||||
items = api_get(f"repos/{REPO}/issues?state=open") or []
|
||||
candidates = []
|
||||
for i in items:
|
||||
# Skip PRs: pull_request field is non-null for PRs
|
||||
if i.get('pull_request') is not None:
|
||||
continue
|
||||
if i['assignee'] is not None:
|
||||
continue
|
||||
labels = [l['name'] for l in i['labels']]
|
||||
if any(tag in labels for tag in ['needs-design', 'blocked', 'needs-reproduction']):
|
||||
continue
|
||||
candidates.append(i)
|
||||
return candidates
|
||||
|
||||
def create_claim_branch(issue_number):
|
||||
branch = f"claim/{issue_number}"
|
||||
subprocess.run(["git", "fetch", "origin"], capture_output=True, cwd=REPO_DIR)
|
||||
subprocess.run(["git", "checkout", "-B", branch, "origin/main"], capture_output=True, cwd=REPO_DIR)
|
||||
subprocess.run(["git", "commit", "--allow-empty", "-m", f"Claim issue #{issue_number} for {AGENT}"], capture_output=True, cwd=REPO_DIR)
|
||||
r = subprocess.run(["git", "push", "-u", "origin", branch], capture_output=True, cwd=REPO_DIR)
|
||||
return r.returncode == 0
|
||||
|
||||
def assign_issue(issue_number):
|
||||
payload = {"assignee": AGENT}
|
||||
result = api_post(f"repos/{REPO}/issues/{issue_number}/assignees", payload)
|
||||
return result is not None
|
||||
|
||||
def add_comment(issue_number, body):
|
||||
payload = {"body": body}
|
||||
return api_post(f"repos/{REPO}/issues/{issue_number}/comments", payload) is not None
|
||||
|
||||
def main():
|
||||
# Jitter 0-60s
|
||||
time.sleep(random.randint(0, 60))
|
||||
log("Claim task cycle starting...")
|
||||
state_file = os.path.join(REPO_DIR, ".claim-state.json")
|
||||
try:
|
||||
with open(state_file) as f:
|
||||
state = json.load(f)
|
||||
except:
|
||||
state = {}
|
||||
if state.get('current_claim'):
|
||||
log(f"Already working on issue #{state['current_claim']} (branch {state.get('work_branch')})")
|
||||
return
|
||||
issues = get_open_unassigned_issues()
|
||||
if not issues:
|
||||
log("No unassigned issues available.")
|
||||
return
|
||||
issues.sort(key=lambda i: compute_utility(i), reverse=True)
|
||||
for issue in issues:
|
||||
num = issue['number']
|
||||
title = issue['title']
|
||||
labels = [lbl['name'] for lbl in issue.get('labels', [])]
|
||||
log(f"Attempting to claim issue #{num}: {title} (labels={labels})")
|
||||
if create_claim_branch(num):
|
||||
assign_issue(num)
|
||||
slug = ''.join(c if c.isalnum() else '-' for c in title.lower())[:40].strip('-')
|
||||
work_branch = f"{AGENT}/{num}-{slug}"
|
||||
subprocess.run(["git", "checkout", "-b", work_branch, "main"], cwd=REPO_DIR, capture_output=True)
|
||||
state = {
|
||||
'current_claim': num,
|
||||
'claim_branch': f'claim/{num}',
|
||||
'work_branch': work_branch,
|
||||
'claimed_at': datetime.utcnow().isoformat() + 'Z',
|
||||
'issue_title': title,
|
||||
'labels': labels
|
||||
}
|
||||
with open(state_file, 'w') as f:
|
||||
json.dump(state, f, indent=2)
|
||||
add_comment(num, f"Agent `{AGENT}` claiming this task. (automated)")
|
||||
log(f"✅ Claimed issue #{num}. Work branch: {work_branch}")
|
||||
return
|
||||
else:
|
||||
log(f"Claim failed for #{num} (branch exists). Trying next...")
|
||||
log("Could not claim any issue; all taken or unavailable.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
REPO_DIR = '/opt/aitbc'
|
||||
main()
|
||||
139
scripts/monitor-prs.py
Normal file
139
scripts/monitor-prs.py
Normal file
@@ -0,0 +1,139 @@
|
||||
#!/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"✅ Auto‑approved (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()
|
||||
120
scripts/qa-cycle.py
Executable file
120
scripts/qa-cycle.py
Executable file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
import os, sys, json, subprocess, tempfile, shutil, glob, re
|
||||
import random, time, logging
|
||||
from datetime import datetime
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
AGENT = os.getenv("AGENT_NAME", "aitbc")
|
||||
LOG_DIR = "/var/log/aitbc"
|
||||
QA_LOG = os.path.join(LOG_DIR, "qa_cycle.log")
|
||||
|
||||
def run_cmd(cmd, capture=True, check=False):
|
||||
result = subprocess.run(cmd, shell=True, capture_output=capture, text=True)
|
||||
if check and result.returncode != 0:
|
||||
logger.error(f"Command failed: {cmd}")
|
||||
logger.error(f"stderr: {result.stderr}")
|
||||
return None
|
||||
return result.stdout if capture else result.returncode
|
||||
|
||||
def feature_tests():
|
||||
logger.info("=== FEATURE TESTS ===")
|
||||
for mod in ["aiohttp", "fastapi", "click", "pydantic"]:
|
||||
cmd = f"python3 -c 'import {mod}; print({mod}.__version__)'"
|
||||
out = run_cmd(cmd)
|
||||
if out is None:
|
||||
logger.error(f"Import failed: {mod}")
|
||||
else:
|
||||
logger.info(f"{mod} import OK: {out.strip()}")
|
||||
out = run_cmd("/opt/aitbc/cli_venv/bin/aitbc --help")
|
||||
if out:
|
||||
logger.info("CLI help works")
|
||||
else:
|
||||
logger.error("CLI help failed")
|
||||
out = run_cmd("curl -s http://localhost:8010/health || true")
|
||||
if out and 'ok' in out.lower():
|
||||
logger.info("Brother chain health endpoint OK")
|
||||
else:
|
||||
logger.warning("Brother chain health check inconclusive")
|
||||
|
||||
def bug_sentinel():
|
||||
logger.info("=== BUG SENTINEL ===")
|
||||
logs = glob.glob("/var/log/aitbc/*.log")
|
||||
patterns = ["ERROR", "CRITICAL", "FATAL", "Traceback", "Exception"]
|
||||
found = []
|
||||
for logfile in logs:
|
||||
try:
|
||||
with open(logfile) as f:
|
||||
lines = f.readlines()[-200:]
|
||||
except:
|
||||
continue
|
||||
for line in lines:
|
||||
if any(p in line for p in patterns):
|
||||
found.append(f"{logfile}: {line.strip()}")
|
||||
if found:
|
||||
logger.warning(f"Found {len(found)} error patterns (sample):")
|
||||
for item in found[:5]:
|
||||
logger.warning(f" {item}")
|
||||
else:
|
||||
logger.info("No error patterns in recent logs")
|
||||
|
||||
def code_review():
|
||||
logger.info("=== CODE REVIEW ===")
|
||||
py_files = run_cmd("find /opt/aitbc -name '*.py' -type f | head -30").splitlines()
|
||||
issues = []
|
||||
for f in py_files:
|
||||
try:
|
||||
with open(f) as fp:
|
||||
content = fp.read()
|
||||
except:
|
||||
continue
|
||||
if re.search(r'except\s*:', content):
|
||||
issues.append(f"{f}: bare except")
|
||||
if re.search(r'def\s+\w+\s*\([^)]*=\s*[\[\{\}]', content):
|
||||
issues.append(f"{f}: mutable default argument")
|
||||
if 'print(' in content and 'if __name__' not in content:
|
||||
issues.append(f"{f}: print statement in library code")
|
||||
if re.search(r'(password|secret|key)\s*=\s*[\'"][^\'"]+[\'"]', content, re.IGNORECASE):
|
||||
issues.append(f"{f}: possible hardcoded secret")
|
||||
if issues:
|
||||
logger.warning(f"Found {len(issues)} code quality issues (sample):")
|
||||
for i in issues[:5]:
|
||||
logger.warning(f" {i}")
|
||||
else:
|
||||
logger.info("No obvious code quality issues detected")
|
||||
|
||||
def scenario_runner():
|
||||
logger.info("=== SCENARIO RUNNER ===")
|
||||
tmp = tempfile.mkdtemp(prefix="aitbc_qa_")
|
||||
try:
|
||||
run_cmd(f"git init {tmp}", check=True)
|
||||
run_cmd(f"git -C {tmp} config user.email 'qa@aitbc'", check=True)
|
||||
run_cmd(f"git -C {tmp} config user.name 'QA Agent'", check=True)
|
||||
run_cmd(f"echo 'test' > {tmp}/test.txt", check=True)
|
||||
run_cmd(f"git -C {tmp} add .", check=True)
|
||||
run_cmd(f"git -C {tmp} commit -m 'initial'", check=True)
|
||||
run_cmd(f"git -C {tmp} checkout -b claim/123", check=True)
|
||||
run_cmd(f"git -C {tmp} commit --allow-empty -m 'Claim issue 123'", check=True)
|
||||
logger.info("Git workflow test passed")
|
||||
except Exception as e:
|
||||
logger.error(f"Git workflow test failed: {e}")
|
||||
finally:
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
def main():
|
||||
logger.info(f"QA CYCLE STARTED for {AGENT}")
|
||||
start = datetime.now()
|
||||
# Jitter: 0-900 seconds (15 minutes)
|
||||
jitter = random.randint(0, 900)
|
||||
logger.info(f"Sleeping {jitter}s before start...")
|
||||
time.sleep(jitter)
|
||||
feature_tests()
|
||||
bug_sentinel()
|
||||
code_review()
|
||||
scenario_runner()
|
||||
elapsed = datetime.now() - start
|
||||
logger.info(f"QA CYCLE COMPLETED in {elapsed.total_seconds():.1f}s")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user