Architect8999 commited on
Commit
ffaeef0
Β·
verified Β·
1 Parent(s): e2d9760

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +39 -21
  2. verification_loop.py +13 -16
app.py CHANGED
@@ -15,7 +15,6 @@ Full loop:
15
  11. Webhook server runs in parallel for event-driven triggers
16
  """
17
 
18
- import glob
19
  import hashlib
20
  import json
21
  import os
@@ -61,6 +60,10 @@ from verification_loop import (
61
  )
62
  from webhook_server import set_job_dispatcher, start_webhook_server
63
  from worker_pool import MAX_WORKERS, run_parallel_audit
 
 
 
 
64
 
65
  # ──────────────────────────────────────────────────────────────
66
  # SECRETS β€” env only, never hardcoded
@@ -186,7 +189,13 @@ def write_mcp_config() -> str:
186
  "mcpServers": {
187
  "fetch-docs": {
188
  "command": "uvx", "args": ["mcp-server-fetch"],
189
- "env": {"FETCH_ALLOWED_DOMAINS": "docs.python.org,pypi.org,docs.github.com,packaging.python.org,peps.python.org,semver.org"}
 
 
 
 
 
 
190
  },
191
  "github-manager": {
192
  "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"],
@@ -331,7 +340,7 @@ def run_aider(mcp_config_path: str, prompt: str, context_files: list[str]) -> tu
331
  def process_failing_test(
332
  test_path: str,
333
  initial_failure: str,
334
- pytest_bin: str,
335
  mcp_config_path: str,
336
  job_id: str,
337
  branch_name: str,
@@ -419,8 +428,8 @@ def process_failing_test(
419
 
420
  # ── Step 5: RE-RUN TESTS ────────────────────────────────
421
  ui_log(f"Verifying fix β€” re-running tests (attempt {attempt_num})...", "INFO")
422
- test_output, test_code = run_subprocess_safe(
423
- [pytest_bin, test_path, "-v", "--tb=short"], cwd=REPO_DIR, timeout=120, raise_on_error=False
424
  )
425
 
426
  attempt = VerificationAttempt(
@@ -432,7 +441,7 @@ def process_failing_test(
432
 
433
  # ── Step 6: SAST gate ────────────────────────────────────
434
  ui_log("Running SAST gate on AI diff...", "SAST")
435
- sast_report = run_sast_gate(diff_text, changed_files, REPO_DIR)
436
  log_audit_event("SAST_SCAN", job_id, repo_ref, MODEL, {
437
  "attempt": attempt_num, "passed": sast_report.passed,
438
  "findings": len(sast_report.findings), "blocked_reason": sast_report.blocked_reason,
@@ -451,7 +460,7 @@ def process_failing_test(
451
 
452
  # ── Step 7: Supply chain gate ────────────────────────────
453
  ui_log("Running supply chain gate...", "SUPPLY")
454
- sc_report = run_supply_chain_gate(diff_text, REPO_DIR)
455
  log_audit_event("SUPPLY_CHAIN_SCAN", job_id, repo_ref, MODEL, {
456
  "attempt": attempt_num, "passed": sc_report.passed,
457
  "new_packages": sc_report.new_packages, "blocked_reason": sc_report.blocked_reason,
@@ -537,7 +546,7 @@ def process_failing_test(
537
  # ──────────────────────────────────────────────────────────────
538
  def process_audit_test(
539
  test_path: str,
540
- pytest_bin: str,
541
  mcp_config_path: str,
542
  tenant_id: str,
543
  target_repo: str,
@@ -557,8 +566,8 @@ def process_audit_test(
557
  job_id = upsert_job(tenant_id, target_repo, test_path, JobStatus.RUNNING)
558
  ui_log(f"Testing: {test_path} [job:{job_id}]")
559
 
560
- initial_output, pytest_code = run_subprocess_safe(
561
- [pytest_bin, test_path, "-v", "--tb=short"], cwd=REPO_DIR, timeout=120, raise_on_error=False
562
  )
563
 
564
  if pytest_code == 0:
@@ -575,7 +584,7 @@ def process_audit_test(
575
  log_audit_event("TEST_FAIL", job_id, target_repo, MODEL, {"test": test_path}, "FAILED")
576
 
577
  result = process_failing_test(
578
- test_path, initial_output, pytest_bin, mcp_config_path,
579
  job_id, branch_name, target_repo=target_repo,
580
  )
581
 
@@ -644,19 +653,28 @@ def enterprise_audit_loop(repo_override: str = None, branch: str = "main", speci
644
  ui_log("Syncing to latest origin/main...")
645
  safe_git_pull()
646
 
647
- pytest_bin = setup_target_venv()
 
 
 
 
 
648
 
 
649
  if specific_test:
650
- test_files = [os.path.join(REPO_DIR, specific_test)] if os.path.exists(os.path.join(REPO_DIR, specific_test)) else []
 
 
 
 
651
  else:
652
- test_files = sorted(glob.glob(f"{REPO_DIR}/**/test_*.py", recursive=True))
653
 
654
- ui_log(f"Discovered {len(test_files)} test file(s).")
655
 
656
- relative_tests = [os.path.relpath(t, REPO_DIR) for t in test_files]
657
  pool_result = run_parallel_audit(
658
  relative_tests, process_audit_test,
659
- pytest_bin=pytest_bin, mcp_config_path=mcp_config_path,
660
  tenant_id=TENANT_ID, target_repo=target_repo,
661
  )
662
  ui_log(
@@ -673,7 +691,8 @@ def enterprise_audit_loop(repo_override: str = None, branch: str = "main", speci
673
  if all_green and RED_TEAM_ENABLED and relative_tests:
674
  ui_log("All tests GREEN β€” activating Red Team CEGIS.", "RED")
675
  run_red_team_cegis(
676
- repo_dir=REPO_DIR, pytest_bin=pytest_bin,
 
677
  mcp_config_path=mcp_config_path,
678
  blue_team_fn=process_failing_test,
679
  tenant_id=TENANT_ID,
@@ -952,7 +971,7 @@ THEME = gr.themes.Base(
952
  button_primary_text_color="#ffffff",
953
  )
954
 
955
- with gr.Blocks(theme=THEME, title="Rhodawk AI β€” Code Review Monster") as demo:
956
 
957
  gr.HTML("""
