Step 252: diagnostic delta streaming (getDiagnosticsDelta, version tracking)
DiagnosticVersionTracker records snapshots and computes added/removed deltas between versions. Agents can efficiently check if their fix resolved an error without re-fetching all diagnostics. 21 MCP tools total. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,7 @@ struct AgentPermissionPolicy {
|
||||
method == "getASTSubtree" ||
|
||||
method == "getASTDiff" ||
|
||||
method == "getDiagnostics" ||
|
||||
method == "getDiagnosticsDelta" ||
|
||||
method == "getQuickFixes") {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -598,13 +598,32 @@ inline json handleHeadlessAgentRequest(HeadlessEditorState& state,
|
||||
std::string src = params.value("source", "");
|
||||
diags = filterBySource(diags, src);
|
||||
}
|
||||
// Record snapshot for delta tracking
|
||||
auto allDiagsUnfiltered = collectAllDiagnostics(state.activeAST());
|
||||
state.active()->diagTracker.recordSnapshot(allDiagsUnfiltered);
|
||||
return headlessRpcResult(id, {
|
||||
{"diagnostics", diagnosticsToJson(diags)},
|
||||
{"count", (int)diags.size()},
|
||||
{"version", state.active()->versionTracker.version}
|
||||
{"version", state.active()->diagTracker.version}
|
||||
});
|
||||
}
|
||||
|
||||
// --- getDiagnosticsDelta ---
|
||||
if (method == "getDiagnosticsDelta") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
return headlessRpcError(id, -32031, "Role not permitted");
|
||||
auto err = headlessRequireAST(state, id);
|
||||
if (!err.is_null()) return err;
|
||||
auto params = request.contains("params") ? request["params"]
|
||||
: json::object();
|
||||
int sinceVersion = params.value("sinceVersion", 0);
|
||||
auto currentDiags = collectAllDiagnostics(state.activeAST());
|
||||
state.active()->diagTracker.recordSnapshot(currentDiags);
|
||||
auto delta = state.active()->diagTracker.getDelta(
|
||||
currentDiags, sinceVersion);
|
||||
return headlessRpcResult(id, deltaToJson(delta));
|
||||
}
|
||||
|
||||
// --- getQuickFixes ---
|
||||
if (method == "getQuickFixes") {
|
||||
if (!AgentPermissionPolicy::canInvoke(role, method))
|
||||
|
||||
@@ -67,6 +67,7 @@ struct HeadlessBufferState {
|
||||
Orchestrator orchestrator;
|
||||
IncrementalOptimizer incrementalOptimizer;
|
||||
ASTVersionTracker versionTracker;
|
||||
DiagnosticVersionTracker diagTracker;
|
||||
std::string language = "python";
|
||||
std::string path = "(untitled)";
|
||||
std::string editBuf;
|
||||
|
||||
@@ -669,6 +669,23 @@ private:
|
||||
return callWhetstone("getDiagnostics", args);
|
||||
};
|
||||
|
||||
// whetstone_get_diagnostics_delta
|
||||
tools_.push_back({"whetstone_get_diagnostics_delta",
|
||||
"Get only the diagnostics that changed since a given version. "
|
||||
"Returns added and removed diagnostics for efficient "
|
||||
"mutate-then-check loops. Use the version from a previous "
|
||||
"getDiagnostics or getDiagnosticsDelta response.",
|
||||
{{"type", "object"}, {"properties", {
|
||||
{"sinceVersion", {{"type", "integer"},
|
||||
{"description",
|
||||
"Version number from a previous diagnostics response"}}}
|
||||
}}, {"required", {"sinceVersion"}}}
|
||||
});
|
||||
toolHandlers_["whetstone_get_diagnostics_delta"] =
|
||||
[this](const json& args) {
|
||||
return callWhetstone("getDiagnosticsDelta", args);
|
||||
};
|
||||
|
||||
// whetstone_get_quick_fixes
|
||||
tools_.push_back({"whetstone_get_quick_fixes",
|
||||
"Get all applicable quick-fix actions for a node or the entire "
|
||||
|
||||
@@ -409,3 +409,115 @@ inline QuickFix findQuickFix(Module* ast, const std::string& diagCode,
|
||||
}
|
||||
return {}; // empty fix (mutation will be null)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Step 252: DiagnosticVersionTracker — track diagnostic changes
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// Key that identifies a unique diagnostic instance
|
||||
struct DiagnosticKey {
|
||||
std::string code;
|
||||
std::string nodeId;
|
||||
int line = 0;
|
||||
|
||||
bool operator==(const DiagnosticKey& o) const {
|
||||
return code == o.code && nodeId == o.nodeId && line == o.line;
|
||||
}
|
||||
bool operator<(const DiagnosticKey& o) const {
|
||||
if (code != o.code) return code < o.code;
|
||||
if (nodeId != o.nodeId) return nodeId < o.nodeId;
|
||||
return line < o.line;
|
||||
}
|
||||
};
|
||||
|
||||
inline DiagnosticKey keyFromDiagnostic(const StructuredDiagnostic& d) {
|
||||
return {d.code, d.nodeId, d.line};
|
||||
}
|
||||
|
||||
struct DiagnosticDelta {
|
||||
std::vector<StructuredDiagnostic> added;
|
||||
std::vector<StructuredDiagnostic> removed;
|
||||
int version = 0;
|
||||
};
|
||||
|
||||
class DiagnosticVersionTracker {
|
||||
public:
|
||||
int version = 0;
|
||||
|
||||
// Record current diagnostics snapshot and bump version
|
||||
void recordSnapshot(const std::vector<StructuredDiagnostic>& diags) {
|
||||
previousDiags_ = currentDiags_;
|
||||
previousVersion_ = version;
|
||||
currentDiags_ = diags;
|
||||
++version;
|
||||
}
|
||||
|
||||
// Compute delta: what changed since the given version
|
||||
DiagnosticDelta getDelta(
|
||||
const std::vector<StructuredDiagnostic>& currentDiags,
|
||||
int sinceVersion) const {
|
||||
DiagnosticDelta delta;
|
||||
delta.version = version;
|
||||
|
||||
if (sinceVersion >= version) {
|
||||
// No changes since that version
|
||||
return delta;
|
||||
}
|
||||
|
||||
// Build key sets for previous and current
|
||||
std::set<DiagnosticKey> prevKeys;
|
||||
std::map<DiagnosticKey, StructuredDiagnostic> prevMap;
|
||||
for (const auto& d : previousDiags_) {
|
||||
auto key = keyFromDiagnostic(d);
|
||||
prevKeys.insert(key);
|
||||
prevMap[key] = d;
|
||||
}
|
||||
|
||||
std::set<DiagnosticKey> currKeys;
|
||||
std::map<DiagnosticKey, StructuredDiagnostic> currMap;
|
||||
for (const auto& d : currentDiags) {
|
||||
auto key = keyFromDiagnostic(d);
|
||||
currKeys.insert(key);
|
||||
currMap[key] = d;
|
||||
}
|
||||
|
||||
// Added: in current but not in previous
|
||||
for (const auto& key : currKeys) {
|
||||
if (prevKeys.find(key) == prevKeys.end()) {
|
||||
delta.added.push_back(currMap[key]);
|
||||
}
|
||||
}
|
||||
|
||||
// Removed: in previous but not in current
|
||||
for (const auto& key : prevKeys) {
|
||||
if (currKeys.find(key) == currKeys.end()) {
|
||||
delta.removed.push_back(prevMap[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
// Reset tracker
|
||||
void reset() {
|
||||
version = 0;
|
||||
previousVersion_ = 0;
|
||||
currentDiags_.clear();
|
||||
previousDiags_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
int previousVersion_ = 0;
|
||||
std::vector<StructuredDiagnostic> currentDiags_;
|
||||
std::vector<StructuredDiagnostic> previousDiags_;
|
||||
};
|
||||
|
||||
inline json deltaToJson(const DiagnosticDelta& delta) {
|
||||
return {
|
||||
{"added", diagnosticsToJson(delta.added)},
|
||||
{"removed", diagnosticsToJson(delta.removed)},
|
||||
{"addedCount", (int)delta.added.size()},
|
||||
{"removedCount", (int)delta.removed.size()},
|
||||
{"version", delta.version}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user