This commit is contained in:
2026-07-09 07:25:14 +02:00
parent 230e828b81
commit a930537207
19 changed files with 762 additions and 2 deletions
+1
View File
@@ -151,6 +151,7 @@ mod tests {
metadata,
spawner: None,
context_reporter: None,
diagnostics: None,
}
}
+68
View File
@@ -0,0 +1,68 @@
//! Shared LSP-diagnostics reporting for the edit/write tools. After a file is written we ask
//! the (optional) diagnostics source to re-analyze it and append any error-severity items to
//! the tool output so the model sees mistakes it just introduced. Best-effort: no source, a
//! slow server, or a timeout all just mean "no diagnostics" — never a tool failure.
use std::path::Path;
use std::time::Duration;
use harness_core::lsp::Severity;
use harness_core::tool::{ToolCtx, ToolOutput};
/// docs/09-integrations.md: wait up to 1.5s for the server to (re)publish after the change.
const DIAGNOSTICS_WAIT: Duration = Duration::from_millis(1500);
/// Touches `path` in the language server and appends error-severity diagnostics to `output`
/// (both as a human-readable block in the text and the full set in metadata under `diagnostics`).
pub async fn append_diagnostics(ctx: &ToolCtx, path: &Path, display_name: &str, output: &mut ToolOutput) {
let Some(source) = &ctx.diagnostics else {
return;
};
source.touch(path).await;
let diagnostics = source.diagnostics(path, DIAGNOSTICS_WAIT).await;
if diagnostics.is_empty() {
return;
}
let errors: Vec<_> = diagnostics
.iter()
.filter(|d| d.severity == Severity::Error)
.collect();
if !errors.is_empty() {
output
.output
.push_str("\n\nLSP errors detected in this file, please fix:");
for diag in &errors {
output.output.push('\n');
output.output.push_str(&diag.display_line(display_name));
}
}
// Full set (all severities) into metadata for the TUI.
let items: Vec<serde_json::Value> = diagnostics
.iter()
.map(|d| {
serde_json::json!({
"line": d.line,
"character": d.character,
"severity": severity_str(d.severity),
"message": d.message,
"source": d.source,
})
})
.collect();
if let serde_json::Value::Object(map) = &mut output.metadata {
map.insert("diagnostics".into(), serde_json::Value::Array(items));
} else {
output.metadata = serde_json::json!({ "diagnostics": items });
}
}
fn severity_str(severity: Severity) -> &'static str {
match severity {
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Info => "info",
Severity::Hint => "hint",
}
}
+3 -1
View File
@@ -196,8 +196,9 @@ impl Tool for EditTool {
.map_err(|e| ToolError::Other(format!("{}: {e}", path.display())))?;
let (added, removed) = diff_stats(&content_old, &content_new);
let mut output = ToolOutput::new(pattern, "Edit applied successfully.".to_string());
let mut output = ToolOutput::new(pattern.clone(), "Edit applied successfully.".to_string());
output.metadata = serde_json::json!({"diff": diff, "added": added, "removed": removed});
crate::diagnostics::append_diagnostics(&ctx, &path, &pattern, &mut output).await;
Ok(output)
}
}
@@ -234,6 +235,7 @@ mod tests {
metadata,
spawner: None,
context_reporter: None,
diagnostics: None,
}
}
+1
View File
@@ -132,6 +132,7 @@ mod tests {
metadata,
spawner: None,
context_reporter: None,
diagnostics: None,
}
}
+1
View File
@@ -161,6 +161,7 @@ mod tests {
metadata,
spawner: None,
context_reporter: None,
diagnostics: None,
}
}
+1
View File
@@ -1,4 +1,5 @@
mod bash;
mod diagnostics;
mod edit;
mod glob;
mod grep;
+1
View File
@@ -148,6 +148,7 @@ mod tests {
metadata,
spawner: None,
context_reporter: None,
diagnostics: None,
}
}
+2
View File
@@ -80,6 +80,7 @@ impl Tool for WriteTool {
format!("wrote {} bytes", params.content.len()),
);
output.metadata = serde_json::json!({"diff": diff, "added": added, "removed": removed});
crate::diagnostics::append_diagnostics(&ctx, &path, &params.file_path, &mut output).await;
Ok(output)
}
}
@@ -116,6 +117,7 @@ mod tests {
metadata,
spawner: None,
context_reporter: None,
diagnostics: None,
}
}