feat(analyzers): add shellcheck analyzer for shell scripts#36
Conversation
Adds ShellAnalyzer that runs shellcheck --format=json on .sh/.bash files, maps severity levels (error->HIGH, warning->MEDIUM, info/style->LOW), and returns Finding objects. Wired into _get_analyzers() in __main__.py. 9 tests covering parsing, severity mapping, exclude paths, edge cases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 800b2e7a42
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| SemgrepAnalyzer(), # opt-in: requires ARBITER_ENABLE_SEMGREP=1 | ||
| DeadCodeAnalyzer(), | ||
| DuplicationAnalyzer(), | ||
| ShellAnalyzer(), |
There was a problem hiding this comment.
Skip ShellAnalyzer in Python-only diff scoring
Registering ShellAnalyzer() in _get_analyzers() makes commit/diff scoring paths invoke shellcheck even when scoring only changed *.py files. In diff_analyzer._run_analyzers_on_files, each analyzer is run once per changed Python file and findings are then filtered to Python targets, so shellcheck findings cannot contribute to the score but still incur repeated scans/subprocess calls; on repos with shellcheck installed this creates a significant avoidable runtime regression for score_commit/score_diff and historical commit scoring.
Useful? React with 👍 / 👎.
| shell_files: list[Path] = [] | ||
| for pattern in ("**/*.sh", "**/*.bash"): | ||
| for f in repo_path.glob(pattern): | ||
| if not any(part in excludes for part in f.relative_to(repo_path).parts): |
There was a problem hiding this comment.
Normalize exclude paths before part-wise matching
The exclude check compares each path component against raw exclude_paths, which breaks common exclude values like tests/ or nested paths like scripts/ci (Path.parts yields tests, scripts, ci). That means shell files inside user-excluded directories are still sent to shellcheck, producing findings from paths users explicitly asked to skip.
Useful? React with 👍 / 👎.
Phase 2 polyglot expansion: - ESLint analyzer for JavaScript/TypeScript (eslint JSON output) - Go analyzer via golangci-lint (JSON output, go.mod detection) - Rust analyzer via cargo clippy (NDJSON output, Cargo.toml detection) - Language detection module with 25+ languages, `languages` CLI subcommand - All analyzers follow the same Analyzer interface pattern Combined with shellcheck (#36), Arbiter now scores Python, Shell, JavaScript, TypeScript, Go, and Rust. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 2 polyglot expansion: - ESLint analyzer for JavaScript/TypeScript (eslint JSON output) - Go analyzer via golangci-lint (JSON output, go.mod detection) - Rust analyzer via cargo clippy (NDJSON output, Cargo.toml detection) - Language detection module with 25+ languages, `languages` CLI subcommand - All analyzers follow the same Analyzer interface pattern Combined with shellcheck (#36), Arbiter now scores Python, Shell, JavaScript, TypeScript, Go, and Rust. Co-authored-by: Claude (agent) <claude@agents.hummbl.io> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
ShellAnalyzerthat runsshellcheck --format=jsonon.sh/.bashfiles and maps findings to Arbiter'sFindingdataclass_get_analyzers()in__main__.pyalongside existing analyzersTest plan
tests/test_shell_analyzer.pycovering:is_available()checksshutil.which("shellcheck").bashextension files are discovered🤖 Generated with Claude Code