Files
heretek-actions/tests/README.md
T
John Smith 6ae3e16aba feat(lint): add lint-ultimate action wrapping MegaLinter with envelope
Tier 1.1 of the quality expansion design. Adds the lint-ultimate
composite action that runs MegaLinter (or Super-Linter) over the
repo and emits a per-linter checks[] plus per-finding findings[]
envelope, matching the standard .agent/output.json contract.

## Changes

### Action
- .github/actions/lint-ultimate/action.yml: composite action, runs
  oxsecurity/megalinter:v7 via Docker, uploads the JSON report and
  stdout as lint-ultimate-reports artifact, uploads the envelope as
  agent-output-lint for downstream consumers.
- .github/actions/lint-ultimate/build-envelope.sh: parses
  megalinter-report.json, maps each linter category to a check[]
  entry and each finding to a findings[] entry. Falls back to an
  empty envelope when the report is missing or unreadable.

### Library
- .github/actions/agent-envelope.sh: add add_finding() helper
  mirroring add_check(). Optional fields (rule, file, line, column,
  suggested_fix) default to null; numbers coerce when non-empty.

### Library fixes (pre-existing bugs surfaced by the new test)
- GITHUB_REPOSITORY default and owner/repo prefix strip now set -u
  safe. Previously the original --arg repo_name reference threw on
  unbound-variable in any non-GitHub runner.
- GITHUB_RUN_ID now passed as --argjson (numeric or null) instead of
  --arg with tonumber? in the filter. The prior form silently
  produced empty output when run_id was unset (jq 1.8.2 quirk where
  tonumber? on null returns Empty and jq emits no output).

### Schema
- .agent/schema.json: enum agent_action extended with all twelve new
  values from the quality-check expansion design (lint-ultimate,
  coverage, actionlint, codeql, lefthook-ci, test-matrix, reviewdog,
  quality-gate, semantic-release, dependabot, docker-ci). Future
  Tier 1/2/3 PRs pick the right value.

### Tests
- tests/: ship the previously untracked harness (run-action.sh,
  run-all.sh, README.md, fixtures for all CI actions) plus the
  new megalinter-passing and megalinter-failing fixtures.
- tests/unit/test-lint-ultimate-parser.sh: parser unit test that
  runs build-envelope.sh against a fabricated megalinter-report.json
  and asserts the envelope shape. No Docker required.
- tests/golden/lint-ultimate/README.md: documents that goldens are
  generated on first real-CI run (per tests/README.md first-run
  convention) and provides a no-Docker local check.

### Matrix
- tests/run-all.sh: megalinter-passing and megalinter-failing added
  to MATRIX (paired with lint-ultimate action).

### Docs
- README.md: lint-ultimate row added to the CI Composite Actions
  table; full example block added between python-ci and ---.

## Risk

- Pre-existing agent-envelope.sh changes affect all envelope-writing
  callers; the same fixes apply everywhere (none had a green test
  before). Behavior is unchanged when GITHUB_* env vars are set
  (real CI).
- No existing tests to regress against (tests/ was untracked).
  Unit test added in this PR exercises the new parser path.

Refs: docs/superpowers/specs/2026-07-30-code-quality-expansion-design.md
2026-07-30 14:38:20 -04:00

3.4 KiB

Test Harness

Composite actions in .github/actions/ are notoriously hard to test in isolation. This harness uses act to run each action against a fixture repo and diff the produced .agent/output.json against a golden snapshot.

Layout

tests/
├── run-action.sh             # Runner — invokes act on a fixture
├── run-all.sh                # Run all action/fixture pairs in parallel
├── fixtures/                 # Tiny repos with intentional state
│   ├── rust-passing/         # cargo test passes
│   ├── rust-failing/         # cargo test fails on purpose
│   ├── js-passing/           # eslint + vitest green
│   ├── js-failing/           # vitest fails
│   ├── python-passing/       # ruff + pytest green
│   ├── python-failing/       # pytest fails
│   ├── docker-passing/       # clean Dockerfile
│   └── docker-failing/       # bad Dockerfile (hadolint warnings)
├── golden/                   # Expected envelopes, keyed by action/fixture
│   ├── rust-ci/
│   │   ├── rust-passing.json
│   │   └── rust-failing.json
│   └── ...
└── work/                     # Sandboxes — gitignored, transient

Usage

# Run a single action against a fixture
tests/run-action.sh rust-passing rust-ci

# First run with no golden: writes the snapshot
tests/run-action.sh rust-failing rust-ci    # creates golden/rust-ci/rust-failing.json

# Subsequent runs diff against golden — non-zero exit on mismatch
tests/run-action.sh rust-passing rust-ci

CI Integration

tests/run-all.sh iterates over all action/fixture pairs and reports a summary:

tests/run-all.sh                # parallel (default jobs = nproc)
tests/run-all.sh --serial       # sequential
tests/run-all.sh --dry-run      # print matrix, don't execute
tests/run-all.sh --only rust-ci # run only matching actions
tests/run-all.sh --jobs 4       # cap parallel workers

Wire it into a ci workflow:

name: Action Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: |
          curl -fsSL https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
      - run: tests/run-all.sh

Conventions

  1. One fixture per scenario, not per language. rust-failing is the same fixture reused for rust-ci failing-case and (later) for coverage failing-coverage-case.
  2. Goldens strip volatile fields — created_at, duration_ms, repository.sha, repository.run_id. Don't add those to golden files.
  3. First-run creates the golden. On any schema change, delete goldens to regenerate. Review the diff carefully before committing.
  4. Fixtures stay tiny. A passing rust fixture is ~10 lines of Cargo.toml + one test file. If a fixture grows past 50 lines, split it.

Known Limitations

  • act does not perfectly emulate GitHub-hosted runners — network, secrets, and some GITHUB_* env vars behave differently. Envelope schema validation is the main check; behavioral testing requires real runners.
  • Docker-in-Docker (docker build inside act) is slow. For docker-ci, prefer docker buildx build --load=false or skip image builds in tests.
  • No full JSON-schema validator — the harness only checks that agent_action is in the schema enum. Add ajv / jsonschema if you need exhaustive validation.