Files
zesdex/src/app/review/mod.rs
T

212 lines
5.7 KiB
Rust
Raw Normal View History

use std::collections::HashMap;
use crate::app::state::rest::AppStateRest;
use crate::app::state::runtime::TurnEvent;
use crate::app::state::types::{AgentMode, Origin, Toast, ToastKind};
use crate::app::subagent::context::build_subagent_context;
use crate::app::subagent::engine::run_subagent;
use crate::app::subagent::spawn::AgentDefinition;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Confidence {
Human,
Verified,
Unverified,
Auto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum LessonLifecycle {
New,
Active,
Stale,
Contradicted,
Superseded,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum LessonScope {
Project,
Global,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Provenance {
pub session_turn: String,
pub session_id: String,
pub reviewer: Origin,
}
#[derive(Debug, Clone)]
pub struct Lesson {
pub name: String,
pub content: String,
pub confidence: Confidence,
pub outcome: Option<String>,
pub lifecycle: LessonLifecycle,
pub scope: LessonScope,
pub contradiction_with: Option<String>,
pub provenance: Provenance,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ViolationEscalation {
None,
Warning,
Escalate,
Block,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ShadowStatus {
Trial,
Graduated,
Rejected,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShadowCheck {
pub pattern: String,
pub trial_window: u32,
pub trial_count: u32,
pub trial_passed: u32,
pub status: ShadowStatus,
}
pub struct ReviewSystem {
pub pending: bool,
pub queue_capacity: usize,
pub repeated_violations: HashMap<String, u32>,
pub shadow_violations: Vec<ShadowCheck>,
pub violation_window: u32,
}
impl ReviewSystem {
pub fn new() -> Self {
ReviewSystem {
pending: false,
queue_capacity: 1,
repeated_violations: HashMap::new(),
shadow_violations: Vec::new(),
violation_window: 10,
}
}
pub fn reset(&mut self) {
self.pending = false;
}
pub fn check_escalation(&self, pattern: &str) -> ViolationEscalation {
let count = self.repeated_violations.get(pattern).copied().unwrap_or(0);
match count {
0 | 1 => ViolationEscalation::None,
2 => ViolationEscalation::Warning,
3 | 4 => ViolationEscalation::Escalate,
_ => ViolationEscalation::Block,
}
}
pub fn increment_violation(&mut self, pattern: &str) -> ViolationEscalation {
let entry = self.repeated_violations.entry(pattern.to_string()).or_insert(0);
*entry += 1;
self.check_escalation(pattern)
}
pub fn should_skip_review(consecutive_empty: u32) -> bool {
consecutive_empty >= 3
}
}
pub fn create_pending_lesson(name: &str, content: &str, provenance: Provenance) -> Lesson {
Lesson {
name: name.to_string(),
content: content.to_string(),
confidence: Confidence::Unverified,
outcome: None,
lifecycle: LessonLifecycle::New,
scope: LessonScope::Project,
contradiction_with: None,
provenance,
}
}
pub fn apply_lesson_calibration(state: &mut AppStateRest, lesson: &Lesson) {
if lesson.confidence != Confidence::Unverified {
return;
}
if state.mode.auto_approve() {
return;
}
state.push_toast(Toast::new(
ToastKind::Lesson,
format!("Lesson '{}' is pending review. Keep or discard?", lesson.name),
));
}
pub fn should_trigger_review(state: &AppStateRest, origin: Origin) -> bool {
if state.mode == AgentMode::Plan {
return false;
}
if origin != Origin::Main {
return false;
}
let runtime = match &state.session_runtime {
Some(r) => r,
None => return false,
};
if !state.settings.review_enabled {
return false;
}
if runtime.edit_count > 0 && runtime.edit_count % 5 == 0 {
return true;
}
if runtime.consecutive_empty_reviews >= state.settings.adaptive_review_max_skip {
return true;
}
false
}
pub fn trigger_review(state: &mut AppStateRest) -> anyhow::Result<()> {
let def = AgentDefinition::new(
"quality-reviewer".to_string(),
"reviewer".to_string(),
);
let mut ctx = build_subagent_context(def);
ctx.session_dir = state.session_dir.clone();
ctx.system_prompt = format!(
"You are a code quality reviewer. Review the recent code changes \
for correctness, security, and adherence to best practices. \
Use read-only tools (read, grep, glob, recall, remember) to \
inspect the session files and provide a concise review verdict. \
Session directory: {:?}",
state.session_dir
);
let (tx, _rx) = tokio::sync::mpsc::channel(32);
let turn_events = state.turn_events.clone();
std::thread::spawn(move || {
let result = run_subagent(ctx, tx);
let message = match result {
Ok(verdict) => {
let first_line = verdict.lines().next().unwrap_or(&verdict);
format!("Quality review: {}", first_line)
}
Err(e) => format!("Quality review failed: {}", e),
};
if let Ok(mut q) = turn_events.lock() {
q.push_back(TurnEvent::SystemNote {
kind: "review".to_string(),
message,
});
}
});
state.push_toast(Toast::new(
ToastKind::Info,
"Quality review triggered".to_string(),
));
Ok(())
}