Skip to content

Repository files navigation

Reference GitHub Action Package

Product-neutral reference implementation for the VibeCode QA GitHub Action Package charter, composed with TypeScript v1, Testing v1, and Security v1.

The action itself is deliberately small: it validates a release tag against semantic versioning and emits normalized version outputs. The point of the repository is not the feature — it is the shape: a complete action.yml contract, validated inputs, a reproducible committed bundle, stable exit behaviour, minimum-permission examples, and CI that runs the action the way a consumer runs it.

github-action-package is a charter, not yet a versioned rubric. The page linked above carries candidate rules (R-GHA-1 … R-GHA-10) but no v1 edition. This repo is the reference implementation those rules are being read against.

Official docs first

If you only need to build an action, start with GitHub's own path:

This repository is not a replacement for those. It is a VCQA fixture that shows how the charter is judged once metadata completeness, permissions, bundle freshness, release tags, and consumer-shaped smoke evidence are all required at once.

Usage

Minimum permissions: none. This action reads no repository data, calls no API, and needs no GITHUB_TOKEN, so an empty permissions: block is enough. Every example below states its permissions explicitly rather than inheriting the workflow default.

Validate a pushed tag

name: Release gate
on:
  push:
    tags: ['v*']

permissions: {}

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: vibecodeqa/ref-github-action-package@v1
        id: tag
        with:
          tag: ${{ github.ref }}
          allow-prerelease: 'false'
      # Hyphenated output names need index syntax; `outputs.major-tag` would parse as subtraction.
      - env:
          VERSION: ${{ steps.tag.outputs.version }}
          MAJOR_TAG: ${{ steps.tag.outputs['major-tag'] }}
        run: echo "Releasing $VERSION (major tag $MAJOR_TAG)"

Report without failing the job

name: Tag report
on:
  workflow_dispatch:
    inputs:
      candidate:
        description: Tag to inspect
        required: true

permissions: {}

jobs:
  inspect:
    runs-on: ubuntu-latest
    steps:
      - uses: vibecodeqa/ref-github-action-package@v1
        id: tag
        with:
          tag: ${{ inputs.candidate }}
          fail-on-invalid: 'false'
      - if: steps.tag.outputs.valid != 'true'
        env:
          REASON: ${{ steps.tag.outputs.reason }}
        run: echo "Rejected because $REASON"

Pin by commit SHA

Privileged consumers — anything running with contents: write, id-token: write, or a secret in scope — should pin the action by commit SHA rather than by tag:

name: Privileged release
on:
  push:
    tags: ['v*']

permissions:
  contents: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      # vibecodeqa/[email protected]
      - uses: vibecodeqa/ref-github-action-package@0000000000000000000000000000000000000000
        with:
          tag: ${{ github.ref }}

Replace the zero SHA with the commit the release tag points at. contents: write above is required by the surrounding release job, not by this action.

Inputs

Input Required Default Description
tag yes — Tag to validate. Accepts v1.2.3 or refs/tags/v1.2.3, so github.ref can be passed straight through.
prefix no v Required leading prefix before the version core. Set to '' to accept bare versions.
allow-prerelease no true Whether pre-release tags such as v1.2.3-rc.1 are accepted.
fail-on-invalid no true Whether an invalid tag fails the step, or is reported through outputs instead.

Outputs

Output Description
valid "true" when the tag passed validation and policy.
reason ok, invalid-input, missing-prefix, invalid-semver, or prerelease-not-allowed.
version Canonical version without the prefix, e.g. 1.2.3-rc.1.
major / minor / patch Version numbers as strings, or "" when invalid.
prerelease Pre-release identifiers such as rc.1, or "".
build Build metadata such as 20260809, or "".
major-tag Moving major tag a release workflow should update, e.g. v1.
is-prerelease "true" when the version carries pre-release identifiers.

Every output is written on every path, success or failure, so consumers never have to guess whether a key exists.

Exit behaviour

Failure signalling is part of the contract, not an implementation detail:

Situation Annotation Step result
Valid tag ::notice success
Invalid tag, fail-on-invalid: true (default) ::error — Invalid tag [reason] failure
Pre-release rejected by policy ::error — Policy rejection [prerelease-not-allowed] failure
Invalid tag, fail-on-invalid: false ::warning success, valid=false
Malformed action inputs one ::error per problem failure, always

An expected policy rejection and an unexpected runtime failure are distinguishable from the log line alone. Malformed inputs always fail: a misconfigured gate is not a gate.

Token and secret scoping

  • The action declares no secret inputs and reads no environment variable other than the INPUT_* values the runtime sets for its declared inputs. src/main.ts contains no process.env access at all, and a unit test asserts that.
  • It never reads GITHUB_TOKEN and makes no network calls, so permissions: {} is sufficient.
  • Input values are never interpolated into a shell string. The action is a Node process; the tag is parsed by an anchored, non-backtracking grammar in src/semver.ts before anything else looks at it.
  • If you fork this and add a token-shaped input, register it with core.setSecret() at first use and keep it out of outputs, job summaries, and uploaded artifacts.

