claude-dev-env 1.28.1 → 1.29.0
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.
- package/agents/caveman.md +74 -0
- package/hooks/blocking/code_rules_enforcer.py +82 -7
- package/hooks/blocking/code_rules_path_utils.py +31 -0
- package/hooks/blocking/es_exe_path_rewriter.py +159 -0
- package/hooks/blocking/hedging_language_blocker.py +12 -2
- package/hooks/blocking/test_code_rules_enforcer.py +148 -0
- package/hooks/blocking/test_code_rules_enforcer_config_path.py +123 -0
- package/hooks/blocking/test_code_rules_enforcer_magic_allowlist.py +1 -1
- package/hooks/blocking/test_code_rules_path_utils.py +52 -0
- package/hooks/blocking/test_es_exe_path_rewriter.py +369 -0
- package/hooks/blocking/test_hedging_language_blocker.py +7 -6
- package/hooks/config/dynamic_stderr_handler.py +22 -0
- package/hooks/config/path_rewriter_constants.py +13 -0
- package/hooks/config/project_paths_reader.py +78 -0
- package/hooks/config/setup_project_paths_constants.py +41 -0
- package/hooks/config/test_dynamic_stderr_handler.py +48 -0
- package/hooks/config/test_messages.py +5 -1
- package/hooks/config/test_path_rewriter_constants.py +57 -0
- package/hooks/config/test_project_paths_reader.py +149 -0
- package/hooks/config/test_setup_project_paths_constants.py +74 -0
- package/hooks/git-hooks/test_config.py +1 -0
- package/hooks/git-hooks/test_gate_utils.py +1 -0
- package/hooks/git-hooks/test_pre_commit.py +1 -0
- package/hooks/git-hooks/test_pre_push.py +1 -0
- package/hooks/hooks.json +10 -0
- package/hooks/session/test_untracked_repo_detector.py +192 -0
- package/hooks/session/untracked_repo_detector.py +103 -0
- package/hooks/validators/exempt_paths.py +17 -14
- package/hooks/validators/test_exempt_paths.py +65 -0
- package/hooks/validators/test_git_checks.py +17 -17
- package/package.json +1 -1
- package/scripts/config/__init__.py +1 -0
- package/scripts/config/groq_bugteam_config.py +118 -0
- package/scripts/config/test_groq_bugteam_config.py +72 -0
- package/scripts/groq_bugteam.README.md +129 -0
- package/scripts/groq_bugteam.py +586 -0
- package/scripts/setup_project_paths.py +347 -0
- package/scripts/test_groq_bugteam.py +391 -0
- package/scripts/test_setup_project_paths.py +532 -0
- package/scripts/test_setup_project_paths_config.py +6 -0
- package/skills/bugteam/CONSTRAINTS.md +1 -1
- package/skills/bugteam/PROMPTS.md +1 -1
- package/skills/bugteam/SKILL.md +5 -5
- package/skills/bugteam/SKILL_EVALS.md +5 -5
- package/skills/bugteam/reference/audit-and-teammates.md +3 -3
- package/skills/bugteam/reference/audit-contract.md +159 -0
- package/skills/bugteam/reference/team-setup.md +2 -2
- package/skills/bugteam/scripts/bugteam_preflight.py +66 -0
- package/skills/bugteam/scripts/test_bugteam_preflight.py +189 -0
- package/skills/copilot-review/SKILL.md +145 -0
- package/skills/findbugs/SKILL.md +14 -22
- package/skills/qbug/SKILL.md +56 -13
- package/skills/qbug/test_qbug_skill_audit_schema.py +156 -0
- package/skills/qbug/test_qbug_skill_post_fix_audit.py +103 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"""Tests for groq_bugteam.py pure logic.
|
|
2
|
+
|
|
3
|
+
Network calls (Groq HTTP) and filesystem/git side effects are out of scope for
|
|
4
|
+
unit tests; they are exercised in the live end-to-end run.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import importlib.util
|
|
10
|
+
import pathlib
|
|
11
|
+
import sys
|
|
12
|
+
import urllib.error
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
sys.path.insert(0, str(pathlib.Path(__file__).parent))
|
|
17
|
+
for _cached in list(sys.modules):
|
|
18
|
+
if _cached == "config" or _cached.startswith("config."):
|
|
19
|
+
del sys.modules[_cached]
|
|
20
|
+
|
|
21
|
+
from config import groq_bugteam_config # noqa: E402
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _load_groq_bugteam_module():
|
|
25
|
+
scripts_directory = pathlib.Path(__file__).parent
|
|
26
|
+
sys.path.insert(0, str(scripts_directory))
|
|
27
|
+
for cached_module_name in list(sys.modules):
|
|
28
|
+
if cached_module_name == "config" or cached_module_name.startswith("config."):
|
|
29
|
+
del sys.modules[cached_module_name]
|
|
30
|
+
module_path = scripts_directory / "groq_bugteam.py"
|
|
31
|
+
module_spec = importlib.util.spec_from_file_location("groq_bugteam", module_path)
|
|
32
|
+
loaded_module = importlib.util.module_from_spec(module_spec)
|
|
33
|
+
sys.modules["groq_bugteam"] = loaded_module
|
|
34
|
+
module_spec.loader.exec_module(loaded_module)
|
|
35
|
+
return loaded_module
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
groq_bugteam = _load_groq_bugteam_module()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestConstantsSourcedFromConfig:
|
|
42
|
+
def test_endpoint_is_imported_from_config(self):
|
|
43
|
+
assert groq_bugteam.GROQ_API_ENDPOINT == groq_bugteam_config.GROQ_API_ENDPOINT
|
|
44
|
+
|
|
45
|
+
def test_primary_model_is_imported_from_config(self):
|
|
46
|
+
assert groq_bugteam.GROQ_PRIMARY_MODEL == groq_bugteam_config.GROQ_PRIMARY_MODEL
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestClampText:
|
|
50
|
+
def test_returns_text_unchanged_when_under_limit(self):
|
|
51
|
+
assert groq_bugteam.clamp_text("hello world", 100) == "hello world"
|
|
52
|
+
|
|
53
|
+
def test_truncates_long_text_with_marker(self):
|
|
54
|
+
long_text = "a" * 1000
|
|
55
|
+
clamped = groq_bugteam.clamp_text(long_text, 200)
|
|
56
|
+
assert "truncated" in clamped
|
|
57
|
+
assert len(clamped) < len(long_text)
|
|
58
|
+
assert clamped.startswith("a")
|
|
59
|
+
assert clamped.endswith("a")
|
|
60
|
+
|
|
61
|
+
def test_preserves_head_and_tail(self):
|
|
62
|
+
text = "HEAD" + ("x" * 1000) + "TAIL"
|
|
63
|
+
clamped = groq_bugteam.clamp_text(text, 100)
|
|
64
|
+
assert clamped.startswith("HEAD")
|
|
65
|
+
assert clamped.endswith("TAIL")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class TestParseJsonObject:
|
|
69
|
+
def test_parses_clean_json(self):
|
|
70
|
+
parsed = groq_bugteam.parse_json_object('{"findings": []}')
|
|
71
|
+
assert parsed == {"findings": []}
|
|
72
|
+
|
|
73
|
+
def test_extracts_json_from_surrounding_prose(self):
|
|
74
|
+
noisy_response = 'Sure, here is the result:\n\n{"findings": [{"severity": "P1"}]}\n\nLet me know if you need more.'
|
|
75
|
+
parsed = groq_bugteam.parse_json_object(noisy_response)
|
|
76
|
+
assert parsed == {"findings": [{"severity": "P1"}]}
|
|
77
|
+
|
|
78
|
+
def test_raises_when_no_json_present(self):
|
|
79
|
+
with pytest.raises(ValueError):
|
|
80
|
+
groq_bugteam.parse_json_object("no braces here")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class TestNormalizeFindings:
|
|
84
|
+
def test_drops_findings_with_unknown_files(self):
|
|
85
|
+
raw_findings = [
|
|
86
|
+
{
|
|
87
|
+
"severity": "P0",
|
|
88
|
+
"category": "H",
|
|
89
|
+
"file": "known.py",
|
|
90
|
+
"line": 10,
|
|
91
|
+
"title": "t",
|
|
92
|
+
"description": "d",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"severity": "P1",
|
|
96
|
+
"category": "A",
|
|
97
|
+
"file": "unknown.py",
|
|
98
|
+
"line": 5,
|
|
99
|
+
"title": "t2",
|
|
100
|
+
"description": "d2",
|
|
101
|
+
},
|
|
102
|
+
]
|
|
103
|
+
normalized = groq_bugteam.normalize_findings(raw_findings, {"known.py": ""})
|
|
104
|
+
assert len(normalized) == 1
|
|
105
|
+
assert normalized[0]["file"] == "known.py"
|
|
106
|
+
|
|
107
|
+
def test_coerces_non_string_line_to_int(self):
|
|
108
|
+
raw_findings = [
|
|
109
|
+
{
|
|
110
|
+
"severity": "P0",
|
|
111
|
+
"category": "H",
|
|
112
|
+
"file": "a.py",
|
|
113
|
+
"line": "42",
|
|
114
|
+
"title": "t",
|
|
115
|
+
"description": "d",
|
|
116
|
+
},
|
|
117
|
+
]
|
|
118
|
+
normalized = groq_bugteam.normalize_findings(raw_findings, {"a.py": ""})
|
|
119
|
+
assert normalized[0]["line"] == 42
|
|
120
|
+
|
|
121
|
+
def test_defaults_to_zero_line_on_bad_value(self):
|
|
122
|
+
raw_findings = [
|
|
123
|
+
{
|
|
124
|
+
"severity": "P1",
|
|
125
|
+
"category": "H",
|
|
126
|
+
"file": "a.py",
|
|
127
|
+
"line": "not-a-number",
|
|
128
|
+
"title": "t",
|
|
129
|
+
"description": "d",
|
|
130
|
+
},
|
|
131
|
+
]
|
|
132
|
+
normalized = groq_bugteam.normalize_findings(raw_findings, {"a.py": ""})
|
|
133
|
+
assert normalized[0]["line"] == 0
|
|
134
|
+
|
|
135
|
+
def test_clamps_invalid_severity_to_p2(self):
|
|
136
|
+
raw_findings = [
|
|
137
|
+
{
|
|
138
|
+
"severity": "CRITICAL",
|
|
139
|
+
"category": "H",
|
|
140
|
+
"file": "a.py",
|
|
141
|
+
"line": 1,
|
|
142
|
+
"title": "t",
|
|
143
|
+
"description": "d",
|
|
144
|
+
},
|
|
145
|
+
]
|
|
146
|
+
normalized = groq_bugteam.normalize_findings(raw_findings, {"a.py": ""})
|
|
147
|
+
assert normalized[0]["severity"] == "P2"
|
|
148
|
+
|
|
149
|
+
def test_keeps_single_letter_category(self):
|
|
150
|
+
raw_findings = [
|
|
151
|
+
{
|
|
152
|
+
"severity": "P0",
|
|
153
|
+
"category": "HIJ",
|
|
154
|
+
"file": "a.py",
|
|
155
|
+
"line": 1,
|
|
156
|
+
"title": "t",
|
|
157
|
+
"description": "d",
|
|
158
|
+
},
|
|
159
|
+
]
|
|
160
|
+
normalized = groq_bugteam.normalize_findings(raw_findings, {"a.py": ""})
|
|
161
|
+
assert normalized[0]["category"] == "H"
|
|
162
|
+
|
|
163
|
+
def test_handles_empty_input(self):
|
|
164
|
+
assert groq_bugteam.normalize_findings([], {"a.py": ""}) == []
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class TestGroupFindingsByFile:
|
|
168
|
+
def test_groups_findings_and_preserves_global_indexes(self):
|
|
169
|
+
findings = [
|
|
170
|
+
{
|
|
171
|
+
"file": "a.py",
|
|
172
|
+
"severity": "P0",
|
|
173
|
+
"category": "H",
|
|
174
|
+
"line": 1,
|
|
175
|
+
"title": "t1",
|
|
176
|
+
"description": "d1",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"file": "b.py",
|
|
180
|
+
"severity": "P1",
|
|
181
|
+
"category": "A",
|
|
182
|
+
"line": 2,
|
|
183
|
+
"title": "t2",
|
|
184
|
+
"description": "d2",
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"file": "a.py",
|
|
188
|
+
"severity": "P2",
|
|
189
|
+
"category": "E",
|
|
190
|
+
"line": 3,
|
|
191
|
+
"title": "t3",
|
|
192
|
+
"description": "d3",
|
|
193
|
+
},
|
|
194
|
+
]
|
|
195
|
+
grouped = groq_bugteam.group_findings_by_file(findings)
|
|
196
|
+
assert set(grouped.keys()) == {"a.py", "b.py"}
|
|
197
|
+
assert [index for index, _ in grouped["a.py"]] == [0, 2]
|
|
198
|
+
assert [index for index, _ in grouped["b.py"]] == [1]
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class TestBuildReviewBody:
|
|
202
|
+
def test_returns_clean_body_when_no_findings(self):
|
|
203
|
+
body = groq_bugteam.build_review_body([], "llama-3.3-70b-versatile", "", [])
|
|
204
|
+
assert "clean" in body
|
|
205
|
+
assert "llama-3.3-70b-versatile" in body
|
|
206
|
+
|
|
207
|
+
def test_counts_severities_and_lists_findings(self):
|
|
208
|
+
findings = [
|
|
209
|
+
{
|
|
210
|
+
"severity": "P0",
|
|
211
|
+
"category": "H",
|
|
212
|
+
"file": "a.py",
|
|
213
|
+
"line": 10,
|
|
214
|
+
"title": "SQL injection",
|
|
215
|
+
"description": "trace",
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"severity": "P1",
|
|
219
|
+
"category": "F",
|
|
220
|
+
"file": "b.py",
|
|
221
|
+
"line": 5,
|
|
222
|
+
"title": "silent except",
|
|
223
|
+
"description": "trace2",
|
|
224
|
+
},
|
|
225
|
+
]
|
|
226
|
+
fix_outcomes = [
|
|
227
|
+
{"finding_index": 0, "status": "fixed"},
|
|
228
|
+
{"finding_index": 1, "status": "skipped", "reason": "too complex"},
|
|
229
|
+
]
|
|
230
|
+
body = groq_bugteam.build_review_body(
|
|
231
|
+
findings, "llama-3.3-70b-versatile", "abc1234", fix_outcomes
|
|
232
|
+
)
|
|
233
|
+
assert "1 P0 / 1 P1 / 0 P2" in body
|
|
234
|
+
assert "abc1234" in body
|
|
235
|
+
assert "SQL injection" in body
|
|
236
|
+
assert "silent except" in body
|
|
237
|
+
assert "fixed" in body
|
|
238
|
+
assert "skipped: too complex" in body
|
|
239
|
+
|
|
240
|
+
def test_marks_findings_without_outcome_as_not_attempted(self):
|
|
241
|
+
findings = [
|
|
242
|
+
{
|
|
243
|
+
"severity": "P2",
|
|
244
|
+
"category": "E",
|
|
245
|
+
"file": "a.py",
|
|
246
|
+
"line": 1,
|
|
247
|
+
"title": "dead code",
|
|
248
|
+
"description": "d",
|
|
249
|
+
},
|
|
250
|
+
]
|
|
251
|
+
body = groq_bugteam.build_review_body(
|
|
252
|
+
findings, "llama-3.3-70b-versatile", "", []
|
|
253
|
+
)
|
|
254
|
+
assert "not attempted" in body
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class TestIsRecoverableHttpError:
|
|
258
|
+
def _make_error(self, status_code: int) -> urllib.error.HTTPError:
|
|
259
|
+
return urllib.error.HTTPError(
|
|
260
|
+
url="x", code=status_code, msg="", hdrs=None, fp=None
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
@pytest.mark.parametrize("status", [408, 429, 500, 502, 503, 504])
|
|
264
|
+
def test_recoverable_statuses(self, status):
|
|
265
|
+
assert groq_bugteam.is_recoverable_http_error(self._make_error(status)) is True
|
|
266
|
+
|
|
267
|
+
@pytest.mark.parametrize("status", [400, 401, 403, 404, 422])
|
|
268
|
+
def test_non_recoverable_statuses(self, status):
|
|
269
|
+
assert groq_bugteam.is_recoverable_http_error(self._make_error(status)) is False
|
|
270
|
+
|
|
271
|
+
def test_413_triggers_skip_to_next_model(self):
|
|
272
|
+
assert groq_bugteam.should_skip_to_next_model(self._make_error(413)) is True
|
|
273
|
+
|
|
274
|
+
@pytest.mark.parametrize("status", [400, 401, 403, 429, 500, 503])
|
|
275
|
+
def test_other_statuses_do_not_trigger_model_skip(self, status):
|
|
276
|
+
assert groq_bugteam.should_skip_to_next_model(self._make_error(status)) is False
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class TestBuildFixUserMessage:
|
|
280
|
+
def test_embeds_file_content_byte_for_byte_with_trailing_newline(self):
|
|
281
|
+
original_content = "line1\nline2\n"
|
|
282
|
+
message = groq_bugteam.build_fix_user_message("some.py", original_content, findings_block="[]")
|
|
283
|
+
assert original_content in message
|
|
284
|
+
assert "line2\n</current_file_contents>" in message
|
|
285
|
+
|
|
286
|
+
def test_embeds_file_content_byte_for_byte_without_trailing_newline(self):
|
|
287
|
+
original_content = "line1\nline2"
|
|
288
|
+
message = groq_bugteam.build_fix_user_message("some.py", original_content, findings_block="[]")
|
|
289
|
+
assert f"{original_content}\n</current_file_contents>" in message
|
|
290
|
+
assert "line2\n\n</current_file_contents>" not in message
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class TestShouldWriteFixedFile:
|
|
294
|
+
def test_does_not_write_when_no_finding_applied(self):
|
|
295
|
+
assert groq_bugteam.should_write_fixed_file(
|
|
296
|
+
applied_indexes=set(),
|
|
297
|
+
updated_content="new",
|
|
298
|
+
current_content="old",
|
|
299
|
+
) is False
|
|
300
|
+
|
|
301
|
+
def test_does_not_write_when_content_unchanged(self):
|
|
302
|
+
assert groq_bugteam.should_write_fixed_file(
|
|
303
|
+
applied_indexes={0},
|
|
304
|
+
updated_content="same",
|
|
305
|
+
current_content="same",
|
|
306
|
+
) is False
|
|
307
|
+
|
|
308
|
+
def test_writes_when_finding_applied_and_content_changed(self):
|
|
309
|
+
assert groq_bugteam.should_write_fixed_file(
|
|
310
|
+
applied_indexes={0},
|
|
311
|
+
updated_content="new",
|
|
312
|
+
current_content="old",
|
|
313
|
+
) is True
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class TestPreserveTrailingNewline:
|
|
317
|
+
def test_adds_trailing_newline_when_original_had_one(self):
|
|
318
|
+
preserved = groq_bugteam.preserve_trailing_newline(
|
|
319
|
+
original="line1\nline2\n", updated="line1\nfixed2"
|
|
320
|
+
)
|
|
321
|
+
assert preserved == "line1\nfixed2\n"
|
|
322
|
+
|
|
323
|
+
def test_strips_trailing_newline_when_original_lacked_one(self):
|
|
324
|
+
preserved = groq_bugteam.preserve_trailing_newline(
|
|
325
|
+
original="no newline", updated="fixed content\n"
|
|
326
|
+
)
|
|
327
|
+
assert preserved == "fixed content"
|
|
328
|
+
|
|
329
|
+
def test_keeps_matching_form_unchanged(self):
|
|
330
|
+
assert (
|
|
331
|
+
groq_bugteam.preserve_trailing_newline(original="x\n", updated="y\n")
|
|
332
|
+
== "y\n"
|
|
333
|
+
)
|
|
334
|
+
assert (
|
|
335
|
+
groq_bugteam.preserve_trailing_newline(original="x", updated="y") == "y"
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class TestIsSafeRelativePath:
|
|
340
|
+
def test_rejects_absolute_posix_path(self):
|
|
341
|
+
assert groq_bugteam.is_safe_relative_path("/etc/passwd") is False
|
|
342
|
+
|
|
343
|
+
def test_rejects_parent_directory_escape(self):
|
|
344
|
+
assert groq_bugteam.is_safe_relative_path("../../etc/passwd") is False
|
|
345
|
+
|
|
346
|
+
def test_rejects_embedded_parent_reference(self):
|
|
347
|
+
assert groq_bugteam.is_safe_relative_path("src/../../etc/passwd") is False
|
|
348
|
+
|
|
349
|
+
def test_accepts_simple_relative_path(self):
|
|
350
|
+
assert groq_bugteam.is_safe_relative_path("src/foo.py") is True
|
|
351
|
+
|
|
352
|
+
def test_accepts_nested_relative_path(self):
|
|
353
|
+
assert groq_bugteam.is_safe_relative_path("packages/mod/scripts/foo.py") is True
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class TestDecodeSubprocessStderr:
|
|
357
|
+
def test_decodes_bytes_input(self):
|
|
358
|
+
decoded = groq_bugteam.decode_subprocess_stderr(b"fatal: broken")
|
|
359
|
+
assert decoded == "fatal: broken"
|
|
360
|
+
|
|
361
|
+
def test_returns_str_input_unchanged(self):
|
|
362
|
+
assert groq_bugteam.decode_subprocess_stderr("fatal: broken") == "fatal: broken"
|
|
363
|
+
|
|
364
|
+
def test_handles_none_input(self):
|
|
365
|
+
assert groq_bugteam.decode_subprocess_stderr(None) == ""
|
|
366
|
+
|
|
367
|
+
def test_replaces_undecodable_bytes(self):
|
|
368
|
+
decoded = groq_bugteam.decode_subprocess_stderr(b"\xff\xfe broken")
|
|
369
|
+
assert "broken" in decoded
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class TestRunPipelineRefusals:
|
|
373
|
+
def test_rejects_missing_api_key(self, monkeypatch):
|
|
374
|
+
monkeypatch.delenv("GROQ_API_KEY", raising=False)
|
|
375
|
+
result = groq_bugteam.run_pipeline({"diff": "anything"})
|
|
376
|
+
assert "error" in result
|
|
377
|
+
assert "GROQ_API_KEY" in result["error"]
|
|
378
|
+
|
|
379
|
+
def test_rejects_empty_diff(self, monkeypatch):
|
|
380
|
+
monkeypatch.setenv("GROQ_API_KEY", "gsk_test_placeholder_value")
|
|
381
|
+
result = groq_bugteam.run_pipeline({"diff": " ", "files_content": {}})
|
|
382
|
+
assert "error" in result
|
|
383
|
+
assert "diff is empty" in result["error"]
|
|
384
|
+
|
|
385
|
+
def test_rejects_fixes_without_worktree(self, monkeypatch):
|
|
386
|
+
monkeypatch.setenv("GROQ_API_KEY", "gsk_test_placeholder_value")
|
|
387
|
+
result = groq_bugteam.run_pipeline(
|
|
388
|
+
{"diff": "some diff", "files_content": {"a.py": ""}, "apply_fixes": True}
|
|
389
|
+
)
|
|
390
|
+
assert "error" in result
|
|
391
|
+
assert "worktree_path" in result["error"]
|