diff --git a/ai_client.py b/ai_client.py index 428a595..89c2db0 100644 --- a/ai_client.py +++ b/ai_client.py @@ -1764,6 +1764,43 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str, raise _classify_deepseek_error(e) from e +def run_tier4_analysis(stderr: str) -> str: + """ + Stateless Tier 4 QA analysis of an error message. + Uses gemini-2.5-flash-lite to summarize the error and suggest a fix. + """ + if not stderr or not stderr.strip(): + return "" + + try: + _ensure_gemini_client() + prompt = ( + f"You are a Tier 4 QA Agent specializing in error analysis.\n" + f"Analyze the following stderr output from a PowerShell command:\n\n" + f"```\n{stderr}\n```\n\n" + f"Provide a concise summary of the failure and suggest a fix in approximately 20 words." + ) + + # Use flash-lite for cost-effective stateless analysis + model_name = "gemini-2.5-flash-lite" + + # We don't use the chat session here to keep it stateless + resp = _gemini_client.models.generate_content( + model=model_name, + contents=prompt, + config=types.GenerateContentConfig( + temperature=0.0, + max_output_tokens=150, + ) + ) + + analysis = resp.text.strip() + return analysis + except Exception as e: + # We don't want to crash the main loop if QA analysis fails + return f"[QA ANALYSIS FAILED] {e}" + + # ------------------------------------------------------------------ unified send def send( diff --git a/tests/test_tier4_interceptor.py b/tests/test_tier4_interceptor.py index 10e6453..d03b61d 100644 --- a/tests/test_tier4_interceptor.py +++ b/tests/test_tier4_interceptor.py @@ -100,3 +100,29 @@ def test_run_powershell_optional_qa_callback(): assert "STDERR:\nerror" in output assert "EXIT CODE: 1" in output + +def test_end_to_end_tier4_integration(): + """ + Verifies that shell_runner.run_powershell correctly uses ai_client.run_tier4_analysis. + """ + import ai_client + + script = "Invoke-Item non_existent_file" + base_dir = "." + stderr_content = "Invoke-Item : Cannot find path 'C:\\non_existent_file' because it does not exist." + + mock_result = MagicMock() + mock_result.stdout = "" + mock_result.stderr = stderr_content + mock_result.returncode = 1 + + expected_analysis = "Path does not exist. Verify the file path and ensure the file is present before invoking." + + with patch("subprocess.run", return_value=mock_result), \ + patch("shutil.which", return_value="powershell.exe"), \ + patch("ai_client.run_tier4_analysis", return_value=expected_analysis) as mock_analysis: + + output = run_powershell(script, base_dir, qa_callback=ai_client.run_tier4_analysis) + + mock_analysis.assert_called_once_with(stderr_content) + assert f"QA ANALYSIS:\n{expected_analysis}" in output