Skip to content

Commit df0e514

Browse files
committed
feat(context): render STRUCTURED CHANGES section in LLM prompt
Formats SymbolDiff entries as concise one-line descriptions showing parent scope, parameter changes, return type changes, and body modification magnitude. Section omitted when no structural diffs exist.
1 parent 9fee38f commit df0e514

2 files changed

Lines changed: 77 additions & 3 deletions

File tree

src/domain/context.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ pub struct PromptContext {
4444
/// e.g., "src/services/context.rs <-> tests/context.rs (test file)"
4545
pub test_correlations: Vec<String>,
4646
/// Structured semantic changes for modified symbols (from AstDiffer).
47-
/// Used by the prompt formatter in T1-4 to show semantic diffs to the LLM.
48-
#[allow(dead_code)]
47+
/// Formatted as a `STRUCTURED CHANGES:` section in the prompt.
4948
pub structured_changes: Vec<SymbolDiff>,
5049
}
5150

@@ -69,6 +68,17 @@ impl PromptContext {
6968
)
7069
};
7170

71+
let structured_section = if self.structured_changes.is_empty() {
72+
String::new()
73+
} else {
74+
let lines: Vec<String> = self
75+
.structured_changes
76+
.iter()
77+
.map(|d| d.format_oneline())
78+
.collect();
79+
format!("\nSTRUCTURED CHANGES:\n{}\n", lines.join("\n"))
80+
};
81+
7282
let imports_section = if self.import_changes.is_empty() {
7383
String::new()
7484
} else {
@@ -160,7 +170,7 @@ impl PromptContext {
160170
SUMMARY: {summary}
161171
FILES: {files}
162172
SUGGESTED TYPE: {commit_type}{scope}
163-
{group_rationale}{evidence}{primary_change}{symbols}{connections}{imports}{related}
173+
{group_rationale}{evidence}{primary_change}{symbols}{structured}{connections}{imports}{related}
164174
DIFF:
165175
{diff}
166176
{constraints}{breaking}{metadata_breaking}{locale}{focus}{history}
@@ -179,6 +189,7 @@ Respond with ONLY this JSON:
179189
.unwrap_or_default(),
180190
evidence = evidence_section,
181191
symbols = symbols_section,
192+
structured = structured_section,
182193
connections = connections_section,
183194
imports = imports_section,
184195
related = related_section,

tests/context.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,6 +1746,69 @@ fn test_correlations_capped_at_5() {
17461746
assert!(ctx.test_correlations.len() <= 5);
17471747
}
17481748

1749+
// ─── Structured changes in prompt ─────────────────────────────────────────────
1750+
1751+
#[test]
1752+
fn structured_changes_shown_in_prompt() {
1753+
use commitbee::domain::diff::{ChangeDetail, SymbolDiff};
1754+
1755+
let changes = make_staged_changes(vec![make_file_change(
1756+
"src/lib.rs",
1757+
ChangeStatus::Modified,
1758+
"+pub fn validate(input: &str, strict: bool) -> Result<()> { Ok(()) }",
1759+
1,
1760+
0,
1761+
)]);
1762+
let diffs = vec![SymbolDiff {
1763+
name: "validate".into(),
1764+
file: "src/lib.rs".into(),
1765+
line: 1,
1766+
parent_scope: Some("Validator".into()),
1767+
changes: vec![
1768+
ChangeDetail::ParamAdded("strict: bool".into()),
1769+
ChangeDetail::ReturnTypeChanged {
1770+
old: "bool".into(),
1771+
new: "Result<()>".into(),
1772+
},
1773+
],
1774+
}];
1775+
let ctx = ContextBuilder::build(&changes, &[], &diffs, &default_config());
1776+
let prompt = ctx.to_prompt();
1777+
assert!(
1778+
prompt.contains("STRUCTURED CHANGES:"),
1779+
"prompt should contain structured changes section"
1780+
);
1781+
assert!(
1782+
prompt.contains("Validator::validate()"),
1783+
"should show parent scope"
1784+
);
1785+
assert!(
1786+
prompt.contains("+param strict: bool"),
1787+
"should show added param"
1788+
);
1789+
assert!(
1790+
prompt.contains("return bool"),
1791+
"should show return type change"
1792+
);
1793+
}
1794+
1795+
#[test]
1796+
fn empty_structured_changes_not_shown() {
1797+
let changes = make_staged_changes(vec![make_file_change(
1798+
"src/lib.rs",
1799+
ChangeStatus::Modified,
1800+
"+code\n",
1801+
1,
1802+
0,
1803+
)]);
1804+
let ctx = ContextBuilder::build(&changes, &[], &[], &default_config());
1805+
let prompt = ctx.to_prompt();
1806+
assert!(
1807+
!prompt.contains("STRUCTURED CHANGES:"),
1808+
"empty structured changes should not appear"
1809+
);
1810+
}
1811+
17491812
#[test]
17501813
fn python_comment_only_change_classified_as_docs() {
17511814
let changes = make_staged_changes(vec![make_file_change(

0 commit comments

Comments
 (0)