configuration

This guide covers all configuration options for Lintro and the underlying tools it integrates. Learn how to customize behavior, set tool-specific options, and optimize

Configuration Guide

This guide covers all configuration options for Lintro and the underlying tools it integrates. Learn how to customize behavior, set tool-specific options, and optimize Lintro for your project.

TL;DR: Lintro uses your existing tool configs (.prettierrc, pyproject.toml, etc.) automatically. It only provides fallback defaults when no native config exists. Use enforce.line_length to ensure consistent settings across all tools via CLI injection.

Configuration Model: 5-Tier System

Lintro uses a clear 5-tier configuration model that separates concerns:

TierPurposeWhen Applied
executionWhat tools run and howAlways
enforceCross-cutting settings (line_length, target_python)Always (via CLI flags)
defaultsFallback config when no native config existsOnly when no native config
toolsPer-tool enable/disable and config sourceAlways
aiAI-powered summaries and fixesWhen enabled + API key set

The five tiers above form the LintroConfig model’s core configuration story (lintro/config/lintro_config.py). Two additional optional sections — review (diff-review checklist) and score (health-score weights, see Health Score below) — configure specific commands rather than tool resolution.

Key Principles

  1. Native configs are respected by default - Tools use their own .prettierrc, pyproject.toml [tool.ruff], etc.
  2. enforce settings override via CLI flags - Line length and target Python are injected as CLI arguments to ensure consistency
  3. defaults provide fallbacks - Only used when a tool has no native config file
  4. Simple and transparent - Users know exactly which config is used

Tiered Configuration Flow

The configuration system works in a specific order:

  1. Execution Tier - Determines which tools run and in what order

    • enabled_tools: Empty list means all enabled tools run. An explicit named --tools list on the CLI bypasses this allowlist; default runs and --tools all remain filtered by it.
    • tool_order: Controls execution order (priority, alphabetical, or custom)
    • fail_fast: Whether to stop on first tool failure
    • parallel: Whether to run tools in parallel (default: true)
    • max_workers: Maximum parallel workers, 1-32 (default: CPU count)
    • auto_install_deps: Auto-install Node.js dependencies if missing, for the tools that need a project dependency tree (tsc, vue-tsc, svelte-check, astro-check). Unset by default, in which case Lintro falls back to container auto-detection (enabled inside containers, disabled otherwise)
  2. Enforce Tier - Cross-cutting settings injected as CLI flags

    • These settings override native configs via CLI arguments
    • Example: line_length: 88 becomes --line-length 88 for ruff/black
    • Applied to all tools that support the setting
  3. Defaults Tier - Fallback configuration when no native config exists

    • Only used if tool’s native config file is not found
    • Example: If .prettierrc doesn’t exist, use defaults.prettier
    • Generated as temporary config files when needed
  4. Tools Tier - Per-tool enable/disable, config source, and auto-install

    • enabled: Whether the tool is enabled
    • config_source: Optional explicit path to native config file
    • auto_install: Per-tool auto-install override (true/false/omit to inherit global)
  5. AI Tier - AI-powered summaries and fix suggestions (opt-in)

    • enabled: Whether AI features are active (default: false)
    • provider: AI provider to use (anthropic or openai)
    • Applied only when enabled and a valid API key is set

Configuration Resolution Example

For a tool like Prettier:

  1. Check if tool is enabled (tools.prettier.enabled)
  2. Check for native config (.prettierrc, .prettierrc.json, etc.)
  3. If native config found:
    • Use native config
    • Inject enforce.line_length as --print-width CLI flag
    • Ignore defaults.prettier
  4. If no native config found:
    • Generate temp config from defaults.prettier
    • Inject enforce.line_length as --print-width CLI flag
    • Use generated config

This ensures consistent behavior while respecting tool-specific configurations.

Lintro Configuration

Configuration File: .lintro-config.yaml

Create a .lintro-config.yaml in your project root:

# Tier 1: EXECUTION - What tools run and how
execution:
  enabled_tools: [] # Empty = all enabled tools run
  tool_order: priority # priority | alphabetical | [custom list]
  fail_fast: false
  parallel: true # Run tools in parallel (default: true)
  max_workers: 10 # Max parallel workers, 1-32 (default: CPU count)
  auto_install_deps: false # Auto-install Node.js deps if missing

# Tier 2: ENFORCE - Cross-cutting settings injected via CLI flags
# These OVERRIDE native configs for consistency
enforce:
  line_length: 88 # Injected as --line-length, --print-width, etc.
  target_python: py313 # Injected as --target-version

# Tier 3: DEFAULTS - Fallback config when NO native config exists
# Only used if tool's native config file is not found
defaults:
  prettier:
    semi: true
    singleQuote: true
  yamllint:
    extends: default

# Tier 4: TOOLS - Per-tool enable/disable, config source, and auto-install
tools:
  ruff:
    enabled: true
  prettier:
    enabled: true
    config_source: '.prettierrc' # Optional: explicit native config path
  tsc:
    auto_install: true # Override global auto_install for this tool only

Configuration Report Command

Use lintro config to view the current configuration status for all tools:

# View configuration report
lintro config

# Show detailed output including native configs
lintro config --verbose

# Output as JSON for scripting
lintro config --json

The config command shows:

  • Enforce settings: Central line_length, target_python
  • Tool execution order: Based on configured strategy (priority, alphabetical, or custom)
  • Per-tool configuration: Whether enabled, native config found
  • Defaults applied: Which tools are using fallback defaults

Health Score

lintro check computes a single, deterministic 0-100 health score that aggregates every issue across every tool into one trackable, CI-gateable, shareable number.

lintro check                  # normal output + health score line at the end
lintro check --score          # print ONLY the score (for scripts/badges)
lintro check --fail-under 75  # exit 1 if the score is below 75
lintro check --output-format json   # score included under summary.health_score

Scoring model

Every issue is normalised to one of three severities (ERROR, WARNING, INFO) and weighted, then mapped onto 0-100 with a smoothly saturating penalty:

weighted  = error_weight   * n_errors
          + warning_weight * n_warnings
          + info_weight    * n_info

score     = floor( 100 * scale / (scale + weighted) )

With the default weights (ERROR=10, WARNING=3, INFO=1) and scale=100, this has the following guaranteed properties:

  • Zero issues → exactly 100. A clean run is unambiguous.
  • Any issue → strictly below 100 (floor keeps it at most 99).
  • Monotonic. Adding an issue, or raising its severity, never raises the score.
  • Bounded to [0, 100] and deterministic — the result depends only on the severity counts and the configured weights/scale, never on ordering or timing.

The score hits 50 when the total weighted penalty equals scale (e.g. ten ERROR issues, or ~33 WARNING issues, with the defaults).

Score tiers

ScoreTier
75-100great
50-74needs-work
0-49critical

Configuring the weights

Weights and the smoothing scale are tunable via the score section:

# .lintro-config.yaml
score:
  error_weight: 10 # penalty per ERROR issue
  warning_weight: 3 # penalty per WARNING issue
  info_weight: 1 # penalty per INFO issue
  scale: 100 # larger = the score decays more slowly

JSON output

In --output-format json the score is added additively under summary.health_score, leaving all existing keys untouched:

{
  "summary": {
    "total_issues": 3,
    "total_fixed": 0,
    "total_remaining": 3,
    "health_score": {
      "score": 88,
      "tier": "great",
      "severity_counts": { "error": 1, "warning": 0, "info": 0 },
      "weighted_penalty": 10.0
    }
  }
}

Tool Timeouts in the JSON Report

A tool timeout is an execution failure, never a lint finding. Every tool accounts for one the same way, so a consumer can tell an infrastructure flake apart from a genuine finding using evidence about its own run.

A timed-out tool reports:

  • success: false — the run failed, and the process exit code stays non-zero.
  • timed_out: true — the machine-readable marker to classify on. No need to regex-match the human-readable output string.
  • issues_count counting only genuine findings. No synthetic TIMEOUT pseudo-issue is invented, and the timeout never reaches summary.total_issues. Issues legitimately detected before the timeout (for example the pre-fix check of a format run) are still reported.

summary.timed_out_tools lists the tools that timed out, in execution order:

{
  "summary": {
    "total_issues": 0,
    "total_fixed": 0,
    "total_remaining": 0,
    "timed_out_tools": ["mypy"]
  },
  "results": [
    {
      "tool": "mypy",
      "success": false,
      "issues_count": 0,
      "timed_out": true,
      "output": "mypy execution timed out (300s limit exceeded)..."
    }
  ]
}

This makes the conservative CI classification expressible: at least one tool timed out AND total_issues == 0. A run with a real finding still reports a non-zero total_issues, and a non-timeout tool failure reports timed_out: false, so neither is ever mistaken for a flake.

Artifacts Under GitHub Actions

When GITHUB_ACTIONS=true is detected, lintro auto-emits two side-channel artifacts regardless of --output-format, so the console/grid output every existing consumer parses is untouched and no second invocation is needed:

FormatPathPurpose
SARIF.lintro/artifacts/sarif/results.sarif.jsonGitHub Code Scanning ingestion
JSON.lintro/artifacts/json/results.jsonStructured CI evidence (see above)

SARIF omits clean tools and omits failures that produced no issues, so it cannot be used to classify a timeout without failing open. The JSON report covers every tool that ran and carries the per-tool timed_out flag.

Any format listed in execution.artifacts is emitted in addition to these, in every environment.

Output Presentation

Purely cosmetic console output is controlled by the output section. These settings never affect machine-readable documents (JSON/SARIF) or on-disk artifacts.

# .lintro-config.yaml
output:
  art: true # Show decorative ASCII art after a run (default: true)

The decorative ASCII art printed after check/fmt is only emitted to an interactive terminal. It is always suppressed when:

  • stdout is not a TTY (piped output, CI logs, redirected files), or
  • output.art: false is set in config, or
  • the --no-art flag is passed to lintro check / lintro format.

The art is never written to .lintro/run-*/report.md, console.log, or any --output-format stream regardless of these settings.

lintro check --no-art   # suppress art for this run only
lintro format --no-art

Command-Line Options

Global Options

# Output options
lintro check                  # Use grid formatting
lintro check --output results.txt            # Save output to file
lintro check --group-by [file|code|none|auto] # Group issues

# Tool selection
lintro check --tools ruff,prettier           # Run specific tools only
lintro check --all                           # Run all available tools

# File filtering
lintro check --exclude "*.pyc,venv"          # Exclude patterns
lintro check --include-venv                  # Include virtual environments
lintro check path/to/files                   # Check specific paths

