Skip to content

External tool adapters

CodeMore's native catalog (48 rules) covers TypeScript / JavaScript / SQL / Python. Polyglot repos sometimes need broader coverage. The --external-tools flag wraps four industry-standard linters and routes their output through the same codemore-report.json schema — so the agent loop (apply_fix / validate_fix / suggest_fix) keeps working uniformly.

Quick start

codemore scan ./my-repo --external-tools ruff
codemore scan ./my-repo --external-tools ruff,biome
codemore scan ./my-repo --external-tools all   # ruff + golangci + clippy + biome

Off by default. None of the adapters run unless --external-tools is passed.

Silent skip on missing binary. If a tool isn't installed on $PATH, CodeMore prints a one-line install hint to stderr and continues with whatever IS installed. The scan never errors because of a missing external tool.

Supported tools

ToolLanguagesInstallOutput mode
ruffPythonpip install ruffruff check --output-format json
golangci-lintGoinstallergolangci-lint run --out-format json
clippyRustrustup component add clippycargo clippy --message-format=json
biomeTypeScript / JavaScript / JSONnpm i -g @biomejs/biomebiome check --reporter=json

Rule-id namespace

External findings appear in your report with the rule id prefixed ext:<tool>:<original-rule-id>:

SourceExample rule id
ruffext:ruff:F401 (unused import), ext:ruff:S102 (exec usage)
golangciext:golangci:errcheck, ext:golangci:gosec
clippyext:clippy:needless_collect, ext:clippy:useless_format
biomeext:biome:lint/correctness/noUnusedVariables, ext:biome:lint/security/noDangerouslySetInnerHtml

Native rule ids stay clean (e.g. vibe-auth-bola, core-quality-empty-except). The namespace makes it trivial to filter agent fixes to native-only or external-only via --packs (native packs only) or by inspecting id.startsWith('ext:') in your downstream pipeline.

Severity translation

Each adapter has a static map from the tool's native rule category to our canonical severity. Examples:

SourceNativeCodeMore
ruff S-rules (bandit/security)warningBLOCKER
ruff F-rules (pyflakes)warningMAJOR
ruff E/W-rules (pycodestyle style)warningMINOR
golangci-lint gosecwarningBLOCKER
golangci-lint errcheck / staticcheckwarningMAJOR
clippy errorerrorBLOCKER
clippy warningwarningMAJOR
biome lint/security/*errorBLOCKER
biome lint/correctness/*errorMAJOR
biome lint/style/*warningMINOR

Confidence is fixed at 0.8 for every external finding — we trust the tools but not blindly.

Suppression interop

External-tool findings are suppressible the same way as native rules:

# Reason: this print is the CLI's user-facing output.
# codemore-ignore-next-line: ext:ruff:T201
print("scan complete")
// Reason: errcheck false positive on a defer-Close idiom.
// codemore-ignore-next-line: ext:golangci:errcheck
defer file.Close()

The suppression filter is in our pipeline, not the external tool — so even if ruff doesn't understand our comment format, the suppression still removes the finding from the final report.

How adapters work

Each adapter is a thin spawn-and-parse wrapper:

  1. spawn(<binary>, [...args], { cwd: root }).
  2. Buffer stdout. Tolerate stderr.
  3. On exit:

- Code 0 = no findings (normal). - Code 1 = findings present (normal for tools that exit 1 on lint hits). - Higher codes = error → log to stderr, return empty result. 4. Parse stdout as JSON / NDJSON. 5. Translate each entry into ReportIssue with ext:<tool>:<code> id, severity-mapped, confidence 0.8, deep-link citation.

Sources: - daemon/external/index.ts — dispatcher. - daemon/external/ruff.ts — Python. - daemon/external/golangci.ts — Go. - daemon/external/clippy.ts — Rust. - daemon/external/biome.ts — TS/JS/JSON.

Each adapter is ~150 lines, predominantly the spawn lifecycle. Adding a new adapter for a 5th tool follows the same template.

Performance

All requested tools run in parallel with the native walk. The dispatcher awaits them at the merge point. On a 100-file project, expect:

  • Native pack: ~100ms
  • Ruff: ~50ms (Rust binary, very fast)
  • golangci-lint: 5-30s (cold cache, depends on linter set)
  • clippy: 10-60s (first run includes incremental compile)
  • biome: ~200ms

Tools that exceed the per-tool timeoutMs (default 60s) are killed with a single [tool] timeout notice.

When NOT to enable external tools

The wedge of CodeMore is "the static analyzer your AI agent reads, for vibe-coded apps." The native catalog is the IP. External tools add coverage when you have a polyglot repo OR want stylistic coverage we don't ship natively. If your project is pure TS/JS/SQL/Python, the native catalog covers it and you don't need --external-tools.

Known limitations (v1)

  • Biome's span-to-line conversion reads each file from disk on demand (cached). On 1000+ findings this adds ~50ms; not yet streamed.
  • Clippy requires a Cargo.toml at the scan root or a workspace member — running on a non-Rust directory produces zero findings with a no Cargo.toml in scan root notice.
  • Golangci-lint's per-linter config is honored as long as the user maintains their own .golangci.yml in the scan root.
  • We don't yet stream tool output — all findings are buffered before they're merged. Long-running scans may feel quiet for tens of seconds.