misterdev 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- misterdev/__init__.py +3 -0
- misterdev/agent.py +2166 -0
- misterdev/agent_helpers.py +194 -0
- misterdev/analyzers/__init__.py +0 -0
- misterdev/analyzers/project_analyzer/__init__.py +246 -0
- misterdev/analyzers/project_analyzer/detection.py +146 -0
- misterdev/analyzers/project_analyzer/merge.py +137 -0
- misterdev/analyzers/project_analyzer/overview.py +312 -0
- misterdev/analyzers/project_analyzer/prompts.py +83 -0
- misterdev/cli.py +370 -0
- misterdev/config.py +521 -0
- misterdev/core/__init__.py +0 -0
- misterdev/core/audit.py +88 -0
- misterdev/core/config.py +10 -0
- misterdev/core/context/__init__.py +0 -0
- misterdev/core/context/change_tracker.py +218 -0
- misterdev/core/context/contracts/__init__.py +63 -0
- misterdev/core/context/contracts/_log.py +9 -0
- misterdev/core/context/contracts/_text.py +12 -0
- misterdev/core/context/contracts/extraction.py +30 -0
- misterdev/core/context/contracts/python_generic.py +42 -0
- misterdev/core/context/contracts/registry.py +127 -0
- misterdev/core/context/contracts/rust_line.py +225 -0
- misterdev/core/context/contracts/rust_tree_sitter.py +141 -0
- misterdev/core/context/lsp.py +174 -0
- misterdev/core/context/scratchpad.py +111 -0
- misterdev/core/context/topography/__init__.py +43 -0
- misterdev/core/context/topography/_log.py +10 -0
- misterdev/core/context/topography/cache.py +91 -0
- misterdev/core/context/topography/engine.py +172 -0
- misterdev/core/context/topography/graph.py +675 -0
- misterdev/core/context/topography/nodes.py +55 -0
- misterdev/core/context/topography/parsers.py +95 -0
- misterdev/core/context/topography/syntax.py +54 -0
- misterdev/core/economics/__init__.py +0 -0
- misterdev/core/economics/context_budget.py +175 -0
- misterdev/core/economics/embeddings.py +232 -0
- misterdev/core/economics/free_models.py +108 -0
- misterdev/core/economics/llm_cache.py +105 -0
- misterdev/core/economics/model_catalog.py +79 -0
- misterdev/core/economics/model_ledger.py +331 -0
- misterdev/core/economics/model_selector.py +281 -0
- misterdev/core/execution/__init__.py +0 -0
- misterdev/core/execution/bounded.py +50 -0
- misterdev/core/execution/container.py +221 -0
- misterdev/core/execution/error_classifier.py +366 -0
- misterdev/core/execution/error_resolver.py +201 -0
- misterdev/core/execution/governance.py +283 -0
- misterdev/core/execution/outcomes.py +50 -0
- misterdev/core/execution/progress.py +120 -0
- misterdev/core/execution/project.py +231 -0
- misterdev/core/execution/registry.py +97 -0
- misterdev/core/execution/runtime.py +279 -0
- misterdev/core/gitcmd.py +39 -0
- misterdev/core/integration/__init__.py +0 -0
- misterdev/core/integration/mcp.py +368 -0
- misterdev/core/integration/mcp_gather.py +186 -0
- misterdev/core/models.py +35 -0
- misterdev/core/modes.py +184 -0
- misterdev/core/planning/__init__.py +0 -0
- misterdev/core/planning/advisor.py +89 -0
- misterdev/core/planning/assessment.py +135 -0
- misterdev/core/planning/decomposer.py +387 -0
- misterdev/core/planning/metacognition.py +103 -0
- misterdev/core/planning/sovereign.py +308 -0
- misterdev/core/planning/targets.py +201 -0
- misterdev/core/reporting/__init__.py +0 -0
- misterdev/core/reporting/report.py +377 -0
- misterdev/core/reporting/report_view.py +151 -0
- misterdev/core/task.py +163 -0
- misterdev/core/verification/__init__.py +0 -0
- misterdev/core/verification/claim_verifier.py +210 -0
- misterdev/core/verification/critic.py +324 -0
- misterdev/core/verification/gatekeeper/__init__.py +631 -0
- misterdev/core/verification/gatekeeper/constants.py +138 -0
- misterdev/core/verification/gatekeeper/helpers.py +28 -0
- misterdev/core/verification/goal_check.py +219 -0
- misterdev/core/verification/independent.py +68 -0
- misterdev/core/verification/mutation_gate.py +221 -0
- misterdev/core/verification/preflight.py +95 -0
- misterdev/core/verification/spec_tests.py +175 -0
- misterdev/core/verification/validator.py +495 -0
- misterdev/core/verification/vision_verify.py +185 -0
- misterdev/core/verification/web_verify.py +408 -0
- misterdev/environments/__init__.py +0 -0
- misterdev/environments/base_env.py +18 -0
- misterdev/environments/container_env.py +87 -0
- misterdev/environments/venv_env.py +42 -0
- misterdev/llm/__init__.py +0 -0
- misterdev/llm/client/__init__.py +152 -0
- misterdev/llm/client/base.py +382 -0
- misterdev/llm/client/edits.py +70 -0
- misterdev/llm/client/embeddings.py +121 -0
- misterdev/llm/client/errors.py +134 -0
- misterdev/llm/client/providers.py +535 -0
- misterdev/llm/client/response.py +24 -0
- misterdev/llm/prompt_manager.py +82 -0
- misterdev/llm/responses/__init__.py +34 -0
- misterdev/llm/responses/apply.py +131 -0
- misterdev/llm/responses/json_extract.py +80 -0
- misterdev/llm/responses/models.py +43 -0
- misterdev/llm/responses/parsing.py +494 -0
- misterdev/logging_setup.py +20 -0
- misterdev/mcp_server.py +208 -0
- misterdev/nl_cli.py +149 -0
- misterdev/plugins.py +115 -0
- misterdev/py.typed +0 -0
- misterdev/task_executors/__init__.py +0 -0
- misterdev/task_executors/base_executor.py +10 -0
- misterdev/task_executors/markdown_plan_executor/__init__.py +90 -0
- misterdev/task_executors/markdown_plan_executor/commands_mixin.py +82 -0
- misterdev/task_executors/markdown_plan_executor/context_mixin.py +221 -0
- misterdev/task_executors/markdown_plan_executor/critic_spec_mixin.py +174 -0
- misterdev/task_executors/markdown_plan_executor/edits_mixin.py +251 -0
- misterdev/task_executors/markdown_plan_executor/execute_mixin.py +727 -0
- misterdev/task_executors/markdown_plan_executor/gates_mixin.py +203 -0
- misterdev/task_executors/markdown_plan_executor/git_mixin.py +219 -0
- misterdev/task_executors/markdown_plan_executor/helpers.py +521 -0
- misterdev/task_executors/markdown_plan_executor/llm_mixin.py +238 -0
- misterdev/task_executors/markdown_plan_executor/results_mixin.py +23 -0
- misterdev/tools/__init__.py +19 -0
- misterdev/tools/base_tool.py +14 -0
- misterdev/tools/command.py +75 -0
- misterdev/tools/file_io.py +69 -0
- misterdev/tools/formatter.py +26 -0
- misterdev/tools/git_tool.py +90 -0
- misterdev/utils/__init__.py +0 -0
- misterdev/utils/file_utils.py +169 -0
- misterdev/utils/process.py +23 -0
- misterdev-0.2.0.dist-info/METADATA +326 -0
- misterdev-0.2.0.dist-info/RECORD +136 -0
- misterdev-0.2.0.dist-info/WHEEL +5 -0
- misterdev-0.2.0.dist-info/entry_points.txt +3 -0
- misterdev-0.2.0.dist-info/licenses/COMMERCIAL_LICENSE.md +34 -0
- misterdev-0.2.0.dist-info/licenses/LICENSE +661 -0
- misterdev-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"""Build and test error classification.
|
|
2
|
+
|
|
3
|
+
Categorizes raw compiler/test output into actionable error types so the
|
|
4
|
+
LLM gets targeted fix guidance instead of raw error dumps.
|
|
5
|
+
|
|
6
|
+
Categories:
|
|
7
|
+
syntax - Parse/syntax errors (fix the malformed code)
|
|
8
|
+
missing_import - Unresolved import/use (add the import or dependency)
|
|
9
|
+
wrong_type - Type mismatch, wrong argument count (fix signature or call site)
|
|
10
|
+
missing_export - Symbol exists but isn't pub/exported (add visibility modifier)
|
|
11
|
+
missing_symbol - Function/struct/field doesn't exist yet (create it or fix the name)
|
|
12
|
+
test_assertion - Test assertion failed (fix logic, not syntax)
|
|
13
|
+
link_error - Linker/unresolved external (fix Cargo.toml or feature flags)
|
|
14
|
+
unknown - Unclassifiable
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import Dict, List, Tuple
|
|
18
|
+
|
|
19
|
+
from misterdev.logging_setup import setup_logger
|
|
20
|
+
|
|
21
|
+
logger = setup_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ErrorCategory:
|
|
25
|
+
SYNTAX = "syntax"
|
|
26
|
+
MISSING_IMPORT = "missing_import"
|
|
27
|
+
WRONG_TYPE = "wrong_type"
|
|
28
|
+
MISSING_EXPORT = "missing_export"
|
|
29
|
+
MISSING_SYMBOL = "missing_symbol"
|
|
30
|
+
TEST_ASSERTION = "test_assertion"
|
|
31
|
+
LINK_ERROR = "link_error"
|
|
32
|
+
MANIFEST = "manifest_error"
|
|
33
|
+
FILE_NOT_FOUND = "file_not_found"
|
|
34
|
+
UNKNOWN = "unknown"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# (category, indicator_strings)
|
|
38
|
+
_INDICATORS: List[Tuple[str, List[str]]] = [
|
|
39
|
+
(
|
|
40
|
+
ErrorCategory.SYNTAX,
|
|
41
|
+
[
|
|
42
|
+
"syntax error",
|
|
43
|
+
"SyntaxError",
|
|
44
|
+
"unexpected token",
|
|
45
|
+
"unexpected eof",
|
|
46
|
+
"unterminated",
|
|
47
|
+
"invalid syntax",
|
|
48
|
+
"expected one of",
|
|
49
|
+
"expected `,`",
|
|
50
|
+
"expected `{`",
|
|
51
|
+
"expected `;`",
|
|
52
|
+
# clang/gcc/swiftc
|
|
53
|
+
"expected ';'",
|
|
54
|
+
"expected expression",
|
|
55
|
+
"expected '}'",
|
|
56
|
+
"expected declaration",
|
|
57
|
+
"extraneous",
|
|
58
|
+
"expected identifier",
|
|
59
|
+
],
|
|
60
|
+
),
|
|
61
|
+
(
|
|
62
|
+
ErrorCategory.MISSING_IMPORT,
|
|
63
|
+
[
|
|
64
|
+
"unresolved import",
|
|
65
|
+
"ModuleNotFoundError",
|
|
66
|
+
"No module named",
|
|
67
|
+
"could not find crate",
|
|
68
|
+
"use of undeclared crate",
|
|
69
|
+
"cannot find module",
|
|
70
|
+
"ImportError",
|
|
71
|
+
# swift / C / C++
|
|
72
|
+
"no such module",
|
|
73
|
+
"cannot find interface declaration",
|
|
74
|
+
],
|
|
75
|
+
),
|
|
76
|
+
(
|
|
77
|
+
ErrorCategory.MISSING_EXPORT,
|
|
78
|
+
[
|
|
79
|
+
"is private",
|
|
80
|
+
"not accessible",
|
|
81
|
+
"module is private",
|
|
82
|
+
"function is private",
|
|
83
|
+
"struct is private",
|
|
84
|
+
"pub(crate)",
|
|
85
|
+
"inaccessible",
|
|
86
|
+
# swift / C++ / C#
|
|
87
|
+
"is inaccessible due to",
|
|
88
|
+
"marked private",
|
|
89
|
+
"not visible",
|
|
90
|
+
"inaccessible due to its protection level",
|
|
91
|
+
],
|
|
92
|
+
),
|
|
93
|
+
(
|
|
94
|
+
ErrorCategory.WRONG_TYPE,
|
|
95
|
+
[
|
|
96
|
+
"type mismatch",
|
|
97
|
+
"expected type",
|
|
98
|
+
"mismatched types",
|
|
99
|
+
"TypeError",
|
|
100
|
+
"incompatible type",
|
|
101
|
+
"cannot convert",
|
|
102
|
+
"expected struct",
|
|
103
|
+
"expected enum",
|
|
104
|
+
"wrong number of",
|
|
105
|
+
"arguments were supplied",
|
|
106
|
+
"the trait bound",
|
|
107
|
+
"is not satisfied",
|
|
108
|
+
# swift / clang
|
|
109
|
+
"cannot convert value of type",
|
|
110
|
+
"cannot initialize",
|
|
111
|
+
"no viable conversion",
|
|
112
|
+
"no matching function for call",
|
|
113
|
+
"argument of type",
|
|
114
|
+
"incompatible pointer",
|
|
115
|
+
# C# / roslyn
|
|
116
|
+
"cannot implicitly convert type",
|
|
117
|
+
"cannot convert from",
|
|
118
|
+
],
|
|
119
|
+
),
|
|
120
|
+
(
|
|
121
|
+
ErrorCategory.MISSING_SYMBOL,
|
|
122
|
+
[
|
|
123
|
+
"not found in this scope",
|
|
124
|
+
"cannot find value",
|
|
125
|
+
"cannot find function",
|
|
126
|
+
"cannot find type",
|
|
127
|
+
"no field",
|
|
128
|
+
"no method named",
|
|
129
|
+
"no variant",
|
|
130
|
+
"NameError",
|
|
131
|
+
"AttributeError",
|
|
132
|
+
"has no member",
|
|
133
|
+
"unknown field",
|
|
134
|
+
# swift / clang
|
|
135
|
+
"use of undeclared identifier",
|
|
136
|
+
"use of unresolved identifier",
|
|
137
|
+
"no member named",
|
|
138
|
+
"has no member named",
|
|
139
|
+
# C# / roslyn
|
|
140
|
+
"does not contain a definition for",
|
|
141
|
+
"does not exist in the current context",
|
|
142
|
+
],
|
|
143
|
+
),
|
|
144
|
+
(
|
|
145
|
+
ErrorCategory.TEST_ASSERTION,
|
|
146
|
+
[
|
|
147
|
+
"assertion failed",
|
|
148
|
+
"AssertionError",
|
|
149
|
+
"assert_eq",
|
|
150
|
+
"assert_ne",
|
|
151
|
+
"panicked at",
|
|
152
|
+
"test result: FAILED",
|
|
153
|
+
"left:",
|
|
154
|
+
"right:",
|
|
155
|
+
# XCTest / ctest
|
|
156
|
+
"XCTAssert",
|
|
157
|
+
"XCTFail",
|
|
158
|
+
"failed (",
|
|
159
|
+
"tests failed out of",
|
|
160
|
+
],
|
|
161
|
+
),
|
|
162
|
+
(
|
|
163
|
+
ErrorCategory.LINK_ERROR,
|
|
164
|
+
[
|
|
165
|
+
"linker",
|
|
166
|
+
"undefined reference",
|
|
167
|
+
"unresolved external",
|
|
168
|
+
"link error",
|
|
169
|
+
"multiple definition",
|
|
170
|
+
"duplicate symbol",
|
|
171
|
+
# apple ld / clang
|
|
172
|
+
"undefined symbols for architecture",
|
|
173
|
+
"symbol(s) not found",
|
|
174
|
+
"ld: ",
|
|
175
|
+
"linker command failed",
|
|
176
|
+
],
|
|
177
|
+
),
|
|
178
|
+
(
|
|
179
|
+
ErrorCategory.MANIFEST,
|
|
180
|
+
[
|
|
181
|
+
"failed to parse manifest",
|
|
182
|
+
"could not find `cargo.toml`",
|
|
183
|
+
"missing either a `[package]`",
|
|
184
|
+
"virtual manifest",
|
|
185
|
+
"invalid toml",
|
|
186
|
+
"expected value",
|
|
187
|
+
"duplicate key",
|
|
188
|
+
"error parsing pyproject.toml",
|
|
189
|
+
"invalid package.json",
|
|
190
|
+
# swiftpm / meson / cmake
|
|
191
|
+
"could not find package.swift",
|
|
192
|
+
"manifest parse error",
|
|
193
|
+
"neither directory contains a build file",
|
|
194
|
+
"cmake error",
|
|
195
|
+
"does not appear to contain cmakelists.txt",
|
|
196
|
+
],
|
|
197
|
+
),
|
|
198
|
+
(
|
|
199
|
+
ErrorCategory.FILE_NOT_FOUND,
|
|
200
|
+
[
|
|
201
|
+
"no such file or directory",
|
|
202
|
+
"file not found",
|
|
203
|
+
"cannot open",
|
|
204
|
+
"enoent",
|
|
205
|
+
],
|
|
206
|
+
),
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
# Fix guidance per category
|
|
210
|
+
FIX_GUIDANCE = {
|
|
211
|
+
ErrorCategory.SYNTAX: (
|
|
212
|
+
"This is a SYNTAX error. The code is malformed. "
|
|
213
|
+
"Fix the specific line indicated. Do not restructure; just correct the syntax."
|
|
214
|
+
),
|
|
215
|
+
ErrorCategory.MISSING_IMPORT: (
|
|
216
|
+
"This is a MISSING IMPORT error. A module, crate, or package is referenced but not imported. "
|
|
217
|
+
"Add the correct 'use' statement or dependency. Check Cargo.toml or import statements."
|
|
218
|
+
),
|
|
219
|
+
ErrorCategory.MISSING_EXPORT: (
|
|
220
|
+
"This is a VISIBILITY error. The symbol exists but is not public. "
|
|
221
|
+
"Add 'pub' to the declaration, or change the import path to use a re-export."
|
|
222
|
+
),
|
|
223
|
+
ErrorCategory.WRONG_TYPE: (
|
|
224
|
+
"This is a TYPE MISMATCH error. The function signature or argument types don't match the call site. "
|
|
225
|
+
"Check the interface contracts above. Match the exact types expected."
|
|
226
|
+
),
|
|
227
|
+
ErrorCategory.MISSING_SYMBOL: (
|
|
228
|
+
"This is a MISSING SYMBOL error. A function, struct, field, or method is referenced but doesn't exist. "
|
|
229
|
+
"Either create it, fix the spelling, or check which module it should come from."
|
|
230
|
+
),
|
|
231
|
+
ErrorCategory.TEST_ASSERTION: (
|
|
232
|
+
"This is a TEST ASSERTION failure. The code compiles and runs but produces wrong results. "
|
|
233
|
+
"Focus on the LOGIC, not the syntax. Check the algorithm, boundary conditions, and data flow."
|
|
234
|
+
),
|
|
235
|
+
ErrorCategory.LINK_ERROR: (
|
|
236
|
+
"This is a LINKER error. Check that all referenced symbols are defined and that the "
|
|
237
|
+
"needed libraries/dependencies and feature flags are declared in the build manifest "
|
|
238
|
+
"(Cargo.toml, CMakeLists.txt, package config) and linked."
|
|
239
|
+
),
|
|
240
|
+
ErrorCategory.MANIFEST: (
|
|
241
|
+
"This is a MANIFEST/CONFIG error. The project manifest (Cargo.toml, pyproject.toml, "
|
|
242
|
+
"package.json) is malformed or missing required sections. Ensure required sections exist "
|
|
243
|
+
"(e.g. [package] with name and version for Cargo.toml) and that the file is valid TOML/JSON."
|
|
244
|
+
),
|
|
245
|
+
ErrorCategory.FILE_NOT_FOUND: (
|
|
246
|
+
"This is a FILE NOT FOUND error. A referenced file or path does not exist. "
|
|
247
|
+
"Create the missing file or correct the path; check for typos and relative-path assumptions."
|
|
248
|
+
),
|
|
249
|
+
ErrorCategory.UNKNOWN: (
|
|
250
|
+
"Error type could not be classified. Read the full error output carefully and fix the root cause."
|
|
251
|
+
),
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# Rust compiler error codes -> category (authoritative, no fuzzy matching needed)
|
|
256
|
+
_RUST_ERROR_CODES: Dict[str, str] = {
|
|
257
|
+
"E0061": ErrorCategory.WRONG_TYPE, # wrong number of arguments
|
|
258
|
+
"E0106": ErrorCategory.SYNTAX, # missing lifetime specifier
|
|
259
|
+
"E0277": ErrorCategory.WRONG_TYPE, # trait bound not satisfied
|
|
260
|
+
"E0308": ErrorCategory.WRONG_TYPE, # mismatched types
|
|
261
|
+
"E0369": ErrorCategory.WRONG_TYPE, # binary operation not supported
|
|
262
|
+
"E0382": ErrorCategory.WRONG_TYPE, # use of moved value
|
|
263
|
+
"E0412": ErrorCategory.MISSING_SYMBOL, # cannot find type
|
|
264
|
+
"E0422": ErrorCategory.MISSING_SYMBOL, # cannot find struct/variant
|
|
265
|
+
"E0425": ErrorCategory.MISSING_SYMBOL, # cannot find value/function
|
|
266
|
+
"E0432": ErrorCategory.MISSING_IMPORT, # unresolved import
|
|
267
|
+
"E0433": ErrorCategory.MISSING_IMPORT, # failed to resolve: use of undeclared crate
|
|
268
|
+
"E0603": ErrorCategory.MISSING_EXPORT, # private item
|
|
269
|
+
"E0614": ErrorCategory.WRONG_TYPE, # cannot dereference
|
|
270
|
+
"E0615": ErrorCategory.MISSING_SYMBOL, # attempted to take value of method
|
|
271
|
+
"E0624": ErrorCategory.MISSING_EXPORT, # associated item is private
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# C# / Roslyn compiler error codes -> category (authoritative)
|
|
276
|
+
_CSHARP_ERROR_CODES: Dict[str, str] = {
|
|
277
|
+
"CS0103": ErrorCategory.MISSING_SYMBOL, # name does not exist in context
|
|
278
|
+
"CS0117": ErrorCategory.MISSING_SYMBOL, # type has no definition for member
|
|
279
|
+
"CS1061": ErrorCategory.MISSING_SYMBOL, # no definition / extension method
|
|
280
|
+
"CS0246": ErrorCategory.MISSING_IMPORT, # type/namespace not found (using?)
|
|
281
|
+
"CS0234": ErrorCategory.MISSING_IMPORT, # namespace member does not exist
|
|
282
|
+
"CS0029": ErrorCategory.WRONG_TYPE, # cannot implicitly convert
|
|
283
|
+
"CS1503": ErrorCategory.WRONG_TYPE, # argument cannot convert from
|
|
284
|
+
"CS0019": ErrorCategory.WRONG_TYPE, # operator cannot be applied
|
|
285
|
+
"CS1002": ErrorCategory.SYNTAX, # ; expected
|
|
286
|
+
"CS1513": ErrorCategory.SYNTAX, # } expected
|
|
287
|
+
"CS1519": ErrorCategory.SYNTAX, # invalid token
|
|
288
|
+
"CS0122": ErrorCategory.MISSING_EXPORT, # inaccessible protection level
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# Tie-break priority (lower rank = wins a tie). Categories that block the build
|
|
293
|
+
# outright and are more fundamental come first: a syntax/manifest failure that
|
|
294
|
+
# also trips a downstream type/symbol indicator is the syntax/manifest error.
|
|
295
|
+
# Mirrors the order categories are declared in ``_INDICATORS``.
|
|
296
|
+
_TIE_BREAK_ORDER: List[str] = [cat for cat, _ in _INDICATORS]
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _tie_break_rank(category: str) -> int:
|
|
300
|
+
try:
|
|
301
|
+
return _TIE_BREAK_ORDER.index(category)
|
|
302
|
+
except ValueError:
|
|
303
|
+
return len(_TIE_BREAK_ORDER)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def classify_error(error_output: str) -> str:
|
|
307
|
+
"""Classify build/test error output into a category.
|
|
308
|
+
|
|
309
|
+
Checks structured compiler error codes first (Rust, then C#/Roslyn) for an
|
|
310
|
+
exact, authoritative match, then falls back to keyword scoring for other
|
|
311
|
+
languages and test output.
|
|
312
|
+
"""
|
|
313
|
+
# Fast path: Rust structured error codes
|
|
314
|
+
for code, category in _RUST_ERROR_CODES.items():
|
|
315
|
+
if f"error[{code}]" in error_output:
|
|
316
|
+
return category
|
|
317
|
+
# Fast path: C#/Roslyn error codes (e.g. "error CS0103:")
|
|
318
|
+
for code, category in _CSHARP_ERROR_CODES.items():
|
|
319
|
+
if f"{code}:" in error_output:
|
|
320
|
+
return category
|
|
321
|
+
|
|
322
|
+
lower = error_output.lower()
|
|
323
|
+
|
|
324
|
+
# Score each category by number of matching indicators
|
|
325
|
+
scores: Dict[str, int] = {}
|
|
326
|
+
for category, indicators in _INDICATORS:
|
|
327
|
+
score = sum(1 for ind in indicators if ind.lower() in lower)
|
|
328
|
+
if score > 0:
|
|
329
|
+
scores[category] = score
|
|
330
|
+
|
|
331
|
+
if not scores:
|
|
332
|
+
return ErrorCategory.UNKNOWN
|
|
333
|
+
|
|
334
|
+
# Highest indicator count wins; ties resolve by an explicit priority (more
|
|
335
|
+
# fundamental, build-blocking categories first) rather than relying on the
|
|
336
|
+
# implicit dict-insertion order, so the result is stable across refactors.
|
|
337
|
+
return max(scores, key=lambda c: (scores[c], -_tie_break_rank(c)))
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def classify_and_guide(error_output: str) -> Tuple[str, str]:
|
|
341
|
+
"""Classify an error and return (category, fix_guidance)."""
|
|
342
|
+
category = classify_error(error_output)
|
|
343
|
+
guidance = FIX_GUIDANCE.get(category, FIX_GUIDANCE[ErrorCategory.UNKNOWN])
|
|
344
|
+
logger.info(f"Error classified as: {category}")
|
|
345
|
+
return category, guidance
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def format_classified_error(error_output: str, max_lines: int = 50) -> str:
|
|
349
|
+
"""Classify error and format with guidance for LLM prompt."""
|
|
350
|
+
category, guidance = classify_and_guide(error_output)
|
|
351
|
+
|
|
352
|
+
# Truncate raw output
|
|
353
|
+
lines = error_output.splitlines()
|
|
354
|
+
if len(lines) > max_lines:
|
|
355
|
+
truncated = (
|
|
356
|
+
"\n".join(lines[:max_lines])
|
|
357
|
+
+ f"\n... ({len(lines) - max_lines} more lines)"
|
|
358
|
+
)
|
|
359
|
+
else:
|
|
360
|
+
truncated = error_output
|
|
361
|
+
|
|
362
|
+
return (
|
|
363
|
+
f"### Error Classification: {category.upper()}\n"
|
|
364
|
+
f"**Fix Strategy**: {guidance}\n\n"
|
|
365
|
+
f"### Raw Error Output\n```\n{truncated}\n```"
|
|
366
|
+
)
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Error attribution and resolution for build/test failures.
|
|
2
|
+
|
|
3
|
+
Parses compiler and test-runner output to locate the source files and
|
|
4
|
+
line numbers responsible for each error, then formats that information
|
|
5
|
+
for injection into LLM prompts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, List, Optional
|
|
13
|
+
|
|
14
|
+
from misterdev.logging_setup import setup_logger
|
|
15
|
+
|
|
16
|
+
logger = setup_logger(__name__)
|
|
17
|
+
|
|
18
|
+
# Patterns that match common error location formats:
|
|
19
|
+
# file.py:42: error message
|
|
20
|
+
# file.py:42:10: error message
|
|
21
|
+
# File "file.py", line 42
|
|
22
|
+
# --> file.py:42:10
|
|
23
|
+
_LOCATION_PATTERNS: list[re.Pattern] = [
|
|
24
|
+
re.compile(
|
|
25
|
+
r"^(?P<file>[^\s:]+\.(?:py|js|ts|rs|go|java|c|cpp|h|rb|cs))"
|
|
26
|
+
r":(?P<line>\d+)(?::\d+)?[:\s]",
|
|
27
|
+
re.MULTILINE,
|
|
28
|
+
),
|
|
29
|
+
re.compile(r'File "(?P<file>[^"]+)", line (?P<line>\d+)', re.MULTILINE),
|
|
30
|
+
re.compile(r"-->\s*(?P<file>[^\s:]+):(?P<line>\d+):\d+", re.MULTILINE),
|
|
31
|
+
re.compile(r"at (?P<file>[^\s(]+)\(.*:(?P<line>\d+)\)", re.MULTILINE),
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ErrorLocation:
|
|
36
|
+
"""A single attributed error location."""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
file: str,
|
|
41
|
+
line: int,
|
|
42
|
+
snippet: str = "",
|
|
43
|
+
symbol: Optional[str] = None,
|
|
44
|
+
symbol_key: Optional[str] = None,
|
|
45
|
+
):
|
|
46
|
+
self.file = file
|
|
47
|
+
self.line = line
|
|
48
|
+
self.snippet = snippet
|
|
49
|
+
self.symbol = symbol
|
|
50
|
+
# Unique graph key (``file_path:name``) of the attributed symbol, so
|
|
51
|
+
# caller lookup never conflates same-named symbols in other files.
|
|
52
|
+
self.symbol_key = symbol_key
|
|
53
|
+
|
|
54
|
+
def __repr__(self) -> str: # pragma: no cover
|
|
55
|
+
return f"ErrorLocation({self.file}:{self.line})"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ErrorResolver:
|
|
59
|
+
"""Resolves build/test error output to source locations.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
project_path:
|
|
64
|
+
Root directory of the project (used to read source snippets).
|
|
65
|
+
dependency_graph:
|
|
66
|
+
Optional symbol graph (a ``SymbolGraph`` with a ``symbols`` mapping).
|
|
67
|
+
When supplied, each error location is attributed to its enclosing symbol
|
|
68
|
+
and its callers are surfaced. Pass ``None`` when unavailable; attribution
|
|
69
|
+
then degrades to file:line + snippet only.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(self, project_path: Path, dependency_graph: Optional[Any] = None):
|
|
73
|
+
self.project_path = project_path
|
|
74
|
+
self.graph = dependency_graph
|
|
75
|
+
|
|
76
|
+
# ------------------------------------------------------------------
|
|
77
|
+
# Public API
|
|
78
|
+
# ------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
def resolve_errors(self, error_output: str) -> List[ErrorLocation]:
|
|
81
|
+
"""Parse *error_output* and return attributed :class:`ErrorLocation` objects."""
|
|
82
|
+
locations: List[ErrorLocation] = []
|
|
83
|
+
seen: set[tuple[str, int]] = set()
|
|
84
|
+
|
|
85
|
+
for pattern in _LOCATION_PATTERNS:
|
|
86
|
+
for match in pattern.finditer(error_output):
|
|
87
|
+
file_str = match.group("file")
|
|
88
|
+
try:
|
|
89
|
+
line_no = int(match.group("line"))
|
|
90
|
+
except (ValueError, IndexError):
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
key = (file_str, line_no)
|
|
94
|
+
if key in seen:
|
|
95
|
+
continue
|
|
96
|
+
seen.add(key)
|
|
97
|
+
|
|
98
|
+
snippet = self._read_snippet(file_str, line_no)
|
|
99
|
+
symbol_key = self._symbol_key_at(file_str, line_no)
|
|
100
|
+
locations.append(
|
|
101
|
+
ErrorLocation(
|
|
102
|
+
file=file_str,
|
|
103
|
+
line=line_no,
|
|
104
|
+
snippet=snippet,
|
|
105
|
+
symbol=self._symbol_name(symbol_key),
|
|
106
|
+
symbol_key=symbol_key,
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if not locations:
|
|
111
|
+
logger.debug(
|
|
112
|
+
"ErrorResolver: no structured locations found in error output."
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return locations
|
|
116
|
+
|
|
117
|
+
def format_for_llm(self, locations: List[ErrorLocation]) -> str:
|
|
118
|
+
"""Format attributed locations as a prompt-ready string."""
|
|
119
|
+
if not locations:
|
|
120
|
+
return ""
|
|
121
|
+
|
|
122
|
+
lines = ["## Error Attribution"]
|
|
123
|
+
for loc in locations[:10]: # cap to avoid bloating context
|
|
124
|
+
lines.append(f"\n### {loc.file}:{loc.line}")
|
|
125
|
+
if loc.symbol:
|
|
126
|
+
lines.append(f"- Symbol: `{loc.symbol}`")
|
|
127
|
+
callers = self._callers(loc.symbol_key)
|
|
128
|
+
if callers:
|
|
129
|
+
lines.append(f"- Called by: {', '.join(callers)}")
|
|
130
|
+
if loc.snippet:
|
|
131
|
+
lines.append(f"```\n{loc.snippet}\n```")
|
|
132
|
+
|
|
133
|
+
return "\n".join(lines)
|
|
134
|
+
|
|
135
|
+
# ------------------------------------------------------------------
|
|
136
|
+
# Internal helpers
|
|
137
|
+
# ------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
def _read_snippet(self, file_str: str, line_no: int, context: int = 5) -> str:
|
|
140
|
+
"""Return a few lines of source around *line_no* for context."""
|
|
141
|
+
# Try relative to project root first, then as absolute path.
|
|
142
|
+
candidates = [
|
|
143
|
+
self.project_path / file_str,
|
|
144
|
+
Path(file_str),
|
|
145
|
+
]
|
|
146
|
+
for path in candidates:
|
|
147
|
+
if path.exists():
|
|
148
|
+
try:
|
|
149
|
+
source_lines = path.read_text(
|
|
150
|
+
encoding="utf-8", errors="replace"
|
|
151
|
+
).splitlines()
|
|
152
|
+
start = max(0, line_no - context - 1)
|
|
153
|
+
end = min(len(source_lines), line_no + context)
|
|
154
|
+
numbered = [
|
|
155
|
+
f"{'>' if i + 1 == line_no else ' '} {i + 1:4d} | {source_lines[i]}"
|
|
156
|
+
for i in range(start, end)
|
|
157
|
+
]
|
|
158
|
+
return "\n".join(numbered)
|
|
159
|
+
except OSError:
|
|
160
|
+
return ""
|
|
161
|
+
return ""
|
|
162
|
+
|
|
163
|
+
def _rel_file(self, file_str: str) -> Optional[str]:
|
|
164
|
+
"""Project-relative form of an error path for symbol-graph lookup, or
|
|
165
|
+
None when it lies outside the project."""
|
|
166
|
+
try:
|
|
167
|
+
p = Path(file_str)
|
|
168
|
+
if p.is_absolute():
|
|
169
|
+
if p.is_relative_to(self.project_path):
|
|
170
|
+
return str(p.relative_to(self.project_path))
|
|
171
|
+
return None
|
|
172
|
+
return file_str
|
|
173
|
+
except (ValueError, OSError):
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
def _symbol_key_at(self, file_str: str, line_no: int) -> Optional[str]:
|
|
177
|
+
"""Graph key of the symbol enclosing the error line (the 0-vs-1-indexed
|
|
178
|
+
and sub-target path handling lives on ``SymbolGraph``). None when there
|
|
179
|
+
is no usable graph or the path is outside the project."""
|
|
180
|
+
at = getattr(self.graph, "symbol_at_line", None)
|
|
181
|
+
if at is None:
|
|
182
|
+
return None
|
|
183
|
+
rel = self._rel_file(file_str)
|
|
184
|
+
if rel is None:
|
|
185
|
+
return None
|
|
186
|
+
return at(rel, line_no)
|
|
187
|
+
|
|
188
|
+
def _symbol_name(self, key: Optional[str]) -> Optional[str]:
|
|
189
|
+
"""Display name for an attributed symbol key, or None."""
|
|
190
|
+
symbols = getattr(self.graph, "symbols", None)
|
|
191
|
+
if not key or not symbols:
|
|
192
|
+
return None
|
|
193
|
+
node = symbols.get(key)
|
|
194
|
+
return node.name if node else None
|
|
195
|
+
|
|
196
|
+
def _callers(self, key: Optional[str]) -> List[str]:
|
|
197
|
+
"""Names of symbols that call the attributed symbol (by unique key)."""
|
|
198
|
+
of = getattr(self.graph, "callers_of", None)
|
|
199
|
+
if of is None or not key:
|
|
200
|
+
return []
|
|
201
|
+
return of(key)
|