Confirmation Prompt Options

# Skip confirmation prompts (auto-accept)
lintro check --yes
lintro check -y

# Useful in scripts and CI where no interactive input is available
lintro format --yes --tools ruff,prettier

The --yes/-y flag is available for check, format, and test commands. It skips any confirmation prompts that would otherwise pause execution.

Note: Confirmation prompts are automatically skipped in non-TTY environments (CI pipelines, Docker containers, scripts with redirected output), so --yes is typically only needed when running interactively and you want to avoid prompts.

Node.js Dependency Options

# Auto-install Node.js dependencies if node_modules is missing
lintro check --auto-install --tools tsc

# Useful for TypeScript projects where dependencies aren't installed
lintro check src/ --tools tsc,oxlint --auto-install

The --auto-install flag is available for both check and format commands. When enabled, Lintro runs bun install (or npm install if bun is unavailable) before running the tools that need a project’s dependency tree to work: tsc, vue-tsc, svelte-check and astro-check.

It does not apply to standalone Node.js binaries such as prettier, oxlint, oxfmt, stylelint or markdownlint-cli2. Those run fine without a local node_modules: if the project has not installed them, resolution falls through to a binary on PATH and then to a version-pinned bunx/npx fetch — see Node.js Tool Resolution. When one of them cannot be resolved at all, Lintro reports a ⏭️ SKIP row with the reason instead of a pass — installing project dependencies would not have helped.

You can also enable this globally via configuration:

# .lintro-config.yaml
execution:
  auto_install_deps: true

Per-tool auto-install lets you override the global setting for individual tools:

# .lintro-config.yaml
tools:
  tsc:
    auto_install: true # Always auto-install deps for tsc
  prettier:
    auto_install: false # Never auto-install deps for prettier
  oxlint:
    # Omit auto_install to inherit the global setting

Resolution order: per-tool auto_install > global auto_install_deps > false

Container Auto-Detection

Lintro automatically detects container environments (Docker, Podman, LXC, Kubernetes) and enables auto-install by default when running in a container. This means Node.js tools work out of the box in Docker without any configuration.

You can override this behavior explicitly via configuration or the --auto-install CLI flag:

# .lintro-config.yaml — disable auto-install even inside a container
execution:
  auto_install_deps: false

See the Docker Usage Guide for more details on container behavior.

Tool-Specific Options

# Tool-specific options (key=value; lists use |)
lintro check --tool-options "ruff:line_length=88,prettier:print_width=80"

# Example with lists and booleans
lintro check --tool-options "ruff:select=E|F|W,ruff:preview=True"

# Exclude patterns
lintro check --exclude "*.pyc,venv,node_modules"

Environment Variables

Lintro reads the following environment variables at runtime. Most configuration is done through .lintro-config.yaml and CLI flags; these variables cover a few runtime overrides.

# Override the directory where run logs/artifacts are written (default: .lintro)
export LINTRO_LOG_DIR=/tmp/lintro-runs

# Timeout (seconds) for tool version checks (default: 30)
export LINTRO_VERSION_TIMEOUT=60

# Force Docker install-context detection (set to 1)
export LINTRO_DOCKER=1

# Opt in to loading external (third-party) plugins. Disabled by default.
export LINTRO_ENABLE_EXTERNAL_PLUGINS=1

# AI config overlays (flag > env > .lintro-config.yaml > default). See
# docs/ai-features.md "Invocation overrides".
export LINTRO_AI_PROVIDER=cursor
export LINTRO_AI_MODEL=cursor-grok-4.6-high
export LINTRO_AI_TRANSPORT=cli
export LINTRO_AI_ENABLED=1
export LINTRO_AI_MAX_COST_USD=0 # 0 = uncapped; a positive number is a USD cap
VariableDescriptionDefault
LINTRO_LOG_DIRBase directory for run logs and artifacts.lintro
LINTRO_VERSION_TIMEOUTTimeout in seconds for tool version checks (must be >= 1)30
LINTRO_DOCKERForce Docker install-context detection when set to 1-
LINTRO_CONFIGShown in the lintro environment report; informational only-
LINTRO_ENABLE_EXTERNAL_PLUGINSOpt in to loading external (third-party) plugins (1/0)0
LINTRO_AI_PROVIDEROverride ai.provider (anthropic / openai / cursor)-
LINTRO_AI_MODELOverride ai.model-
LINTRO_AI_TRANSPORTOverride ai.transport (api / cli)-
LINTRO_AI_ENABLEDOverride ai.enabled (1/0/true/false)-
LINTRO_AI_MAX_COST_USDOverride ai.max_cost_usd (positive USD cap; 0 = uncapped)-

Note: There is no environment variable for tool timeouts, verbosity, exclude patterns, output format, or auto-install. Use CLI flags (--exclude, --output-format, --auto-install) or .lintro-config.yaml for those settings. Auto-install is resolved from the --auto-install flag, then execution.auto_install_deps, then container auto-detection — not from an environment variable.

External Plugins (Trust Model)

Lintro can load third-party tool plugins published as Python packages that expose a lintro.plugins entry point. Because loading a plugin imports and executes its code, external plugins are disabled by default — a default installation never runs third-party plugin code at startup. This is a security boundary: any package installed in the same environment could otherwise execute arbitrary code every time Lintro runs. See SECURITY.md for the full threat model.

Enable external plugins only after you have reviewed and trust them, using either mechanism:

# .lintro-config.yaml
plugins:
  # Opt in and restrict loading to an explicit allowlist (recommended).
  # Names match the entry-point name or the distribution (package) name.
  trusted:
    - my-org-tool
    - another-plugin
  # Optional: enable loading of ALL discovered plugins (no allowlist).
  # Prefer 'trusted' over this blanket toggle.
  enabled: false

Equivalent pyproject.toml:

[tool.lintro.plugins]
trusted = ["my-org-tool", "another-plugin"]
enabled = false

Or, for a one-off/CI run, the environment variable:

export LINTRO_ENABLE_EXTERNAL_PLUGINS=1

Resolution rules:

  • Loading is enabled when LINTRO_ENABLE_EXTERNAL_PLUGINS is truthy or the plugins config opts in (a trusted allowlist is itself an opt-in, as is enabled: true).
  • When a trusted allowlist is present, only plugins whose entry-point name or distribution name is listed are loaded; all others are skipped and logged, regardless of how loading was enabled.
  • With no allowlist configured, enabling loads all discovered lintro.plugins entry points — use this only in fully trusted environments.

Pre-Execution Summary

Before running tools, Lintro displays a configuration summary table showing the effective settings for the current run:

  • Environment: Local, Container, or CI
  • Auto-install: Whether auto-install is enabled (and the source — CLI flag, config, or container detection)
  • Tools: Which tools will run
  • Skipped tools: Which tools were skipped and why

This summary is shown for all output formats except JSON (--output-format json).

Skipped Tools

When tools are skipped, Lintro reports them in the summary table with a SKIP status and a note explaining the reason. Common skip reasons:

ReasonDescription
node_modules not foundNode.js deps missing and auto-install is disabled
disabled in configTool disabled via tools.<name>.enabled: false
not in enabled_toolsTool not in execution.enabled_tools allowlist (default/--tools all only; named --tools bypasses)
deferred to <tool>Framework tool preferred (e.g., tsc to vue-tsc)
Version check messagesTool version below minimum required

Skipped tools do not affect exit codes — only tools that run and find issues contribute to a non-zero exit.

Disabling a tool from pyproject.toml

The tools: section of .lintro-config.yaml has two equivalent pyproject.toml spellings — a flat per-tool table, or a nested tool/tools table:

[tool.lintro.trufflehog]
enabled = false

# Equivalent, mirroring the YAML `tools:` section
[tool.lintro.tool.trufflehog]
enabled = false

pyproject.toml is a fallback: when a .lintro-config.yaml exists, it is the only configuration source and these tables are not consulted.

Project Setup with lintro init

Run lintro init to detect project languages, select an install profile, and generate a .lintro-config.yaml tailored to your stack:

lintro init                        # auto-detect languages and write config
lintro init --minimal              # fewer defaults
lintro init --profile python       # use a specific profile
lintro init --force                # overwrite existing config

If a config file already exists, lintro init merges new tool entries without clobbering user-managed sections. Use --force to replace the file entirely.

After init, run lintro install --profile recommended and lintro doctor to install and verify tools.

Doctor: Config-Aware Health Checks

lintro doctor respects execution.enabled_tools and per-tool tools.<name>.enabled settings. By default, disabled tools are shown separately and not counted as failures:

lintro doctor                      # check enabled tools only
lintro doctor --all                # check every manifest tool
lintro doctor --tools ruff,mypy    # explicit tools override config filtering
lintro doctor --json               # machine-readable output for CI

The --json output includes per-tool fields: installed, recommended, min_version, status (OK, MISSING, OUTDATED, INCOMPATIBLE, DISABLED, UNKNOWN), install_hint, and upgrade_hint.

