import ShellcheckCompatRepoTable from "@/app/components/docs/ShellcheckCompatRepoTable";

# ShellCheck Compatibility

Shuck can expose a ShellCheck-shaped CLI for teams that want to migrate existing editor integrations, CI jobs, or local workflows without changing every call site at once.

This mode is powered by Shuck's parser and linter, not by embedding ShellCheck itself. That means the interface is intentionally familiar, while the implementation continues to evolve with the rest of Shuck.

## Activate compatibility mode

There are two supported ways to turn the compatibility surface on:

1. Invoke the binary as `shellcheck`.
2. Set `SHUCK_SHELLCHECK_COMPAT=1` before running `shuck`.

```bash
# Make shuck available as `shellcheck`
ln -s "$(command -v shuck)" ~/bin/shellcheck
shellcheck --version

# Or force compat mode for a single command
SHUCK_SHELLCHECK_COMPAT=1 shuck --version
```

Compat mode only affects that invocation. Running `shuck check ...` directly still uses Shuck's native CLI.

## What it supports today

The compatibility surface currently covers the pieces that are most useful for migration:

- Familiar `--help` and `--version` output
- Common output formats: `tty`, `json`, `json1`, `checkstyle`, `gcc`, `diff`, and `quiet`
- ShellCheck-style code filtering with `--include` and `--exclude`
- Severity filtering with `--severity`
- Source resolution controls like `--check-sourced`, `--external-sources`, and `--source-path`
- Shell overrides for `sh`, `bash`, `dash`, `ksh`, and `busybox`
- Optional-check discovery with `--list-optional`
- Standard stdin usage with `-` as the input path

```bash
# Machine-readable output for editor tooling or CI
SHUCK_SHELLCHECK_COMPAT=1 shuck -f json1 script.sh

# Only keep selected SC codes
SHUCK_SHELLCHECK_COMPAT=1 shuck --include=SC2086,SC2154 script.sh

# Follow sourced files and add extra lookup roots
SHUCK_SHELLCHECK_COMPAT=1 shuck -a -P scripts:lib main.sh
```

## `.shellcheckrc` support

Compat mode searches for `.shellcheckrc` from the current working directory upward. You can keep the default search, skip it with `--norc`, or point at a specific file with `--rcfile`.

Supported config keys today are `check-sourced`, `color`, `disable`, `enable`, `extended-analysis`, `external-sources`, `format`, `include`, `severity`, `shell`, `source-path`, and `wiki-link-count`.

```bash
# Use the nearest .shellcheckrc
shellcheck script.sh

# Ignore discovered config
shellcheck --norc script.sh

# Use an explicit config file
shellcheck --rcfile ci/shellcheckrc script.sh
```

<h2 id="rules-lint">Use Shuck with rules_lint</h2>

If your Bazel repo already uses [`rules_lint`](https://github.com/aspect-build/rules_lint) for shell scripts, you can swap Shuck in without rewriting the lint aspect itself. The trick is to keep exposing a `shellcheck` executable target and let Shuck handle the CLI compatibility layer behind that name.

One workable pattern is:

1. Fetch the platform-specific Shuck release artifacts.
2. Re-export each downloaded binary as an executable named `shellcheck`.
3. Keep `lint_shellcheck_aspect` pointed at your `//tools:shellcheck` target.

```bzl
# repositories.bzl
def shuck_deps():
    SHUCK_VERSION = "<shuck-version>"

    http_archive(
        name = "shuck",
        build_file = "//tools:shuck.BUILD.bazel",
        sha256 = "<linux-x86_64-sha256>",
        strip_prefix = "shuck-cli-x86_64-unknown-linux-gnu",
        urls = ["https://github.com/ewhauser/shuck/releases/download/v{}/shuck-cli-x86_64-unknown-linux-gnu.tar.xz".format(SHUCK_VERSION)],
    )

    http_archive(
        name = "shuck_linux_arm64",
        build_file = "//tools:shuck.BUILD.bazel",
        sha256 = "<linux-arm64-sha256>",
        strip_prefix = "shuck-cli-aarch64-unknown-linux-gnu",
        urls = ["https://github.com/ewhauser/shuck/releases/download/v{}/shuck-cli-aarch64-unknown-linux-gnu.tar.xz".format(SHUCK_VERSION)],
    )

    http_archive(
        name = "shuck_osx",
        build_file = "//tools:shuck.BUILD.bazel",
        sha256 = "<darwin-arm64-sha256>",
        strip_prefix = "shuck-cli-aarch64-apple-darwin",
        urls = ["https://github.com/ewhauser/shuck/releases/download/v{}/shuck-cli-aarch64-apple-darwin.tar.xz".format(SHUCK_VERSION)],
    )
```

```bzl
# tools/shuck.BUILD.bazel
package(default_visibility = ["//visibility:public"])

genrule(
    name = "shellcheck_bin",
    srcs = ["shuck"],
    outs = ["shellcheck"],
    cmd = "cp $(location shuck) $@",
    executable = True,
)

filegroup(
    name = "file",
    srcs = [":shellcheck_bin"],
)
```

```bzl
# tools/BUILD.bazel
alias(
    name = "shellcheck",
    actual = select({
        "@bazel_tools//src/conditions:linux_x86_64": "@shuck//:shellcheck_bin",
        "@bazel_tools//src/conditions:linux_aarch64": "@shuck_linux_arm64//:shellcheck_bin",
        "@bazel_tools//src/conditions:darwin_arm64": "@shuck_osx//:shellcheck_bin",
    }),
    visibility = ["//visibility:public"],
)
```

For `rules_lint`, that alias should resolve to the executable target rather than a forwarding `filegroup`.

```bzl
# tools/lint.bzl
load("@aspect_rules_lint//lint:lint_test.bzl", "lint_test")
load("@aspect_rules_lint//lint:shellcheck.bzl", "lint_shellcheck_aspect")

shellcheck = lint_shellcheck_aspect(
    binary = "//tools:shellcheck",
    config = "//:.shellcheckrc",
)

shellcheck_test = lint_test(aspect = shellcheck)
```

That keeps your existing `rules_lint` entry point, target names, and `.shellcheckrc` file in place while swapping in Shuck as the implementation. In practice, the migration boundary stays at the tool label instead of spreading through every lint target in the repo.

## Migration notes

- Exit status follows the usual ShellCheck shape: `0` for no diagnostics, `1` when diagnostics are reported, and `2` for file or runtime errors.
- Diagnostic codes, suppressions, and filters use `SCxxxx` identifiers, so existing `# shellcheck disable=...` comments continue to work.
- `--list-optional` is available, but not every named optional check is implemented yet. Treat that catalog as a compatibility target rather than a guarantee that every check can already be enabled.
- If you want Shuck-only features such as the native multi-dialect workflow or automatic fixes, use the regular [`shuck check` docs](/docs/linting) instead of the compatibility surface.

## Repository conformance

This table tracks the curated large-corpus repositories that Shuck uses for ShellCheck compatibility work. Green means the checked-in corpus metadata has no known reviewed repo issues. Yellow means the repo currently has known reviewed divergences or is skipped because ShellCheck does not support it in the corpus harness.

<ShellcheckCompatRepoTable />
