shuck

Zsh Support

Shuck supports zsh as a first-class shell dialect, including common startup files and zsh-specific plugin environments.

This guide focuses on the zsh-specific pieces that are easy to miss:

  • which files Shuck treats as zsh automatically;
  • how plugin resolution works for common oh-my-zsh setups;
  • when to add config for dynamic plugin lists or custom layouts.

Automatic file detection

Shuck already treats these as shell inputs without requiring a shebang:

  • *.zsh
  • .zshrc
  • .zshenv
  • .zprofile
  • .zlogin
  • .zlogout

If your zsh files use a custom name, tell Shuck explicitly with per-file-shell:

[lint]
per-file-shell = { "dotfiles/zsh/**" = "zsh" }

See the Configuration guide and Settings Reference for the surrounding config surface.

What works automatically

Shuck can resolve real plugin entrypoints for common static oh-my-zsh configs.

For example, this works without extra config when the framework root and plugin list are statically visible:

ZSH="$HOME/.oh-my-zsh"
plugins=(git docker zsh-autosuggestions)
ZSH_THEME=agnoster
source "$ZSH/oh-my-zsh.sh"

In that shape, Shuck resolves:

  • plugins from $ZSH/plugins/<name>/<name>.plugin.zsh
  • built-in themes from $ZSH/themes/<name>.zsh-theme

Those resolved files are summarized through the normal semantic source-closure pipeline, so their functions, bindings, and reads become available to the analyzed zsh file.

That still only covers what Shuck can recover from real source files. If your zsh setup also relies on framework conventions or settings that are consumed outside the current file, use Contracts to describe that extra behavior.

When to add config

The source file is still the preferred source of truth. Add config when your real zsh setup is harder to recover statically.

Machine-local framework roots

If the file hides the framework root behind machine-local setup, add a root mapping:

[lint.zsh.plugins]
roots = { oh-my-zsh = "~/.oh-my-zsh" }

Dynamic plugin lists

If the file computes plugins=(...) dynamically, declare the logical plugin loads you want Shuck to bring into scope:

[lint.zsh.plugins]
plugin-loads = [
  { pattern = "**/.zshrc", framework = "oh-my-zsh", name = "git" },
  { pattern = "**/.zshrc", framework = "oh-my-zsh", name = "docker" },
]

Theme loads follow the same pattern:

[lint.zsh.plugins]
theme-loads = [
  { pattern = "**/.zshrc", framework = "oh-my-zsh", name = "agnoster" },
]

Custom layouts

If your setup does not match the built-in framework layout, attach raw entrypoints directly:

[lint.zsh.plugins]
entrypoints = [
  { pattern = "**/.zshrc", paths = ["./vendor/prompt/prompt.plugin.zsh"] },
]

Contracts for framework behavior

Plugin resolution tells Shuck which files to import. Contracts tell Shuck about the surrounding runtime behavior that those files do not fully expose.

For example, an oh-my-zsh plugin can consume settings from .zshrc even when those settings are not read lexically in the startup file itself:

[[lint.contracts.custom]]
id = "oh-my-zsh-tmux"
when = { type = "zsh_plugin", framework = "oh-my-zsh", plugin = "tmux" }
files = ["**/.zshrc"]
consumes = { prefixes = ["ZSH_TMUX_"] }

See the Contracts guide for the broader model and the Settings Reference for the exact key shape.

CLI equivalents

Everything under [lint.zsh.plugins] also has a matching shuck check flag.

The most useful ones are:

  • --zsh-plugin-resolution and --no-zsh-plugin-resolution
  • --zsh-plugin-root and --extend-zsh-plugin-root
  • --zsh-plugin and --extend-zsh-plugin
  • --zsh-theme and --extend-zsh-theme
  • --zsh-plugin-entrypoint and --extend-zsh-plugin-entrypoint

Example:

shuck check \
  --zsh-plugin-root 'oh-my-zsh=~/.oh-my-zsh' \
  --zsh-plugin '**/.zshrc:oh-my-zsh:git' \
  --zsh-theme '**/.zshrc:oh-my-zsh:agnoster' \
  .

Use the replace forms when you want to set the full value at the command line, and the extend- forms when you want to add on top of config that is already present.

Current scope

Today the built-in resolver is intentionally conservative.

Supported directly:

  • static oh-my-zsh plugin resolution
  • static oh-my-zsh built-in theme resolution
  • explicit plugin and theme loads from config or CLI
  • explicit raw entrypoints from config or CLI

Not supported automatically:

  • executing framework loaders or plugin-manager DSLs
  • inferring plugin installs from managers such as zinit, zplug, or antigen
  • inventing plugin loads when the source and config do not identify them statically

When your setup falls outside the automatic cases, use plugin-loads, theme-loads, or entrypoints to describe only the pieces Shuck should import.