Skip to content

3.0.0 - 2026-09-03

Choose a tag to compare

@github-actions github-actions released this 04 Sep 03:49

Release Notes

Added

  • Thirteen more registered languages: Shell, Swift, C, C++, C#, Ruby, PHP, Vue,
    Svelte, Terraform, Elixir, SQL and Docker. Each brings the deterministic tool
    its ecosystem actually ships - shellcheck, swiftlint, cppcheck, dotnet format,
    rubocop, phpcs, tflint, credo, sqlfluff, hadolint - with Vue and Svelte
    routed to the eslint the project already configures. Every tool's output
    shape was captured from the real binary rather than from documentation, and
    tests/real_tool_output.rs holds those captures against the shipped specs, so
    a tool changing its format fails there instead of silently reporting every
    file clean.
  • A config_files entry may be a *.ext glob, matching any file in the
    directory with that extension. C# is the first language whose workspace is
    identified by a glob rather than a fixed name: MSBuild has to run from the
    directory holding the .csproj or .sln, and those are named after the
    project. Only a leading *. is a glob, so every existing literal marker
    still costs one exists rather than a directory read.
  • A language can claim a whole file name, not only an extension.
    Path::extension answers None for Dockerfile, Gemfile and Rakefile,
    so an extension-only registry dropped them ahead of both analysis layers -
    the same silent pass unregistered .java produced, on files most
    repositories have. Extension is matched first, so a name can never shadow a
    language that claims the suffix and Dockerfile.ts stays TypeScript.
  • filename_prefixes, claiming a name's dotted variants.
    Dockerfile.dev/Dockerfile.prod/Containerfile.web are an unbounded
    family an exact list cannot cover, and unclaimed they were dropped from
    walk, staged and diff modes exactly as Dockerfile itself once was. The
    extension lookup still runs first, and a variant must carry a non-empty
    suffix after the dot, so Dockerfile. itself is not claimed.
  • A registry well-formedness test over every language, asserting the
    contracts the type system cannot state: dot-prefixed single-suffix ASCII
    extensions, non-empty command and timeout on every tool, no config_flag
    without config_files, and single-component vendored_dirs. Each of
    those was once broken silently - a vendor/bundle entry that could never
    match anything was the one that existed on disk.
  • .shellcheckrc at the repository root, opting drep's own scripts/ into
    shellcheck through its own gate. shellcheck is now a required install for
    committing script changes here.

