fix(nvim): resolve config path via stdpath so <leader>rc opens init.l⦠#870
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: ci | |
| # Core is authored once here, then vendored into all 9 OS repos via git subtree. | |
| # A defect that lands here fans out N-way β so gate every push/PR with the same | |
| # audit the maintainer runs locally. CI installs the linters that a bare box may | |
| # lack (shellcheck/shfmt/luacheck/zsh) and then hands off entirely to | |
| # scripts/audit-core.sh, so there is ONE definition of "Core is healthy". | |
| on: | |
| push: | |
| branches: ["**"] | |
| pull_request: | |
| permissions: | |
| contents: read | |
| # One in-flight run per ref. This both (a) cancels a superseded run when you push | |
| # again, and (b) dedupes the push-event + pull_request-event double-trigger on a | |
| # same-repo PR: both share this ref-keyed group, so only the latest survives. | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| # The linters CI installs are pinned for a REPRODUCIBLE 9-repo gate β a new upstream | |
| # release can't silently turn CI red with zero repo changes. Those pins now live in | |
| # ONE place, scripts/tool-versions.env, which each job loads into $GITHUB_ENV via the | |
| # "Load pinned tool versions" step below (no version literals left in this file). The | |
| # audit's version-consistency section keeps the .pre-commit-config.yaml revs in step. | |
| jobs: | |
| # Change detection so a docs- or nvim-only push doesn't pay the full matrix. | |
| # SAFE DEFAULT: if the diff base can't be resolved (first push, force-push, a | |
| # shallow checkout), every flag is 'true' and nothing downstream is skipped β a | |
| # detection miss must never hide a needed check from the 9-repo fan-out. Infra | |
| # changes (scripts/, .github/, the manifest, the pre-commit/Makefile config) are | |
| # cross-cutting and likewise force the full run. | |
| changes: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| shell: ${{ steps.classify.outputs.shell }} | |
| nvim: ${{ steps.classify.outputs.nvim }} | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 # full history so the diff base resolves | |
| - id: classify | |
| env: | |
| EVENT: ${{ github.event_name }} | |
| BASE_REF: ${{ github.base_ref }} | |
| BEFORE: ${{ github.event.before }} | |
| run: | | |
| set -uo pipefail | |
| base="" | |
| if [ "$EVENT" = pull_request ]; then | |
| git fetch -q origin "$BASE_REF" 2>/dev/null && base="origin/$BASE_REF" || base="" | |
| elif [ -n "${BEFORE:-}" ] && [ "$BEFORE" != 0000000000000000000000000000000000000000 ]; then | |
| base="$BEFORE" | |
| fi | |
| if [ -z "$base" ] || ! git rev-parse -q --verify "$base^{commit}" >/dev/null 2>&1; then | |
| echo "diff base unknown β running everything (safe default)" | |
| files="__ALL__" | |
| else | |
| files="$(git diff --name-only "$base" HEAD)" | |
| echo "changed files vs $base:"; printf '%s\n' "$files" | sed 's/^/ /' | |
| fi | |
| # The pathβgate mapping lives in scripts/ci-classify.sh β shellcheck'd, | |
| # unit-tested (scripts/test-core.sh), and FAIL-CLOSED on an unrecognised | |
| # path β instead of drift-prone greps inlined in this YAML. It prints | |
| # `shell=β¦`/`nvim=β¦`, which we echo and append straight to $GITHUB_OUTPUT. | |
| out="$(printf '%s\n' "$files" | ./scripts/ci-classify.sh)" | |
| printf 'β %s\n' "$out" | tr '\n' ' '; echo | |
| echo "$out" >> "$GITHUB_OUTPUT" | |
| audit: | |
| needs: changes | |
| # Core fans out to macOS (dotfiles-MacBook) AND six Linux distros, so gate | |
| # on BOTH userlands β not just Linux. The portable checks (shellcheck, zsh -n, | |
| # manifest/exec-bit) are OS-agnostic, but the BEHAVIORAL tests and bin/clip* | |
| # exercise real runtime: macOS ships bash 3.2 (no `mapfile`/assoc-array niceties | |
| # GNU bash 4+ has) and BSD coreutils, which Ubuntu alone can never surface. | |
| # fail-fast off so a macOS-only break doesn't hide a Linux-only one (and vice | |
| # versa) β we want to see every failing platform in one run. | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| # Single source of truth for the pinned linter versions: load them into the | |
| # job env so every step below (and the cache keys) see the same numbers as | |
| # `make setup` does locally. Only KEY=VALUE lines, so comments are skipped. | |
| - name: Load pinned tool versions | |
| run: grep -E '^[A-Z0-9_]+=' scripts/tool-versions.env >> "$GITHUB_ENV" | |
| # ββ Tool cache: shellcheck/actionlint/gitleaks are pinned release tarballs that | |
| # were re-downloaded on EVERY run (only luacheck + pre-commit were cached). Install | |
| # them into a cached, PATH-exported dir keyed on tool-versions.env, so the common | |
| # shell-only push restores them instead of paying three network fetches. A pin bump | |
| # rotates the key automatically. Linux-only (macOS uses brew for shellcheck; the | |
| # other two are Linux-only gates). ββ | |
| - name: Set up cached tool dir on PATH (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| mkdir -p "$HOME/.local/core-tools/bin" | |
| echo "$HOME/.local/core-tools/bin" >> "$GITHUB_PATH" | |
| - name: Cache pinned CLI tools (shellcheck/actionlint/gitleaks) | |
| id: toolcache | |
| if: runner.os == 'Linux' | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ~/.local/core-tools | |
| key: core-tools-${{ runner.os }}-${{ hashFiles('scripts/tool-versions.env') }} | |
| # ββ Linux: pinned single-binary gate tools (shellcheck/actionlint/gitleaks) via the | |
| # shared composite action β ONE definition of each install, also used by freshness.yml | |
| # (B8). Idempotent: it no-ops anything the tool cache already restored, so the | |
| # cache-hit fast path is preserved without a per-tool conditional. ββ | |
| - name: Install pinned CLI tools (Linux) | |
| if: runner.os == 'Linux' | |
| uses: ./.github/actions/setup-core-tools | |
| with: | |
| shellcheck: "true" | |
| actionlint: "true" | |
| gitleaks: "true" | |
| - name: Install zsh (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| set -euxo pipefail | |
| # Drop GitHub's flaky third-party apt repos (packages.microsoft.com/azure) first: | |
| # they intermittently serve an unsigned InRelease and fail the whole update (exit 100). | |
| sudo find /etc/apt/sources.list.d -type f \( -iname '*microsoft*' -o -iname '*azure*' \) -delete 2>/dev/null || true | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends zsh | |
| # ββ macOS: shellcheck via Homebrew (no pinned-binary tarball published for | |
| # macOS the way Linux gets one; brew is the supported path). zsh is preinstalled | |
| # on the macOS runner, so there is no zsh step here. ββ | |
| - name: Install shellcheck (macOS) | |
| if: runner.os == 'macOS' | |
| run: brew install shellcheck | |
| # Cache the luarocks tree so luacheck (and its lua deps) aren't recompiled | |
| # from source on every run. Key on the pinned version AND the OS (the matrix | |
| # runs both): bump the version and every cache key rotates automatically. | |
| - name: Cache luacheck (luarocks tree) | |
| id: luacache | |
| # luacheck only lints nvim/ β skip its (slow) install when nvim/ is untouched. | |
| if: needs.changes.outputs.nvim == 'true' | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ~/.luarocks | |
| # `-lua54` busts any tree a prior run cached against brew's now-default Lua 5.5 | |
| # (which luacheck 1.2.0 can't load) and pins the cache to the 5.4 install below. | |
| key: luacheck-${{ runner.os }}-${{ env.LUACHECK_VERSION }}-lua54 | |
| - name: Install luacheck (Linux, pinned) | |
| if: runner.os == 'Linux' && needs.changes.outputs.nvim == 'true' | |
| run: | | |
| set -euxo pipefail | |
| sudo apt-get install -y --no-install-recommends luarocks | |
| # --local installs under ~/.luarocks (the cached path above). | |
| luarocks --local install luacheck "${LUACHECK_VERSION}" | |
| echo "$HOME/.luarocks/bin" >> "$GITHUB_PATH" | |
| - name: Install luacheck (macOS, pinned) | |
| if: runner.os == 'macOS' && needs.changes.outputs.nvim == 'true' | |
| run: | | |
| set -euxo pipefail | |
| # Pin to Lua 5.4: brew's luarocks now pulls Lua 5.5 as its default interpreter, | |
| # and luacheck 1.2.0 (the last release) fails to even load under 5.5 β 5.5 made | |
| # some locals const, tripping "attempt to assign to const variable" in luacheck's | |
| # own source. Install luacheck against an explicit [email protected] so the gate is stable | |
| # until a luacheck release supports 5.5. --lua-dir points luarocks at that keg. | |
| brew install [email protected] luarocks | |
| luarocks --lua-version 5.4 --lua-dir "$(brew --prefix [email protected])" --local install luacheck "${LUACHECK_VERSION}" | |
| echo "$HOME/.luarocks/bin" >> "$GITHUB_PATH" | |
| # markdownlint gates the docs (the deliverable on this showcase repo). Pinned | |
| # to match the .pre-commit-config.yaml hook rev so author-time and CI agree. | |
| # Node is preinstalled on both runners; -g puts the bin on PATH for the audit. | |
| - name: Install markdownlint-cli2 (pinned) | |
| run: npm install -g "markdownlint-cli2@${MARKDOWNLINT_VERSION}" | |
| # actionlint (workflows) + gitleaks (secrets) are installed by the shared composite | |
| # action above (Linux-only; workflows + secrets are OS-agnostic so the macOS leg's | |
| # audit skips both sections). See the "Install pinned CLI tools (Linux)" step. | |
| # ββ neovim: Linux gets the maintainer's pinned release tarball via the same shared | |
| # composite action (idempotent; the action no-ops if nvim is already present). macOS | |
| # uses brew below. Drives test-core.sh's headless config-load smoke test. ββ | |
| - name: Install neovim (Linux, pinned) | |
| if: runner.os == 'Linux' && needs.changes.outputs.nvim == 'true' | |
| uses: ./.github/actions/setup-core-tools | |
| with: | |
| neovim: "true" | |
| - name: Install neovim (macOS) | |
| if: runner.os == 'macOS' && needs.changes.outputs.nvim == 'true' | |
| run: brew install neovim | |
| # PyYAML for the audit's YAML-parse gate (section 6). The Linux audit runs | |
| # --strict (below), which fails if an IN-SCOPE gate skips for a missing tool β so | |
| # the always-on YAML check must have its importable `yaml`. tomllib/json are stdlib | |
| # (Python β₯3.11, which the runner ships); only PyYAML needs installing. Linux-only: | |
| # the macOS leg isn't --strict, and Alpine installs py3-yaml in its own job. | |
| - name: Install PyYAML (Linux, for the audit's YAML parse gate) | |
| if: runner.os == 'Linux' | |
| run: sudo apt-get install -y --no-install-recommends python3-yaml | |
| # Tolerant: luacheck/nvim are skipped for non-nvim changes, so report what's | |
| # actually present instead of failing on a deliberately-absent tool. | |
| - name: Tool versions | |
| run: | | |
| for t in shellcheck luacheck zsh bash markdownlint-cli2 actionlint gitleaks nvim; do | |
| if command -v "$t" >/dev/null 2>&1; then | |
| printf '%s: ' "$t"; "$t" --version | head -1 | |
| else | |
| echo "$t: (not installed for this change)" | |
| fi | |
| done | |
| # Scope the audit to the areas the classifier flagged, so a docs- or nvim-only | |
| # push skips the gates it can't affect. The cheap structural/config/markdown/ | |
| # workflow/version checks always run regardless of scope (see audit-core.sh). | |
| # FAIL-CLOSED: the classifier forces BOTH flags true for infra/unknown changes, | |
| # so this resolves to the full "shell,nvim" run there β never a narrowed audit. | |
| - name: Run Core audit (manifest, exec bits, syntax, lint, behavioral) | |
| env: | |
| SHELL_CHANGED: ${{ needs.changes.outputs.shell }} | |
| NVIM_CHANGED: ${{ needs.changes.outputs.nvim }} | |
| run: | | |
| scope="" | |
| [ "$SHELL_CHANGED" = true ] && scope="shell" | |
| [ "$NVIM_CHANGED" = true ] && scope="${scope:+$scope,}nvim" | |
| [ -z "$scope" ] && scope="none" | |
| # --strict on the Linux leg: every IN-SCOPE gate's tool is installed here, so a | |
| # SKIP would mean a tool silently failed to install β a false green this repo's | |
| # whole thesis is against. Out-of-scope skips (a narrowed run) don't trip it. | |
| # macOS stays non-strict (actionlint/gitleaks are Linux-only gates there). | |
| strict="" | |
| [ "$RUNNER_OS" = Linux ] && strict="--strict" | |
| echo "audit scope: $scope ${strict:+(strict)}" | |
| ./scripts/audit-core.sh --scope "$scope" $strict | |
| # Alpine (musl libc + busybox coreutils) is a named target repo, and functions.zsh | |
| # + the maint/test scripts explicitly claim busybox compatibility β yet the matrix | |
| # above only proves glibc Ubuntu and BSD macOS, never musl/busybox. This leg closes | |
| # that asserted-but-unverified gap. We deliberately do NOT use `container: alpine` | |
| # (its musl libc breaks the glibc Node that actions/checkout ships, a known failure); | |
| # instead we check out on the host and run the SAME scripts/audit-core.sh inside an Alpine | |
| # container with the workspace bind-mounted. Only bash/zsh/git/shellcheck/python are | |
| # installed β luacheck, markdownlint and nvim skip gracefully here and are already | |
| # covered on the glibc legs. The value is everything ELSE running against busybox | |
| # applets (awk/grep/sed/date/tar/gzip): the behavioral suite, the clip ladder, and | |
| # the manifest/exec-bit/syntax checks, on the one userland Ubuntu can never surface. | |
| audit-alpine: | |
| needs: changes | |
| # Alpine re-validates the SHELL layer against musl/busybox; a docs- or nvim-only | |
| # change can't affect that, so skip the whole container spin unless shell changed. | |
| if: needs.changes.outputs.shell == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Audit on Alpine (musl + busybox) | |
| run: | | |
| docker run --rm -v "$PWD:/core" -w /core alpine:3.21@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d sh -euc ' | |
| apk add --no-cache bash zsh git shellcheck python3 py3-yaml | |
| # The repo is owned by the host runner uid but the container runs as root; | |
| # without this, git 2.35+ refuses it as "dubious ownership" and the audit | |
| # cannot read ls-files/ls-tree. Scope the exception to the mounted path. | |
| git config --global --add safe.directory /core | |
| # This leg exists to re-validate the SHELL layer on musl/busybox; it only | |
| # runs when shell changed (job-level if:), and nvim tooling is not installed | |
| # here β so scope it to shell (the nvim sections skip as out-of-scope). | |
| ./scripts/audit-core.sh --scope shell | |
| ' | |
| # Arch (glibc, ROLLING release) is a named target repo, and the Alpine leg above | |
| # only proves musl/busybox while the matrix proves Ubuntu LTS + BSD macOS β none of | |
| # which surface the BLEEDING-EDGE GNU toolchain Arch actually runs. Arch ships newer | |
| # zsh/bash/coreutils/git than Ubuntu LTS, so a deprecation or behavior change lands | |
| # here MONTHS before an LTS bump would catch it β the early-warning a rolling target | |
| # is supposed to give. Mirrors audit-alpine: check out on the host, then run the SAME | |
| # scripts/audit-core.sh inside an Arch container with the workspace bind-mounted (no | |
| # `container: archlinux` β same uniform pattern; only the image + package step differ). | |
| audit-arch: | |
| needs: changes | |
| # Arch re-validates the SHELL layer against the rolling GNU toolchain; a docs- or | |
| # nvim-only change can't affect that, so skip the container spin unless shell changed. | |
| if: needs.changes.outputs.shell == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Audit on Arch (rolling glibc toolchain) | |
| run: | | |
| docker run --rm -v "$PWD:/core" -w /core archlinux:latest@sha256:681569955d1d17313ef7134acc8b5cd8adcda2fc24709bed472d95e1cf3d71a1 sh -euc ' | |
| # -Syu (not bare -Sy): a partial upgrade is unsupported on Arch and can pull | |
| # mismatched libs. The container is ephemeral, so the full sync+upgrade is the | |
| # correct AND safe idiom here β the same rule dotfiles-Arch ships to real hosts. | |
| pacman -Syu --noconfirm --needed bash zsh git shellcheck python python-yaml | |
| # Same "dubious ownership" fix as the Alpine leg: repo is host-uid-owned but | |
| # the container runs as root, so git 2.35+ would refuse ls-files/ls-tree. | |
| git config --global --add safe.directory /core | |
| # Shell scope only: this leg re-validates zsh/bash against the rolling | |
| # toolchain; nvim/lua tooling is not installed here (skips out-of-scope) and | |
| # is already covered on the glibc Ubuntu leg. | |
| ./scripts/audit-core.sh --scope shell | |
| ' | |
| # Author-time hygiene gate. The .pre-commit-config.yaml hooks only fire for | |
| # contributors who ran `pre-commit install`; without this job a push carrying | |
| # trailing whitespace, a missing final newline, a stray `<<<<<<<` conflict marker, | |
| # or a shebang script that isn't +x sails through green and subtree-syncs into all | |
| # 9 repos. The audit job deliberately does NOT replicate these file-hygiene hooks | |
| # (audit-core.sh owns manifest/lint/behavioral), so run the SAME config here β | |
| # author-time == CI, one source of truth, zero drift. SKIP the three hooks the | |
| # audit job already owns so nothing runs twice and this stays the fast lane. | |
| # Linux-only: file hygiene is OS-agnostic, unlike the audit's dual-userland matrix. | |
| precommit: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| # Single source of truth for the pinned pre-commit version (see the audit job). | |
| - name: Load pinned tool versions | |
| run: grep -E '^[A-Z0-9_]+=' scripts/tool-versions.env >> "$GITHUB_ENV" | |
| # Cache the hook environments pre-commit clones+builds, keyed on the config so a | |
| # hook-rev bump (or a new hook) rotates the key automatically. | |
| - name: Cache pre-commit hooks | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ~/.cache/pre-commit | |
| key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} | |
| # pipx is preinstalled on the runner and installs to ~/.local/bin (already on | |
| # PATH), sidestepping PEP-668 "externally-managed-environment" pip errors. | |
| - name: Install pre-commit (pinned) | |
| run: pipx install "pre-commit==${PRECOMMIT_VERSION}" | |
| - name: Run hygiene hooks (audit-owned hooks skipped) | |
| env: | |
| SKIP: audit-core,shellcheck,markdownlint-cli2 | |
| run: pre-commit run --all-files --show-diff-on-failure | |
| # Startup-perf REGRESSION GATE. Core invests in startup speed (cached tool-init in | |
| # tools.zsh, deferred plugins in plugins.zsh); a gross regression β re-introducing a | |
| # per-shell subprocess, un-deferring heavy work, an uncached eval β would fan out to | |
| # all 9 repos silently. This job now ENFORCES a budget instead of only reporting: | |
| # bench-core.sh fails the build when the canonical-chain MEAN exceeds | |
| # CORE_BENCH_BUDGET_MS. The budget is deliberately GENEROUS (the hermetic chain runs | |
| # ~25 ms; 120 ms is ~5x headroom) and the mean is taken over 50 warmed runs, so | |
| # ordinary shared-runner noise can't trip it β only a real regression sustains a 5x | |
| # mean. The precise number still prints in the log for trend-watching. No | |
| # continue-on-error: a breach is now a red build, not a buried log line. Linux-only. | |
| bench: | |
| needs: changes | |
| # Startup perf is a property of the zsh load chain; a docs- or nvim-only change | |
| # can't move it, so only benchmark when the shell layer actually changed. | |
| if: needs.changes.outputs.shell == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Install zsh + hyperfine | |
| run: | | |
| set -euxo pipefail | |
| # Drop GitHub's flaky third-party apt repos (packages.microsoft.com/azure) first β | |
| # an unsigned InRelease there otherwise fails the whole update (exit 100). | |
| sudo find /etc/apt/sources.list.d -type f \( -iname '*microsoft*' -o -iname '*azure*' \) -delete 2>/dev/null || true | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends zsh hyperfine | |
| - name: Benchmark Core startup (budget gate) | |
| env: | |
| CORE_BENCH_RUNS: "50" | |
| CORE_BENCH_BUDGET_MS: "120" | |
| run: ./scripts/bench-core.sh |