claude-dev-env 1.28.0 → 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 -12
- 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,118 @@
|
|
|
1
|
+
"""Centralized configuration for groq_bugteam.py.
|
|
2
|
+
|
|
3
|
+
All module-level scalar constants live here per the repo's ``constants-location``
|
|
4
|
+
rule. Import into the script and bind local aliases where needed.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
GROQ_API_ENDPOINT = "https://api.groq.com/openai/v1/chat/completions"
|
|
8
|
+
GROQ_PRIMARY_MODEL = "llama-3.3-70b-versatile"
|
|
9
|
+
GROQ_FALLBACK_MODEL = "llama-3.1-8b-instant"
|
|
10
|
+
GROQ_REQUEST_TIMEOUT_SECONDS = 90
|
|
11
|
+
GROQ_AUDIT_MAX_COMPLETION_TOKENS = 2500
|
|
12
|
+
GROQ_FIX_MAX_COMPLETION_TOKENS = 8000
|
|
13
|
+
GROQ_AUDIT_TEMPERATURE = 0.1
|
|
14
|
+
GROQ_FIX_TEMPERATURE = 0.1
|
|
15
|
+
|
|
16
|
+
MAXIMUM_FILE_CONTENT_CHARACTERS = 60000
|
|
17
|
+
MAXIMUM_DIFF_CHARACTERS = 80000
|
|
18
|
+
MAXIMUM_FINDINGS_PER_PR = 20
|
|
19
|
+
|
|
20
|
+
GROQ_RETRY_BACKOFF_SECONDS = (2, 4, 8)
|
|
21
|
+
|
|
22
|
+
REVIEW_BODY_HEADER_TEMPLATE = "## groq-bugteam audit: {p0} P0 / {p1} P1 / {p2} P2"
|
|
23
|
+
NO_FINDINGS_REVIEW_BODY = (
|
|
24
|
+
"## groq-bugteam audit: clean\n\n"
|
|
25
|
+
"Groq ({model}) reviewed the diff against categories A-J and found no issues."
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
AUDIT_SYSTEM_PROMPT = """You are an adversarial code reviewer auditing a pull request diff.
|
|
29
|
+
|
|
30
|
+
Inspect ONLY lines added or modified in the diff. Pre-existing code on
|
|
31
|
+
untouched lines is out of scope. Cite file:line for every finding -- the line
|
|
32
|
+
number MUST refer to the NEW side of the diff (post-change line number).
|
|
33
|
+
|
|
34
|
+
Investigate these ten categories. Skip a category silently when you find
|
|
35
|
+
nothing; do not emit verified-clean entries.
|
|
36
|
+
|
|
37
|
+
A. API contract verification (signatures, return types, async/await)
|
|
38
|
+
B. Selector / query / engine compatibility
|
|
39
|
+
C. Resource cleanup and lifecycle (file handles, connections, processes, locks)
|
|
40
|
+
D. Variable scoping, ordering, unbound references
|
|
41
|
+
E. Dead code, unused imports, dead parameters
|
|
42
|
+
F. Silent failures (catch-all excepts, unconditional success returns)
|
|
43
|
+
G. Off-by-one, bounds, integer overflow
|
|
44
|
+
H. Security boundaries (injection, path traversal, auth bypass, secret leakage)
|
|
45
|
+
I. Concurrency hazards (race conditions, missing awaits, shared mutable state)
|
|
46
|
+
J. Magic values and configuration drift
|
|
47
|
+
|
|
48
|
+
Severity rubric:
|
|
49
|
+
- P0: crashes, data loss, security breach, broken production invariant
|
|
50
|
+
- P1: incorrect behavior, resource leak, regression on common path
|
|
51
|
+
- P2: style, dead code, minor DRY violations
|
|
52
|
+
|
|
53
|
+
Respond with JSON only -- no prose outside the JSON object. Shape:
|
|
54
|
+
|
|
55
|
+
{
|
|
56
|
+
"findings": [
|
|
57
|
+
{
|
|
58
|
+
"severity": "P0" | "P1" | "P2",
|
|
59
|
+
"category": "A" | ... | "J",
|
|
60
|
+
"file": "relative path from repo root",
|
|
61
|
+
"line": int,
|
|
62
|
+
"title": "one-line summary",
|
|
63
|
+
"description": "2-3 sentences with concrete trace; reference the diff line"
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
Cap findings at the top 20 most important. If no bugs, return {"findings": []}.
|
|
69
|
+
|
|
70
|
+
FILE-TYPE GUARDRAILS. Do not apply code-specific categories to non-code files:
|
|
71
|
+
- JSON / YAML / TOML / INI / Markdown / plain text: only flag CATEGORY H
|
|
72
|
+
(security) or J (real configuration drift that breaks something). Do NOT
|
|
73
|
+
flag E (dead code), A (API contract), or D (variable scoping) on these
|
|
74
|
+
files. A version string in package.json is NOT a magic value.
|
|
75
|
+
- Lockfiles and auto-generated manifests: skip entirely.
|
|
76
|
+
- Changelogs: skip entirely.
|
|
77
|
+
|
|
78
|
+
QUALITY BAR. Only emit a finding if you can point at the specific line in
|
|
79
|
+
the DIFF that introduced it AND describe the failure mode concretely. Skip
|
|
80
|
+
speculative style complaints.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
FIX_SYSTEM_PROMPT = """You are a focused bug-fixer. You receive one file's full
|
|
84
|
+
contents plus a list of findings that apply to that file. Produce the full
|
|
85
|
+
corrected file contents.
|
|
86
|
+
|
|
87
|
+
Rules:
|
|
88
|
+
1. Address every listed finding. If a finding is not actionable, leave the
|
|
89
|
+
file unchanged and explain in the ``skipped`` array.
|
|
90
|
+
2. Modify ONLY the lines required to address the findings. Preserve all other
|
|
91
|
+
code exactly -- comments, whitespace, blank lines, import order.
|
|
92
|
+
3. Do not add new comments or docstrings unless the finding explicitly asks
|
|
93
|
+
for one.
|
|
94
|
+
4. Do not introduce new imports unless required by a fix.
|
|
95
|
+
5. Output JSON only. Shape:
|
|
96
|
+
|
|
97
|
+
{
|
|
98
|
+
"updated_content": "full corrected file contents",
|
|
99
|
+
"applied_finding_indexes": [0, 2, ...],
|
|
100
|
+
"skipped": [
|
|
101
|
+
{"finding_index": 1, "reason": "one-line reason"}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
When you cannot produce a safe fix, set ``updated_content`` equal to the input
|
|
106
|
+
BYTE-FOR-BYTE (same whitespace, same trailing newline, same indentation) and
|
|
107
|
+
list every finding in ``skipped``. NEVER reformat or re-indent a file whose
|
|
108
|
+
findings you are skipping.
|
|
109
|
+
|
|
110
|
+
If ``applied_finding_indexes`` is empty, ``updated_content`` MUST equal the
|
|
111
|
+
input exactly.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
JSON_INDENT_SPACES = 2
|
|
115
|
+
PIPELINE_FAILURE_EXIT_CODE = 2
|
|
116
|
+
|
|
117
|
+
TEXT_CLAMP_HEAD_PARTS = 1
|
|
118
|
+
TEXT_CLAMP_TOTAL_PARTS = 2
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Existence and coherence checks for groq_bugteam_config.
|
|
2
|
+
|
|
3
|
+
These are not business-behavior tests — the config module is constants only —
|
|
4
|
+
but the tdd_enforcer hook requires a co-located test file, and the readability
|
|
5
|
+
rule wants every constant referenced by at least two callers. The checks below
|
|
6
|
+
keep those invariants observable and fail loudly if someone edits the config
|
|
7
|
+
into an inconsistent state.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import importlib.util
|
|
13
|
+
import pathlib
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _load_config_module():
|
|
18
|
+
module_path = pathlib.Path(__file__).parent / "groq_bugteam_config.py"
|
|
19
|
+
module_spec = importlib.util.spec_from_file_location(
|
|
20
|
+
"groq_bugteam_config", module_path
|
|
21
|
+
)
|
|
22
|
+
loaded_module = importlib.util.module_from_spec(module_spec)
|
|
23
|
+
sys.modules["groq_bugteam_config"] = loaded_module
|
|
24
|
+
module_spec.loader.exec_module(loaded_module)
|
|
25
|
+
return loaded_module
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
groq_bugteam_config = _load_config_module()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_primary_and_fallback_models_are_different():
|
|
32
|
+
assert (
|
|
33
|
+
groq_bugteam_config.GROQ_PRIMARY_MODEL
|
|
34
|
+
!= groq_bugteam_config.GROQ_FALLBACK_MODEL
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_endpoint_is_https():
|
|
39
|
+
assert groq_bugteam_config.GROQ_API_ENDPOINT.startswith("https://")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_json_indent_spaces_is_positive_integer():
|
|
43
|
+
assert isinstance(groq_bugteam_config.JSON_INDENT_SPACES, int)
|
|
44
|
+
assert groq_bugteam_config.JSON_INDENT_SPACES > 0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_pipeline_failure_exit_code_is_non_zero_and_non_one():
|
|
48
|
+
# Reserve 0 for success and 1 for "bad stdin" — failure code must distinguish.
|
|
49
|
+
assert groq_bugteam_config.PIPELINE_FAILURE_EXIT_CODE not in (0, 1)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_text_clamp_head_parts_fits_within_total():
|
|
53
|
+
assert (
|
|
54
|
+
0
|
|
55
|
+
< groq_bugteam_config.TEXT_CLAMP_HEAD_PARTS
|
|
56
|
+
< groq_bugteam_config.TEXT_CLAMP_TOTAL_PARTS
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_request_timeout_is_generous_enough_for_cold_start():
|
|
61
|
+
# Groq free-tier cold-start latency has been observed at 60s+; anything
|
|
62
|
+
# under 60 risks killing healthy requests mid-response.
|
|
63
|
+
assert groq_bugteam_config.GROQ_REQUEST_TIMEOUT_SECONDS >= 60
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_fix_budget_exceeds_audit_budget():
|
|
67
|
+
# Fix responses return full file contents; audit responses return just
|
|
68
|
+
# findings JSON — fix must have strictly more headroom.
|
|
69
|
+
assert (
|
|
70
|
+
groq_bugteam_config.GROQ_FIX_MAX_COMPLETION_TOKENS
|
|
71
|
+
> groq_bugteam_config.GROQ_AUDIT_MAX_COMPLETION_TOKENS
|
|
72
|
+
)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# groq_bugteam
|
|
2
|
+
|
|
3
|
+
Single-pass adaptation of the [`bugteam` skill](../skills/bugteam/SKILL.md) that replaces the Claude Code agent-team orchestration with direct calls to [Groq](https://console.groq.com)'s chat completions API. No orchestrated team, no multi-loop convergence, no `TeamCreate`: one audit call, one fix call, one commit-and-push per PR.
|
|
4
|
+
|
|
5
|
+
Lives at `packages/claude-dev-env/scripts/groq_bugteam.py`. Stateless, PII-free, env-driven.
|
|
6
|
+
|
|
7
|
+
## When to reach for this
|
|
8
|
+
|
|
9
|
+
`/bugteam` requires `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` plus an Anthropic-side environment that will sign commits on PR head branches. When either is missing — for example, a CI runner, a personal dev box, or any non-Claude-Code shell — this script gives you the audit/fix loop without the orchestration.
|
|
10
|
+
|
|
11
|
+
When you already have `/bugteam` available, prefer it: the clean-room agent isolation + 10-loop convergence produces much lower false-positive rates than a single Groq pass.
|
|
12
|
+
|
|
13
|
+
## Pipeline
|
|
14
|
+
|
|
15
|
+
1. Read PR metadata + unified diff + file contents from stdin (JSON).
|
|
16
|
+
2. POST one audit request to Groq with the diff and files. Parse findings as JSON.
|
|
17
|
+
3. Group findings by file; for each file, POST one fix request. Parse `updated_content`.
|
|
18
|
+
4. Write files that had at least one applied finding, `git add`, `git commit`, `git push HEAD:<head_ref>`.
|
|
19
|
+
5. Emit JSON on stdout: findings, fix outcomes, commit SHA, review body, audit/fix model.
|
|
20
|
+
|
|
21
|
+
The caller posts the review to GitHub. This script does not touch the GitHub API.
|
|
22
|
+
|
|
23
|
+
## Stdin schema
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"pr_number": 123,
|
|
28
|
+
"owner": "someone",
|
|
29
|
+
"repo": "some-repo",
|
|
30
|
+
"base_ref": "main",
|
|
31
|
+
"head_ref": "feat/some-branch",
|
|
32
|
+
"diff": "<full `git diff origin/<base>...HEAD` output>",
|
|
33
|
+
"files_content": {"path/to/file.py": "<current file contents>"},
|
|
34
|
+
"worktree_path": "/abs/path/to/git/worktree/on/head_ref",
|
|
35
|
+
"apply_fixes": true
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Stdout schema
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"findings": [
|
|
44
|
+
{"severity": "P0|P1|P2", "category": "A..J", "file": "...", "line": 42,
|
|
45
|
+
"title": "...", "description": "..."}
|
|
46
|
+
],
|
|
47
|
+
"fix_outcomes": [
|
|
48
|
+
{"finding_index": 0, "status": "fixed|skipped|not_addressed|fix_call_failed",
|
|
49
|
+
"reason": "only when skipped/fix_call_failed"}
|
|
50
|
+
],
|
|
51
|
+
"commit_sha": "abc123...",
|
|
52
|
+
"review_body": "## groq-bugteam audit: 1 P0 / 2 P1 / 0 P2\n...",
|
|
53
|
+
"audit_model": "llama-3.3-70b-versatile",
|
|
54
|
+
"fix_model": "llama-3.3-70b-versatile"
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Required environment
|
|
59
|
+
|
|
60
|
+
- `GROQ_API_KEY` — from https://console.groq.com/keys. Free tier is enough for a single PR.
|
|
61
|
+
- `git` on PATH, configured to push to the target remote.
|
|
62
|
+
- Python 3.10+. No external deps (stdlib `urllib.request` only).
|
|
63
|
+
|
|
64
|
+
## Minimal invocation
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Assumes you already have a git worktree checked out to the PR head branch.
|
|
68
|
+
export GROQ_API_KEY=gsk_...
|
|
69
|
+
|
|
70
|
+
python3 - <<'EOF' | python3 packages/claude-dev-env/scripts/groq_bugteam.py
|
|
71
|
+
import json, subprocess, pathlib
|
|
72
|
+
worktree = pathlib.Path("/tmp/pr-123-worktree")
|
|
73
|
+
diff = subprocess.check_output(["git","-C",str(worktree),"diff","origin/main...HEAD"], text=True)
|
|
74
|
+
changed = subprocess.check_output(["git","-C",str(worktree),"diff","--name-only","origin/main...HEAD"], text=True).strip().split("\n")
|
|
75
|
+
files = {p: (worktree/p).read_text() for p in changed if (worktree/p).is_file()}
|
|
76
|
+
print(json.dumps({
|
|
77
|
+
"pr_number": 123, "owner": "someone", "repo": "some-repo",
|
|
78
|
+
"base_ref": "main", "head_ref": "feat/some-branch",
|
|
79
|
+
"diff": diff, "files_content": files,
|
|
80
|
+
"worktree_path": str(worktree), "apply_fixes": True,
|
|
81
|
+
}))
|
|
82
|
+
EOF
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Caller responsibilities
|
|
86
|
+
|
|
87
|
+
Because this script is intentionally narrow, the caller handles:
|
|
88
|
+
|
|
89
|
+
- **Worktree setup.** `git worktree add -f -B <head_ref> <path> origin/<head_ref>`.
|
|
90
|
+
- **File filtering.** Skip autogenerated files (CHANGELOG.md, lockfiles) and files >40KB to stay under Groq's free-tier per-request TPM limit. Chunk the call file-by-file when the whole-PR call hits 413.
|
|
91
|
+
- **Commit signing.** If your environment enforces signing, set `git config commit.gpgsign false` in the worktree or provide a signing key the hook accepts.
|
|
92
|
+
- **Finding triage.** Groq's single-pass audit produces false positives — test files flagged for code-rules that exempt them, JSON configs flagged for "path traversal," version bumps flagged as "magic values." The caller should filter before posting reviews. See the filter heuristics that shipped with this repo's first live run (`claude/groq-bugteam-prs-S9rwU`): drop test files, drop JSON/YAML/doc files for non-security categories, dedupe `(file, line, category)`, drop chunked-mode P2s, cap at top 10-15 per PR.
|
|
93
|
+
- **Posting PR reviews.** The script emits `review_body` but does not POST. Use `gh api` or the GitHub MCP review-write endpoint.
|
|
94
|
+
|
|
95
|
+
## Known limitations vs `/bugteam`
|
|
96
|
+
|
|
97
|
+
| `/bugteam` | `groq_bugteam.py` |
|
|
98
|
+
|---|---|
|
|
99
|
+
| Fresh-context audit subagent each loop | Single Groq call, no re-audit |
|
|
100
|
+
| Separate fix subagent (clean-room) | Same Groq call stream (state bleeds) |
|
|
101
|
+
| 10-loop convergence with cap | One audit, one fix, done |
|
|
102
|
+
| `code-quality-agent` + `clean-coder` models | `llama-3.3-70b-versatile` (+ 8b fallback on 413) |
|
|
103
|
+
| Full CODE_RULES gate before every audit | No gate — caller's responsibility |
|
|
104
|
+
| Posts per-finding inline review comments anchored to diff lines | Single review body with findings listed as markdown |
|
|
105
|
+
| Rewrites PR body cumulatively | Does not touch PR body |
|
|
106
|
+
|
|
107
|
+
Expect 2-5x more noise per finding than `/bugteam`. The caller's filter step is not optional.
|
|
108
|
+
|
|
109
|
+
## Free-tier rate limits
|
|
110
|
+
|
|
111
|
+
As of the live run on 2026-04-22 the Groq free tier enforces:
|
|
112
|
+
|
|
113
|
+
- `llama-3.3-70b-versatile`: 12,000 tokens/minute, 1,000 requests/day.
|
|
114
|
+
- `llama-3.1-8b-instant`: 6,000 tokens/minute, 14,400 requests/day.
|
|
115
|
+
|
|
116
|
+
The script falls back to the 8b model on HTTP 413 (Groq returns 413 with `type: tokens` when the request TPM would exceed the cap). Most PRs >40KB of diff-plus-context will hit the cap. The caller can work around this by chunking: invoke the script once per changed file with `apply_fixes: false` and combine findings, or upgrade to a paid tier.
|
|
117
|
+
|
|
118
|
+
## Tests
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
cd packages/claude-dev-env/scripts
|
|
122
|
+
python3 -m pytest test_groq_bugteam.py -v
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The test suite covers the pure-logic helpers (`clamp_text`, `parse_json_object`, `normalize_findings`, `group_findings_by_file`, `build_review_body`, `should_write_fixed_file`, `preserve_trailing_newline`, `is_safe_relative_path`, `decode_subprocess_stderr`, `build_fix_user_message`, HTTP error classification, pipeline refusals) and the co-located config invariants. Network calls to Groq and filesystem/git side effects are not unit-tested; exercise them with a live run.
|
|
126
|
+
|
|
127
|
+
## Why this exists
|
|
128
|
+
|
|
129
|
+
The jl-cmd/claude-code-config `/bugteam` skill is excellent when Claude Code is the runtime. When it isn't — CI, cron, a dev box — you still want the audit/fix pattern. This script is the minimum-viable port.
|