2026-07-13 05:23:38 +07:00
|
|
|
//! Auto-subagent orchestration: the main agent automatically delegates
|
|
|
|
|
//! review, test-generation, architecture-review, and security-review tasks
|
|
|
|
|
//! to subagents without requiring explicit tool calls from the LLM.
|
|
|
|
|
//!
|
|
|
|
|
//! Two modes:
|
|
|
|
|
//! - **Inline** (`spawn_quick_review`): runs synchronously within the turn
|
|
|
|
|
//! after each write/edit tool call. Results are fed back into the LLM
|
|
|
|
|
//! conversation so the agent can act on feedback immediately.
|
|
|
|
|
//! - **Background** (`spawn_background_*`): runs asynchronously on a
|
|
|
|
|
//! dedicated OS thread at the end of a turn. Reports results via
|
|
|
|
|
//! `TurnEvent::SystemNote`, consumed by the TUI on the next Tick.
|
|
|
|
|
//!
|
|
|
|
|
//! Why inline vs background:
|
|
|
|
|
//! - Inline reviews give the agent an immediate feedback loop ("I just
|
|
|
|
|
//! wrote this file, let me check if it's correct before continuing").
|
|
|
|
|
//! - Background reviews catch broader concerns (missing tests, architectural
|
|
|
|
|
//! drift, security issues) without blocking the main agent's flow.
|
|
|
|
|
|
|
|
|
|
use std::path::Path;
|
|
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
|
use std::collections::VecDeque;
|
|
|
|
|
use crate::app::state::runtime::TurnEvent;
|
|
|
|
|
use crate::app::subagent::context::build_subagent_context;
|
|
|
|
|
use crate::app::subagent::engine::run_subagent;
|
|
|
|
|
use crate::app::subagent::spawn::AgentDefinition;
|
|
|
|
|
use crate::app::subagent::event::SubagentEvent;
|
|
|
|
|
|
|
|
|
|
/// File extensions that should not trigger auto-review (config, lock, data).
|
|
|
|
|
const SKIP_REVIEW_EXTENSIONS: &[&str] = &[
|
|
|
|
|
".lock", ".md", ".txt", ".json", ".toml", ".yaml", ".yml",
|
|
|
|
|
".svg", ".png", ".jpg", ".ico", ".woff", ".woff2",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/// File names that should not trigger auto-review.
|
|
|
|
|
const SKIP_REVIEW_FILES: &[&str] = &[
|
|
|
|
|
"Cargo.lock", "yarn.lock", "package-lock.json",
|
|
|
|
|
".gitignore", ".env", ".env.example",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/// Maximum LLM steps for a quick-review subagent. Keeps reviews fast.
|
|
|
|
|
const QUICK_REVIEW_MAX_STEPS: usize = 2;
|
|
|
|
|
|
|
|
|
|
/// Maximum LLM steps for background subagents (test gen, arch, security).
|
|
|
|
|
const BG_SUBAGENT_MAX_STEPS: usize = 8;
|
|
|
|
|
|
|
|
|
|
/// ─── Helpers ───
|
2026-07-13 06:22:31 +07:00
|
|
|
///
|
2026-07-13 05:23:38 +07:00
|
|
|
/// Check whether a file path is worth auto-reviewing (not config/lock/data).
|
|
|
|
|
pub fn is_reviewable_path(path: &str) -> bool {
|
|
|
|
|
let lower = path.to_lowercase();
|
|
|
|
|
if SKIP_REVIEW_FILES.iter().any(|f| lower.ends_with(f)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if SKIP_REVIEW_EXTENSIONS.iter().any(|e| lower.ends_with(e)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Skip paths that are clearly generated or vendored
|
|
|
|
|
if lower.contains("/target/") || lower.contains("/node_modules/")
|
|
|
|
|
|| lower.contains("/.git/") || lower.contains("/vendor/")
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Determine whether a file change looks like it modifies production logic
|
|
|
|
|
/// (vs. tests, config, or documentation) — used to decide if a test-gen
|
|
|
|
|
/// or security-review background subagent should fire.
|
|
|
|
|
fn is_production_code(path: &str) -> bool {
|
|
|
|
|
let lower = path.to_lowercase();
|
|
|
|
|
// Skip test files — they don't need test-gen from another agent
|
|
|
|
|
if lower.contains("test") || lower.contains("spec") || lower.contains("_test.") {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-07-13 08:12:02 +07:00
|
|
|
// Only source files — use Path::extension() to avoid clippy
|
|
|
|
|
// case_sensitive_file_extension_comparisons lint
|
|
|
|
|
std::path::Path::new(&lower)
|
|
|
|
|
.extension()
|
|
|
|
|
.and_then(|ext| ext.to_str())
|
|
|
|
|
.is_some_and(|ext| {
|
|
|
|
|
matches!(
|
|
|
|
|
ext,
|
|
|
|
|
"rs" | "ts" | "tsx" | "js" | "jsx" | "go" | "py" | "java" | "kt" | "swift"
|
|
|
|
|
| "c" | "cpp" | "h" | "hpp"
|
|
|
|
|
)
|
|
|
|
|
})
|
2026-07-13 05:23:38 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// ─── Inline Quick Review (synchronous, feeds back to LLM) ───
|
2026-07-13 06:22:31 +07:00
|
|
|
///
|
2026-07-13 05:23:38 +07:00
|
|
|
/// Spawn a lightweight inline code review subagent for the given file.
|
|
|
|
|
///
|
|
|
|
|
/// The subagent reads the file (read-only), checks for common issues,
|
|
|
|
|
/// and returns a concise text verdict. This runs synchronously so the
|
|
|
|
|
/// main agent's `run_agent_turn` can inject the result back into the
|
|
|
|
|
/// LLM conversation for immediate action.
|
|
|
|
|
///
|
|
|
|
|
/// Returns `Ok(verdict)` if the review completed, or an error if the
|
|
|
|
|
/// subagent could not be spawned or failed internally. Callers should
|
|
|
|
|
/// log and swallow errors gracefully — a failed inline review should
|
|
|
|
|
/// never interrupt the main agent's flow.
|
|
|
|
|
pub fn spawn_quick_review(
|
|
|
|
|
file_path: &str,
|
|
|
|
|
session_dir: &Path,
|
|
|
|
|
workspaces: &[std::path::PathBuf],
|
|
|
|
|
) -> anyhow::Result<String> {
|
|
|
|
|
let prompt = format!(
|
|
|
|
|
"{}\n\nFile to review: {}",
|
|
|
|
|
crate::resources::AUTO_REVIEWER_PROMPT,
|
|
|
|
|
file_path,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let def = AgentDefinition::new(
|
|
|
|
|
"quick-reviewer".to_string(),
|
|
|
|
|
"reviewer".to_string(),
|
|
|
|
|
)
|
|
|
|
|
.with_system_prompt(prompt)
|
|
|
|
|
.with_max_steps(QUICK_REVIEW_MAX_STEPS);
|
|
|
|
|
|
2026-07-13 08:12:02 +07:00
|
|
|
let mut ctx = build_subagent_context(&def);
|
2026-07-13 05:23:38 +07:00
|
|
|
ctx.session_dir = session_dir.to_path_buf();
|
|
|
|
|
ctx.workspaces = workspaces.to_vec();
|
|
|
|
|
|
|
|
|
|
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
|
|
|
|
let _drain = std::thread::spawn(move || {
|
|
|
|
|
while let Some(event) = rx.blocking_recv() {
|
|
|
|
|
match &event {
|
2026-07-13 08:12:02 +07:00
|
|
|
SubagentEvent::ToolCall { tool, .. } => {
|
|
|
|
|
tracing::debug!("[auto-review] tool call: {}", tool);
|
2026-07-13 05:23:38 +07:00
|
|
|
}
|
2026-07-13 08:12:02 +07:00
|
|
|
SubagentEvent::ToolResult { tool, .. } => {
|
|
|
|
|
tracing::debug!("[auto-review] tool result: {}", tool);
|
2026-07-13 05:23:38 +07:00
|
|
|
}
|
|
|
|
|
SubagentEvent::Completed { .. } => {
|
|
|
|
|
tracing::debug!("[auto-review] completed");
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-13 08:12:02 +07:00
|
|
|
let verdict = run_subagent(&ctx, &tx)?;
|
2026-07-13 05:23:38 +07:00
|
|
|
tracing::info!(
|
|
|
|
|
"[auto-review] quick review for '{}': {}",
|
|
|
|
|
file_path,
|
|
|
|
|
verdict.lines().next().unwrap_or(&verdict),
|
|
|
|
|
);
|
|
|
|
|
Ok(verdict)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-13 08:12:02 +07:00
|
|
|
/// ─── Background Subagent Spawners (async, report via `SystemNote`) ───
|
2026-07-13 06:22:31 +07:00
|
|
|
///
|
2026-07-13 05:23:38 +07:00
|
|
|
/// Spawn a background subagent that generates tests for modified files.
|
|
|
|
|
///
|
|
|
|
|
/// Uses the test-generator prompt and has read-write access so it can
|
|
|
|
|
/// create test files. Runs in a separate OS thread and reports completion
|
|
|
|
|
/// via `TurnEvent::SystemNote { kind: "bg-test-gen" }`.
|
|
|
|
|
pub fn spawn_background_test_gen(
|
|
|
|
|
file_paths: &[String],
|
|
|
|
|
session_dir: &Path,
|
|
|
|
|
workspaces: &[std::path::PathBuf],
|
|
|
|
|
turn_events: &Arc<Mutex<VecDeque<TurnEvent>>>,
|
|
|
|
|
) {
|
|
|
|
|
if file_paths.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let paths = file_paths.to_vec();
|
|
|
|
|
let sd = session_dir.to_path_buf();
|
|
|
|
|
let ws = workspaces.to_vec();
|
|
|
|
|
let events = turn_events.clone();
|
|
|
|
|
|
|
|
|
|
std::thread::spawn(move || {
|
|
|
|
|
tracing::info!(
|
|
|
|
|
"[bg-test-gen] spawning for {} file(s): {:?}",
|
|
|
|
|
paths.len(),
|
|
|
|
|
paths,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let file_list = paths.join("\n");
|
|
|
|
|
let prompt = format!(
|
|
|
|
|
"{}\n\nModified files that need tests:\n{}",
|
|
|
|
|
crate::resources::TEST_GENERATOR_PROMPT,
|
|
|
|
|
file_list,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let def = AgentDefinition::new(
|
|
|
|
|
"test-generator".to_string(),
|
|
|
|
|
"coder".to_string(), // needs write access
|
|
|
|
|
)
|
|
|
|
|
.with_system_prompt(prompt)
|
|
|
|
|
.with_max_steps(BG_SUBAGENT_MAX_STEPS);
|
|
|
|
|
|
2026-07-13 08:12:02 +07:00
|
|
|
let mut ctx = build_subagent_context(&def);
|
2026-07-13 05:23:38 +07:00
|
|
|
ctx.session_dir = sd;
|
|
|
|
|
ctx.workspaces = ws;
|
|
|
|
|
|
|
|
|
|
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
|
|
|
|
let _drain = std::thread::spawn(move || {
|
|
|
|
|
while let Some(event) = rx.blocking_recv() {
|
|
|
|
|
match &event {
|
2026-07-13 08:12:02 +07:00
|
|
|
SubagentEvent::ToolCall { tool, .. } => {
|
|
|
|
|
tracing::debug!("[bg-test-gen] tool: {}", tool);
|
2026-07-13 05:23:38 +07:00
|
|
|
}
|
2026-07-13 08:12:02 +07:00
|
|
|
SubagentEvent::ToolResult { tool, .. } => {
|
|
|
|
|
tracing::debug!("[bg-test-gen] result: {}", tool);
|
2026-07-13 05:23:38 +07:00
|
|
|
}
|
2026-07-13 08:12:02 +07:00
|
|
|
SubagentEvent::StepCompleted { .. } => {
|
|
|
|
|
tracing::trace!("[bg-test-gen] step done");
|
2026-07-13 05:23:38 +07:00
|
|
|
}
|
2026-07-13 08:12:02 +07:00
|
|
|
SubagentEvent::StepFailed { step, error } => {
|
|
|
|
|
tracing::warn!("[bg-test-gen] step {} failed: {}", step, error);
|
2026-07-13 05:23:38 +07:00
|
|
|
}
|
|
|
|
|
SubagentEvent::Completed { .. } => {
|
|
|
|
|
tracing::debug!("[bg-test-gen] completed");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-13 08:12:02 +07:00
|
|
|
let result = run_subagent(&ctx, &tx);
|
2026-07-13 05:23:38 +07:00
|
|
|
let message = match &result {
|
|
|
|
|
Ok(output) => {
|
|
|
|
|
let first = output.lines().next().unwrap_or(output);
|
2026-07-13 08:12:02 +07:00
|
|
|
format!("Auto test-gen: {first}")
|
2026-07-13 05:23:38 +07:00
|
|
|
}
|
2026-07-13 08:12:02 +07:00
|
|
|
Err(e) => format!("Auto test-gen failed: {e}"),
|
2026-07-13 05:23:38 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Ok(mut q) = events.lock() {
|
|
|
|
|
q.push_back(TurnEvent::SystemNote {
|
|
|
|
|
kind: "bg-test-gen".to_string(),
|
|
|
|
|
message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Spawn a background architecture-review subagent.
|
|
|
|
|
///
|
|
|
|
|
/// Inspects the modified files for architectural consistency (layering,
|
|
|
|
|
/// coupling, module boundaries). Reports via
|
|
|
|
|
/// `TurnEvent::SystemNote { kind: "bg-arch-review" }`.
|
|
|
|
|
pub fn spawn_background_arch_review(
|
|
|
|
|
file_paths: &[String],
|
|
|
|
|
session_dir: &Path,
|
|
|
|
|
workspaces: &[std::path::PathBuf],
|
|
|
|
|
turn_events: &Arc<Mutex<VecDeque<TurnEvent>>>,
|
|
|
|
|
) {
|
|
|
|
|
if file_paths.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let paths = file_paths.to_vec();
|
|
|
|
|
let sd = session_dir.to_path_buf();
|
|
|
|
|
let ws = workspaces.to_vec();
|
|
|
|
|
let events = turn_events.clone();
|
|
|
|
|
|
|
|
|
|
std::thread::spawn(move || {
|
|
|
|
|
let file_list = paths.join("\n");
|
|
|
|
|
let prompt = format!(
|
|
|
|
|
"{}\n\nModified files for architecture review:\n{}",
|
|
|
|
|
crate::resources::ARCH_REVIEWER_PROMPT,
|
|
|
|
|
file_list,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let def = AgentDefinition::new(
|
|
|
|
|
"arch-reviewer".to_string(),
|
|
|
|
|
"reviewer".to_string(),
|
|
|
|
|
)
|
|
|
|
|
.with_system_prompt(prompt)
|
|
|
|
|
.with_max_steps(BG_SUBAGENT_MAX_STEPS);
|
|
|
|
|
|
2026-07-13 08:12:02 +07:00
|
|
|
let mut ctx = build_subagent_context(&def);
|
2026-07-13 05:23:38 +07:00
|
|
|
ctx.session_dir = sd;
|
|
|
|
|
ctx.workspaces = ws;
|
|
|
|
|
|
|
|
|
|
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
|
|
|
|
let _drain = std::thread::spawn(move || {
|
|
|
|
|
while let Some(event) = rx.blocking_recv() {
|
|
|
|
|
match &event {
|
2026-07-13 08:12:02 +07:00
|
|
|
SubagentEvent::ToolCall { tool, .. } => {
|
|
|
|
|
tracing::debug!("[bg-arch] tool: {}", tool);
|
2026-07-13 05:23:38 +07:00
|
|
|
}
|
2026-07-13 08:12:02 +07:00
|
|
|
SubagentEvent::ToolResult { tool, .. } => {
|
|
|
|
|
tracing::debug!("[bg-arch] result: {}", tool);
|
2026-07-13 05:23:38 +07:00
|
|
|
}
|
|
|
|
|
SubagentEvent::Completed { .. } => {
|
|
|
|
|
tracing::debug!("[bg-arch] completed");
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-13 08:12:02 +07:00
|
|
|
let result = run_subagent(&ctx, &tx);
|
2026-07-13 05:23:38 +07:00
|
|
|
let message = match &result {
|
|
|
|
|
Ok(output) => {
|
|
|
|
|
let first = output.lines().next().unwrap_or(output);
|
2026-07-13 08:12:02 +07:00
|
|
|
format!("Architecture review: {first}")
|
2026-07-13 05:23:38 +07:00
|
|
|
}
|
2026-07-13 08:12:02 +07:00
|
|
|
Err(e) => format!("Architecture review failed: {e}"),
|
2026-07-13 05:23:38 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Ok(mut q) = events.lock() {
|
|
|
|
|
q.push_back(TurnEvent::SystemNote {
|
|
|
|
|
kind: "bg-arch-review".to_string(),
|
|
|
|
|
message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Spawn a background security-review subagent.
|
|
|
|
|
///
|
|
|
|
|
/// Checks modified files for security vulnerabilities. Reports via
|
|
|
|
|
/// `TurnEvent::SystemNote { kind: "bg-security-review" }`.
|
|
|
|
|
pub fn spawn_background_security_review(
|
|
|
|
|
file_paths: &[String],
|
|
|
|
|
session_dir: &Path,
|
|
|
|
|
workspaces: &[std::path::PathBuf],
|
|
|
|
|
turn_events: &Arc<Mutex<VecDeque<TurnEvent>>>,
|
|
|
|
|
) {
|
|
|
|
|
if file_paths.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only review production code files for security — test files and
|
|
|
|
|
// config files are out of scope for security review.
|
|
|
|
|
let prod_paths: Vec<String> = file_paths
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|p| is_production_code(p))
|
|
|
|
|
.cloned()
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
if prod_paths.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let paths = prod_paths;
|
|
|
|
|
let sd = session_dir.to_path_buf();
|
|
|
|
|
let ws = workspaces.to_vec();
|
|
|
|
|
let events = turn_events.clone();
|
|
|
|
|
|
|
|
|
|
std::thread::spawn(move || {
|
|
|
|
|
let file_list = paths.join("\n");
|
|
|
|
|
let prompt = format!(
|
|
|
|
|
"{}\n\nModified files for security review:\n{}",
|
|
|
|
|
crate::resources::SECURITY_REVIEWER_PROMPT,
|
|
|
|
|
file_list,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let def = AgentDefinition::new(
|
|
|
|
|
"security-reviewer".to_string(),
|
|
|
|
|
"reviewer".to_string(),
|
|
|
|
|
)
|
|
|
|
|
.with_system_prompt(prompt)
|
|
|
|
|
.with_max_steps(BG_SUBAGENT_MAX_STEPS);
|
|
|
|
|
|
2026-07-13 08:12:02 +07:00
|
|
|
let mut ctx = build_subagent_context(&def);
|
2026-07-13 05:23:38 +07:00
|
|
|
ctx.session_dir = sd;
|
|
|
|
|
ctx.workspaces = ws;
|
|
|
|
|
|
|
|
|
|
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
|
|
|
|
let _drain = std::thread::spawn(move || {
|
|
|
|
|
while let Some(event) = rx.blocking_recv() {
|
|
|
|
|
match &event {
|
2026-07-13 08:12:02 +07:00
|
|
|
SubagentEvent::ToolCall { tool, .. } => {
|
|
|
|
|
tracing::debug!("[bg-security] tool: {}", tool);
|
2026-07-13 05:23:38 +07:00
|
|
|
}
|
2026-07-13 08:12:02 +07:00
|
|
|
SubagentEvent::ToolResult { tool, .. } => {
|
|
|
|
|
tracing::debug!("[bg-security] result: {}", tool);
|
2026-07-13 05:23:38 +07:00
|
|
|
}
|
|
|
|
|
SubagentEvent::Completed { .. } => {
|
|
|
|
|
tracing::debug!("[bg-security] completed");
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-13 08:12:02 +07:00
|
|
|
let result = run_subagent(&ctx, &tx);
|
2026-07-13 05:23:38 +07:00
|
|
|
let message = match &result {
|
|
|
|
|
Ok(output) => {
|
|
|
|
|
let first = output.lines().next().unwrap_or(output);
|
2026-07-13 08:12:02 +07:00
|
|
|
format!("Security review: {first}")
|
2026-07-13 05:23:38 +07:00
|
|
|
}
|
2026-07-13 08:12:02 +07:00
|
|
|
Err(e) => format!("Security review failed: {e}"),
|
2026-07-13 05:23:38 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Ok(mut q) = events.lock() {
|
|
|
|
|
q.push_back(TurnEvent::SystemNote {
|
|
|
|
|
kind: "bg-security-review".to_string(),
|
|
|
|
|
message,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Convenience: spawn all applicable background subagents for a set of edited
|
|
|
|
|
/// file paths. Called once at the end of a main agent turn.
|
|
|
|
|
///
|
|
|
|
|
/// Flow: always spawns arch-review and security-review if there are
|
|
|
|
|
/// reviewable production files → spawns test-gen only if there are source
|
|
|
|
|
/// files that aren't already tests.
|
|
|
|
|
pub fn spawn_all_background(
|
|
|
|
|
file_paths: &[String],
|
|
|
|
|
session_dir: &Path,
|
|
|
|
|
workspaces: &[std::path::PathBuf],
|
|
|
|
|
turn_events: &Arc<Mutex<VecDeque<TurnEvent>>>,
|
|
|
|
|
) {
|
|
|
|
|
if file_paths.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Background test-gen: only for non-test source files
|
|
|
|
|
let source_paths: Vec<String> = file_paths
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|p| is_production_code(p))
|
|
|
|
|
.cloned()
|
|
|
|
|
.collect();
|
|
|
|
|
spawn_background_test_gen(&source_paths, session_dir, workspaces, turn_events);
|
|
|
|
|
|
|
|
|
|
// Background arch review: for all files that are reviewable
|
|
|
|
|
let reviewable: Vec<String> = file_paths
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|p| is_reviewable_path(p))
|
|
|
|
|
.cloned()
|
|
|
|
|
.collect();
|
|
|
|
|
spawn_background_arch_review(&reviewable, session_dir, workspaces, turn_events);
|
|
|
|
|
|
|
|
|
|
// Background security review: only production source files
|
|
|
|
|
spawn_background_security_review(&source_paths, session_dir, workspaces, turn_events);
|
|
|
|
|
}
|