Node.js Package Manager Policy {#node-package-manager-policy}

lintro install used to pick bun whenever bun happened to be on PATH, and to install globally regardless of what the project said. In an npm-first repository that created two authorities: your own commands and your editor used the project’s local dependency, while lintro installed and upgraded a global one (#2005).

Which manager, in priority order:

  1. Explicit choicelintro install --node-package-manager npm.
  2. packageManager metadata — the Corepack field in package.json ("packageManager": "pnpm@9.1.0").
  3. Lockfile evidencebun.lock/bun.lockb, pnpm-lock.yaml, yarn.lock, package-lock.json, npm-shrinkwrap.json.
  4. Available manager — bun if installed, otherwise npm, then pnpm, then yarn. bun and npm are lintro’s own preference; pnpm and yarn are included so a machine that only has those still gets a command it can run. This is the only step where lintro’s own preference decides anything.

Availability does not veto the first three. If your project is npm-locked but only bun is installed, lintro still tells you to run npm install -D … rather than quietly writing a bun.lock into your repository.

Where it installs. Inside a Node project (anything with a package.json at or above the working directory), lintro adds a dev dependencynpm install -D <pkg>@<ver> — because the project owns its tool versions and a lockfile-pinned dependency is what Node.js Tool Resolution will run. Global installs are reserved for --global or for an environment with no project manifest at all (a bare CI runner, a container image, your $HOME). The upward search for a manifest stops at the first directory containing a .git entry, and never treats $HOME itself as a project root unless that is where you ran the command — a stray ~/package.json must never collect your tools.

Project pins are never replaced implicitly. If package.json declares a version that differs from lintro’s recommendation, --upgrade reports the difference and asks for an explicit decision instead of rewriting your manifest:

$ lintro install prettier --upgrade
  Node package manager: npm (from lockfile) → project dev dependency

  prettier   Upgrade prettier explicitly: this project pins 3.1.0 in package.json but
             lintro recommends 3.9.4. Run `npm install -D prettier@3.9.4` to adopt
             lintro's version, or keep the project pin.

The comparison is deliberately literal, not a semver range solve: a spec that names the recommended version exactly — 3.9.4, ^3.9.4, ~3.9.4, =3.9.4, v3.9.4 — is not a conflict and upgrades normally. On a first install of a declared-but-missing package, those same spellings emit the manager’s install-all command (npm install, bun install, …) so the lockfile pin is restored, rather than a versioned add that would rewrite the range. Anything else, including a wider range such as ^3.9.0 that a resolver would satisfy with 3.9.4, is reported so you decide. Erring toward asking is deliberate: the cost of a needless question is far below the cost of silently rewriting someone’s package.json.

Planning matches execution. The version probe for npm-installed tools resolves through the same chain a check uses, so lintro install reports on the binary lintro check will actually run rather than on whatever a bare name finds on PATH.

FlagEffect
--node-package-manager {bun,npm,pnpm,yarn}Force the manager, overriding packageManager and lockfiles.
--globalInstall npm-managed tools globally instead of as project dev dependencies.

Install Lock / Export

Use lintro install --write-lock to capture the resolved install plan:

lintro install --profile recommended --write-lock

This writes .lintro-install.lock.json containing every tool in the plan with its version, install hint, and status (to_install, to_upgrade, ok, outdated, manual, skipped), plus the selected profile and detected languages. Share this file with teammates or CI to reproduce the same tool set.

Tool Configuration

Lintro respects each tool’s native configuration files, allowing you to leverage existing setups.

Enforce Settings (Cross-Cutting Concerns)

The enforce tier contains settings that MUST be consistent across tools. These are injected directly as CLI flags to each tool, overriding their native configs.

enforce:
  line_length: 88 # Injected as --line-length (ruff, black) or --print-width (prettier)
  target_python: py313 # Injected as --target-version (ruff, black)

How CLI injection works:

ToolCLI Flag for line_lengthCLI Flag for target_python
Ruff--line-length 88--target-version py313
Black--line-length 88--target-version py313
Prettier--print-width 88N/A

Tools without CLI support:

Some tools (Yamllint, Markdownlint) don’t have CLI flags for line length. For these:

  • Use the defaults tier to provide fallback config
  • Or configure their native config files manually

Defaults Tier (Fallback Config)

The defaults tier provides fallback configuration for tools that have no native config file. This is useful for ensuring consistent settings without creating multiple config files.

defaults:
  prettier:
    semi: true
    singleQuote: true
    tabWidth: 2
    trailingComma: es5

  yamllint:
    extends: default
    rules:
      line-length:
        max: 88

  markdownlint:
    MD013:
      line_length: 88
      code_blocks: false
      tables: false

When defaults are applied:

  1. Lintro checks if the tool has a native config file (e.g., .prettierrc)
  2. If NO native config exists, Lintro generates a temp file from defaults
  3. If native config EXISTS, defaults are ignored (native config is used)

Tool Ordering Configuration

Lintro supports configurable tool execution order. By default, tools run in priority order (formatters before linters), but you can change this behavior.

[tool.lintro]
# Tool order strategy: "priority" (default), "alphabetical", or "custom"
tool_order = "priority"

# For "custom" strategy, specify the order explicitly
tool_order_custom = ["prettier", "black", "ruff", "markdownlint", "yamllint"]

# Override individual tool priorities (lower = runs first)
tool_priorities = { ruff = 5, black = 10, prettier = 1 }

Tool Order Strategies:

StrategyDescription
priorityFormatters run before linters based on priority values (default)
alphabeticalTools run in alphabetical order by name
customTools run in order specified by tool_order_custom

Default Tool Priorities:

ToolPriorityType
prettier10Formatter
black15Formatter
ruff20Linter/Formatter
markdownlint30Linter
html_validate30Linter
yamllint35Linter
pydoclint40Linter
bandit45Security
hadolint50Infrastructure
vale50Linter (docs)
actionlint55Infrastructure
pytest100Test Runner

Lower priority values run first. This ensures formatters run before linters, avoiding false positives from linters detecting issues that formatters would fix.

Post-checks Configuration

Black is integrated as a post-check tool by default. Post-checks run after the main tools complete and can be configured to enforce failure if issues are found. This avoids double-formatting with Ruff and keeps formatting decisions explicit.

[tool.lintro.post_checks]
enabled = true
tools = ["black"]        # Black runs after core tools
enforce_failure = true   # Fail the run if Black finds issues in check mode

Notes:

  • With post-checks enabled for Black, Ruff’s format/format_check stages can be disabled or overridden via CLI when desired.
  • In lintro check, Black runs with --check and contributes to failure when enforce_failure is true. In lintro format, Black formats files in the post-check phase.

Black Options via --tool-options

You can override Black behavior on the CLI. Supported options include line_length, target_version, fast, preview, and diff.

# Increase line length and target a specific Python version
lintro check --tool-options "black:line_length=100,black:target_version=py313"

# Enable fast and preview modes
lintro format --tool-options "black:fast=True,black:preview=True"

# Show diffs during formatting (in addition to applying changes)
lintro format --tool-options "black:diff=True"

These options can also be set in pyproject.toml under [tool.lintro.black]:

[tool.lintro.black]
line_length = 100
target_version = "py313"
fast = false
preview = false
diff = false

Ruff vs Black Policy (Python)

Lintro enforces Ruff-first linting and Black-first formatting when Black is configured as a post-check.

  • Ruff: primary linter (keep strict rules like COM812 trailing commas and E501 line length enabled for checks)
  • Black: primary formatter (applies formatting during post-checks; performs safe line breaking where Ruff’s auto-format may be limited)

Runtime behavior with Black as post-check:

  • lintro format

    • Ruff fixes lint issues only (Ruff format=False) unless explicitly overridden
    • Black performs formatting in the post-check phase
  • lintro check

    • Ruff runs lint checks (Ruff format_check=False) unless explicitly overridden
    • Black runs --check as a post-check to enforce formatting

Overrides when needed:

# Force Ruff to format during fmt
lintro format --tool-options ruff:format=True

# Force Ruff to include format check during check
lintro check --tool-options ruff:format_check=True

Rationale:

  • Avoids double-formatting churn (Ruff format followed by Black format) while preserving Ruff’s stricter lint rules (e.g., COM812, E501).
  • Black’s safe wrapping is preferred for long lines; Ruff continues to enforce lint limits during checks.

Node.js Tool Resolution {#nodejs-tool-resolution}

Every Node.js tool resolves the same way. There is one chain, implemented once in NodeJSBuilder (lintro/tools/core/command_builders.py), and it applies to astro check, commitlint, html-validate, markdownlint-cli2, oxfmt, oxlint, prettier, stylelint, svelte-check, tsc and vue-tsc alike:

  1. node_modules/.bin/<binary>, searched upward from the directory being checked until the nearest package.json or .git (whichever is hit first). A nested package with its own package.json stops there; a decoy node_modules above that boundary is ignored. This is the preferred answer: it is lockfile-pinned, offline, and it is the same binary your editor and your own npm run scripts use.
  2. the PATH-resolved absolute path of <binary> — a global install (bun add -g, npm install -g) or a Homebrew formula.
  3. bunx <package>@<pinned> / npx --yes --package <package>@<pinned> <binary> — a registry fetch at the version Lintro pins in its manifest. bunx is tried first, then npx. bunx keeps the short form when the executable name matches the package name (bunx prettier@<pinned>); it uses --package when they differ (bunx --package typescript@<pinned> tsc). npx always uses --yes --package so it cannot hang waiting for a TTY prompt.
  4. bare <binary> — last resort, fails if nothing is installed.

@latest is never resolved at runtime, for any tool. Branch 3 emits a one-time warning because it needs network access to the npm registry and imposes the pinned package’s own engines floor on your runtime; a failure on that branch is reported with install guidance rather than the tool’s raw error.

Which install should I use? A project devDependency (bun add -D <pkg> / npm install -D <pkg>) is the best answer for every Node tool — it wins the chain and pins the version through your lockfile. A global or Homebrew install is a valid fallback and is used whenever no project-local install is present.

Changed after v0.115.0 (#1811). The chain above used to apply only to html-validate. Other Node tools either went straight to bunx <binary>/npx <binary> (never consulting PATH, and resolving @latest) or preferred PATH ahead of any project-local install. Two consequences worth checking after upgrading:

  • A project-local install now wins. If a project pins an older tool version as a devDependency while you relied on a newer global one, Lintro now runs the local pin — the same version your editor runs. Remove the devDependency, or upgrade it, if you wanted the global.
  • prettier is no longer PATH-only. It previously had no Node builder at all and was resolved as a bare prettier name against PATH; a project’s lockfile-pinned prettier was never used. It now follows the same chain as everything else.

Also fixed on the way: commitlint and markdownlint-cli2 had no npx branch, so on a machine with npm but no bun a devDependency was unreachable and the tool reported a skip. Both now resolve like every other Node tool.

Because the version check (verify_tool_version) resolves through this same chain, the binary Lintro version-gates is now the binary Lintro runs, for every Node tool rather than just for html-validate.

Python Tools

Ruff Configuration

File: pyproject.toml

[tool.ruff]
# Basic configuration
line-length = 88
target-version = "py313"
exclude = [
    ".bzr",
    ".direnv",
    ".eggs",
    ".git",
    ".mypy_cache",
    ".ruff_cache",
    ".venv",
    "__pypackages__",
    "migrations",
]

# Rule selection
select = [
    "E",   # pycodestyle errors
    "W",   # pycodestyle warnings
    "F",   # Pyflakes
    "I",   # isort
    "N",   # pep8-naming
    "D",   # pydocstyle
    "UP",  # pyupgrade
    "B",   # flake8-bugbear
    "C4",  # flake8-comprehensions
    "SIM", # flake8-simplify
]

ignore = [
    "D100", # Missing docstring in public module
    "D104", # Missing docstring in public package
]

# Per-file ignores
[tool.ruff.per-file-ignores]
"tests/**/*.py" = ["D100", "D103"]
"__init__.py" = ["F401"]

# Import sorting
[tool.ruff.isort]
known-first-party = ["lintro"]
force-single-line = true

# Docstring configuration
[tool.ruff.pydocstyle]
convention = "google"

Alternative: setup.cfg

[tool:ruff]
line-length = 88
select = E,W,F,I,N,D
exclude = .git,__pycache__,.venv

Mypy Configuration

  • Default run mode: --strict with --ignore-missing-imports enabled to avoid third-party stub noise (applied unless you override via set_options() or --tool-options).
  • Config discovery: mypy auto-discovers pyproject.toml [tool.mypy], mypy.ini, or setup.cfg [mypy] and the discovered config is passed via --config-file. When a native config provides exclude, Lintro does not add its default test/test_samples excludes; otherwise, it applies the defaults plus .lintro-ignore.
  • Recommended overrides when needed:
# Disable strict mode temporarily
lintro check --tools mypy --tool-options mypy:strict=False

# Surface missing-import errors (no ignore)
lintro check --tools mypy --tool-options mypy:ignore_missing_imports=False

# Pin target Python version
lintro check --tools mypy --tool-options mypy:python_version=3.13

Bandit Configuration

File: pyproject.toml

[tool.bandit]
exclude_dirs = ["tests", "venv", ".git"]
tests = ["B101,B102,B103"]  # Specific test IDs to run
skips = ["B101"]            # Test IDs to skip
confidence = "MEDIUM"       # Minimum confidence level
severity = "LOW"           # Minimum severity level

[tool.bandit.assert_used]
exclude = ["test_*.py"]     # Files to exclude from assert_used test

File: .bandit

[bandit]
exclude = tests,venv,.git
tests = B101,B102,B103
skips = B101
confidence = MEDIUM
severity = LOW

[[tool.bandit.assert_used]]
exclude = test_*.py

Available Options:

  • tests: Comma-separated list of test IDs to run
  • skips: Comma-separated list of test IDs to skip
  • exclude: Comma-separated list of paths to exclude
  • exclude_dirs: List of directories to exclude (pyproject.toml only)
  • severity: Minimum severity level (LOW, MEDIUM, HIGH)
  • confidence: Minimum confidence level (LOW, MEDIUM, HIGH)
  • baseline: Path to baseline report for comparison

Semgrep Configuration

Semgrep is a fast, open-source static analysis tool for security scanning and code quality enforcement across 30+ languages.

File: .semgrep.yaml or .semgrep.yml

rules:
  - id: custom-security-rule
    pattern: eval(...)
    message: 'Avoid using eval() - potential code injection'
    languages: [python]
    severity: ERROR

Available Options via --tool-options:

OptionTypeDescription
configstringRule config: auto, p/python, p/javascript, or path
excludelistPatterns to exclude from scanning
includelistPatterns to include in scanning
severitystringMinimum severity: INFO, WARNING, ERROR
timeout_thresholdintPer-file timeout in seconds
jobsintNumber of parallel jobs

Example Usage:

# Use Python security rules
lintro check --tools semgrep --tool-options "semgrep:config=p/python"

# Filter by severity
lintro check --tools semgrep --tool-options "semgrep:severity=ERROR"

# Exclude test files
lintro check --tools semgrep --tool-options "semgrep:exclude=tests/*|vendor/*"

Gitleaks Configuration

File: .gitleaks.toml

# Custom rule example
[[rules]]
id = "custom-api-key"
description = "Custom API Key Pattern"
regex = '''custom_api_key_[a-zA-Z0-9]{32}'''
tags = ["key", "custom"]

# Allowlist to ignore false positives
[allowlist]
paths = [
    '''test_samples/''',
    '''\.git/''',
]
regexes = [
    '''EXAMPLE''',
    '''test_''',
]

Available Options:

OptionTypeDescription
no_gitbooleanScan without git history (files only)
configstringPath to custom gitleaks config file
baseline_pathstringPath to baseline file (ignore known secrets)
redactbooleanRedact secrets in output (default: true)
max_target_megabytesintegerSkip files larger than this size in MB

Usage Examples:

# Basic scan with default config
lintro check --tools gitleaks

# Scan with git history (not just files)
lintro check --tools gitleaks --tool-options gitleaks:no_git=False

# Use custom config file
lintro check --tools gitleaks --tool-options gitleaks:config=.gitleaks.toml

# Use baseline to ignore known secrets
lintro check --tools gitleaks --tool-options gitleaks:baseline_path=gitleaks-baseline.json

# Limit file size to scan
lintro check --tools gitleaks --tool-options gitleaks:max_target_megabytes=10

TruffleHog Configuration

TruffleHog is a secrets scanner with 800+ provider-specific detectors and optional live credential verification. Lintro runs it in filesystem mode. Verification is disabled by default (--no-verification) so default scans make no outbound network calls; verification can be re-enabled per run, in which case TruffleHog may contact third-party providers to test candidate credentials (accept that trade-off before enabling it). TruffleHog is configured via CLI options (there is no default config file).

Install: brew install trufflehog or GitHub Releases

Available Options:

OptionTypeDescription
no_verificationbooleanDisable live credential verification (default: true)
resultsstringFilter result type — single value (see note below)
configstringPath to a custom detector configuration file
exclude_pathsstringPath to a file of newline-separated exclude regexes
concurrencyintegerNumber of concurrent workers

Note: --tool-options uses commas to separate options, so a comma-separated results value (e.g. verified,unverified) cannot be passed on the CLI. Use a single value such as results=unverified, or leave it unset to report every result type.

Usage Examples:

# Basic scan (verification disabled by default)
lintro check --tools trufflehog

# Raise worker concurrency
lintro check --tools trufflehog --tool-options trufflehog:concurrency=8

# Only report unverified results (single value; commas are not CLI-safe here)
lintro check --tools trufflehog --tool-options trufflehog:results=unverified

# Explicitly enable live verification (makes network calls — off by default)
lintro check --tools trufflehog --tool-options trufflehog:no_verification=False

OSV-Scanner Configuration

OSV-Scanner is Google’s vulnerability scanner using the Open-Source Vulnerabilities (OSV) database. It scans lockfiles for known vulnerabilities across multiple ecosystems including PyPI, npm, Go, Rust, Ruby, PHP, .NET, and Java.

Install: go install github.com/google/osv-scanner/v2/cmd/osv-scanner@latest or download from GitHub Releases.

File: .osv-scanner.toml

# Ignore specific vulnerabilities (ignoreUntil is required for lintro classification)
[[IgnoredVulns]]
id = "GHSA-xxxx-xxxx-xxxx"
ignoreUntil = 2026-12-31
reason = "Not applicable to this project"

# Override package scanning behavior
[[PackageOverrides]]
name = "example-package"
ecosystem = "PyPI"
ignore = true
reason = "False positive"

Available Options:

OptionTypeDescription
timeoutintegerScan timeout in seconds (default: 120)
check_suppressionsbooleanRun probe scan to detect stale suppressions (default: true)

When check_suppressions is enabled, lintro runs a second osv-scanner scan without suppressions to classify each .osv-scanner.toml entry as Active (vulnerability still present), Stale (vulnerability resolved upstream — safe to remove), or Expired (past the ignoreUntil date). Results appear in the summary table Notes column and in JSON output under metadata.suppressions.

Usage Examples:

# Scan for vulnerabilities in lockfiles
lintro check --tools osv_scanner

# With custom timeout for slow networks
lintro check --tools osv_scanner --tool-options "osv_scanner:timeout=300"

# Skip suppression staleness check
lintro check --tools osv_scanner --tool-options "osv_scanner:check_suppressions=false"

# Ignore vendored or nested checkouts (equivalent to a .lintro-ignore entry)
lintro check --tools osv_scanner --exclude ".claude"

Excluding lockfiles: osv-scanner performs its own recursive lockfile discovery, so lintro applies the resolved exclusion set — .lintro-ignore entries plus --exclude patterns — to the lockfile paths it reports. Both mechanisms use the same gitignore semantics as file discovery and yield identical results. Directory patterns (.claude, vendor/) drop every finding from that tree — useful when nested checkouts multiply the same finding — and file patterns (bun.lock, *.lock) suppress individual lockfiles.

pip-audit Configuration

pip-audit is the Python Packaging Authority (PyPA) scanner for Python dependencies with known vulnerabilities. It queries the PyPI Advisory Database and OSV, complementing bandit (which scans source code) by scanning the dependency surface. It audits requirements*.txt files (via -r) and Python projects declared in pyproject.toml / setup.py.

Install: pip install pip-audit, uv add pip-audit, or brew install pip-audit.

File: none. pip-audit has no native config file; suppressions are passed on the command line by upstream and are not exposed via lintro.

Available Options:

OptionTypeDescription
timeoutintegerScan timeout in seconds (default: 120)

Notes:

  • pip-audit’s JSON output carries no severity field, so lintro reports severity as UNKNOWN.
  • Advisory IDs (PYSEC/GHSA/CVE) link to the corresponding osv.dev page.
  • Requirements discovery is recursive: nested files such as requirements/base.txt or services/api/requirements.txt are picked up, while vendored/generated trees (node_modules, .venv, venv, vendor, .git, __pycache__) are skipped. To audit a file outside this scope, pass it explicitly on the command line.

Usage Examples:

# Scan requirements and project manifests for vulnerable dependencies
lintro check --tools pip_audit

# With a longer timeout for slow networks
lintro check --tools pip_audit --tool-options "pip_audit:timeout=300"

pydoclint Configuration

File: pyproject.toml

[tool.pydoclint]
style = "google"
arg-type-hints-in-docstring = false
arg-type-hints-in-signature = true
check-return-types = false
check-arg-order = true
skip-checking-short-docstrings = true

Available Options:

  • style: google, numpy, sphinx
  • arg-type-hints-in-docstring: Require types in docstring (default: true)
  • arg-type-hints-in-signature: Require type annotations (default: true)
  • check-return-types: Validate return types match (default: true)
  • check-arg-order: Verify argument order matches signature
  • skip-checking-short-docstrings: Skip single-line docstrings

Frontend Tools

Prettier Configuration

Prettier handles formatting for CSS, HTML, JSON, YAML, Markdown, and GraphQL files.

Note: JavaScript and TypeScript files are handled by oxfmt for better performance (30x faster). See Oxfmt Configuration for JS/TS formatting options.

File: .prettierrc

{
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": true,
  "quoteProps": "as-needed",
  "trailingComma": "es5",
  "bracketSpacing": true,
  "arrowParens": "avoid",
  "printWidth": 80,
  "endOfLine": "lf"
}

File: prettier.config.js

module.exports = {
  tabWidth: 2,
  semi: true,
  singleQuote: true,
  trailingComma: 'es5',
  bracketSpacing: true,
  arrowParens: 'avoid',
  printWidth: 80,

  // Override for specific file types
  overrides: [
    {
      files: '*.json',
      options: {
        tabWidth: 4,
      },
    },
    {
      files: '*.md',
      options: {
        printWidth: 120,
        proseWrap: 'always',
      },
    },
  ],
};

File: package.json

{
  "prettier": {
    "tabWidth": 2,
    "semi": true,
    "singleQuote": true
  }
}

Ignore Files: .prettierignore

node_modules/
dist/
build/
coverage/
*.min.js
*.min.css

TypeScript Tools

TypeScript Compiler (tsc) Configuration

The TypeScript Compiler provides static type checking for TypeScript projects. Lintro wraps tsc --noEmit to check types without generating output files.

Installation:

# Homebrew (macOS/Linux)
brew install typescript

# npm
npm install -D typescript

# bun
bun add -D typescript

A project devDependency is preferred: a type checker must match the project’s own TypeScript version, because a different compiler reports different diagnostics. A global or Homebrew tsc is used when the project has no local install. See Node.js Tool Resolution.

File: tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "skipLibCheck": true,
    "noEmit": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Available Options via --tool-options:

OptionTypeDescription
projectstringPath to tsconfig.json (—project)
strictbooleanEnable all strict type checking options
skip_lib_checkbooleanSkip type checking of declaration files (default: true)
timeoutintegerExecution timeout in seconds (default: 60)

Usage Examples:

# Basic type check (uses tsconfig.json if present)
lintro check src/ --tools tsc

# Enable strict mode
lintro check src/ --tools tsc --tool-options "tsc:strict=True"

# Use specific config file
lintro check src/ --tools tsc --tool-options "tsc:project=tsconfig.build.json"

# Disable library check for faster execution
lintro check src/ --tools tsc --tool-options "tsc:skip_lib_check=True"

# Combine with other JS/TS tools
lintro check src/ --tools tsc,oxlint,oxfmt

Oxlint Configuration

Oxlint is a fast JavaScript/TypeScript linter (50-100x faster than ESLint) with 661+ built-in rules from ESLint, TypeScript, React, JSX-a11y, Unicorn, and more.

Native Config Detection:

Lintro detects these oxlint config files:

  • .oxlintrc.json
  • oxlint.json

When a native config exists, Lintro uses it automatically and skips generating defaults.

Installation:

# bun (recommended)
bun add -D oxlint

# npm
npm install -D oxlint

Lintro prefers the project’s own node_modules/.bin/oxlint, then a binary on PATH, then a version-pinned bunx/npx fetch, so a global or Homebrew install works too. See Node.js Tool Resolution.

File: .oxlintrc.json

{
  "rules": {
    "no-debugger": "error",
    "no-console": "warn",
    "eqeqeq": "error"
  },
  "plugins": ["react", "unicorn"],
  "ignorePatterns": ["dist/**", "node_modules/**"]
}

Available Options via --tool-options:

OptionTypeDescription
configstringPath to config file (—config)
tsconfigstringPath to tsconfig.json (—tsconfig)
allowlist[str]Rules to allow (turn off)
denylist[str]Rules to deny (report as errors)
warnlist[str]Rules to warn on (report as warnings)
quietbooleanSuppress warnings, only report errors (—quiet)
timeoutintegerExecution timeout in seconds (default: 30)

Usage Examples:

# Basic check
lintro check --tools oxlint

# Auto-fix issues
lintro format --tools oxlint

# Suppress warnings (errors only)
lintro check --tools oxlint --tool-options "oxlint:quiet=True"

# Deny specific rules (report as errors)
lintro check --tools oxlint --tool-options "oxlint:deny=no-debugger|no-console"

# Allow specific rules (ignore them)
lintro check --tools oxlint --tool-options "oxlint:allow=no-unused-vars"

# Use custom config file
lintro check --tools oxlint --tool-options "oxlint:config=.oxlintrc.custom.json"

# Specify tsconfig for TypeScript projects
lintro check --tools oxlint --tool-options "oxlint:tsconfig=tsconfig.app.json"

Stylelint Configuration

Stylelint is a mighty, configurable linter and fixer for CSS, SCSS, Sass, and Less stylesheets, with 100+ built-in rules and --fix support.

Stylelint requires a configuration to run. When no config is resolvable, Lintro skips stylelint as a non-error (rather than surfacing stylelint’s hard ConfigurationError).

Native Config Detection:

Stylelint resolves configuration per file (walking upward). Lintro supports:

  • .stylelintrc, .stylelintrc.json, .stylelintrc.yaml, .stylelintrc.yml
  • .stylelintrc.js, .stylelintrc.cjs, .stylelintrc.mjs
  • stylelint.config.js, stylelint.config.cjs, stylelint.config.mjs
  • a stylelint key in package.json

.stylelintignore is honored by the underlying tool.

Installation:

# bun (recommended, with a shareable config)
bun add -D stylelint stylelint-config-standard

# npm
npm install -D stylelint stylelint-config-standard

Lintro prefers the project’s own node_modules/.bin/stylelint, then a binary on PATH, then a version-pinned bunx/npx fetch, so a global install works too. See Node.js Tool Resolution.

File: .stylelintrc.json

{
  "extends": ["stylelint-config-standard"],
  "rules": {
    "color-hex-length": "short",
    "block-no-empty": true,
    "declaration-block-no-duplicate-properties": true
  }
}

Available Options via --tool-options:

OptionTypeDescription
configstringPath to a stylelint config file (--config)
verbose_fix_outputbooleanInclude raw stylelint output in fix()
timeoutintegerExecution timeout in seconds (default: 30)

Usage Examples:

# Basic check
lintro check styles/ --tools stylelint

# Auto-fix issues
lintro format styles/ --tools stylelint

# Use a specific config file
lintro check styles/ --tools stylelint --tool-options "stylelint:config=.stylelintrc.json"

# Increase timeout for large stylesheets
lintro check styles/ --tools stylelint --tool-options "stylelint:timeout=60"

Oxfmt Configuration

Oxfmt is a fast JavaScript/TypeScript formatter (30x faster than Prettier) that provides Prettier-compatible formatting with minimal configuration.

Native Config Detection:

Lintro detects these oxfmt config files:

  • .oxfmtrc.json
  • .oxfmtrc.jsonc (supports comments)

When a native config exists, Lintro uses it automatically and skips generating defaults.

Installation:

# bun (recommended)
bun add -D oxfmt

# npm
npm install -D oxfmt

Lintro prefers the project’s own node_modules/.bin/oxfmt, then a binary on PATH, then a version-pinned bunx/npx fetch, so a global or Homebrew install works too. See Node.js Tool Resolution.

File: .oxfmtrc.json or .oxfmtrc.jsonc

{
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": true,
  "trailingComma": "es5"
}

Available Options via --tool-options:

OptionTypeDescription
configstringPath to config file (—config)
ignore_pathstringPath to ignore file (—ignore-path)
timeoutintegerExecution timeout in seconds (default: 30)

Note: Formatting options (printWidth, tabWidth, useTabs, semi, singleQuote) are only supported via config file (.oxfmtrc.json), not CLI flags.

Usage Examples:

# Basic check
lintro check --tools oxfmt

# Format files
lintro format --tools oxfmt

# Use custom config file
lintro format --tools oxfmt --tool-options "oxfmt:config=.oxfmtrc.custom.json"

# Use custom ignore file
lintro format --tools oxfmt --tool-options "oxfmt:ignore_path=.oxfmtignore"

# Increase timeout
lintro format --tools oxfmt --tool-options "oxfmt:timeout=60"

Web Framework Tools

Astro Check Configuration

Astro Check is Astro’s built-in type checking command that provides TypeScript diagnostics for .astro files including frontmatter scripts, component props, and template expressions.

Installation:

# bun (recommended)
bun add -D @astrojs/check

# npm
npm install -D @astrojs/check

astro check needs @astrojs/check, and Lintro runs Astro with CI=1 so its interactive “install @astrojs/check?” prompt cannot complete. Add that package as a devDependency; astro is already a production dependency in an Astro project, and installing it with -D would move it out of dependencies. A project-local install of @astrojs/check is strongly preferred here; see Node.js Tool Resolution.

Native Config: astro.config.mjs, astro.config.ts, or astro.config.js

Astro check uses your project’s astro.config and tsconfig.json for configuration. No additional configuration is needed for Lintro.

Available Options via --tool-options:

OptionTypeDescription
rootstringRoot directory for the Astro project
timeoutintegerExecution timeout in seconds (default: 120)

Usage Examples:

# Check Astro project
lintro check src/ --tools astro-check

# Check with specific root directory
lintro check . --tools astro-check --tool-options "astro-check:root=./packages/web"

# Auto-install dependencies before checking
lintro check src/ --tools astro-check --auto-install

Svelte Check Configuration

Svelte Check is the official type checker and linter for Svelte components. It provides TypeScript type checking, unused CSS detection, and accessibility hints for .svelte files.

Installation:

# bun (recommended)
bun add -D svelte-check

# npm
npm install -D svelte-check

Native Config: svelte.config.js or svelte.config.ts

Svelte Check uses your project’s svelte.config and tsconfig.json for configuration. No additional configuration is needed for Lintro.

Available Options via --tool-options:

OptionTypeDescription
thresholdstringMinimum severity to report: error or warning (default: warning)
tsconfigstringPath to tsconfig.json file
timeoutintegerExecution timeout in seconds (default: 120)

Usage Examples:

# Check Svelte project
lintro check src/ --tools svelte-check

# Check with warning threshold
lintro check src/ --tools svelte-check --tool-options "svelte-check:threshold=warning"

# Check with specific tsconfig
lintro check . --tools svelte-check --tool-options "svelte-check:tsconfig=./tsconfig.app.json"

# Auto-install dependencies before checking
lintro check src/ --tools svelte-check --auto-install

Vue-tsc Configuration

Vue-tsc is the TypeScript type checker for Vue Single File Components (SFCs). It extends tsc with Vue-specific type checking capabilities for .vue files.

Installation:

# bun (recommended)
bun add -D vue-tsc

# npm
npm install -D vue-tsc

Native Config: tsconfig.json or tsconfig.app.json

Vue-tsc uses your project’s tsconfig.json for configuration. No additional configuration is needed for Lintro.

Available Options via --tool-options:

OptionTypeDescription
projectstringPath to tsconfig.json file
strictbooleanEnable strict type checking mode
skip_lib_checkbooleanSkip checking declaration files (default: true)
use_project_filesbooleanUse tsconfig’s include patterns (default: false)
timeoutintegerExecution timeout in seconds (default: 120)

Usage Examples:

# Check Vue project
lintro check src/ --tools vue-tsc

# Check with specific tsconfig
lintro check . --tools vue-tsc --tool-options "vue-tsc:project=tsconfig.app.json"

# Enable strict mode
lintro check src/ --tools vue-tsc --tool-options "vue-tsc:strict=true"

# Auto-install dependencies before checking
lintro check src/ --tools vue-tsc --auto-install

html-validate Configuration

html-validate is an offline HTML validator that checks documents for standards compliance, best practices, and accessibility (WCAG) issues. It is check-only — the tool ships no autofixer. By default it inspects *.html, *.htm, *.vue, and *.svelte files.

Installation:

# bun (recommended)
bun add -D html-validate

# npm
npm install -D html-validate

A project-local devDependency is the supported configuration. It is the first and most reliable branch of Lintro’s executable resolution, it is lockfile-pinned, and it needs no registry access at check time. A global install (-g) does not populate node_modules/.bin, so it lands on a later, weaker branch.

html-validate uses the shared Node.js chain — as of a release after v0.115.0 (#1811) so does every other Node.js tool; see Node.js Tool Resolution.

Executable resolution order:

  1. node_modules/.bin/html-validate, searched upward from the directory being checked until the nearest package.json or .git.
  2. the PATH-resolved absolute path of html-validate (e.g. a global install).
  3. bunx html-validate@<pinned> — registry fallback, only if bunx is available.
  4. npx --yes --package html-validate@<pinned> html-validate — registry fallback, only if npx is available.
  5. bare html-validate (fails if nothing is installed).

<pinned> is the version Lintro pins in its manifest; @latest is never resolved at runtime. Branches 3 and 4 emit a one-time warning because they require network access to the npm registry, and a failure on either path is reported with install guidance rather than html-validate’s raw error.

Node runtime requirement: the pinned html-validate (currently 11.6.2) declares engines: { "node": "^22.22.0 || >= 24.8.0" }. Any consumer that installs it, or that reaches the bunx/npx fallback, needs a Node runtime satisfying that range — Node 20, 21, and 22.0–22.21 are not supported. Size your CI matrix accordingly.

Native Config: .htmlvalidate.json, .htmlvalidate.js, .htmlvalidate.cjs, or .htmlvalidate.mjs

html-validate reads its rule configuration from the project’s native config file when present. No additional configuration is required for Lintro.

Available Options via --tool-options:

OptionTypeDescription
timeoutintegerExecution timeout in seconds (default: 120)

Usage Examples:

# Check HTML files
lintro check src/ --tools html-validate

# Check the whole project
lintro check . --tools html-validate

# Auto-install dependencies before checking
lintro check src/ --tools html-validate --auto-install

SQL Tools

SQLFluff Configuration

File: .sqlfluff

[sqlfluff]
dialect = ansi
templater = jinja
exclude_rules = L016,L031

[sqlfluff:indentation]
indent_unit = space
tab_space_size = 4

[sqlfluff:layout:type:comma]
line_position = trailing

[sqlfluff:rules:capitalisation.keywords]
capitalisation_policy = upper

[sqlfluff:rules:capitalisation.identifiers]
extended_capitalisation_policy = lower

File: pyproject.toml

[tool.sqlfluff.core]
dialect = "ansi"
templater = "jinja"
exclude_rules = ["L016", "L031"]

[tool.sqlfluff.indentation]
indent_unit = "space"
tab_space_size = 4

[tool.sqlfluff.rules.capitalisation.keywords]
capitalisation_policy = "upper"

[tool.sqlfluff.rules.capitalisation.identifiers]
extended_capitalisation_policy = "lower"

Available Options:

OptionTypeDescription
dialectstringSQL dialect (ansi, bigquery, postgres, mysql, etc.)
exclude_ruleslistList of rules to exclude from checking
ruleslistList of specific rules to include
templaterstringTemplater to use (raw, jinja, python, placeholder)

Supported Dialects:

  • ansi - ANSI SQL standard
  • bigquery - Google BigQuery
  • clickhouse - ClickHouse
  • databricks - Databricks SQL
  • db2 - IBM Db2
  • exasol - Exasol
  • hive - Apache Hive
  • mysql - MySQL
  • oracle - Oracle Database
  • postgres - PostgreSQL
  • redshift - Amazon Redshift
  • snowflake - Snowflake
  • soql - Salesforce SOQL
  • sparksql - Apache Spark SQL
  • sqlite - SQLite
  • teradata - Teradata
  • tsql - T-SQL (Microsoft SQL Server)

Usage Examples:

# Basic SQL check
lintro check --tools sqlfluff

# Format SQL files
lintro format --tools sqlfluff

# Check with specific dialect
lintro check --tools sqlfluff --tool-options sqlfluff:dialect=postgres

# Exclude specific rules
lintro check --tools sqlfluff --tool-options sqlfluff:exclude_rules=L010,L014

# Use jinja templater
lintro check --tools sqlfluff --tool-options sqlfluff:templater=jinja

YAML Tools

Yamllint Configuration

File: .yamllint

extends: default

rules:
  # Line length
  line-length:
    max: 120
    level: warning

  # Indentation
  indentation:
    spaces: 2
    indent-sequences: true
    check-multi-line-strings: false

  # Comments
  comments:
    min-spaces-from-content: 2

  # Document start
  document-start:
    present: false

  # Truthy values
  truthy:
    allowed-values: ['true', 'false']
    check-keys: true

File: pyproject.toml

[tool.yamllint]
extends = "default"

[tool.yamllint.rules.line-length]
max = 120

[tool.yamllint.rules.indentation]
spaces = 2

Markdown Tools

Markdownlint-cli2 Configuration {#markdownlint-cli2-configuration}

Markdownlint-cli2 supports configuration via JSON, JSONC, YAML, or TOML files. Lintro defers to markdownlint-cli2’s native configuration discovery, which searches upward from the file being checked.

File: .markdownlint.json

{
  "default": true,
  "MD013": {
    "line_length": 120
  },
  "MD041": false
}

File: .markdownlint.yaml

default: true
MD013:
  line_length: 120
MD041: false

File: .markdownlint-cli2.jsonc

{
  "config": {
    "default": true,
    "MD013": { "line_length": 120 },
  },
}

Available Options:

  • Configuration files are discovered automatically by markdownlint-cli2
  • Rules can be enabled/disabled via configuration files
  • Lintro respects markdownlint-cli2’s native configuration discovery
  • Future versions may expose additional options via [tool.lintro.markdownlint-cli2] in pyproject.toml

Prose / Documentation Tools

Vale Configuration {#vale-configuration}

Vale is a syntax-aware prose linter. Lintro defers to Vale’s native configuration discovery, which walks upward from each linted file to find a .vale.ini.

Vale requires a configuration to run. When none is resolvable, Lintro skips vale as a non-error (rather than surfacing vale’s E100 runtime error), keeping mixed-language runs clean.

Native config detection: .vale.ini, _vale.ini, vale.ini.

File: .vale.ini

MinAlertLevel = suggestion

[*.md]
BasedOnStyles = Vale

Installation:

brew install vale
# or download from https://github.com/errata-ai/vale/releases

Available --tool-options:

OptionTypeDescription
configstringPath to a Vale config file (maps to --config)
min_alert_levelstringMinimum alert level: suggestion, warning, or error
timeoutintPer-run timeout in seconds (default 30)

Usage:

lintro check docs/ --tools vale
lintro check docs/ --tools vale --tool-options vale:min_alert_level=warning
lintro check docs/ --tools vale --tool-options vale:config=.vale.ini

Rust Tools

Clippy Configuration

Clippy is Rust’s official linter and is configured through Cargo.toml or a separate clippy.toml file. Lintro automatically discovers and runs clippy on Rust projects by finding Cargo.toml files.

File: Cargo.toml

[package]
name = "my-rust-project"
version = "0.1.0"

[lints.clippy]
# Enable all lints
pedantic = "warn"
# Or be more restrictive
# pedantic = { level = "warn", priority = -1 }

# Disable specific lints
too_many_arguments = "allow"
type_complexity = "allow"

# Configure lint levels
needless_return = "warn"
unused_variables = "error"

File: clippy.toml (alternative)

# Clippy-specific configuration
too-many-arguments-threshold = 10
type-complexity-threshold = 100
cognitive-complexity-threshold = 15

# Disable specific lints
disallowed-names = []

Available Options:

  • pedantic: Enable all lints that are typically only enabled in CI
  • nursery: Enable newer, more experimental lints
  • restriction: Enable very strict lints that may be overly restrictive for some projects
  • cargo: Enable lints that check Cargo.toml files

Lintro usage:

# Check Rust code with Clippy
lintro check --tools clippy

# Auto-fix Clippy issues where possible
lintro format --tools clippy

# Check specific Rust directories
lintro check src/ --tools clippy

golangci-lint Configuration

golangci-lint is the de-facto Go meta-linter, running 100+ sub-linters in parallel. Lintro targets golangci-lint v2 and automatically discovers Go modules by finding go.mod files; non-Go projects are skipped. It requires the Go toolchain to be installed. Linter selection and rule tuning are configured through the project’s native config file.

Installation:

brew install golangci-lint
# or see https://golangci-lint.run/welcome/install/

File: .golangci.yml (also .golangci.yaml, .golangci.toml, .golangci.json)

version: '2'
linters:
  enable:
    - errcheck
    - staticcheck
    - ineffassign
    - govet

Available Options:

OptionTypeDescription
timeoutintegerExecution timeout in seconds (120)

Linter enable/disable and per-linter settings live in the native .golangci.* config file rather than as lintro --tool-options.

Lintro usage:

# Check Go code with golangci-lint
lintro check --tools golangci_lint

# Auto-fix issues where the underlying linters support it
lintro format --tools golangci_lint

# Increase the timeout for a large module
lintro check --tools golangci_lint --tool-options golangci_lint:timeout=300

Cargo-deny Configuration

Cargo-deny checks Rust dependencies for license compliance, security advisories, banned crates, and duplicate dependencies. It uses deny.toml for configuration and runs via cargo deny check.

Installation:

# cargo install
cargo install cargo-deny

# Or via cargo-binstall for faster installation
cargo binstall cargo-deny

File: deny.toml

[advisories]
db-path = "~/.cargo/advisory-db"
db-urls = ["https://github.com/rustsec/advisory-db"]

[licenses]
allow = ["MIT", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", "ISC"]

[bans]
multiple-versions = "warn"
wildcards = "allow"

[sources]
unknown-registry = "warn"
unknown-git = "warn"

Available Options via --tool-options:

OptionTypeDescription
timeoutintegerExecution timeout in seconds (default: 60)

Lintro usage:

# Check Rust project with cargo-deny
lintro check --tools cargo_deny

# Check specific Rust directory
lintro check my-crate/ --tools cargo_deny

# Set a longer timeout for large workspaces
lintro check . --tools cargo_deny --tool-options "cargo_deny:timeout=120"

Shell Tools

ShellCheck Configuration

ShellCheck is a static analysis tool for shell scripts. It identifies bugs, syntax issues, and suggests improvements for bash/sh/dash/ksh/zsh scripts. Unlike formatters, ShellCheck focuses on finding potential bugs and problematic patterns.

Installation:

# macOS
brew install shellcheck

# Debian/Ubuntu
apt-get install shellcheck

# Fedora
dnf install ShellCheck

File: .shellcheckrc

# Exclude specific codes
disable=SC2086,SC2046

# Set default shell dialect
shell=bash

# Set minimum severity level
severity=warning

Lintro options via --tool-options:

# Set minimum severity level (error, warning, info, style)
lintro check --tools shellcheck --tool-options "shellcheck:severity=warning"

# Force shell dialect (bash, sh, dash, ksh, zsh)
lintro check --tools shellcheck --tool-options "shellcheck:shell=bash"

# Exclude specific codes
lintro check --tools shellcheck --tool-options "shellcheck:exclude=SC2086|SC2046"

# Follow repo-local sourced files (resolves SC1091 for SCRIPT_DIR sourcing)
lintro check --tools shellcheck \
  --tool-options "shellcheck:external_sources=True,shellcheck:source_paths=SCRIPTDIR"

Available Options:

OptionTypeDescription
severitystrMinimum severity: error, warning, info, style
excludelist[str]List of codes to exclude (e.g., SC2086, SC2046)
shellstrForce shell dialect: bash, sh, dash, ksh, zsh
external_sourcesboolFollow sourced files external to the script (-x). Default False
source_pathslist[str]Search paths for sourced files (--source-path=...); supports the SCRIPTDIR token. Setting this implies external_sources (-x is enabled automatically)

Source-following (SC1091):

Scripts that source repo-local helpers with the runtime-safe pattern below emit SC1091 (“Not following …”) by default, because ShellCheck cannot statically resolve the dynamic path:

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../lib/common.sh"

Opt in to source-following (off by default, so existing projects are unaffected) by pointing source_paths at the directories ShellCheck should search. ShellCheck’s literal SCRIPTDIR token resolves relative to each script’s own directory, which matches the pattern above without hard-coding repo paths. Because ShellCheck ignores --source-path unless -x is active, setting source_paths automatically enables external_sources, so you do not need to set both — configuring paths is enough. (You may still set external_sources = true on its own to follow sources found on the default search path.)

pyproject.toml:

[tool.lintro.shellcheck]
# source_paths implies external_sources; -x is added automatically.
source_paths = ["SCRIPTDIR", "scripts/lib"]

With this enabled, the SC1091 above is resolved without per-line # shellcheck disable=SC1091 or # shellcheck source=... suppressions.

Common ShellCheck Codes:

CodeDescription
SC2086Double quote to prevent globbing and word splitting
SC2046Quote this to prevent word splitting
SC2002Useless use of cat
SC2006Use $(…) notation instead of backticks
SC2034Variable appears unused
SC2155Declare and assign separately

Inline ignoring:

#!/bin/bash

# shellcheck disable=SC2086
echo $unquoted_variable

# Or use a directive that applies to the whole file at the top:
# shellcheck disable=SC2086,SC2046

Lintro usage:

# Check shell scripts with ShellCheck
lintro check --tools shellcheck

# Check with warning level (ignores info and style)
lintro check --tools shellcheck --tool-options "shellcheck:severity=warning"

# Check specific shell directories
lintro check scripts/ --tools shellcheck

Shfmt Configuration

Shfmt is a shell script formatter that supports POSIX, Bash, mksh, and bats shells. It formats shell scripts to ensure consistent style and can detect formatting issues in diff mode.

Installation:

# macOS
brew install shfmt

# Linux (via Go)
go install mvdan.cc/sh/v3/cmd/shfmt@latest

# Via npm/bun
bun add -g shfmt

File: .editorconfig (shfmt respects EditorConfig)

[*.sh]
indent_style = tab
indent_size = 4
shell_variant = bash
binary_next_line = true
switch_case_indent = true
space_redirects = false

Lintro options via --tool-options:

# Set indentation to 4 spaces (0 for tabs)
lintro check --tools shfmt --tool-options "shfmt:indent=4"

# Enable binary operators at start of line
lintro check --tools shfmt --tool-options "shfmt:binary_next_line=True"

# Indent switch cases
lintro check --tools shfmt --tool-options "shfmt:switch_case_indent=True"

# Add space after redirect operators
lintro check --tools shfmt --tool-options "shfmt:space_redirects=True"

# Set language dialect (bash, posix, mksh, bats)
lintro check --tools shfmt --tool-options "shfmt:language_dialect=bash"

# Enable code simplification
lintro check --tools shfmt --tool-options "shfmt:simplify=True"

Available Options:

OptionTypeDescription
indentintIndentation size. 0 for tabs, >0 for spaces
binary_next_lineboolBinary ops like && and | may start a line
switch_case_indentboolIndent switch cases
space_redirectsboolRedirect operators followed by space
language_dialectstrShell dialect: bash, posix, mksh, bats
simplifyboolSimplify code where possible

Lintro usage:

# Check shell scripts with shfmt
lintro check --tools shfmt

# Auto-format shell scripts
lintro format --tools shfmt

# Check specific shell directories
lintro check scripts/ --tools shfmt

Dotenv Tools

dotenv-linter Configuration

dotenv-linter is a fast, Rust-based linter and fixer for .env files. It detects duplicate keys, lowercase keys, incorrect delimiters, unordered keys, and stray whitespace, and can auto-fix most of them.

Installation:

# macOS
brew install dotenv-linter

# Cargo
cargo install dotenv-linter

# Binary releases
# https://github.com/dotenv-linter/dotenv-linter/releases

Native config: dotenv-linter has no config file; behavior is controlled entirely via CLI flags (surfaced through --tool-options).

Lintro options via --tool-options:

# Recursively scan directories for .env files
lintro check --tools dotenv_linter --tool-options "dotenv_linter:recursive=True"

# Skip specific checks (maps to --ignore-checks)
lintro check --tools dotenv_linter \
  --tool-options "dotenv_linter:skip_checks=LowercaseKey|UnorderedKey"

# Exclude paths from linting
lintro check --tools dotenv_linter --tool-options "dotenv_linter:exclude=vendor"

# Validate against a schema file
lintro check --tools dotenv_linter --tool-options "dotenv_linter:schema=env.schema.json"

# Auto-fix issues in place (no .env.bak backups are created)
lintro format --tools dotenv_linter

Available Options:

OptionTypeDescription
recursiveboolRecursively scan directories for .env files. Default False
excludelist[str]File or directory paths to exclude
skip_checkslist[str]Check names to bypass (maps to --ignore-checks)
schemastrPath to a schema file to validate .env contents

Note: real .env files are frequently .gitignored, so they may not be discovered during a normal repository scan. Point Lintro at the file explicitly, or commit a template such as .env.example for the linter to check.

TOML Tools

Taplo Configuration

File: taplo.toml or .taplo.toml

# Taplo configuration
[formatting]
align_entries = false
align_comments = true
array_trailing_comma = true
array_auto_expand = true
array_auto_collapse = true
compact_arrays = true
compact_inline_tables = false
column_width = 80
indent_tables = false
indent_entries = false
indent_string = "  "
trailing_newline = true
reorder_keys = false
allowed_blank_lines = 2
crlf = false

[[rule]]
# Apply to all TOML files
include = ["**/*.toml"]
keys = ["Cargo.toml"]

[rule.formatting]
reorder_keys = true

Available Options:

OptionTypeDescription
schemastringPath or URL to JSON schema for validation
aligned_arraysbooleanAlign array entries vertically
aligned_entriesbooleanAlign table entries (key = value)
array_trailing_commabooleanAdd trailing comma in multi-line arrays
indent_stringstringIndentation string (default: 2 spaces)
reorder_keysbooleanReorder keys alphabetically

Usage Examples:

# Basic TOML check
lintro check --tools taplo

# Format TOML files
lintro format --tools taplo

# Check with aligned entries
lintro check --tools taplo --tool-options taplo:aligned_entries=true

# Format with specific indent
lintro format --tools taplo --tool-options taplo:indent_string="    "

# Use custom schema for validation
lintro check --tools taplo --tool-options taplo:schema=pyproject.schema.json

Infrastructure Tools

Hadolint Configuration

File: .hadolint.yaml

ignored:
  - DL3008 # Pin versions in apt-get install
  - DL3009 # Delete apt-get lists
  - DL3015 # Avoid additional packages

trustedRegistries:
  - docker.io
  - gcr.io

allowedRegistries:
  - docker.io
  - gcr.io
  - quay.io

Inline ignoring:

# hadolint ignore=DL3008
RUN apt-get update && apt-get install -y \
    python3 \
    python3-pip

Actionlint Configuration

Actionlint validates GitHub Actions workflows. Lintro discovers workflow files under /.github/workflows/ when you run lintro check . and invokes the actionlint binary.

  • Discovery: YAML files filtered to those in /.github/workflows/
  • Defaults: Lintro does not pass special flags; native actionlint defaults are used
  • Local install: use scripts/utils/install-tools.sh --local to place actionlint on PATH
  • Docker/CI: the Docker image installs actionlint during build, so CI tests run it
# Validate workflows only
lintro check --tools actionlint

# Validate workflows along with other tools
lintro check --tools ruff,actionlint

Git Tools

Commitlint Configuration

Commitlint validates git commit messages against the Conventional Commits specification. Unlike file-based tools, it inspects git state: Lintro runs commitlint --last to validate the repository’s most recent commit message.

  • Requires a config; Lintro skips it as a non-error when none is present.
  • Install: bun add -D @commitlint/cli @commitlint/config-conventional or npm install -D @commitlint/cli @commitlint/config-conventional. A project devDependency is preferred; a global or Homebrew install is used when there is no local one. See Node.js Tool Resolution.
  • Cannot auto-fix — amend the commit to satisfy the rules.

File: commitlint.config.js (or .commitlintrc.{js,cjs,json,yaml,yml}, or a commitlint key in package.json)

// commitlint.config.js
module.exports = { extends: ['@commitlint/config-conventional'] };

Available --tool-options:

OptionTypeDefaultDescription
timeoutint30Max seconds to wait for commitlint
# Validate the latest commit message
lintro check --tools commitlint

# Increase the timeout
lintro check --tools commitlint --tool-options "commitlint:timeout=60"

Project-Specific Configuration

Multi-Language Projects

For projects with multiple languages, organize configuration by component:

project/
├── .lintro.toml              # Lintro-specific config
├── pyproject.toml            # Python tools
├── .prettierrc               # JavaScript/CSS
├── .yamllint                 # YAML files
├── .hadolint.yaml           # Docker files
├── frontend/
│   └── .prettierrc          # Frontend-specific overrides
└── backend/
    └── pyproject.toml       # Backend-specific overrides

Lintro Project Configuration

File: .lintro.toml (future feature)

[lintro]
default_tools = ["ruff", "pydoclint", "prettier", "yamllint"]
table_format = true
group_by = "auto"
exclude_patterns = ["migrations", "node_modules", "dist"]

[lintro.timeouts]
default = 30
pydoclint = 45
prettier = 60

[lintro.paths]
python = ["src/", "tests/"]
javascript = ["frontend/", "assets/"]
yaml = [".github/", "config/"]
docker = ["Dockerfile*", "docker/"]

[lintro.output]
format = "table"
save_to_file = true
file_prefix = "lintro-report"

Output System: Auto-Generated Reports

Lintro now generates all output formats for every run in a timestamped directory under .lintro/ (e.g., .lintro/run-20240722-153000/).

You do not need to specify output format or file options. Each run produces:

  • console.log: The full console output
  • results.json: Machine-readable results
  • report.md: Human-readable Markdown report
  • report.html: Web-viewable HTML report
  • summary.csv: Spreadsheet-friendly summary

This ensures you always have every format available for your workflow, CI, or reporting needs.

AI Configuration

Lintro includes optional AI-powered features for actionable summaries and interactive fix suggestions. See the full AI Features Guide for detailed usage.

Quick Setup

# Install AI dependencies (published package)
uv pip install 'lintro[ai]'
# Or from source checkout:
uv sync --extra ai

# Set API key for your configured provider
# Anthropic (default): ANTHROPIC_API_KEY
# OpenAI:              OPENAI_API_KEY
# Custom:              set ai.api_key_env in config to use any env var name
export ANTHROPIC_API_KEY=sk-ant-...
# .lintro-config.yaml
ai:
  enabled: true
  lint: true # AI summaries / --fix on chk/fmt
  review: false # lintro review (opt-in separately)
  provider: anthropic

AI CLI Flags

FlagEffect
(none)AI summary only (1 API call)
--fixAdd interactive AI fix suggestions

Config Defaults for Flags

Set default_fix to avoid typing the flag every time:

ai:
  enabled: true
  lint: true
  review: true
  default_fix: false # only run --fix when explicitly requested

Full AI Config Reference

SettingTypeDefaultDescription
enabledboolfalseMaster switch; ANDs with lint / review
lintboolfalseEnable AI lint summaries on chk/fmt
reviewboolfalseEnable the lintro review AI diff review
providerstringanthropicAI provider (anthropic or openai)
modelstring(default)Model override
api_key_envstring(default)Custom env var for API key
default_fixboolfalseAlways run --fix in check
auto_applyboolfalseApply fixes without confirmation
auto_apply_safe_fixesbooltrueAuto-apply safe-style fixes in non-interactive
max_tokensint4096Max tokens per request
max_fix_attemptsint20Max issues to attempt fixing per run
max_parallel_callsint5Concurrent AI calls (1-20); honored with a cost cap; n−1 overshoot possible
max_retriesint2Max retries for transient errors (0-10)
max_cost_usdfloatnullLegacy USD cap; prefer profiles. Overlay 0 = uncapped (YAML 0 is $0)
api_timeoutfloat60.0Legacy timeout (s); prefer transports.*.timeout
transportsobjectempty profilesPer-transport profiles (api / cli) — see AI review transports
validate_after_groupboolfalseValidate immediately after each accepted group
show_cost_estimatebooltrueShow token/cost info in output
context_linesint15Lines of context sent for fix generation (1-100)
fix_search_radiusint5Line search radius for fix application (1-50)
checkpoint_retentionint10Git checkpoint refs kept (>=0; 0 = current only)
checkpoint_fmtboolfalseGit checkpoint before lintro format mutations
retry_base_delayfloat1.0Initial retry delay in seconds (min 0.1)
retry_max_delayfloat30.0Maximum retry delay in seconds (min 1.0)
retry_backoff_factorfloat2.0Retry delay multiplier (min 1.0)
transcript_loggingboolfalseOpt-in NDJSON logging of AI provider traffic
transcript_retentionint10Max transcript files kept under .lintro-cache

Idiom Review Tool (idiom-review)

The idiom-review tool uses AI to find issues that syntax-matching linters cannot: code that is syntactically correct but non-idiomatic or redundantly duplicated across files. Unlike the AI summary and --fix flows, it is a first-class ToolDefinition plugin. It is classified advisory, so it runs under lintro review — never under lintro check or lintro format, whose findings must stay deterministic (#1308).

Install:

uv pip install 'lintro[ai]'
export ANTHROPIC_API_KEY=sk-ant-...   # or OPENAI_API_KEY for OpenAI

The tool is disabled by default and is a no-op until explicitly opted in. When no AI provider is available (missing SDK, key, or credits), it degrades gracefully to a skipped result rather than failing the run. Findings are cached by content hash under .lintro-cache/idiom, so unchanged files cost nothing on repeat runs.

Options:

OptionTypeDefaultDescription
enabledboolfalseOpt-in gate — must be true to run
modestringper-fileper-file · duplication · both
min_confidencestringmediumDrop findings below this level (low/medium/high)
max_filesint25Cap on files reviewed per run (cost bound)
languagestringpythonLanguage to review; set explicitly for other languages

Modes:

  • per-file — flags idiomatic misses per file (e.g. verbose loops instead of any()/all() comprehensions).
  • duplication — flags the same utility logic reimplemented across files, invisible to per-file linters, with a suggested extraction point.
  • both — runs both modes in one pass.

Usage example:

# .lintro-config.yaml
ai:
  enabled: true
  provider: anthropic
  transport: api
tools:
  idiom-review:
    options:
      enabled: true # opt-in gate (default: false)
      mode: per-file # per-file | duplication | both
      min_confidence: medium
      max_files: 25 # cap files reviewed per run (cost bound)

Or enable ad hoc from the CLI without modifying config:

lintro review --advisory-only --tool-options idiom-review:enabled=true

Advanced Configuration

Tool Conflicts and Priorities

Some tools may conflict with each other. Lintro handles this by:

  1. Priority system - Higher priority tools run first
  2. Conflict detection - Warns about conflicting tools
  3. Auto-resolution - Chooses the best tool for each task
# Check for conflicts
lintro list-tools --show-conflicts

# Force conflicting tools to run
lintro check --tools ruff,black --ignore-conflicts

Performance Optimization

Large Codebases

# Use specific tools for faster checks
lintro check --tools ruff

# Process directories separately
lintro check src/ --tools ruff,pydoclint
lintro check tests/ --tools ruff

# Exclude heavy directories
lintro check --exclude "venv,node_modules,migrations"

CI/CD Optimization

# Fast checks for PR validation
lintro check --tools ruff

# Full analysis for main branch
lintro check --all --output full-report.txt

Custom Output Formats

JSON Output (planned)

lintro check --output-format json --output results.json
{
  "summary": {
    "total_issues": 15,
    "tools_run": ["ruff", "pydoclint"],
    "files_checked": 42
  },
  "issues": [
    {
      "file": "src/main.py",
      "line": 12,
      "column": 5,
      "tool": "ruff",
      "code": "F401",
      "message": "'os' imported but unused",
      "severity": "error"
    }
  ]
}

Markdown Output (planned)

lintro check --output-format markdown --output QUALITY_REPORT.md

Integration Patterns

Pre-commit Hooks

File: .pre-commit-config.yaml

repos:
  - repo: local
    hooks:
      - id: lintro-check
        name: Lintro Quality Check
        entry: lintro check --output-format grid
        language: system
        pass_filenames: false
        stages: [commit]

      - id: lintro-fix
        name: Lintro Auto-fix
        entry: lintro format --output-format grid
        language: system
        pass_filenames: false
        stages: [commit]

Makefile Integration

.PHONY: lint fix check quality install-tools

# Quality checks
lint:
	lintro check

fix:
	lintro format

check: lint
	@echo "Quality check completed"

# Comprehensive quality report
quality:
	lintro check --all --output quality-report.txt
	@echo "Full quality report saved to quality-report.txt"

# Tool installation
install-tools:
	pip install ruff pydoclint
	npm install -D prettier

IDE Integration

VS Code Settings

File: .vscode/settings.json

{
  "python.linting.enabled": false,
  "python.formatting.provider": "none",
  "editor.formatOnSave": false,
  "editor.codeActionsOnSave": {
    "source.organizeImports": false
  },
  "files.associations": {
    ".lintro.toml": "toml"
  }
}

File: .vscode/tasks.json

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Lintro Check",
      "type": "shell",
      "command": "lintro",
      "args": ["check", "--output-format grid"],
      "group": "test",
      "presentation": {
        "reveal": "always",
        "panel": "new"
      },
      "problemMatcher": []
    },
    {
      "label": "Lintro Fix",
      "type": "shell",
      "command": "lintro",
      "args": ["format", "--output-format grid"],
      "group": "build"
    }
  ]
}

Troubleshooting Configuration

Common Issues

1. Tool not respecting configuration:

# Check if config file is found
lintro check --tools ruff --verbose

# Verify config file syntax
ruff check --show-settings

2. Conflicting configurations:

# Check for multiple config files
find . -name "*.toml" -o -name ".ruff*" -o -name "setup.cfg"

# Use specific config
ruff check --config custom-ruff.toml

3. Performance issues:

# Profile tool execution
time lintro check --tools ruff --output-format grid

# Use more specific file patterns
lintro check "src/**/*.py" --tools ruff --output-format grid

Debug Configuration

# Enable verbose output
lintro check --verbose --output-format grid

# Check tool availability
lintro list-tools

# Test individual tools
ruff check src/
pydoclint src/main.py
prettier --check package.json

This comprehensive configuration guide should help you customize Lintro to fit your project’s specific needs and integrate seamlessly into your development workflow!