mirror of
https://github.com/Heretek-AI/heretek-actions.git
synced 2026-10-12 02:03:46 +00:00
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
124 lines
4.1 KiB
Bash
Executable File
124 lines
4.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# tests/run-action.sh — Run a Heretek composite action against a fixture repo via `act`
|
|
#
|
|
# Usage:
|
|
# tests/run-action.sh <fixture-name> <action-name> [act-args...]
|
|
#
|
|
# Example:
|
|
# tests/run-action.sh rust-passing rust-ci
|
|
# tests/run-action.sh js-failing js-ci --dryrun
|
|
#
|
|
# Exit codes:
|
|
# 0 — envelope produced and matches golden (or no golden exists yet)
|
|
# 1 — fixture missing
|
|
# 2 — envelope validation failed
|
|
# 3 — golden mismatch
|
|
# 4 — act itself failed
|
|
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
FIXTURES_DIR="${REPO_ROOT}/tests/fixtures"
|
|
GOLDEN_DIR="${REPO_ROOT}/tests/golden"
|
|
WORK_DIR="${REPO_ROOT}/tests/work"
|
|
|
|
FIXTURE="${1:-}"
|
|
ACTION="${2:-}"
|
|
shift 2 || true
|
|
|
|
if [ -z "${FIXTURE}" ] || [ -z "${ACTION}" ]; then
|
|
echo "Usage: $0 <fixture-name> <action-name> [act-args...]" >&2
|
|
exit 1
|
|
fi
|
|
|
|
FIXTURE_PATH="${FIXTURES_DIR}/${FIXTURE}"
|
|
if [ ! -d "${FIXTURE_PATH}" ]; then
|
|
echo "fixture not found: ${FIXTURE_PATH}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Sandbox: copy fixture into work dir so act can checkout freely
|
|
SANDBOX="${WORK_DIR}/${FIXTURE}-$$"
|
|
mkdir -p "${SANDBOX}"
|
|
cp -R "${FIXTURE_PATH}/." "${SANDBOX}/"
|
|
|
|
# Wire up the local heretek-actions checkout as the action source
|
|
# Override GITHUB_ACTION_PATH or use local resolution via act's --action-ref
|
|
# Easiest path: mount the repo as the local actions source
|
|
mkdir -p "${SANDBOX}/.github/actions"
|
|
ln -sfn "${REPO_ROOT}/.github/actions/agent-envelope.sh" "${SANDBOX}/.github/actions/agent-envelope.sh"
|
|
ln -sfn "${REPO_ROOT}/.github/actions/${ACTION}" "${SANDBOX}/.github/actions/${ACTION}"
|
|
|
|
# Minimal workflow that calls the action and uploads the envelope
|
|
WORKFLOW_FILE="${SANDBOX}/.github/workflows/test-${ACTION}.yml"
|
|
mkdir -p "$(dirname "${WORKFLOW_FILE}")"
|
|
cat > "${WORKFLOW_FILE}" <<WORKFLOW
|
|
name: Test ${ACTION}
|
|
on: [push]
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: ./.github/actions/${ACTION}
|
|
with: {}
|
|
- name: Validate envelope against schema
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
jq -e --schema '\${{ inputs.schema }}' '. | type == "object"' .agent/output.json >/dev/null
|
|
WORKFLOW
|
|
|
|
echo "==> Running ${ACTION} against fixture ${FIXTURE}"
|
|
|
|
# Run act. Capture output; tolerate non-zero exit (action may intentionally fail).
|
|
set +e
|
|
act -W "${WORKFLOW_FILE}" --container-architecture linux/amd64 "$@" 2>&1 | tee "${WORKDIR:-/tmp}/act-${FIXTURE}-${ACTION}.log"
|
|
ACT_EXIT=${PIPESTATUS[0]}
|
|
set -e
|
|
|
|
# Find the produced envelope
|
|
ENVELOPE="${SANDBOX}/.agent/output.json"
|
|
if [ ! -f "${ENVELOPE}" ]; then
|
|
echo "no envelope produced (act exit ${ACT_EXIT})" >&2
|
|
rm -rf "${SANDBOX}"
|
|
exit 4
|
|
fi
|
|
|
|
# Validate against schema
|
|
if ! jq -e empty "${ENVELOPE}" >/dev/null 2>&1; then
|
|
echo "envelope is not valid JSON: ${ENVELOPE}" >&2
|
|
rm -rf "${SANDBOX}"
|
|
exit 2
|
|
fi
|
|
|
|
# Validate schema enum fields (lightweight, no full JSON-schema validator needed)
|
|
SCHEMA="${REPO_ROOT}/.agent/schema.json"
|
|
AGENT_ACTION=$(jq -r '.agent_action' "${ENVELOPE}")
|
|
if ! jq -e --argjson env "${ENVELOPE}" '.properties.agent_action.enum | index($env.agent_action) != null' "${SCHEMA}" >/dev/null; then
|
|
echo "envelope agent_action '${AGENT_ACTION}' not in schema enum" >&2
|
|
rm -rf "${SANDBOX}"
|
|
exit 2
|
|
fi
|
|
|
|
GOLDEN="${GOLDEN_DIR}/${ACTION}/${FIXTURE}.json"
|
|
if [ -f "${GOLDEN}" ]; then
|
|
# Diff against golden — strip volatile fields (timestamps, run_id, sha)
|
|
NORMALIZED=$(jq 'del(.created_at, .duration_ms, .repository.run_id, .repository.sha)' "${ENVELOPE}")
|
|
GOLDEN_NORMALIZED=$(jq 'del(.created_at, .duration_ms, .repository.run_id, .repository.sha)' "${GOLDEN}")
|
|
if ! diff <(echo "${GOLDEN_NORMALIZED}") <(echo "${NORMALIZED}") >/dev/null; then
|
|
echo "envelope differs from golden: ${GOLDEN}" >&2
|
|
diff <(echo "${GOLDEN_NORMALIZED}") <(echo "${NORMALIZED}") >&2 || true
|
|
rm -rf "${SANDBOX}"
|
|
exit 3
|
|
fi
|
|
echo "✓ ${ACTION} on ${FIXTURE} matches golden"
|
|
else
|
|
echo "no golden for ${ACTION}/${FIXTURE} — writing first-run snapshot"
|
|
mkdir -p "${GOLDEN_DIR}/${ACTION}"
|
|
jq 'del(.created_at, .duration_ms, .repository.run_id, .repository.sha)' "${ENVELOPE}" > "${GOLDEN}"
|
|
fi
|
|
|
|
rm -rf "${SANDBOX}"
|
|
exit 0
|