Changed

  • parsers.rs is a directory module with one submodule per output shape
    rather than per tool. Five of the checkers drep now ships emit SARIF, so they
    share one parser and a sixth SARIF producer costs a table entry instead of a
    parser. definitions.rs is split by ecosystem for the same reason: both
    files were near the 600-line limit with thirteen languages due.
  • ToolSpec.output_format and diagnostics_stream are fieldless enums
    rather than strings. A typo'd format failed only at runtime when the tool
    ran, and a typo'd stream silently selected stdout; the dispatch match and
    the stream selection are now exhaustive at compile time.
  • ALL_LANGUAGES is concatenated from each ecosystem file's own FAMILY
    slice rather than maintained as one hand-written list, so a language
    defined but never registered is invisible to no one: it would sit
    unregistered in the file that defines it. The registration order now keeps
    each family together, which moves Vue and Svelte up beside TypeScript in
    doctor's listing.
  • cppcheck runs with --error-exitcode=2. It otherwise exits 0 with
    findings, leaving the runner's exit-status guard inoperative for it: a
    cppcheck release that moved or renamed its SARIF stream would have read as
    a permanent clean pass.
  • tflint runs with --recursive, because bare tflint lints only the module
    in its working directory and a commit touching modules/*/ produced no
    findings while passing the layer. Verified against the real binary: the
    recursive run descends, reports each finding's uri relative to the
    invocation directory, and exits non-zero when any module has findings.
    Each module's config is its own - a nested module without .tflint.hcl is
    linted under tflint's defaults rather than the root config.
  • The canonical spelling of a path is resolved only after the exact spelling
    has missed, on both sides of the narrowing. Every resolution is a
    realpath, and under an ordinary checkout nothing ever misses, so building
    the canonical index up front spent one syscall per finding and per planned
    file on an answer nothing read.
  • One lookup answers "which config file is present here" for both the
    eligibility check and config_flag, so the two cannot disagree about what
    counts as present.

Fixed

  • A failed dotnet format run reported every C# file clean, and the same
    hole existed for go vet and tsc. Their parsers skip lines they do not
    recognise by design, so a run whose every line is an error of a shape the
    parser does not know - MSBuild's position-less
    x.csproj : error NETSDK1004 on an un-restored checkout, a rejected tsc
    option - parsed as zero findings on a non-empty stream with a non-zero
    exit, which the empty-stream guard cannot see. A non-zero exit with zero
    parsed findings is now Unavailable for the three skipping formats, with
    the unmatched lines as the detail. The JSON-shaped parsers already error
    on such output.
  • A tflint error run reported every Terraform file clean. tflint reports
    runtime errors - plugins never installed, arguments it dropped - as a
    SARIF tflint-errors run whose results carry no location (verified
    against the real binary); parsed as a finding, the empty path matched
    nothing the run was asked about and was narrowed away. A SARIF result with
    no usable location is now rejected with the tool's own message as the
    detail, so the run is Unavailable. A physicalLocation with no region
    remains a real finding and still defaults to line 1, and the empty
    tflint-errors run a healthy tflint emits stays clean.
  • On a symlinked checkout, a whole-project tool's findings were dropped or
    kept their resolved spelling. The child process runs with
    current_dir(workspace_root), so a tool deriving paths from its cwd
    spells them through resolved symlinks - or emits ../..-carrying
    relatives, as tflint --recursive does - while drep's own paths keep the
    spelling it was given. retain_requested and the run_one rewrite now
    compare through one shared resolver in both the exact and the canonical
    form, instead of each keeping its own slightly different normalisation.
  • shellcheck, rubocop, credo, sqlfluff and ktlint findings with no filename
    rendered as :1:1: ... instead of falling back to the checked file's name
    the way the ruff/eslint parser does.
  • eslint findings all displayed as errors: the per-message severity (1 or
    2) was never read. Same display-truth class as the tsc fix, and just as
    invisible to the gate.
  • A *.csproj glob marker matched a directory named Widget.csproj,
    which would run dotnet format one level above the project it was meant
    to find. Glob markers now match files only.
  • The MSBuild suffix strip ate any trailing bracketed token, project name or
    not, so a message legitimately ending in [field] lost it. Only a
    bracketed project-file path is stripped now.
  • Tool messages reached the terminal raw: the JSON reporters decode \u001b
    escapes faithfully, so a crafted diagnostic carried a working terminal
    escape into the report, and a multi-kilobyte message pushed the actionable
    findings off the screen. The finding line now goes through
    text::excerpt like every other text drep did not write. A tool's own
    stdout and stderr reach the detail field the same way now: that path
    bounded bytes and stripped nothing, so an escape sequence in a tool's
    stderr still reached the terminal through doctor and the failure block.
  • A tool declaring a *.ext glob marker alongside config_flag would have
    been judged configured by the glob and then run without the config it
    cannot start without, because the flag looked its markers up as literal
    paths. Latent rather than live: dotnet format has the glob markers and
    checkstyle has the flag.
  • Ruby's vendored_dirs listed vendor/bundle, dead data the ignore check
    could never match because it compares single path components. Elixir's
    listed deps, which joined the global ignore union and would have
    silently unwalked a checked-in deps/ of real source in a non-Elixir
    repository - the hazard the JVM family already excludes out for.
  • A whole-project tool that reports absolute paths had every finding dropped.
    retain_requested compared reported paths against the caller's list as
    strings, and that list is workspace-relative because plan_tasks builds each
    argument by stripping the workspace root - so dotnet format, which prints
    the absolute path on every diagnostic line, matched nothing and every C# file
    came back clean. Both sides are now compared as absolute paths. tsc and
    clippy were unaffected because both answer relative.
  • go vet diagnostics on Windows were silently dropped. The position parser's
    file group forbade a colon, so an absolute path like C:\src\main.go:12:6:
    never matched, and because that parser skips what it cannot match the run
    reported clean. The group is lazy now, resolved by the mandatory
    :line:col: suffix, and a Go package header still fails to match.
  • cargo diagnostics all displayed as errors. A clippy warning rendered as an
    error because the severity was hardcoded rather than read from the message's
    level. The gate is unaffected either way, since any deterministic finding
    blocks whatever its severity, but the line the user reads now says what the
    compiler said.
  • parse_lines built its rewrite suggestion as command[..len - 1], which
    underflows on a usize when the argv is empty. parse_output is public, so
    a spec naming that format with no command panicked the gate rather than
    reporting a finding. A spec with no rewrite command to name now gets no
    suggestion.
  • tsc warnings were reported as errors. The regex has always matched warning
    alongside error while the parser assigned Severity::Error to every match,
    so the rendered line called a warning something the compiler never said. The
    gate was unaffected - a tool finding blocks whatever its severity - but the
    severity word is now read.
  • json_kind_name was untested: the error for a non-array tool payload
    asserted only the kind it wanted, so the half naming what actually arrived
    passed under any answer and two mutants survived on it.

Install drep-ai 3.0.0

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/slb350/drep/releases/download/v3.0.0/drep-ai-installer.sh | sh

Install prebuilt binaries via Homebrew

brew install slb350/tap/drep

Download drep-ai 3.0.0

File Platform Checksum
drep-ai-aarch64-apple-darwin.tar.xz Apple Silicon macOS checksum
drep-ai-x86_64-apple-darwin.tar.xz Intel macOS checksum
drep-ai-aarch64-unknown-linux-gnu.tar.xz ARM64 Linux checksum
drep-ai-x86_64-unknown-linux-gnu.tar.xz x64 Linux checksum