958
  <div style="padding:20px 0 4px 0; border-bottom:1px solid #1e1e2e; margin-bottom:16px;">
@@ -1007,7 +1026,6 @@ with gr.Blocks(theme=THEME, title="Rhodawk AI β€” Code Review Monster") as demo:
1007
  inbox_chatbot = gr.Chatbot(
1008
  label="",
1009
  height=240,
1010
- bubble_full_width=False,
1011
  type="messages",
1012
  show_label=False,
1013
  container=True,
@@ -1198,4 +1216,4 @@ if __name__ == "__main__":
1198
  start_webhook_server()
1199
  ui_log("Webhook server running. Launching dashboard...")
1200
  port = int(os.environ.get("PORT", 7860))
1201
- demo.launch(server_name="0.0.0.0", server_port=port, share=False, show_error=True)
 
15
  11. Webhook server runs in parallel for event-driven triggers
16
  """
17
 
 
18
  import hashlib
19
  import json
20
  import os
 
60
  )
61
  from webhook_server import set_job_dispatcher, start_webhook_server
62
  from worker_pool import MAX_WORKERS, run_parallel_audit
63
+ from language_runtime import RuntimeFactory, LanguageRuntime, EnvConfig
64
+
65
+ # Module-level runtime handle β€” set once repo is cloned
66
+ _active_runtime: LanguageRuntime | None = None
67
 
68
  # ──────────────────────────────────────────────────────────────
69
  # SECRETS β€” env only, never hardcoded
 
189
  "mcpServers": {
190
  "fetch-docs": {
191
  "command": "uvx", "args": ["mcp-server-fetch"],
192
+ "env": {
193
+ "FETCH_ALLOWED_DOMAINS": (
194
+ ",".join(_active_runtime.get_mcp_domains())
195
+ if _active_runtime
196
+ else "docs.python.org,pypi.org,docs.github.com"
197
+ )
198
+ }
199
  },
200
  "github-manager": {
201
  "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"],
 
340
  def process_failing_test(
341
  test_path: str,
342
  initial_failure: str,
343
+ env_config: EnvConfig,
344
  mcp_config_path: str,
345
  job_id: str,
346
  branch_name: str,
 
428
 
429
  # ── Step 5: RE-RUN TESTS ────────────────────────────────
430
  ui_log(f"Verifying fix β€” re-running tests (attempt {attempt_num})...", "INFO")
431
+ test_output, test_code = _active_runtime.run_tests(
432
+ test_path, REPO_DIR, env_config, timeout=120
433
  )
434
 
435
  attempt = VerificationAttempt(
 
441
 
442
  # ── Step 6: SAST gate ────────────────────────────────────
443
  ui_log("Running SAST gate on AI diff...", "SAST")
444
+ sast_report = _active_runtime.run_sast(diff_text, changed_files, REPO_DIR)
445
  log_audit_event("SAST_SCAN", job_id, repo_ref, MODEL, {
446
  "attempt": attempt_num, "passed": sast_report.passed,
447
  "findings": len(sast_report.findings), "blocked_reason": sast_report.blocked_reason,
 
460
 
461
  # ── Step 7: Supply chain gate ────────────────────────────
462
  ui_log("Running supply chain gate...", "SUPPLY")
463
+ sc_report = _active_runtime.run_supply_chain(diff_text, REPO_DIR)
464
  log_audit_event("SUPPLY_CHAIN_SCAN", job_id, repo_ref, MODEL, {
465
  "attempt": attempt_num, "passed": sc_report.passed,
466
  "new_packages": sc_report.new_packages, "blocked_reason": sc_report.blocked_reason,
 
546
  # ──────────────────────────────────────────────────────────────
547
  def process_audit_test(
548
  test_path: str,
549
+ env_config: EnvConfig, # ← replaces pytest_bin
550
  mcp_config_path: str,
551
  tenant_id: str,
552
  target_repo: str,
 
566
  job_id = upsert_job(tenant_id, target_repo, test_path, JobStatus.RUNNING)
567
  ui_log(f"Testing: {test_path} [job:{job_id}]")
568
 
569
+ initial_output, pytest_code = _active_runtime.run_tests(
570
+ test_path, REPO_DIR, env_config, timeout=120
571
  )
572
 
573
  if pytest_code == 0:
 
584
  log_audit_event("TEST_FAIL", job_id, target_repo, MODEL, {"test": test_path}, "FAILED")
585
 
586
  result = process_failing_test(
587
+ test_path, initial_output, env_config, mcp_config_path,
588
  job_id, branch_name, target_repo=target_repo,
589
  )
590
 
 
653
  ui_log("Syncing to latest origin/main...")
654
  safe_git_pull()
655
 
656
+ # ── Language detection ────────────────────────────────────────
657
+ global _active_runtime
658
+ _active_runtime = RuntimeFactory.for_repo(REPO_DIR)
659
+ ui_log(f"Detected language: {_active_runtime.language.upper()}", "INFO")
660
+
661
+ env_config = _active_runtime.setup_env(REPO_DIR, PERSISTENT_DIR)
662
 
663
+ # ── Test discovery ────────────────────────────────────────────
664
  if specific_test:
665
+ relative_tests = (
666
+ [specific_test]
667
+ if os.path.exists(os.path.join(REPO_DIR, specific_test))
668
+ else []
669
+ )
670
  else:
671
+ relative_tests = _active_runtime.discover_tests(REPO_DIR)
672
 
673
+ ui_log(f"Discovered {len(relative_tests)} test file(s) [{_active_runtime.language}].")
674
 
 
675
  pool_result = run_parallel_audit(
676
  relative_tests, process_audit_test,
677
+ env_config=env_config, mcp_config_path=mcp_config_path,
678
  tenant_id=TENANT_ID, target_repo=target_repo,
679
  )
680
  ui_log(
 
691
  if all_green and RED_TEAM_ENABLED and relative_tests:
692
  ui_log("All tests GREEN β€” activating Red Team CEGIS.", "RED")
693
  run_red_team_cegis(
694
+ repo_dir=REPO_DIR,
695
+ env_config=env_config,
696
  mcp_config_path=mcp_config_path,
697
  blue_team_fn=process_failing_test,
698
  tenant_id=TENANT_ID,
 
971
  button_primary_text_color="#ffffff",
972
  )
973
 
974
+ with gr.Blocks(title="Rhodawk AI β€” Code Review Monster") as demo:
975
 
976
  gr.HTML("""
977
  <div style="padding:20px 0 4px 0; border-bottom:1px solid #1e1e2e; margin-bottom:16px;">
 
1026
  inbox_chatbot = gr.Chatbot(
1027
  label="",
1028
  height=240,
 
1029
  type="messages",
1030
  show_label=False,
1031
  container=True,
 
1216
  start_webhook_server()
1217
  ui_log("Webhook server running. Launching dashboard...")
1218
  port = int(os.environ.get("PORT", 7860))
1219
+ demo.launch(server_name="0.0.0.0", server_port=port, share=False, show_error=True, theme=THEME)
verification_loop.py CHANGED
@@ -20,6 +20,7 @@ import os
20
  import time
21
  from dataclasses import dataclass, field
22
  from typing import Optional
 
23
 
24
  MAX_RETRIES = int(os.getenv("RHODAWK_MAX_RETRIES", "5"))
25
  ADVERSARIAL_REJECTION_MULTIPLIER = int(os.getenv("RHODAWK_ADVERSARIAL_REJECTION_MULTIPLIER", "0"))
@@ -95,14 +96,12 @@ def build_retry_prompt(
95
  f" Fix applied:\n```diff\n{fix.get('fix_diff', '')[:400]}\n```"
96
  )
97
 
98
- sections.append(
99
- f"\nINSTRUCTIONS:\n"
100
- f"1. Fix the source code in '{src_file}' or 'requirements.txt' ONLY. Do NOT modify test files.\n"
101
- f"2. Use the 'fetch-docs' MCP tool to look up library documentation if needed.\n"
102
- f"3. Work on branch '{branch_name}'. Commit the minimal fix when complete.\n"
103
- f"4. The fix MUST be different from all previous attempts.\n"
104
- f"5. Ensure the fix is minimal and does not introduce regressions."
105
- )
106
 
107
  return "\n".join(sections)
108
 
@@ -129,13 +128,11 @@ def build_initial_prompt(
129
  f" What worked:\n```diff\n{fix.get('fix_diff', '')[:400]}\n```"
130
  )
131
 
132
- sections.append(
133
- f"\nINSTRUCTIONS:\n"
134
- f"1. If there is an import error or version conflict, use 'fetch-docs' MCP to look up "
135
- f" documentation on docs.python.org or pypi.org.\n"
136
- f"2. Fix '{src_file}' or 'requirements.txt' to make the test pass. Do NOT modify test files.\n"
137
- f"3. Work on branch '{branch_name}'. Commit the minimal fix when complete.\n"
138
- f"4. The fix must be minimal and must not introduce regressions."
139
- )
140
 
141
  return "\n".join(sections)
 
20
  import time
21
  from dataclasses import dataclass, field
22
  from typing import Optional
23
+ from language_runtime import RuntimeFactory
24
 
25
  MAX_RETRIES = int(os.getenv("RHODAWK_MAX_RETRIES", "5"))
26
  ADVERSARIAL_REJECTION_MULTIPLIER = int(os.getenv("RHODAWK_ADVERSARIAL_REJECTION_MULTIPLIER", "0"))
 
96
  f" Fix applied:\n```diff\n{fix.get('fix_diff', '')[:400]}\n```"
97
  )
98
 
99
+ runtime = RuntimeFactory.for_repo(os.getenv("RHODAWK_REPO_DIR", "/data/repo"))
100
+ sections.append("INSTRUCTIONS:\n" + runtime.get_fix_prompt_instructions(
101
+ test_path=test_path,
102
+ branch_name=branch_name,
103
+ src_hint=src_file,
104
+ ))
 
 
105
 
106
  return "\n".join(sections)
107
 
 
128
  f" What worked:\n```diff\n{fix.get('fix_diff', '')[:400]}\n```"
129
  )
130
 
131
+ runtime = RuntimeFactory.for_repo(os.getenv("RHODAWK_REPO_DIR", "/data/repo"))
132
+ sections.append("INSTRUCTIONS:\n" + runtime.get_fix_prompt_instructions(
133
+ test_path=test_path,
134
+ branch_name=branch_name,
135
+ src_hint=src_file,
136
+ ))
 
 
137
 
138
  return "\n".join(sections)