This repository exposes shared linter entrypoints intended to be called either:
- from this repository root while developing the checker library directly, or
- from a consumer repository root through the submodule path.
Note: When running shell scripts from the Visual Studio Code integrated
terminal, prefix with bash to use WSL or Git Bash (for example,
bash ./bin/run-linters.sh). Similarly, use python to invoke Python
scripts with the Windows Python instance.
The linter entrypoint script uses two roots:
- Library root: derived from the location of the script in this repository.
- Target root: the repository being checked.
By default, the target root is the current working directory. That means the same pattern works in both contexts:
./bin/run-linters.sh./code_checking/bin/run-linters.shBoth commands lint the repository in the current directory, not the directory that contains the shared checker scripts.
The default mode is changed.
In that mode the entrypoint script selects linters based on changed paths
only.
Selection order:
- If
--base-refis supplied, diff againstorigin/<base-ref>...HEAD. - Otherwise, use staged files if any exist.
- Otherwise, use unstaged changed files.
This keeps PR and pre-commit runs focused on the files being modified.
Use --mode full when you intentionally want a whole-repository lint pass,
such as periodic cleanup or baseline validation.
The changed-file linter set currently includes:
codespell(text spelling and typo checks, including filenames)text-hygiene(trailing whitespace and missing final newline)filename-portability(non-ASCII filename guard)shellcheck(shell script linting for*.sh)groovylint(Groovy and Jenkins DSL linting for*.groovy,*.gradle, andJenkinsfile*)markdownlint(Markdown linting for*.mdand*.markdown)yamllint(YAML linting for*.yml,*.yaml, and.yamllint)python(shared Python linting for*.pyusingflake8andpylint)copyright(copyright header check for script/source files)
Fix-capable checks currently support --fix:
verify-executable-modes(sets executable mode in git index for shebang files)text-hygiene(trailing whitespace and final newline fixes)copyright(missing header insertion)
When --fix is enabled, fixes are staged by default. Use --no-stage to
apply fixes without auto-staging.
Groovylint execution also includes a post-lint guard that rejects implicit
script-binding assignments (bare name = value at statement start) to prevent
Jenkins parallel-stage shared-state hazards.
Markdownlint is pinned to markdownlint-cli@0.39.0 to match the version
bundled by the VS Code vscode-markdownlint extension. Using the same version
ensures that rule suppressions in consumer .markdownlint.json files work
consistently between local editor checks and CI runs. markdownlint-cli is
installed via npm and auto-discovers a .markdownlint.json (or
.markdownlint.yaml) config file in the consumer repository root when present.
Current exclusions:
<actual-submodule-path>/*(the library code itself in consumer repos)
Consumer repositories can add this repository's hooks to their
.pre-commit-config.yaml:
repos:
- repo: https://github.com/daos-do/code-checking
rev: main
hooks:
- id: shellcheckLocal pre-commit runs will then use the changed-file mode automatically.
Before running selected linters, the entrypoint script verifies that
code_checking is
at the desired commit resolved from code-checking-ref (or origin/main
when the file is missing). The check is non-mutating and fails with sync
commands if the checkout does not match.
The entrypoint script also performs a centralized tool preflight check so
required linter executables are present before individual linter scripts run.
For local debugging of linter behavior from the repository root:
./bin/run-linters.shFor full pre-commit parity, run the tracked-ref guard first, then run linters:
./checks/guard-code-checking-ref.sh --target-root .
./bin/run-linters.shCurrent tool preflight mapping:
shellchecklinter requiresshellcheckexecutable on PATHcodespelllinter requirescodespellexecutable on PATHgroovylintlinter requiresnpm-groovy-linton PATH;npm-groovy-lintis installed via npm with an explicit version pin inchecks/install-linter-tools.shmarkdownlintlinter requires themarkdownlintCLI on PATH;markdownlint-cliis installed via npm (see the markdownlint version note in the Current Linters section above)pythonlinter requires bothflake8andpylinton PATH and runs both tools against the same changed Python file setyamllintlinter requiresyamllinton PATHcopyrightlinter does not require an external executable and validates# Copyright <years> Hewlett Packard Enterprise Development LPheaders in candidate files
The copyright linter enforces the repository header format below for
candidate script/source files:
# Copyright <years> Hewlett Packard Enterprise Development LP
Where <years> supports compact lists/ranges such as 2024,2026-2027.
Current policy alignment:
- HPE baseline policy requires this notice for covered contribution content.
- Local group policy applies the notice consistently across covered files,
with
--fixavailable to insert missing headers.
For new shell variable names and labels, prefer underscore-separated words when possible so cspell can recognize components without whitelist growth.
Add words to the project dictionary only for stable domain terms that cannot be split naturally.
When adding new linters, use this installation policy:
- Prefer distro package manager installs for Linux hosts.
- Prefer native platform package managers on macOS (Homebrew).
- Use language package managers only as a fallback when no platform package convention exists.
- Avoid system-wide
pipinstalls on Linux (especiallysudo pip). - For Node-based linters, install the Node/npm runtime with the platform
package manager first, then install the linter CLI with
npm. On Ubuntu/WSL,apt-get install --reinstall nodejs npmis preferred because distro Node packages sometimes have a broken ELF interpreter path; the installer detects this and creates a loader-wrapper script automatically. - Keep tools CLI-accessible on PATH so both pre-commit and CI behavior are consistent.
For each new linter integration PR, include:
- setup/install guidance update in
bin/setup-dev.shanddocs/usage.md - preflight failure hints that match the chosen package manager convention
Adding a linter currently requires touching multiple files. That is intentional for now: installation, local runs, pre-commit behavior, and CI all need to stay explicit and reviewable.
For a new linter such as codespell, review and update these areas:
- Linter Selection
checks/detect-linters.shchecks/detect-linters.ps1
Add selection logic so the linter is chosen for the relevant changed files.
- Orchestration
bin/run-linters.shbin/run-linters.ps1
Add a dispatch case that calls the per-linter script.
- Per-linter executor
checks/linters/<linter>/run.shchecks/linters/<linter>/run.ps1
Implement the actual file filtering and tool invocation in run.sh.
Keep run.ps1 as a thin wrapper that delegates to run.sh via
checks/invoke-bash.ps1.
- CI environment
.github/workflows/checks.yml
Install the tool in the workflow job that runs the shared linter entrypoint.
- Pre-commit integration
.pre-commit-hooks.yaml.pre-commit-config.yaml
Usually the shared bin/run-linters.sh hook remains the same, but confirm the
hook metadata still matches the broadened linter set.
- Documentation
docs/linters.mddocs/usage.mdbin/setup-dev.sh/bin/setup-dev.ps1if install guidance changes there
Document what the linter checks, what tool must be installed, and any platform or packaging expectations.
- Optional config files
If the linter needs repository policy/config files, add them explicitly and document how they are copied or used by consumers.
Examples:
.shellcheckrc.yamllint.markdownlint.jsonc(allowdetails/summaryHTML for collapsible documentation notes via MD033allowed_elements)codespellignore/config files if needed
The codespell addition required updates to:
- detection scripts
- top-level entrypoint scripts
- tool preflight
- CI install step
- per-linter
run.shandrun.ps1 - linter documentation
That is normal under the current explicit model.
A fully automatic model where a linter is enabled by dropping one script into a directory is attractive, but it pushes complexity into the infrastructure.
The repository still needs to answer these questions explicitly:
- How is the linter selected from changed files?
- How is the required tool installed in CI?
- How do local users know what to install?
- How do preflight failures give the right install hint?
- Does the linter need config files or IDE integration?
Until those pieces are standardized further, explicit wiring is easier to review and usually easier to maintain.
Use global linter configuration files for repository-wide style standards where the team intentionally differs from tool defaults.
Examples of valid global policy use:
- readability-focused YAML comment alignment rules
- any other site-standard formatting policy that should apply consistently across many files
Do not use global suppression as a blanket false-positive workaround. When a finding is a false positive, or when the recommended rewrite hurts local readability, suppress it in the file where it occurs.
File-local suppression rules:
- place suppression as close as possible to the affected line/block
- include a short comment explaining why suppression is preferred over code rewrite
- keep scope narrow (line/block), not file-wide, unless unavoidable
General rule for all linters:
- prefer code fixes first
- use suppression only when the report is a false positive or when the suggested rewrite makes the code harder to read and maintain
ShellCheck-specific guidance:
- ShellCheck is a common case where a suggested rewrite can reduce clarity;
in that case use a local
shellcheck disable=...with a short rationale comment
For public repositories configure branch protection in consumer repositories to require the following status check before merging:
Basic Source checks— the single job that runs the guard, executable mode verification, and all linters.
This section describes the GitHub Actions and pre-commit check execution design and what each script or directory is responsible for.
- Consumer workflow checks out repository content with submodules.
- Consumer workflow optionally resolves
code-checking-refand checks out that ref incode_checking(otherwise usesorigin/main). - Consumer workflow runs
./code_checking/bin/run-linters.sh. - During that run, the entrypoint script verifies the current
code_checkingcheckout matches the desired ref, then performs changed-file linter selection and execution.
- Ensure your local
code_checkingcheckout is synced to its tracked branch before commit checks (normal flow:git submodule update --remote --init code_checking). - Attempt your commit with staged changes.
- Pre-commit resolves hook entry from this repository hook definition and runs applicable checks.
- If checks pass, the commit proceeds.
- If checks fail, fix issues manually or use auto-fix mode for supported bulk-cleanup cases.
- Retry checks from the command line with
./bin/run-linters.sh. - After checks pass, retry the commit.
Selected checks support --fix to automatically correct certain issues.
Use --fix from the command line:
./bin/run-linters.sh --fixBy default --fix also stages corrected files via git add. To apply fixes
without staging (for example to review changes before staging manually), add
--no-stage:
./bin/run-linters.sh --fix --no-stageCurrent fix coverage:
- Shell script executable mode bits (for files with a shebang)
- Trailing whitespace
- Missing final newline
- Copyright header insertion
By default, pre-commit hooks run without auto-fix (violations are reported but not corrected).
To enable auto-fix in your .pre-commit-config.yaml, add args: [--fix] to
the hook:
repos:
- repo: https://github.com/daos-do/code-checking
rev: main
hooks:
- id: shellcheck
args: [--fix]To enable auto-fix without auto-staging (user preference):
args: [--fix, --no-stage]When --fix is enabled without --no-stage, the hooks will:
- Auto-correct fixable issues and stage them via
git add - Exit with success if all issues were corrected
- Exit with failure if unfixable violations remain
bin/run-linters.(sh|ps1): top-level orchestration entrypoints for CI, local, and pre-commit runs.checks/ensure-code-checking-ref.(sh|ps1): non-mutating verification that checkout matchescode-checking-ref(ororigin/mainby default).checks/detect-linters.(sh|ps1): changed-file analysis and linter selection.checks/linters/<linter>/run.(sh|ps1): per-linter executors that apply file filtering and invoke the tool..pre-commit-hooks.yaml: exported hook definitions used by consumer repositories..pre-commit-config.yaml: local repository pre-commit configuration for development and validation.
Design goals:
- One stable invocation surface for CI and pre-commit.
- Non-mutating validation checks by default.
- Dynamic linter selection based on actual changed files.
- Consumer repositories do not need hardcoded submodule directory names.