Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/commands/review.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,21 @@ pub async fn execute_review(
None
};

// 8b. Save debt tracking snapshot (best-effort, never fails review)
if config.debt.enabled {
let (commit, branch) = get_git_context();
let snapshot = crate::engine::debt_tracker::DebtSnapshot::from_review(
&filtered_response.issues,
gate_result.as_ref(),
commit,
branch,
0, // files_reviewed not tracked per-review yet
None,
None, // duration not passed through yet
);
crate::engine::debt_tracker::save_snapshot(&snapshot, config.debt.history_dir.as_deref());
}

// 9. Return exit code
let exit_code = if gate_result
.as_ref()
Expand Down Expand Up @@ -259,6 +274,35 @@ pub async fn execute_review(
Ok(ReviewResult { exit_code, output })
}

/// Helper to get git commit and branch context for debt tracking.
/// Returns (Some(commit), Some(branch)) if in a git repo, (None, None) otherwise.
/// Results are cached per process to avoid spawning git on every review.
fn get_git_context() -> (Option<String>, Option<String>) {
use std::sync::LazyLock;

static CONTEXT: LazyLock<(Option<String>, Option<String>)> = LazyLock::new(|| {
let commit = std::process::Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.ok()
.and_then(|o| {
if o.status.success() {
String::from_utf8(o.stdout)
.ok()
.map(|s| s.trim().to_string())
} else {
None
}
});

let branch = crate::git::get_current_branch().ok();

(commit, branch)
});

(CONTEXT.0.clone(), CONTEXT.1.clone())
}

/// Get the diff based on the provided options.
fn get_diff(opts: &ReviewOptions, _config: &Config) -> Result<String> {
if let Some(ref diff_file) = opts.diff_file {
Expand Down Expand Up @@ -514,6 +558,21 @@ async fn execute_chunked_review(
None
};

// 8b. Save debt tracking snapshot (best-effort, never fails review)
if config.debt.enabled {
let (commit, branch) = get_git_context();
let snapshot = crate::engine::debt_tracker::DebtSnapshot::from_review(
&filtered_response.issues,
gate_result.as_ref(),
commit,
branch,
chunks.iter().map(|c| c.file_count).sum(),
None,
None,
);
crate::engine::debt_tracker::save_snapshot(&snapshot, config.debt.history_dir.as_deref());
}

// 9. Return exit code
let exit_code = if gate_result
.as_ref()
Expand Down
13 changes: 13 additions & 0 deletions src/config/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ pub struct Config {
pub context_chain: crate::engine::context::types::ContextConfig,
/// File bundling configuration for scan/review grouping.
pub bundling: BundlingConfig,
/// Debt tracking configuration.
pub debt: crate::engine::debt_tracker::DebtConfig,
/// Active quality profile (resolved from .cora.yaml).
pub profile: Option<crate::engine::profiles::Profile>,
}
Expand Down Expand Up @@ -130,6 +132,7 @@ impl Default for Config {
rules_config: RulesConfig::default(),
context_chain: crate::engine::context::types::ContextConfig::default(),
bundling: BundlingConfig::default(),
debt: crate::engine::debt_tracker::DebtConfig::default(),
profile: None,
}
}
Expand Down Expand Up @@ -177,6 +180,8 @@ pub struct CoraFile {
pub rules_engine: Option<RulesSection>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bundling: Option<BundlingSection>,
#[serde(skip_serializing_if = "Option::is_none")]
pub debt: Option<crate::engine::debt_tracker::DebtConfig>,
#[serde(default)]
pub profile: Option<crate::engine::profiles::ProfileRef>,
}
Expand Down Expand Up @@ -466,6 +471,14 @@ impl CoraFile {
config.bundling.coalesce_by_language = v;
}
}
// Merge debt tracking config
if let Some(debt) = &self.debt {
config.debt.enabled = debt.enabled;
if debt.history_dir.is_some() {
config.debt.history_dir.clone_from(&debt.history_dir);
}
config.debt.retention_days = debt.retention_days;
}
// Resolve profile — load built-in or custom, merge into config
if let Some(profile_ref) = &self.profile {
match crate::engine::profiles::resolve_profile(profile_ref) {
Expand Down
Loading
Loading