Skip to content

Commit 9eae025

Browse files
committed
refactor: split review rule runtime helpers
Made-with: Cursor
1 parent ac4c99e commit 9eae025

File tree

5 files changed

+155
-126
lines changed

5 files changed

+155
-126
lines changed

TODO.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,6 @@
1010

1111
## Immediate Queue
1212

13-
- [ ] `src/review/rule_helpers/reporting.rs`
14-
- Split rule-hit aggregation from priority-aware ordering and truncation.
15-
- Split file-grouped finding formatting from shared severity/rule display helpers.
16-
- Split PR summary body assembly from summary-stat rendering.
17-
- [ ] `src/review/rule_helpers/runtime.rs`
18-
- Split rule-context line rendering from `LLMContextChunk` injection.
19-
- Split active-rule lookup/index building from per-comment override mutation.
20-
- Isolate severity/category override parsing and tag/confidence mutation helpers.
2113
- [ ] `src/review/context_helpers/ranking.rs`
2214
- Split chunk dedupe key generation from the dedupe pass.
2315
- Split changed-range extraction and overlap helpers from chunk scoring.

src/review/rule_helpers/runtime.rs

Lines changed: 9 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,17 @@
1-
use std::collections::HashMap;
1+
#[path = "runtime/context.rs"]
2+
mod context;
3+
#[path = "runtime/overrides.rs"]
4+
mod overrides;
5+
#[path = "runtime/parsing.rs"]
6+
mod parsing;
27

3-
use crate::core;
4-
use crate::parsing::parse_smart_category;
5-
6-
pub fn inject_rule_context(
7-
diff: &core::UnifiedDiff,
8-
active_rules: &[core::ReviewRule],
9-
context_chunks: &mut Vec<core::LLMContextChunk>,
10-
) {
11-
if active_rules.is_empty() {
12-
return;
13-
}
14-
15-
let mut lines = Vec::new();
16-
lines.push(
17-
"Active review rules. If a finding maps to a rule, include `RULE: <id>` in the issue."
18-
.to_string(),
19-
);
20-
21-
for rule in active_rules {
22-
let mut attrs = Vec::new();
23-
if let Some(scope) = &rule.scope {
24-
attrs.push(format!("scope={}", scope));
25-
}
26-
if let Some(severity) = &rule.severity {
27-
attrs.push(format!("severity={}", severity));
28-
}
29-
if let Some(category) = &rule.category {
30-
attrs.push(format!("category={}", category));
31-
}
32-
if !rule.tags.is_empty() {
33-
attrs.push(format!("tags={}", rule.tags.join("|")));
34-
}
35-
36-
if attrs.is_empty() {
37-
lines.push(format!("- {}: {}", rule.id, rule.description));
38-
} else {
39-
lines.push(format!(
40-
"- {}: {} ({})",
41-
rule.id,
42-
rule.description,
43-
attrs.join(", ")
44-
));
45-
}
46-
}
47-
48-
context_chunks.push(
49-
core::LLMContextChunk::documentation(diff.file_path.clone(), lines.join("\n"))
50-
.with_provenance(core::ContextProvenance::ActiveReviewRules),
51-
);
52-
}
53-
54-
pub fn apply_rule_overrides(
55-
mut comments: Vec<core::Comment>,
56-
active_rules: &[core::ReviewRule],
57-
) -> Vec<core::Comment> {
58-
if comments.is_empty() || active_rules.is_empty() {
59-
return comments;
60-
}
61-
62-
let mut by_id = HashMap::new();
63-
for rule in active_rules {
64-
by_id.insert(rule.id.to_ascii_lowercase(), rule);
65-
}
66-
67-
for comment in &mut comments {
68-
let Some(rule_id) = comment.rule_id.clone() else {
69-
continue;
70-
};
71-
let key = rule_id.trim().to_ascii_lowercase();
72-
let Some(rule) = by_id.get(&key) else {
73-
continue;
74-
};
75-
76-
comment.rule_id = Some(rule.id.clone());
77-
if let Some(severity) = rule
78-
.severity
79-
.as_deref()
80-
.and_then(parse_rule_severity_override)
81-
{
82-
comment.severity = severity;
83-
}
84-
if let Some(category) = rule
85-
.category
86-
.as_deref()
87-
.and_then(parse_rule_category_override)
88-
{
89-
comment.category = category;
90-
}
91-
92-
let marker = format!("rule:{}", rule.id);
93-
if !comment.tags.iter().any(|tag| tag == &marker) {
94-
comment.tags.push(marker);
95-
}
96-
for tag in &rule.tags {
97-
if !comment.tags.iter().any(|existing| existing == tag) {
98-
comment.tags.push(tag.clone());
99-
}
100-
}
101-
comment.confidence = comment.confidence.max(0.8);
102-
}
103-
104-
comments
105-
}
106-
107-
fn parse_rule_severity_override(value: &str) -> Option<core::comment::Severity> {
108-
match value.trim().to_ascii_lowercase().as_str() {
109-
"critical" | "error" => Some(core::comment::Severity::Error),
110-
"high" | "warning" | "warn" => Some(core::comment::Severity::Warning),
111-
"medium" | "info" | "informational" => Some(core::comment::Severity::Info),
112-
"low" | "suggestion" => Some(core::comment::Severity::Suggestion),
113-
_ => None,
114-
}
115-
}
116-
117-
fn parse_rule_category_override(value: &str) -> Option<core::comment::Category> {
118-
parse_smart_category(value)
119-
}
8+
pub use context::inject_rule_context;
9+
pub use overrides::apply_rule_overrides;
12010

