claude-dev-env 1.38.1 → 1.39.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/CLAUDE.md +10 -36
- package/_shared/pr-loop/audit-reply-template.md +147 -0
- package/_shared/pr-loop/fix-protocol.md +25 -4
- package/_shared/pr-loop/gh-payloads.md +37 -50
- package/_shared/pr-loop/scripts/code_rules_gate.py +0 -60
- package/_shared/pr-loop/scripts/config/post_audit_thread_constants.py +189 -0
- package/_shared/pr-loop/scripts/post_audit_thread.py +947 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -19
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +923 -0
- package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +127 -0
- package/_shared/pr-loop/state-schema.md +1 -1
- package/agents/clean-coder.md +2 -2
- package/bin/install.mjs +6 -7
- package/bin/install.test.mjs +8 -0
- package/commands/doc-gist.md +16 -0
- package/commands/plan.md +0 -2
- package/commands/review-plan.md +1 -1
- package/docs/CODE_RULES.md +122 -2
- package/hooks/blocking/bot_mention_comment_blocker.py +75 -0
- package/hooks/blocking/code_rules_enforcer.py +1143 -129
- package/hooks/blocking/convergence_gate_blocker.py +130 -0
- package/hooks/blocking/destructive_command_blocker.py +74 -0
- package/hooks/blocking/gh_body_arg_blocker.py +30 -0
- package/hooks/blocking/md_to_html_blocker.py +119 -0
- package/hooks/blocking/test_bot_mention_comment_blocker.py +131 -0
- package/hooks/blocking/test_code_rules_enforcer.py +21 -0
- package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +70 -0
- package/hooks/blocking/test_code_rules_enforcer_any_imports_and_cast.py +92 -0
- package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +143 -0
- package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +152 -0
- package/hooks/blocking/test_code_rules_enforcer_bare_except.py +120 -0
- package/hooks/blocking/test_code_rules_enforcer_boundary_types.py +175 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
- package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +50 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_format.py +255 -0
- package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +130 -0
- package/hooks/blocking/test_code_rules_enforcer_stub_implementations.py +141 -0
- package/hooks/blocking/test_code_rules_enforcer_test_branching.py +143 -0
- package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +169 -0
- package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +99 -0
- package/hooks/blocking/test_code_rules_enforcer_typed_dict_pairs.py +141 -0
- package/hooks/blocking/test_convergence_gate_blocker.py +63 -0
- package/hooks/blocking/test_destructive_command_blocker.py +146 -0
- package/hooks/blocking/test_destructive_command_blocker_no_verify.py +102 -0
- package/hooks/blocking/test_gh_body_arg_blocker.py +45 -0
- package/hooks/blocking/test_md_to_html_blocker.py +317 -0
- package/hooks/config/any_type_config.py +7 -0
- package/hooks/config/banned_identifiers_constants.py +11 -0
- package/hooks/config/blocking_check_limits.py +38 -0
- package/hooks/config/bot_mention_comment_blocker_constants.py +20 -0
- package/hooks/config/code_rules_enforcer_constants.py +53 -0
- package/hooks/config/convergence_branch_constants.py +9 -0
- package/hooks/config/doc_gist_auto_publish_constants.py +18 -0
- package/hooks/config/html_companion_constants.py +20 -0
- package/hooks/config/inline_tuple_string_magic_constants.py +22 -0
- package/hooks/config/test_banned_identifiers_constants.py +17 -0
- package/hooks/hooks.json +28 -20
- package/hooks/pyproject.toml +69 -0
- package/hooks/validators/mypy_integration.py +47 -1
- package/hooks/validators/run_all_validators.py +3 -3
- package/hooks/validators/test_mypy_integration.py +50 -1
- package/hooks/workflow/doc_gist_auto_publish.py +144 -0
- package/hooks/workflow/md_to_html_companion.py +365 -0
- package/hooks/workflow/test_doc_gist_auto_publish.py +117 -0
- package/hooks/workflow/test_md_to_html_companion.py +452 -0
- package/package.json +1 -1
- package/rules/gh-body-file.md +2 -0
- package/scripts/Install-SweepEmptyDirs.ps1 +111 -0
- package/scripts/check.ps1 +106 -0
- package/scripts/config/timing.py +11 -0
- package/scripts/sweep_empty_dirs.py +138 -0
- package/scripts/sync_to_cursor/rules.py +1 -1
- package/scripts/test_sweep_empty_dirs.py +183 -0
- package/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +323 -0
- package/skills/_shared/pr-loop/scripts/_cli_utils.py +22 -0
- package/skills/_shared/pr-loop/scripts/_path_resolver.py +165 -0
- package/skills/_shared/pr-loop/scripts/_xml_utils.py +20 -0
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +182 -0
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +185 -0
- package/skills/_shared/pr-loop/scripts/config/__init__.py +0 -0
- package/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py +78 -0
- package/skills/_shared/pr-loop/scripts/init_loop_state.py +135 -0
- package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +175 -0
- package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +182 -0
- package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +206 -0
- package/skills/bugteam/CONSTRAINTS.md +21 -22
- package/skills/bugteam/EXAMPLES.md +3 -3
- package/skills/bugteam/PROMPTS.md +227 -67
- package/skills/bugteam/SKILL.md +114 -455
- package/skills/bugteam/reference/README.md +1 -1
- package/skills/bugteam/reference/audit-and-teammates.md +112 -39
- package/skills/bugteam/reference/audit-contract.md +4 -22
- package/skills/bugteam/reference/copilot-gap-analysis.md +8 -5
- package/skills/bugteam/reference/design-rationale.md +2 -2
- package/skills/bugteam/reference/github-pr-reviews.md +50 -57
- package/skills/bugteam/reference/obstacles/audit-assign-ids.md +13 -0
- package/skills/bugteam/reference/obstacles/audit-capture-excerpts.md +13 -0
- package/skills/bugteam/reference/obstacles/audit-walk-categories.md +13 -0
- package/skills/bugteam/reference/obstacles/audit-write-xml.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-append-summary.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-apply-fixes.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-git-add-commit.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-git-push.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-post-reply.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-publish-summary.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-py-compile.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-read-files.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-resolve-thread.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-test-suite.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-violation-count.md +13 -0
- package/skills/bugteam/reference/obstacles/fix-write-xml.md +13 -0
- package/skills/bugteam/reference/team-setup.md +106 -9
- package/skills/bugteam/reference/teardown-publish-permissions.md +39 -8
- package/skills/bugteam/scripts/README.md +60 -0
- package/skills/bugteam/scripts/_claude_permissions_common.py +358 -0
- package/skills/bugteam/scripts/bugteam_code_rules_gate.py +976 -0
- package/skills/bugteam/scripts/bugteam_fix_hookspath.py +375 -0
- package/skills/bugteam/scripts/bugteam_preflight.py +294 -0
- package/skills/bugteam/scripts/config/bugteam_code_rules_gate_constants.py +25 -0
- package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +26 -0
- package/skills/bugteam/scripts/config/bugteam_preflight_constants.py +35 -0
- package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +20 -0
- package/skills/bugteam/scripts/config/probe_code_rules_enforcer_check_constants.py +12 -0
- package/skills/bugteam/scripts/config/windows_safe_rmtree_constants.py +7 -0
- package/skills/bugteam/scripts/grant_project_claude_permissions.py +175 -0
- package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +107 -0
- package/skills/bugteam/scripts/revoke_project_claude_permissions.py +220 -0
- package/skills/bugteam/scripts/test__claude_permissions_common.py +112 -0
- package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +400 -0
- package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +384 -0
- package/skills/bugteam/scripts/test_bugteam_preflight.py +268 -0
- package/skills/bugteam/scripts/test_claude_permissions_common.py +195 -0
- package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +55 -0
- package/skills/bugteam/scripts/test_probe_code_rules_enforcer_check.py +76 -0
- package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +55 -0
- package/skills/bugteam/scripts/test_windows_safe_rmtree.py +108 -0
- package/skills/bugteam/scripts/windows_safe_rmtree.py +100 -0
- package/skills/bugteam/test_skill_additions.py +1 -11
- package/skills/code/SKILL.md +176 -0
- package/skills/doc-gist/SKILL.md +99 -0
- package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +453 -0
- package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +515 -0
- package/skills/doc-gist/references/examples/03-code-review-pr.html +638 -0
- package/skills/doc-gist/references/examples/04-code-understanding.html +491 -0
- package/skills/doc-gist/references/examples/05-design-system.html +629 -0
- package/skills/doc-gist/references/examples/06-component-variants.html +605 -0
- package/skills/doc-gist/references/examples/07-prototype-animation.html +455 -0
- package/skills/doc-gist/references/examples/08-prototype-interaction.html +396 -0
- package/skills/doc-gist/references/examples/09-slide-deck.html +592 -0
- package/skills/doc-gist/references/examples/10-svg-illustrations.html +492 -0
- package/skills/doc-gist/references/examples/11-status-report.html +528 -0
- package/skills/doc-gist/references/examples/12-incident-report.html +596 -0
- package/skills/doc-gist/references/examples/13-flowchart-diagram.html +395 -0
- package/skills/doc-gist/references/examples/14-research-feature-explainer.html +381 -0
- package/skills/doc-gist/references/examples/15-research-concept-explainer.html +368 -0
- package/skills/doc-gist/references/examples/16-implementation-plan.html +702 -0
- package/skills/doc-gist/references/examples/17-pr-writeup.html +595 -0
- package/skills/doc-gist/references/examples/18-editor-triage-board.html +573 -0
- package/skills/doc-gist/references/examples/19-editor-feature-flags.html +663 -0
- package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +722 -0
- package/skills/doc-gist/references/examples/README.md +5 -0
- package/skills/doc-gist/scripts/config/__init__.py +0 -0
- package/skills/doc-gist/scripts/config/gist_upload_constants.py +16 -0
- package/skills/doc-gist/scripts/gist_upload.py +177 -0
- package/skills/doc-gist/scripts/test_gist_upload.py +51 -0
- package/skills/findbugs/SKILL.md +68 -2
- package/skills/monitor-open-prs/SKILL.md +13 -32
- package/skills/monitor-open-prs/test_skill_contract.py +0 -11
- package/skills/pr-consistency-audit/SKILL.md +112 -0
- package/skills/pr-consistency-audit/reference/detection-rules.md +96 -0
- package/skills/pr-consistency-audit/reference/illustrations.md +78 -0
- package/skills/pr-converge/SKILL.md +227 -23
- package/skills/pr-converge/config/__init__.py +0 -0
- package/skills/pr-converge/config/constants.py +62 -0
- package/skills/pr-converge/reference/convergence-gates.md +138 -44
- package/skills/pr-converge/reference/examples.md +43 -11
- package/skills/pr-converge/reference/fix-protocol.md +6 -5
- package/skills/pr-converge/reference/ground-rules.md +5 -3
- package/skills/pr-converge/reference/multi-pr-orchestration.md +44 -19
- package/skills/pr-converge/reference/obstacles/fix-post-replies.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-push.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-read-filelines.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-reset-state.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-resolve-threads.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-spawn-clean-coder.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-stage-commit.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-trigger-bugbot.md +13 -0
- package/skills/pr-converge/reference/obstacles/fix-write-test.md +13 -0
- package/skills/pr-converge/reference/per-tick.md +90 -31
- package/skills/pr-converge/reference/state-schema.md +22 -1
- package/skills/pr-converge/reference/stop-conditions.md +9 -7
- package/skills/pr-converge/scripts/README.md +34 -46
- package/skills/pr-converge/scripts/check_bugbot_ci.py +174 -0
- package/skills/pr-converge/scripts/check_convergence.py +497 -0
- package/skills/pr-converge/scripts/check_pending_reviews.py +154 -0
- package/skills/pr-converge/scripts/config/pr_converge_constants.py +118 -0
- package/skills/pr-converge/scripts/fetch_copilot_reviews.py +134 -0
- package/skills/pr-converge/scripts/post_fix_reply.py +168 -0
- package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -12
- package/skills/qbug/SKILL.md +132 -27
- package/skills/session-log/SKILL.md +216 -114
- package/skills/session-tidy/SKILL.md +1 -1
- package/skills/skill-builder/SKILL.md +138 -56
- package/skills/skill-builder/references/delegation-map.md +72 -113
- package/skills/skill-builder/references/progressive-disclosure.md +122 -0
- package/skills/skill-builder/references/self-audit-checklist.md +92 -0
- package/skills/skill-builder/references/skill-types.md +228 -0
- package/skills/skill-builder/references/thariq-x-post-skills.json +33 -0
- package/skills/skill-builder/templates/gap-analysis.md +15 -8
- package/skills/skill-builder/workflows/improve-skill.md +86 -57
- package/skills/skill-builder/workflows/new-skill.md +80 -168
- package/skills/skill-builder/workflows/polish-skill.md +78 -54
- package/skills/structure-prompt/SKILL.md +50 -0
- package/skills/structure-prompt/reference/adversarial-tuning.md +62 -0
- package/skills/structure-prompt/reference/block-classification.md +27 -0
- package/skills/structure-prompt/reference/canonical-case.md +48 -0
- package/skills/structure-prompt/reference/citation-depth.md +70 -0
- package/skills/structure-prompt/reference/cleanup.md +33 -0
- package/skills/structure-prompt/reference/constraints.md +33 -0
- package/skills/structure-prompt/reference/directives.md +37 -0
- package/skills/structure-prompt/reference/examples.md +72 -0
- package/skills/structure-prompt/reference/instantiation.md +51 -0
- package/skills/structure-prompt/reference/output-contract.md +72 -0
- package/skills/structure-prompt/reference/per-category.md +23 -0
- package/skills/structure-prompt/reference/persona.md +38 -0
- package/skills/structure-prompt/reference/research.md +33 -0
- package/skills/structure-prompt/reference/structure.md +28 -0
- package/agents/code-standards-agent.md +0 -93
- package/agents/groq-coder.md +0 -113
- package/agents/plan-executor.md +0 -226
- package/agents/project-docs-analyzer.md +0 -53
- package/agents/project-structure-organizer-agent.md +0 -72
- package/agents/skill-to-agent-converter.md +0 -370
- package/agents/skill-writer-agent.md +0 -470
- package/agents/user-docs-writer.md +0 -67
- package/agents/workflow-visual-documenter.md +0 -82
- package/commands/readability-review.md +0 -20
- package/hooks/mypy.ini +0 -2
- package/hooks/notification/attention_needed_notify.py +0 -71
- package/hooks/notification/claude_notification_handler.py +0 -67
- package/hooks/notification/notification_utils.py +0 -267
- package/hooks/notification/subagent_complete_notify.py +0 -381
- package/hooks/notification/test_attention_needed_notify.py +0 -47
- package/hooks/notification/test_claude_notification_handler.py +0 -54
- package/hooks/notification/test_notification_utils.py +0 -91
- package/hooks/notification/test_subagent_complete_notify.py +0 -79
- package/scripts/config/groq_bugteam_config.py +0 -230
- package/scripts/config/test_groq_bugteam_config.py +0 -83
- package/scripts/config/test_spec_implementer_prompt.py +0 -32
- package/scripts/groq_bugteam.README.md +0 -131
- package/scripts/groq_bugteam.py +0 -647
- package/scripts/groq_bugteam_dotenv.py +0 -40
- package/scripts/groq_bugteam_spec.py +0 -226
- package/scripts/test_groq_bugteam.py +0 -529
- package/scripts/test_groq_bugteam_apply_fix_from_spec.py +0 -426
- package/scripts/test_groq_bugteam_dotenv.py +0 -66
- package/scripts/test_groq_bugteam_spec.py +0 -338
- package/skills/bugteam/SKILL_EVALS.md +0 -309
- package/skills/dream/SKILL.md +0 -118
- package/skills/ingest/SKILL.md +0 -40
- package/skills/npm-creator/SKILL.md +0 -187
- package/skills/readability-review/SKILL.md +0 -127
- package/skills/resume-review/SKILL.md +0 -261
- package/skills/rule-audit/SKILL.md +0 -307
- package/skills/rule-creator/SKILL.md +0 -150
- package/skills/searching-obsidian-vault/SKILL.md +0 -131
- package/skills/skill-writer/REFERENCE.md +0 -284
- package/skills/skill-writer/SKILL.md +0 -222
- package/skills/tdd-team/SKILL.md +0 -128
|
@@ -1,529 +0,0 @@
|
|
|
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 re
|
|
12
|
-
import sys
|
|
13
|
-
import urllib.error
|
|
14
|
-
|
|
15
|
-
scripts_directory = pathlib.Path(__file__).parent
|
|
16
|
-
scripts_directory_string = str(scripts_directory)
|
|
17
|
-
if scripts_directory_string not in sys.path:
|
|
18
|
-
sys.path.insert(0, scripts_directory_string)
|
|
19
|
-
for _cached in list(sys.modules):
|
|
20
|
-
if _cached == "config" or _cached.startswith("config."):
|
|
21
|
-
del sys.modules[_cached]
|
|
22
|
-
|
|
23
|
-
import groq_bugteam_dotenv # noqa: E402
|
|
24
|
-
import pytest # noqa: E402
|
|
25
|
-
|
|
26
|
-
from config import groq_bugteam_config # noqa: E402
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def _load_groq_bugteam_module():
|
|
30
|
-
scripts_directory = pathlib.Path(__file__).parent
|
|
31
|
-
scripts_directory_string = str(scripts_directory)
|
|
32
|
-
if scripts_directory_string not in sys.path:
|
|
33
|
-
sys.path.insert(0, scripts_directory_string)
|
|
34
|
-
for cached_module_name in list(sys.modules):
|
|
35
|
-
if cached_module_name == "config" or cached_module_name.startswith("config."):
|
|
36
|
-
del sys.modules[cached_module_name]
|
|
37
|
-
module_path = scripts_directory / "groq_bugteam.py"
|
|
38
|
-
module_spec = importlib.util.spec_from_file_location("groq_bugteam", module_path)
|
|
39
|
-
loaded_module = importlib.util.module_from_spec(module_spec)
|
|
40
|
-
sys.modules["groq_bugteam"] = loaded_module
|
|
41
|
-
module_spec.loader.exec_module(loaded_module)
|
|
42
|
-
return loaded_module
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
groq_bugteam = _load_groq_bugteam_module()
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class TestConstantsSourcedFromConfig:
|
|
49
|
-
def test_endpoint_is_imported_from_config(self):
|
|
50
|
-
assert groq_bugteam.GROQ_API_ENDPOINT == groq_bugteam_config.GROQ_API_ENDPOINT
|
|
51
|
-
|
|
52
|
-
def test_primary_model_is_imported_from_config(self):
|
|
53
|
-
assert groq_bugteam.GROQ_PRIMARY_MODEL == groq_bugteam_config.GROQ_PRIMARY_MODEL
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class TestClampText:
|
|
57
|
-
def test_returns_text_unchanged_when_under_limit(self):
|
|
58
|
-
assert groq_bugteam.clamp_text("hello world", 100) == "hello world"
|
|
59
|
-
|
|
60
|
-
def test_truncates_long_text_with_marker(self):
|
|
61
|
-
long_text = "a" * 1000
|
|
62
|
-
clamped = groq_bugteam.clamp_text(long_text, 200)
|
|
63
|
-
assert "truncated" in clamped
|
|
64
|
-
assert len(clamped) < len(long_text)
|
|
65
|
-
assert clamped.startswith("a")
|
|
66
|
-
assert clamped.endswith("a")
|
|
67
|
-
|
|
68
|
-
def test_preserves_head_and_tail(self):
|
|
69
|
-
text = "HEAD" + ("x" * 1000) + "TAIL"
|
|
70
|
-
clamped = groq_bugteam.clamp_text(text, 100)
|
|
71
|
-
assert clamped.startswith("HEAD")
|
|
72
|
-
assert clamped.endswith("TAIL")
|
|
73
|
-
|
|
74
|
-
@pytest.mark.parametrize("max_characters", [50, 100, 200, 500, 1000])
|
|
75
|
-
def test_output_never_exceeds_max_characters(self, max_characters):
|
|
76
|
-
long_text = "a" * 5000
|
|
77
|
-
clamped = groq_bugteam.clamp_text(long_text, max_characters)
|
|
78
|
-
assert len(clamped) <= max_characters
|
|
79
|
-
|
|
80
|
-
def test_returns_plain_head_when_marker_does_not_fit(self):
|
|
81
|
-
long_text = "a" * 1000
|
|
82
|
-
tiny_budget = 10
|
|
83
|
-
clamped = groq_bugteam.clamp_text(long_text, tiny_budget)
|
|
84
|
-
assert len(clamped) <= tiny_budget
|
|
85
|
-
assert clamped == long_text[:tiny_budget]
|
|
86
|
-
assert "truncated" not in clamped
|
|
87
|
-
|
|
88
|
-
def test_truncation_marker_count_matches_characters_actually_dropped(self):
|
|
89
|
-
long_text = "a" * 1000
|
|
90
|
-
max_characters = 200
|
|
91
|
-
clamped = groq_bugteam.clamp_text(long_text, max_characters)
|
|
92
|
-
marker_match = re.search(r"truncated (\d+) chars", clamped)
|
|
93
|
-
assert marker_match is not None
|
|
94
|
-
reported_truncated_count = int(marker_match.group(1))
|
|
95
|
-
full_marker = f"\n\n... [truncated {reported_truncated_count} chars] ...\n\n"
|
|
96
|
-
preserved_original_length = len(clamped) - len(full_marker)
|
|
97
|
-
actually_truncated_count = len(long_text) - preserved_original_length
|
|
98
|
-
assert reported_truncated_count == actually_truncated_count
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
class TestParseJsonObject:
|
|
102
|
-
def test_parses_clean_json(self):
|
|
103
|
-
parsed = groq_bugteam.parse_json_object('{"findings": []}')
|
|
104
|
-
assert parsed == {"findings": []}
|
|
105
|
-
|
|
106
|
-
def test_extracts_json_from_surrounding_prose(self):
|
|
107
|
-
noisy_response = 'Sure, here is the result:\n\n{"findings": [{"severity": "P1"}]}\n\nLet me know if you need more.'
|
|
108
|
-
parsed = groq_bugteam.parse_json_object(noisy_response)
|
|
109
|
-
assert parsed == {"findings": [{"severity": "P1"}]}
|
|
110
|
-
|
|
111
|
-
def test_raises_when_no_json_present(self):
|
|
112
|
-
with pytest.raises(ValueError):
|
|
113
|
-
groq_bugteam.parse_json_object("no braces here")
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
class TestNormalizeFindings:
|
|
117
|
-
def test_drops_findings_with_unknown_files(self):
|
|
118
|
-
raw_findings = [
|
|
119
|
-
{
|
|
120
|
-
"severity": "P0",
|
|
121
|
-
"category": "H",
|
|
122
|
-
"file": "known.py",
|
|
123
|
-
"line": 10,
|
|
124
|
-
"title": "t",
|
|
125
|
-
"description": "d",
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
"severity": "P1",
|
|
129
|
-
"category": "A",
|
|
130
|
-
"file": "unknown.py",
|
|
131
|
-
"line": 5,
|
|
132
|
-
"title": "t2",
|
|
133
|
-
"description": "d2",
|
|
134
|
-
},
|
|
135
|
-
]
|
|
136
|
-
normalized = groq_bugteam.normalize_findings(raw_findings, {"known.py": ""})
|
|
137
|
-
assert len(normalized) == 1
|
|
138
|
-
assert normalized[0]["file"] == "known.py"
|
|
139
|
-
|
|
140
|
-
def test_coerces_non_string_line_to_int(self):
|
|
141
|
-
raw_findings = [
|
|
142
|
-
{
|
|
143
|
-
"severity": "P0",
|
|
144
|
-
"category": "H",
|
|
145
|
-
"file": "a.py",
|
|
146
|
-
"line": "42",
|
|
147
|
-
"title": "t",
|
|
148
|
-
"description": "d",
|
|
149
|
-
},
|
|
150
|
-
]
|
|
151
|
-
normalized = groq_bugteam.normalize_findings(raw_findings, {"a.py": ""})
|
|
152
|
-
assert normalized[0]["line"] == 42
|
|
153
|
-
|
|
154
|
-
def test_defaults_to_zero_line_on_bad_value(self):
|
|
155
|
-
raw_findings = [
|
|
156
|
-
{
|
|
157
|
-
"severity": "P1",
|
|
158
|
-
"category": "H",
|
|
159
|
-
"file": "a.py",
|
|
160
|
-
"line": "not-a-number",
|
|
161
|
-
"title": "t",
|
|
162
|
-
"description": "d",
|
|
163
|
-
},
|
|
164
|
-
]
|
|
165
|
-
normalized = groq_bugteam.normalize_findings(raw_findings, {"a.py": ""})
|
|
166
|
-
assert normalized[0]["line"] == 0
|
|
167
|
-
|
|
168
|
-
def test_clamps_invalid_severity_to_p2(self):
|
|
169
|
-
raw_findings = [
|
|
170
|
-
{
|
|
171
|
-
"severity": "CRITICAL",
|
|
172
|
-
"category": "H",
|
|
173
|
-
"file": "a.py",
|
|
174
|
-
"line": 1,
|
|
175
|
-
"title": "t",
|
|
176
|
-
"description": "d",
|
|
177
|
-
},
|
|
178
|
-
]
|
|
179
|
-
normalized = groq_bugteam.normalize_findings(raw_findings, {"a.py": ""})
|
|
180
|
-
assert normalized[0]["severity"] == "P2"
|
|
181
|
-
|
|
182
|
-
def test_keeps_single_letter_category(self):
|
|
183
|
-
raw_findings = [
|
|
184
|
-
{
|
|
185
|
-
"severity": "P0",
|
|
186
|
-
"category": "HIJ",
|
|
187
|
-
"file": "a.py",
|
|
188
|
-
"line": 1,
|
|
189
|
-
"title": "t",
|
|
190
|
-
"description": "d",
|
|
191
|
-
},
|
|
192
|
-
]
|
|
193
|
-
normalized = groq_bugteam.normalize_findings(raw_findings, {"a.py": ""})
|
|
194
|
-
assert normalized[0]["category"] == "H"
|
|
195
|
-
|
|
196
|
-
def test_handles_empty_input(self):
|
|
197
|
-
assert groq_bugteam.normalize_findings([], {"a.py": ""}) == []
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
class TestGroupFindingsByFile:
|
|
201
|
-
def test_groups_findings_and_preserves_global_indexes(self):
|
|
202
|
-
findings = [
|
|
203
|
-
{
|
|
204
|
-
"file": "a.py",
|
|
205
|
-
"severity": "P0",
|
|
206
|
-
"category": "H",
|
|
207
|
-
"line": 1,
|
|
208
|
-
"title": "t1",
|
|
209
|
-
"description": "d1",
|
|
210
|
-
},
|
|
211
|
-
{
|
|
212
|
-
"file": "b.py",
|
|
213
|
-
"severity": "P1",
|
|
214
|
-
"category": "A",
|
|
215
|
-
"line": 2,
|
|
216
|
-
"title": "t2",
|
|
217
|
-
"description": "d2",
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
"file": "a.py",
|
|
221
|
-
"severity": "P2",
|
|
222
|
-
"category": "E",
|
|
223
|
-
"line": 3,
|
|
224
|
-
"title": "t3",
|
|
225
|
-
"description": "d3",
|
|
226
|
-
},
|
|
227
|
-
]
|
|
228
|
-
grouped = groq_bugteam.group_findings_by_file(findings)
|
|
229
|
-
assert set(grouped.keys()) == {"a.py", "b.py"}
|
|
230
|
-
assert [index for index, _ in grouped["a.py"]] == [0, 2]
|
|
231
|
-
assert [index for index, _ in grouped["b.py"]] == [1]
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
class TestBuildReviewBody:
|
|
235
|
-
def test_returns_clean_body_when_no_findings(self):
|
|
236
|
-
body = groq_bugteam.build_review_body([], "llama-3.3-70b-versatile", "", [])
|
|
237
|
-
assert "clean" in body
|
|
238
|
-
assert "llama-3.3-70b-versatile" in body
|
|
239
|
-
|
|
240
|
-
def test_counts_severities_and_lists_findings(self):
|
|
241
|
-
findings = [
|
|
242
|
-
{
|
|
243
|
-
"severity": "P0",
|
|
244
|
-
"category": "H",
|
|
245
|
-
"file": "a.py",
|
|
246
|
-
"line": 10,
|
|
247
|
-
"title": "SQL injection",
|
|
248
|
-
"description": "trace",
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
"severity": "P1",
|
|
252
|
-
"category": "F",
|
|
253
|
-
"file": "b.py",
|
|
254
|
-
"line": 5,
|
|
255
|
-
"title": "silent except",
|
|
256
|
-
"description": "trace2",
|
|
257
|
-
},
|
|
258
|
-
]
|
|
259
|
-
fix_outcomes = [
|
|
260
|
-
{"finding_index": 0, "status": "fixed"},
|
|
261
|
-
{"finding_index": 1, "status": "skipped", "reason": "too complex"},
|
|
262
|
-
]
|
|
263
|
-
body = groq_bugteam.build_review_body(
|
|
264
|
-
findings, "llama-3.3-70b-versatile", "abc1234", fix_outcomes
|
|
265
|
-
)
|
|
266
|
-
assert "1 P0 / 1 P1 / 0 P2" in body
|
|
267
|
-
assert "abc1234" in body
|
|
268
|
-
assert "SQL injection" in body
|
|
269
|
-
assert "silent except" in body
|
|
270
|
-
assert "fixed" in body
|
|
271
|
-
assert "skipped: too complex" in body
|
|
272
|
-
|
|
273
|
-
def test_marks_findings_without_outcome_as_not_attempted(self):
|
|
274
|
-
findings = [
|
|
275
|
-
{
|
|
276
|
-
"severity": "P2",
|
|
277
|
-
"category": "E",
|
|
278
|
-
"file": "a.py",
|
|
279
|
-
"line": 1,
|
|
280
|
-
"title": "dead code",
|
|
281
|
-
"description": "d",
|
|
282
|
-
},
|
|
283
|
-
]
|
|
284
|
-
body = groq_bugteam.build_review_body(
|
|
285
|
-
findings, "llama-3.3-70b-versatile", "", []
|
|
286
|
-
)
|
|
287
|
-
assert "not attempted" in body
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
class TestIsRecoverableHttpError:
|
|
291
|
-
def _make_error(self, status_code: int) -> urllib.error.HTTPError:
|
|
292
|
-
return urllib.error.HTTPError(
|
|
293
|
-
url="x", code=status_code, msg="", hdrs=None, fp=None
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
@pytest.mark.parametrize("status", [408, 429, 500, 502, 503, 504])
|
|
297
|
-
def test_recoverable_statuses(self, status):
|
|
298
|
-
assert groq_bugteam.is_recoverable_http_error(self._make_error(status)) is True
|
|
299
|
-
|
|
300
|
-
@pytest.mark.parametrize("status", [400, 401, 403, 404, 422])
|
|
301
|
-
def test_non_recoverable_statuses(self, status):
|
|
302
|
-
assert groq_bugteam.is_recoverable_http_error(self._make_error(status)) is False
|
|
303
|
-
|
|
304
|
-
def test_413_triggers_skip_to_next_model(self):
|
|
305
|
-
assert groq_bugteam.should_skip_to_next_model(self._make_error(413)) is True
|
|
306
|
-
|
|
307
|
-
@pytest.mark.parametrize("status", [400, 401, 403, 429, 500, 503])
|
|
308
|
-
def test_other_statuses_do_not_trigger_model_skip(self, status):
|
|
309
|
-
assert groq_bugteam.should_skip_to_next_model(self._make_error(status)) is False
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
class TestCallGroqWithFallback:
|
|
313
|
-
def _install_fake_transport(self, monkeypatch, fake_post_to_groq):
|
|
314
|
-
monkeypatch.setattr(groq_bugteam, "post_to_groq", fake_post_to_groq)
|
|
315
|
-
monkeypatch.setattr(groq_bugteam.time, "sleep", lambda _seconds: None)
|
|
316
|
-
|
|
317
|
-
def test_non_recoverable_http_error_does_not_attempt_fallback_model(self, monkeypatch):
|
|
318
|
-
attempted_models: list[str] = []
|
|
319
|
-
|
|
320
|
-
def fake_post_to_groq(api_key, model, messages, temperature, max_completion_tokens):
|
|
321
|
-
attempted_models.append(model)
|
|
322
|
-
raise urllib.error.HTTPError(
|
|
323
|
-
url="x", code=401, msg="unauthorized", hdrs=None, fp=None
|
|
324
|
-
)
|
|
325
|
-
|
|
326
|
-
self._install_fake_transport(monkeypatch, fake_post_to_groq)
|
|
327
|
-
with pytest.raises(RuntimeError):
|
|
328
|
-
groq_bugteam.call_groq_with_fallback("k", [], 0.0, 100)
|
|
329
|
-
assert attempted_models == [groq_bugteam.GROQ_PRIMARY_MODEL]
|
|
330
|
-
|
|
331
|
-
def test_413_falls_back_to_secondary_model(self, monkeypatch):
|
|
332
|
-
attempted_models: list[str] = []
|
|
333
|
-
|
|
334
|
-
def fake_post_to_groq(api_key, model, messages, temperature, max_completion_tokens):
|
|
335
|
-
attempted_models.append(model)
|
|
336
|
-
if model == groq_bugteam.GROQ_PRIMARY_MODEL:
|
|
337
|
-
raise urllib.error.HTTPError(
|
|
338
|
-
url="x", code=413, msg="payload too large", hdrs=None, fp=None
|
|
339
|
-
)
|
|
340
|
-
return "ok-content"
|
|
341
|
-
|
|
342
|
-
self._install_fake_transport(monkeypatch, fake_post_to_groq)
|
|
343
|
-
result = groq_bugteam.call_groq_with_fallback("k", [], 0.0, 100)
|
|
344
|
-
assert result.model == groq_bugteam.GROQ_FALLBACK_MODEL
|
|
345
|
-
assert attempted_models[0] == groq_bugteam.GROQ_PRIMARY_MODEL
|
|
346
|
-
assert groq_bugteam.GROQ_FALLBACK_MODEL in attempted_models
|
|
347
|
-
|
|
348
|
-
def test_recoverable_error_retries_same_model_then_falls_back(self, monkeypatch):
|
|
349
|
-
call_log: list[str] = []
|
|
350
|
-
|
|
351
|
-
def fake_post_to_groq(api_key, model, messages, temperature, max_completion_tokens):
|
|
352
|
-
call_log.append(model)
|
|
353
|
-
raise urllib.error.HTTPError(
|
|
354
|
-
url="x", code=503, msg="service unavailable", hdrs=None, fp=None
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
self._install_fake_transport(monkeypatch, fake_post_to_groq)
|
|
358
|
-
with pytest.raises(RuntimeError):
|
|
359
|
-
groq_bugteam.call_groq_with_fallback("k", [], 0.0, 100)
|
|
360
|
-
assert call_log.count(groq_bugteam.GROQ_PRIMARY_MODEL) > 1
|
|
361
|
-
assert groq_bugteam.GROQ_FALLBACK_MODEL in call_log
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
class TestCoerceIndexesToIntSet:
|
|
365
|
-
def test_coerces_string_indexes_to_ints(self):
|
|
366
|
-
assert groq_bugteam.coerce_indexes_to_int_set(["0", "2"]) == {0, 2}
|
|
367
|
-
|
|
368
|
-
def test_drops_non_numeric_entries(self):
|
|
369
|
-
assert groq_bugteam.coerce_indexes_to_int_set(["0", "abc", None, 1]) == {0, 1}
|
|
370
|
-
|
|
371
|
-
def test_handles_none_input(self):
|
|
372
|
-
assert groq_bugteam.coerce_indexes_to_int_set(None) == set()
|
|
373
|
-
|
|
374
|
-
def test_handles_empty_list(self):
|
|
375
|
-
assert groq_bugteam.coerce_indexes_to_int_set([]) == set()
|
|
376
|
-
|
|
377
|
-
def test_accepts_already_int_values(self):
|
|
378
|
-
assert groq_bugteam.coerce_indexes_to_int_set([0, 1, 2]) == {0, 1, 2}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
class TestCoerceSkippedEntries:
|
|
382
|
-
def test_coerces_string_finding_index_to_int(self):
|
|
383
|
-
assert groq_bugteam.coerce_skipped_entries(
|
|
384
|
-
[{"finding_index": "3", "reason": "x"}]
|
|
385
|
-
) == {3: "x"}
|
|
386
|
-
|
|
387
|
-
def test_drops_entries_without_parseable_index(self):
|
|
388
|
-
assert groq_bugteam.coerce_skipped_entries(
|
|
389
|
-
[{"finding_index": "not-a-number", "reason": "x"}]
|
|
390
|
-
) == {}
|
|
391
|
-
|
|
392
|
-
def test_drops_entries_missing_finding_index(self):
|
|
393
|
-
assert groq_bugteam.coerce_skipped_entries([{"reason": "orphan"}]) == {}
|
|
394
|
-
|
|
395
|
-
def test_defaults_reason_to_empty_string(self):
|
|
396
|
-
assert groq_bugteam.coerce_skipped_entries([{"finding_index": 1}]) == {1: ""}
|
|
397
|
-
|
|
398
|
-
def test_handles_none_input(self):
|
|
399
|
-
assert groq_bugteam.coerce_skipped_entries(None) == {}
|
|
400
|
-
|
|
401
|
-
def test_treats_none_reason_as_empty_string(self):
|
|
402
|
-
assert groq_bugteam.coerce_skipped_entries(
|
|
403
|
-
[{"finding_index": 1, "reason": None}]
|
|
404
|
-
) == {1: ""}
|
|
405
|
-
|
|
406
|
-
def test_stringifies_non_string_reasons(self):
|
|
407
|
-
assert groq_bugteam.coerce_skipped_entries(
|
|
408
|
-
[{"finding_index": 1, "reason": 42}]
|
|
409
|
-
) == {1: "42"}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
class TestBuildFixUserMessage:
|
|
413
|
-
def test_embeds_file_content_byte_for_byte_with_trailing_newline(self):
|
|
414
|
-
original_content = "line1\nline2\n"
|
|
415
|
-
message = groq_bugteam.build_fix_user_message("some.py", original_content, findings_block="[]")
|
|
416
|
-
assert original_content in message
|
|
417
|
-
assert "line2\n</current_file_contents>" in message
|
|
418
|
-
|
|
419
|
-
def test_embeds_file_content_byte_for_byte_without_trailing_newline(self):
|
|
420
|
-
original_content = "line1\nline2"
|
|
421
|
-
message = groq_bugteam.build_fix_user_message("some.py", original_content, findings_block="[]")
|
|
422
|
-
assert f"{original_content}\n</current_file_contents>" in message
|
|
423
|
-
assert "line2\n\n</current_file_contents>" not in message
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
class TestShouldWriteFixedFile:
|
|
427
|
-
def test_does_not_write_when_no_finding_applied(self):
|
|
428
|
-
assert groq_bugteam.should_write_fixed_file(
|
|
429
|
-
applied_indexes=set(),
|
|
430
|
-
updated_content="new",
|
|
431
|
-
current_content="old",
|
|
432
|
-
) is False
|
|
433
|
-
|
|
434
|
-
def test_does_not_write_when_content_unchanged(self):
|
|
435
|
-
assert groq_bugteam.should_write_fixed_file(
|
|
436
|
-
applied_indexes={0},
|
|
437
|
-
updated_content="same",
|
|
438
|
-
current_content="same",
|
|
439
|
-
) is False
|
|
440
|
-
|
|
441
|
-
def test_writes_when_finding_applied_and_content_changed(self):
|
|
442
|
-
assert groq_bugteam.should_write_fixed_file(
|
|
443
|
-
applied_indexes={0},
|
|
444
|
-
updated_content="new",
|
|
445
|
-
current_content="old",
|
|
446
|
-
) is True
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
class TestPreserveTrailingNewline:
|
|
450
|
-
def test_adds_trailing_newline_when_original_had_one(self):
|
|
451
|
-
preserved = groq_bugteam.preserve_trailing_newline(
|
|
452
|
-
original="line1\nline2\n", updated="line1\nfixed2"
|
|
453
|
-
)
|
|
454
|
-
assert preserved == "line1\nfixed2\n"
|
|
455
|
-
|
|
456
|
-
def test_strips_trailing_newline_when_original_lacked_one(self):
|
|
457
|
-
preserved = groq_bugteam.preserve_trailing_newline(
|
|
458
|
-
original="no newline", updated="fixed content\n"
|
|
459
|
-
)
|
|
460
|
-
assert preserved == "fixed content"
|
|
461
|
-
|
|
462
|
-
def test_keeps_matching_form_unchanged(self):
|
|
463
|
-
assert (
|
|
464
|
-
groq_bugteam.preserve_trailing_newline(original="x\n", updated="y\n")
|
|
465
|
-
== "y\n"
|
|
466
|
-
)
|
|
467
|
-
assert (
|
|
468
|
-
groq_bugteam.preserve_trailing_newline(original="x", updated="y") == "y"
|
|
469
|
-
)
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
class TestIsSafeRelativePath:
|
|
473
|
-
def test_rejects_absolute_posix_path(self):
|
|
474
|
-
assert groq_bugteam.is_safe_relative_path("/etc/passwd") is False
|
|
475
|
-
|
|
476
|
-
def test_rejects_parent_directory_escape(self):
|
|
477
|
-
assert groq_bugteam.is_safe_relative_path("../../etc/passwd") is False
|
|
478
|
-
|
|
479
|
-
def test_rejects_embedded_parent_reference(self):
|
|
480
|
-
assert groq_bugteam.is_safe_relative_path("src/../../etc/passwd") is False
|
|
481
|
-
|
|
482
|
-
def test_accepts_simple_relative_path(self):
|
|
483
|
-
assert groq_bugteam.is_safe_relative_path("src/foo.py") is True
|
|
484
|
-
|
|
485
|
-
def test_accepts_nested_relative_path(self):
|
|
486
|
-
assert groq_bugteam.is_safe_relative_path("packages/mod/scripts/foo.py") is True
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
class TestDecodeSubprocessStderr:
|
|
490
|
-
def test_decodes_bytes_input(self):
|
|
491
|
-
decoded = groq_bugteam.decode_subprocess_stderr(b"fatal: broken")
|
|
492
|
-
assert decoded == "fatal: broken"
|
|
493
|
-
|
|
494
|
-
def test_returns_str_input_unchanged(self):
|
|
495
|
-
assert groq_bugteam.decode_subprocess_stderr("fatal: broken") == "fatal: broken"
|
|
496
|
-
|
|
497
|
-
def test_handles_none_input(self):
|
|
498
|
-
assert groq_bugteam.decode_subprocess_stderr(None) == ""
|
|
499
|
-
|
|
500
|
-
def test_replaces_undecodable_bytes(self):
|
|
501
|
-
decoded = groq_bugteam.decode_subprocess_stderr(b"\xff\xfe broken")
|
|
502
|
-
assert "broken" in decoded
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
class TestRunPipelineRefusals:
|
|
506
|
-
def test_rejects_missing_api_key(self, monkeypatch, tmp_path):
|
|
507
|
-
monkeypatch.delenv("GROQ_API_KEY", raising=False)
|
|
508
|
-
monkeypatch.setattr(
|
|
509
|
-
groq_bugteam_dotenv,
|
|
510
|
-
"claude_dev_env_dotenv_path",
|
|
511
|
-
lambda: tmp_path / "missing.env",
|
|
512
|
-
)
|
|
513
|
-
result = groq_bugteam.run_pipeline({"diff": "anything"})
|
|
514
|
-
assert "error" in result
|
|
515
|
-
assert "GROQ_API_KEY" in result["error"]
|
|
516
|
-
|
|
517
|
-
def test_rejects_empty_diff(self, monkeypatch):
|
|
518
|
-
monkeypatch.setenv("GROQ_API_KEY", "gsk_test_placeholder_value")
|
|
519
|
-
result = groq_bugteam.run_pipeline({"diff": " ", "files_content": {}})
|
|
520
|
-
assert "error" in result
|
|
521
|
-
assert "diff is empty" in result["error"]
|
|
522
|
-
|
|
523
|
-
def test_rejects_fixes_without_worktree(self, monkeypatch):
|
|
524
|
-
monkeypatch.setenv("GROQ_API_KEY", "gsk_test_placeholder_value")
|
|
525
|
-
result = groq_bugteam.run_pipeline(
|
|
526
|
-
{"diff": "some diff", "files_content": {"a.py": ""}, "apply_fixes": True}
|
|
527
|
-
)
|
|
528
|
-
assert "error" in result
|
|
529
|
-
assert "worktree_path" in result["error"]
|