From nobody Sun Feb 8 08:22:26 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 56BC2298CC4 for ; Sun, 11 Jan 2026 21:29:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768166966; cv=none; b=SgZ5xWyw8RfIMnAtRqzECSpVd+Ex5m0/bTFsqDKeIPBe9XMHeJgVmn2gJ0dDDI5OHA+Ar3bZqeW7niG+EmuGMZELQzg9Fk5yiNRJ7yOW6Q6/9sTHSMdABuMjln0N6KULjQApfOOl3zUHWlrqRLsubGoi4q4nHkJtMfDoFLBhMIM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768166966; c=relaxed/simple; bh=5z/km3cPIT+wVNjJehk/MJfggkGIzoou+eaipgS4gpM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=vBGtRBHoag90B6TYgz5t8ZgqrM78eKVyqfly0oUHGSQmJUd5fpi2ItwlsBYsDLa76VuR25V43CjSHAP+5DahlQj0i+FltvVWFIshHxIg3rLMr35Z2YsHcdoGrH2aqIuOcG7e+XSoOzhawxFYVe2JWBtInnG6eMEM+WP48e48eGA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=DxfaygYe; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="DxfaygYe" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 9B2A6C2BC86; Sun, 11 Jan 2026 21:29:23 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1768166964; bh=5z/km3cPIT+wVNjJehk/MJfggkGIzoou+eaipgS4gpM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=DxfaygYeqKkpMfapP18tCs3jReRweeukjJbcmbHyFPQD706kvpc8PcuHHTMfxCcla UOkVwWEfRjpZFQz1sLF0KiLHrVUoVmAZiYF9dn9hMfAjaWoORJ0H6JUEswnxizY1gB bmA9FJZGRVRHmqGUQr1/s6uC8hyqSmPORgQDLnJ54roNRYhagOk87bmg1bYtafALJi GdBWRGgV/72uuqvZ7beVvppKs8PkeqZvPMHspY1CsMFy2F5yph9+Z13qOU0LvJiPLm s2YEuKggx2xUwApn2cEL8VlSsw+TGbqbJlD/5asjXYRBhmoBh0y+jK+Qn+E2CgVv2J 3lZ6UsOczaoRw== From: Sasha Levin To: tools@kernel.org Cc: linux-kernel@vger.kernel.org, torvalds@linux-foundation.org, broonie@kernel.org, Sasha Levin Subject: [RFC v2 7/7] LLMinus: Add build test integration for semantic conflicts Date: Sun, 11 Jan 2026 16:29:15 -0500 Message-ID: <20260111212915.195056-8-sashal@kernel.org> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260111212915.195056-1-sashal@kernel.org> References: <20251219181629.1123823-1-sashal@kernel.org> <20260111212915.195056-1-sashal@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Detect and resolve build failures that occur after merges complete without textual conflicts. These semantic conflicts happen when changes from different branches are incompatible at build time. When no textual conflicts exist, llminus runs the build to check for issues. If the build fails, it generates a specialized prompt for the LLM to investigate and fix the errors. The resolve prompt now requires build verification before considering resolution complete. Signed-off-by: Sasha Levin --- tools/llminus/src/main.rs | 236 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 224 insertions(+), 12 deletions(-) diff --git a/tools/llminus/src/main.rs b/tools/llminus/src/main.rs index 5c469e23f09a..40aa25e83b3d 100644 --- a/tools/llminus/src/main.rs +++ b/tools/llminus/src/main.rs @@ -1355,13 +1355,15 @@ fn build_merge_commit_message(pull_req: &PullReques= t, summary: &str, resolution: } =20 /// Get current conflicts from the working directory +/// Returns an empty Vec if there are no conflicts (for build-only issues) fn get_current_conflicts() -> Result> { check_repo()?; =20 // Find current conflicts let conflict_paths =3D get_conflicted_files()?; if conflict_paths.is_empty() { - bail!("No conflicts detected. Run this command when you have activ= e merge conflicts."); + // No conflicts - this is fine, might be a build-only issue + return Ok(Vec::new()); } =20 // Parse all conflict regions @@ -1372,10 +1374,6 @@ fn get_current_conflicts() -> Result> { } } =20 - if all_conflicts.is_empty() { - bail!("Could not parse any conflict markers from the conflicted fi= les."); - } - Ok(all_conflicts) } =20 @@ -1436,6 +1434,184 @@ fn try_find_similar_resolutions(n: usize, conflicts= : &[ConflictFile]) -> Vec BuildTestResult { + use std::process::Stdio; + + println!("Running build test: stable build log"); + + let result =3D Command::new("stable") + .args(["build", "log"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output(); + + match result { + Ok(output) =3D> { + let stderr =3D String::from_utf8_lossy(&output.stderr).to_stri= ng(); + let stdout =3D String::from_utf8_lossy(&output.stdout).to_stri= ng(); + + // Combine stderr (warnings/errors) with any stdout for full p= icture + let combined =3D if stderr.is_empty() { + stdout + } else if stdout.is_empty() { + stderr + } else { + format!("{}\n{}", stderr, stdout) + }; + + // Check for actual build issues - look for warning/error patt= erns + let has_issues =3D combined.lines().any(|line| { + let lower =3D line.to_lowercase(); + lower.contains("error:") || lower.contains("warning:") + || lower.contains("error[") || lower.contains("undefin= ed reference") + || lower.contains("fatal error") || lower.contains("ma= ke[") + }); + + BuildTestResult { + success: output.status.success() && !has_issues, + output: combined.trim().to_string(), + } + } + Err(e) =3D> BuildTestResult { + success: false, + output: format!("Failed to run build test: {}", e), + }, + } +} + +/// Build an LLM prompt for fixing build errors (when no merge conflicts e= xist) +fn build_fix_prompt(build_output: &str, merge_ctx: &MergeContext) -> Strin= g { + let mut prompt =3D String::new(); + + // Header with high-stakes framing (matching the existing style) + prompt.push_str("# Linux Kernel Build Issue Resolution\n\n"); + prompt.push_str("You are acting as an experienced kernel maintainer. A= merge completed without "); + prompt.push_str("textual conflicts, but the build test revealed warnin= gs or errors that need to be fixed.\n\n"); + + prompt.push_str("**Important:** Incorrect fixes have historically intr= oduced subtle bugs "); + prompt.push_str("that affected millions of users and took months to di= agnose. A fix that "); + prompt.push_str("silences a warning but has semantic errors is worse t= han no fix at all.\n\n"); + + prompt.push_str("Take the time to fully understand the build failure b= efore attempting "); + prompt.push_str("any fix. If after investigation you're not confident,= say so - it's "); + prompt.push_str("better to escalate to a human than to introduce a sub= tle bug.\n\n"); + + // Merge context + prompt.push_str("## Merge Context\n\n"); + if let Some(ref source) =3D merge_ctx.merge_source { + prompt.push_str(&format!("**Merged:** `{}`\n", source)); + } + if let Some(ref head) =3D merge_ctx.head_branch { + prompt.push_str(&format!("**Into:** `{}`\n", head)); + } + prompt.push_str("\nThe merge itself completed without textual conflict= s, but the build has issues.\n\n"); + + // Build output + prompt.push_str("## Build Output\n\n"); + prompt.push_str("The following warnings/errors were produced by `stabl= e build log` (allmodconfig build):\n\n"); + prompt.push_str("```\n"); + prompt.push_str(build_output); + prompt.push_str("\n```\n\n"); + + // Investigation requirement (matching existing style) + prompt.push_str("## Investigation Required\n\n"); + prompt.push_str("Before attempting any fix, you must conduct thorough = research. "); + prompt.push_str("Rushing to fix without understanding is how subtle bu= gs get introduced. "); + prompt.push_str("Work through each phase below IN ORDER and document y= our findings.\n\n"); + + // Phase 1: Search lore.kernel.org + prompt.push_str("### Phase 1: Search lore.kernel.org for Guidance (DO = THIS FIRST)\n\n"); + prompt.push_str("**CRITICAL:** Before doing ANY other research, search= lore.kernel.org for existing guidance.\n"); + prompt.push_str("Maintainers often discuss build issues when they know= they will occur after merges.\n\n"); + + if let Some(ref source) =3D merge_ctx.merge_source { + prompt.push_str(&format!("1. **Search for the merge itself:** `{}`= \n", source)); + prompt.push_str(&format!(" - URL: `https://lore.kernel.org/all/?= q=3D{}`\n", source.replace('/', "%2F"))); + } + prompt.push_str("2. **Search for similar build issues:**\n"); + prompt.push_str(" - Search for the specific error message or warning= text\n"); + prompt.push_str(" - `\"build error\"` + subsystem name\n\n"); + + // Phase 2: Understand the context + prompt.push_str("### Phase 2: Understand the Context\n\n"); + prompt.push_str("- **Identify the affected files** from the error/warn= ing messages\n"); + prompt.push_str("- **What subsystem is this?** Read the file and nearb= y files to understand its purpose.\n"); + prompt.push_str("- **Who maintains it?** Check `git log --oneline -20`= for recent authors.\n\n"); + + // Phase 3: Trace history + prompt.push_str("### Phase 3: Trace the History\n\n"); + prompt.push_str("**Understand what changed:**\n"); + prompt.push_str("- Run `git log --oneline HEAD~5..HEAD -- ` to s= ee recent changes\n"); + prompt.push_str("- Use `git show ` to read the full commit mes= sages\n"); + prompt.push_str("- Check both sides of the merge: `git diff HEAD^1..HE= AD^2 -- `\n\n"); + + prompt.push_str("**Common causes of post-merge build issues:**\n"); + prompt.push_str("- Missing includes when code from one branch uses typ= es/functions defined in another\n"); + prompt.push_str("- Renamed or removed functions/macros that the merged= code references\n"); + prompt.push_str("- Conflicting definitions (same symbol defined differ= ently in merged branches)\n"); + prompt.push_str("- Changes to function signatures that weren't propaga= ted to all callers\n"); + prompt.push_str("- Linker script or section changes that affect symbol= placement\n\n"); + + // Resolution + prompt.push_str("## Resolution\n\n"); + prompt.push_str("Once you understand the issue:\n\n"); + prompt.push_str("1. Fix the build issues in the affected files\n"); + prompt.push_str("2. Stage your changes with `git add`\n"); + prompt.push_str("3. **COMMIT BEFORE BUILD TEST:** Create a temporary c= ommit to test the build:\n"); + prompt.push_str(" ```bash\n"); + prompt.push_str(" git commit -m \"WIP: testing build fix\"\n"); + prompt.push_str(" ```\n"); + prompt.push_str(" The build system requires all changes to be commit= ted to test them.\n"); + prompt.push_str("4. **MANDATORY BUILD TEST:** Run `stable build log` t= o verify the fix\n"); + prompt.push_str("5. If the build fails, try to fix the issues, amend t= he commit, and re-run the build test\n"); + prompt.push_str("6. **ALWAYS UNDO TEMPORARY COMMIT:** Whether build su= cceeded or failed, you MUST undo the temporary commit:\n"); + prompt.push_str(" ```bash\n"); + prompt.push_str(" git reset --soft HEAD~1\n"); + prompt.push_str(" ```\n"); + prompt.push_str(" This is MANDATORY - the calling script expects sta= ged changes, not a committed state.\n"); + prompt.push_str(" If the build still fails after your best effort, r= eport the failure but STILL do the soft reset.\n\n"); + + // If uncertain + prompt.push_str("## If Uncertain\n\n"); + prompt.push_str("If after investigation you're still uncertain about t= he correct fix:\n\n"); + prompt.push_str("- Explain what you've learned and what remains unclea= r\n"); + prompt.push_str("- Describe the possible fixes you see and their trade= offs\n"); + prompt.push_str("- Recommend whether a human maintainer should review\= n\n"); + prompt.push_str("It's better to flag uncertainty than to silently intr= oduce a bug.\n\n"); + + // Tools available + prompt.push_str("## Tools Available\n\n"); + prompt.push_str("You can use these to investigate:\n\n"); + prompt.push_str("```bash\n"); + prompt.push_str("# View recent changes to the affected file\n"); + prompt.push_str("git log --oneline HEAD~10..HEAD -- \n"); + prompt.push('\n'); + prompt.push_str("# Compare the merge parents\n"); + prompt.push_str("git diff HEAD^1..HEAD^2 -- \n"); + prompt.push('\n'); + prompt.push_str("# Understand a specific commit\n"); + prompt.push_str("git show \n"); + prompt.push('\n'); + prompt.push_str("# Re-run the build test\n"); + prompt.push_str("stable build log\n"); + prompt.push_str("```\n"); + + prompt +} + /// Build the LLM prompt for conflict resolution fn build_resolve_prompt( conflicts: &[ConflictFile], @@ -1612,16 +1788,31 @@ fn build_resolve_prompt( prompt.push_str("1. Edit the conflicted files to produce the correct m= erged result\n"); prompt.push_str("2. Remove all conflict markers (`<<<<<<<`, `=3D=3D=3D= =3D=3D=3D=3D`, `>>>>>>>`)\n"); prompt.push_str("3. Stage the resolved files with `git add`\n"); + prompt.push_str("4. **COMMIT BEFORE BUILD TEST:** Create a temporary c= ommit to test the build:\n"); + prompt.push_str(" ```bash\n"); + prompt.push_str(" git commit -m \"WIP: testing merge resolution\"\n"= ); + prompt.push_str(" ```\n"); + prompt.push_str(" The build system requires all changes to be commit= ted to test them.\n"); + prompt.push_str("5. **MANDATORY BUILD TEST:** Verify the build succeed= s with no warnings or errors:\n"); + prompt.push_str(" ```bash\n"); + prompt.push_str(" stable build log\n"); + prompt.push_str(" ```\n"); + prompt.push_str(" This runs an allmodconfig build and outputs only w= arnings/errors to stderr.\n"); + prompt.push_str(" Review the output for ANY warnings or errors. If t= he build fails or produces warnings,\n"); + prompt.push_str(" you MUST try to fix them, amend the commit, and re= -run the build test.\n"); + prompt.push_str("6. **ALWAYS UNDO TEMPORARY COMMIT:** Whether build su= cceeded or failed, you MUST undo the temporary commit:\n"); + prompt.push_str(" ```bash\n"); + prompt.push_str(" git reset --soft HEAD~1\n"); + prompt.push_str(" ```\n"); + prompt.push_str(" This is MANDATORY - the calling script expects sta= ged changes, not a committed state.\n"); + prompt.push_str(" If the build still fails after your best effort, r= eport the failure but STILL do the soft reset.\n"); if pull_req.is_some() { - prompt.push_str("4. **Do NOT commit** - The tool will handle the c= ommit\n"); - prompt.push_str("5. **IMPORTANT:** Write a detailed explanation of= your resolution to `.git/LLMINUS_RESOLUTION`\n"); + prompt.push_str("7. **IMPORTANT:** Write a detailed explanation of= your resolution to `.git/LLMINUS_RESOLUTION`\n"); prompt.push_str(" This file should contain:\n"); prompt.push_str(" - A summary of each conflict and how you resol= ved it\n"); prompt.push_str(" - The reasoning behind your choices\n"); prompt.push_str(" - Any improvements you made over suggested res= olutions\n"); prompt.push_str(" This will be included in the merge commit mess= age.\n\n"); - } else { - prompt.push_str("4. Commit with a detailed message explaining your= analysis and resolution\n\n"); } =20 // If uncertain @@ -1664,6 +1855,24 @@ fn resolve(command: &str, max_tokens: usize) -> Resu= lt<()> { // Get current conflicts first let conflicts =3D get_current_conflicts()?; =20 + if conflicts.is_empty() { + // No textual conflicts - check for build issues instead + println!("No textual conflicts detected."); + println!("Running build test to check for merge-related build issu= es...\n"); + + let build_result =3D run_build_test(); + + if build_result.success { + println!("Build test passed. No issues to resolve."); + return Ok(()); + } + + // Build failed - invoke LLM to fix the issues + println!("\nBuild test failed! Invoking LLM to resolve build issue= s...\n"); + let prompt =3D build_fix_prompt(&build_result.output, &merge_ctx); + return invoke_llm(command, &prompt); + } + println!("Found {} conflict(s)", conflicts.len()); =20 // Try to find similar historical resolutions (gracefully handles miss= ing database) @@ -2145,7 +2354,7 @@ fn test_resolution_store_roundtrip() { let dir =3D TempDir::new().unwrap(); let store_path =3D dir.path().join("resolutions.json"); =20 - let mut store =3D ResolutionStore { version: 3, resolutions: Vec::= new() }; + let mut store =3D ResolutionStore { version: 3, resolutions: Vec::= new(), processed_commits: Vec::new() }; store.resolutions.push(MergeResolution { commit_hash: "abc123".to_string(), commit_summary: "Test merge".to_string(), @@ -2197,6 +2406,7 @@ fn test_git_in_repo() { =20 #[test] fn test_get_merge_commits() { + let original_dir =3D std::env::current_dir().unwrap(); let dir =3D init_test_repo(); std::env::set_current_dir(dir.path()).unwrap(); =20 @@ -2209,6 +2419,8 @@ fn test_get_merge_commits() { =20 let merges =3D get_merge_commits(None).unwrap(); assert_eq!(merges.len(), 1); + + std::env::set_current_dir(original_dir).unwrap(); } =20 #[test] @@ -2436,7 +2648,7 @@ fn test_build_resolve_prompt_with_pull_request() { assert!(prompt.contains("test.c")); // conflict file assert!(prompt.contains("int ours;")); // ours content assert!(prompt.contains("int theirs;")); // theirs content - assert!(prompt.contains("Do NOT commit")); // pull request specific + assert!(prompt.contains("Write a detailed explanation")); // pull = request specific } =20 #[test] @@ -2461,6 +2673,6 @@ fn test_build_resolve_prompt_without_pull_request() { assert!(!prompt.contains("Pull Request Information")); assert!(prompt.contains("test.c")); assert!(prompt.contains("int ours;")); - assert!(prompt.contains("Commit with a detailed message")); // not= "Do NOT commit" + assert!(!prompt.contains("Write a detailed explanation")); // PR-s= pecific, not in standard prompt } } --=20 2.51.0