diff --git a/.github/actionlint.yml b/.github/actionlint.yml new file mode 100644 index 0000000..2e9302b --- /dev/null +++ b/.github/actionlint.yml @@ -0,0 +1,4 @@ +# See: https://github.com/rhysd/actionlint/blob/main/docs/config.md +self-hosted-runner: + labels: + - ubuntu-latest-m diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6af4987..cf1a8fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,3 +42,28 @@ jobs: - name: Run Tests run: ./scripts/covgate.sh + + surface-lint: + timeout-minutes: 10 + name: surface-lint + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + # yamllint disable rule:line-length + - name: Lint YAML + uses: ibiqlik/action-yamllint@2576378a8e339169678f9939646ee3ee325e845c # v3.1.1 + with: + file_or_dir: . + config_file: .yamllint.yml + + - name: Lint Shell Scripts + uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0 + with: + scandir: ./ + ignore_paths: .agents + + - name: Lint GitHub Actions Workflows + uses: rhysd/actionlint@914e7df21a07ef503a81201c76d2b11c789d3fca # v1.7.12 + # yamllint enable rule:line-length diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0515917..cc8f185 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -26,12 +26,15 @@ jobs: go-version-file: ./go.mod - name: Initialize CodeQL + # yamllint disable rule:line-length uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4 with: languages: go - name: Autobuild + # yamllint disable rule:line-length uses: github/codeql-action/autobuild@38697555549f1db7851b81482ff19f1fa5c4fedc # v4 - name: Perform CodeQL Analysis + # yamllint disable rule:line-length uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4 diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..02c379b --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,21 @@ +# See: https://yamllint.readthedocs.io/en/stable/ + +extends: default + +rules: + document-start: + present: false + line-length: + level: warning + allow-non-breakable-inline-mappings: true + # GitHub Actions uses `on:` as a workflow trigger key; YAML 1.1 treats `on` + # as a truthy boolean alias. Disable key checking to avoid false positives. + truthy: + check-keys: false + # SHA-pinned action comments use a single space before `#`. Reduce the + # minimum from the default 2 to 1 to match the existing convention. + comments: + min-spaces-from-content: 1 + +ignore: | + .agents/ diff --git a/plans/completed/surface-linters.md b/plans/completed/surface-linters.md new file mode 100644 index 0000000..b23a6fa --- /dev/null +++ b/plans/completed/surface-linters.md @@ -0,0 +1,61 @@ +# Surface Linters for gotools + +## Goal + +Add non-Go surface linters (YAML, shell script, GitHub Actions) to the gotools repository, matching what was added to the core repository. + +## Background + +The core repository added surface linters in commits `3360e12`–`6ecf0d7`. The implementation consists of: +- `.yamllint.yml` — yamllint configuration +- `.github/actionlint.yml` — actionlint configuration (minimal) +- `scripts/lint-surface.sh` — runs yamllint, shellcheck, actionlint +- `scripts/install-lint-tools.sh` — installs the three linters +- `surface-lint` CI job in `.github/workflows/ci.yml` + +## Scope + +Repo: `gotools` (`/home/ben/miru/workbench3/gotools`) + +## Steps + +### 1. Add `.yamllint.yml` + +Create `.yamllint.yml` at repo root, adapted from core: +- Same rules (document-start, line-length, truthy, comments) +- Ignore `.agents/` instead of `.venv/` (gotools has `.agents/` subtree, no `.venv/`) +- No `tests/testdata/` ignore needed (gotools has no such dir) + +### 2. Add `.github/actionlint.yml` + +Create `.github/actionlint.yml` with minimal comment (no custom config needed). + +### 3. Add `scripts/lint-surface.sh` + +Copy from core verbatim — checks for yamllint, shellcheck, actionlint, then runs each. +Make executable. + +### 4. Add `scripts/install-lint-tools.sh` + +Copy from core verbatim — installs yamllint (via pip3/.venv), shellcheck (apt/brew), actionlint (go install). +Make executable. + +### 5. Update `.github/workflows/ci.yml` + +Add `surface-lint` job matching core: +- Checkout +- yamllint via `ibiqlik/action-yamllint` (SHA-pinned, same as core) +- shellcheck via `ludeeus/action-shellcheck` (SHA-pinned, same as core) +- actionlint via `rhysd/actionlint` (SHA-pinned, same as core) + +## Test Steps + +- `yamllint -c .yamllint.yml .` passes (run locally with yamllint installed) +- `shellcheck scripts/*.sh` passes +- `actionlint` passes on the workflows +- `scripts/lint-surface.sh` runs end-to-end without error (with tools installed) +- CI `surface-lint` job is syntactically valid (actionlint validates it) + +## Validation + +Preflight must report `clean` before a PR is opened. Run `./scripts/preflight.sh` and confirm no failures. diff --git a/scripts/install-lint-tools.sh b/scripts/install-lint-tools.sh new file mode 100755 index 0000000..41b81cb --- /dev/null +++ b/scripts/install-lint-tools.sh @@ -0,0 +1,67 @@ +#!/bin/sh +set -e + +usage() { + cat <&2 + usage >&2 + exit 1 + ;; + esac +done + +git_repo_root_dir=$(git rev-parse --show-toplevel) +cd "$git_repo_root_dir" + +# --- yamllint --- +echo "==> Installing yamllint..." +if [ -d ".venv" ]; then + .venv/bin/pip install --quiet yamllint +else + pip3 install --quiet yamllint +fi +echo " done." + +# --- shellcheck --- +echo "==> Installing shellcheck..." +if command -v apt-get >/dev/null 2>&1; then + sudo apt-get install -y shellcheck +elif command -v brew >/dev/null 2>&1; then + brew install shellcheck +else + echo "error: no supported package manager found (apt-get, brew)." >&2 + echo "Install shellcheck manually: https://github.com/koalaman/shellcheck#installing" >&2 + exit 1 +fi +echo " done." + +# --- actionlint --- +echo "==> Installing actionlint..." +go install github.com/rhysd/actionlint/cmd/actionlint@latest +echo " done." + +echo "" +echo "All surface linters installed." diff --git a/scripts/lint-surface.sh b/scripts/lint-surface.sh new file mode 100755 index 0000000..84490b6 --- /dev/null +++ b/scripts/lint-surface.sh @@ -0,0 +1,12 @@ +#!/bin/sh +set -e + +command -v yamllint >/dev/null 2>&1 || { echo "error: yamllint not installed" >&2; exit 1; } +command -v shellcheck >/dev/null 2>&1 || { echo "error: shellcheck not installed" >&2; exit 1; } +command -v actionlint >/dev/null 2>&1 || { echo "error: actionlint not installed" >&2; exit 1; } + +yamllint -c .yamllint.yml . + +find . -name '*.sh' ! -path './.agents/*' -exec shellcheck {} + + +actionlint diff --git a/scripts/preflight.sh b/scripts/preflight.sh index 1c371b5..b488f8b 100755 --- a/scripts/preflight.sh +++ b/scripts/preflight.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash set -euo pipefail -# Local validation script that mirrors CI (lint + test + coverage). -# Lint and covgate run in parallel to minimize wall-clock time. +# Local validation script that mirrors CI (lint + covgate + surface lint). +# All three run in parallel to minimize wall-clock time. git_repo_root_dir=$(git rev-parse --show-toplevel) cd "$git_repo_root_dir" @@ -19,12 +19,17 @@ lint_pid=$! ./scripts/covgate.sh & covgate_pid=$! -# Wait for both processes regardless of individual exit codes. -# set -e must not short-circuit here — both must run to completion. +./scripts/lint-surface.sh & +surface_pid=$! + +# Wait for all processes regardless of individual exit codes. +# set -e must not short-circuit here — all must run to completion. lint_rc=0 covgate_rc=0 +surface_rc=0 wait "$lint_pid" || lint_rc=$? wait "$covgate_pid" || covgate_rc=$? +wait "$surface_pid" || surface_rc=$? echo "" @@ -34,8 +39,11 @@ fi if [[ $covgate_rc -ne 0 ]]; then echo "=== Covgate FAILED ===" fi -if [[ $lint_rc -eq 0 && $covgate_rc -eq 0 ]]; then +if [[ $surface_rc -ne 0 ]]; then + echo "=== Surface lint FAILED ===" +fi +if [[ $lint_rc -eq 0 && $covgate_rc -eq 0 && $surface_rc -eq 0 ]]; then echo "=== All checks passed ===" fi -exit $(( lint_rc | covgate_rc )) +exit $(( lint_rc | covgate_rc | surface_rc ))