12111
#[cfg(test)]
12212
mod tests {
12313
use super::*;
14+
use crate::core;
12415
use std::path::PathBuf;
12516

12617
fn build_comment(
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use crate::core;
2+
3+
pub fn inject_rule_context(
4+
diff: &core::UnifiedDiff,
5+
active_rules: &[core::ReviewRule],
6+
context_chunks: &mut Vec<core::LLMContextChunk>,
7+
) {
8+
if active_rules.is_empty() {
9+
return;
10+
}
11+
12+
context_chunks.push(
13+
core::LLMContextChunk::documentation(
14+
diff.file_path.clone(),
15+
build_rule_context_lines(active_rules),
16+
)
17+
.with_provenance(core::ContextProvenance::ActiveReviewRules),
18+
);
19+
}
20+
21+
fn build_rule_context_lines(active_rules: &[core::ReviewRule]) -> String {
22+
let mut lines = Vec::new();
23+
lines.push(
24+
"Active review rules. If a finding maps to a rule, include `RULE: <id>` in the issue."
25+
.to_string(),
26+
);
27+
lines.extend(active_rules.iter().map(format_rule_context_line));
28+
lines.join("\n")
29+
}
30+
31+
fn format_rule_context_line(rule: &core::ReviewRule) -> String {
32+
let attrs = rule_context_attributes(rule);
33+
if attrs.is_empty() {
34+
format!("- {}: {}", rule.id, rule.description)
35+
} else {
36+
format!("- {}: {} ({})", rule.id, rule.description, attrs.join(", "))
37+
}
38+
}
39+
40+
fn rule_context_attributes(rule: &core::ReviewRule) -> Vec<String> {
41+
let mut attrs = Vec::new();
42+
if let Some(scope) = &rule.scope {
43+
attrs.push(format!("scope={}", scope));
44+
}
45+
if let Some(severity) = &rule.severity {
46+
attrs.push(format!("severity={}", severity));
47+
}
48+
if let Some(category) = &rule.category {
49+
attrs.push(format!("category={}", category));
50+
}
51+
if !rule.tags.is_empty() {
52+
attrs.push(format!("tags={}", rule.tags.join("|")));
53+
}
54+
attrs
55+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use std::collections::HashMap;
2+
3+
use crate::core;
4+
5+
use super::parsing::{parse_rule_category_override, parse_rule_severity_override};
6+
7+
type RuleIndex<'a> = HashMap<String, &'a core::ReviewRule>;
8+
9+
pub fn apply_rule_overrides(
10+
mut comments: Vec<core::Comment>,
11+
active_rules: &[core::ReviewRule],
12+
) -> Vec<core::Comment> {
13+
if comments.is_empty() || active_rules.is_empty() {
14+
return comments;
15+
}
16+
17+
let rule_index = build_rule_index(active_rules);
18+
for comment in &mut comments {
19+
apply_comment_rule_override(comment, &rule_index);
20+
}
21+
comments
22+
}
23+
24+
fn build_rule_index(active_rules: &[core::ReviewRule]) -> RuleIndex<'_> {
25+
let mut by_id = HashMap::new();
26+
for rule in active_rules {
27+
by_id.insert(rule.id.to_ascii_lowercase(), rule);
28+
}
29+
by_id
30+
}
31+
32+
fn apply_comment_rule_override(comment: &mut core::Comment, rule_index: &RuleIndex<'_>) {
33+
let Some(rule) = matching_rule(comment, rule_index) else {
34+
return;
35+
};
36+
37+
comment.rule_id = Some(rule.id.clone());
38+
if let Some(severity) = rule
39+
.severity
40+
.as_deref()
41+
.and_then(parse_rule_severity_override)
42+
{
43+
comment.severity = severity;
44+
}
45+
if let Some(category) = rule
46+
.category
47+
.as_deref()
48+
.and_then(parse_rule_category_override)
49+
{
50+
comment.category = category;
51+
}
52+
53+
apply_rule_tags(comment, rule);
54+
comment.confidence = comment.confidence.max(0.8);
55+
}
56+
57+
fn matching_rule<'a>(
58+
comment: &core::Comment,
59+
rule_index: &'a RuleIndex<'_>,
60+
) -> Option<&'a core::ReviewRule> {
61+
let key = comment.rule_id.as_deref()?.trim().to_ascii_lowercase();
62+
rule_index.get(&key).copied()
63+
}
64+
65+
fn apply_rule_tags(comment: &mut core::Comment, rule: &core::ReviewRule) {
66+
let marker = format!("rule:{}", rule.id);
67+
if !comment.tags.iter().any(|tag| tag == &marker) {
68+
comment.tags.push(marker);
69+
}
70+
for tag in &rule.tags {
71+
if !comment.tags.iter().any(|existing| existing == tag) {
72+
comment.tags.push(tag.clone());
73+
}
74+
}
75+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use crate::core;
2+
use crate::parsing::parse_smart_category;
3+
4+
pub fn parse_rule_severity_override(value: &str) -> Option<core::comment::Severity> {
5+
match value.trim().to_ascii_lowercase().as_str() {
6+
"critical" | "error" => Some(core::comment::Severity::Error),
7+
"high" | "warning" | "warn" => Some(core::comment::Severity::Warning),
8+
"medium" | "info" | "informational" => Some(core::comment::Severity::Info),
9+
"low" | "suggestion" => Some(core::comment::Severity::Suggestion),
10+
_ => None,
11+
}
12+
}
13+
14+
pub fn parse_rule_category_override(value: &str) -> Option<core::comment::Category> {
15+
parse_smart_category(value)
16+
}

0 commit comments

Comments
 (0)