Runtime and dependency policy

  • Runtime is pinned in action.yml: runs.using: node24, runs.main: dist/index.js.
  • The package manager is pinned by packageManager in package.json ([email protected]), and CI installs with --frozen-lockfile against the committed pnpm-lock.yaml.
  • One production dependency: @actions/core, pinned to an exact version. Dev dependencies are pinned to exact versions too — no ^ ranges anywhere.
  • dist/index.js is committed because a consumer never builds the action. It is produced only by pnpm build (esbuild, pinned) and CI fails if a clean rebuild differs by one byte.
  • Third-party actions used inside this repo's own workflows are pinned by commit SHA with the human-readable version in a trailing comment.

Audit and licence gates

CI runs two blocking gates on every push and pull request:

  • pnpm audit --audit-level=moderate — fails on any moderate, high, or critical advisory.
  • node scripts/check-licenses.mjs — fails on any resolved package whose licence is outside the allowlist (MIT, ISC, Apache-2.0, BSD-2-Clause, BSD-3-Clause, 0BSD, CC0-1.0, Unlicense, BlueOak-1.0.0, Python-2.0).

Exception policy. Low-severity advisories are reported by pnpm audit but do not block; they are triaged at the next dependency review. Anything moderate or above blocks the build. If an advisory has no fix available, the only accepted route is an acceptedException record in this README naming the advisory, the owner, the compensating control, and an expiry date — never a silenced or continue-on-error job. There are no active exceptions today.

Release process

Releases are cut by pushing tags; nothing is published from a developer's machine.

  1. Update CHANGELOG.md and merge to main.
  2. Verify locally: pnpm verify (lint, typecheck, tests, metadata validation, bundle build).
  3. Confirm git status is clean — a dirty dist/ means the bundle was never rebuilt.
  4. Tag the exact commit with an immutable version tag: git tag v1.2.3 && git push origin v1.2.3. A vX.Y.Z tag is never moved or deleted once pushed.
  5. Move the major tag to the same commit: git tag -f v1 <sha> && git push --force origin v1. v1 is a moving pointer by design; it is documented as moving, and it only ever moves forward within the same major version.
  6. Publish a GitHub release against the vX.Y.Z tag.

Consumer guidance: @v1 for convenience, @v1.2.3 for reproducibility, and a full commit SHA for anything privileged. That ordering is stated in the usage examples above.

Development

corepack enable
pnpm install --frozen-lockfile
pnpm verify          # lint + typecheck + test + action metadata + build
pnpm audit --audit-level=moderate
pnpm check:licenses

pnpm build regenerates dist/index.js. Commit the result: CI rebuilds and runs git diff --exit-code -- dist so a stale bundle cannot ship.

Layout

Path Role
action.yml The public contract: inputs, outputs, branding, runtime.
src/semver.ts Anchored, dependency-free semantic-version grammar.
src/inputs.ts Input parsing and validation — runs before any behaviour.
src/evaluate.ts Pure decision core, no Actions runtime import.
src/main.ts The only module that touches @actions/core.
dist/index.js Generated bundle. Never edited by hand.
scripts/validate-action-metadata.mjs Metadata completeness and declared-versus-read drift.
scripts/check-licenses.mjs Dependency licence allowlist gate.
tests/fixtures/ Behaviour and input-validation fixture tables.

Tests need no repository, no runner, and no token: src/main.test.ts drives the real @actions/core protocol through INPUT_* environment variables and a temporary GITHUB_OUTPUT file.

CI evidence

.github/workflows/ci.yml runs, on every push and pull request:

Job / step What it proves
pnpm install --frozen-lockfile The lockfile is authoritative.
pnpm lint, pnpm typecheck Biome and TypeScript strict mode are clean.
pnpm test Unit and protocol tests pass.
pnpm validate:action action.yml is complete, its runtime is supported, it has not drifted from the source, and every README workflow example declares permissions:.
pnpm build + git diff --exit-code -- dist The committed bundle matches a clean rebuild.
pnpm audit, pnpm check:licenses Dependency and licence gates.
smoke job Runs uses: ./ for a success case, a policy-rejection case, a malformed-tag case, and a non-fatal case, asserting outputs and step conclusions.

The smoke job is a required check, not a reporting job: if the action stops behaving the way a consumer sees it, CI goes red.

VCQA evidence

The tracked report lives at docs/vcqa-report.md.

Licence

MIT — see LICENSE.

About

Product-neutral reference implementation for the VibeCode QA GitHub Action Package standard.

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages