# GitHub Actions Maintainer
[](https://github.com/ThreatFlux/github_actions/actions/workflows/ci.yml)
[](https://github.com/ThreatFlux/github_actions/actions/workflows/security.yml)
[](LICENSE)
[](https://www.rust-lang.org)
General-purpose dependency maintenance in Rust, built from the ThreatFlux Rust CI/CD template. The shipped capabilities cover secure GitHub Action pinning plus latest-version reporting and updates for both GitHub Actions and cargo packages.
## What It Does
`github-actions-maintainer pin` scans workflow files, finds floating GitHub Action refs such as:
```yaml
- uses: actions/checkout@v4
```
and rewrites them to immutable commit SHAs while keeping the original ref as a comment:
```yaml
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
```
That preserves operator intent while eliminating runtime drift from moving tags and branches.
## Current Features
- Pins the ref already declared in the workflow instead of upgrading to a newer major automatically
- Supports GitHub-hosted actions with nested paths such as `github/codeql-action/init@v3`
- Skips local actions, Docker actions, and dynamic expressions like `${{ matrix.action }}`
- Offers `--dry-run` for previewing rewrites before touching files
- Can update actions to the latest GitHub release, with tag fallback when releases are absent
- Can report current tracked versions versus latest upstream versions without rewriting files
- Can scan `Cargo.toml` manifests, find the latest stable crates.io versions, and update supported cargo dependency requirements
- Reports unmanaged cargo dependency shapes such as `path`, `git`, and `workspace = true` entries instead of rewriting them
- Can create a remote branch and pull request with labels instead of rewriting the checked-out repo
- Can scan workflows for explicit Bash/sh and Python usage in `run:` blocks and `shell:` declarations
- Can report baseline workflow policy findings for unpinned actions, missing explicit permissions, write-level permissions, and missing job timeouts
- Retries GitHub API calls with exponential backoff and respects `Retry-After` plus rate-limit reset headers
- Validates token scopes before remote PR creation so missing `repo` or `workflow` permissions fail early
## CLI
```bash
cargo run -- pin --dry-run
cargo run -- update --dry-run
cargo run -- update --cargo --dry-run
cargo run -- update --all
cargo run -- update
cargo run -- status
cargo run -- status --cargo
cargo run -- policy
cargo run -- policy --fail-on-findings
cargo run -- pin --repo /path/to/repo --workflows-path .github/workflows
```
Options:
- `--repo`: repository root to scan, defaults to `.`
- `--workflows-path`: relative workflow directory, defaults to `.github/workflows`
- `--token`: optional GitHub token, also read from `GITHUB_TOKEN`
- `--dry-run`: report rewrites without applying them
- `--cargo`: target cargo package dependencies
- `--github-actions`: target GitHub Actions updates explicitly
- `--all`: target both GitHub Actions and cargo package dependencies
- `--create-pr`: create a remote branch and pull request instead of editing files locally
- `--owner` and `--repo-name`: remote repository coordinates for PR creation
- `--labels`, `--title`, `--commit-message`, `--base-branch`, `--branch-name`: control remote PR creation
- `--check-scripts` / `--check-policies`: enable or disable the `policy` script and policy scans, both enabled by default
- `--fail-on-findings`: make `policy` exit non-zero when it finds script usage or policy violations
- `--extra-files`: comma-separated files to stage into the release commit alongside the version rewrites
- `--phase`: `all` (default), `bump` to commit the version bump without tagging it, or `tag` to release the version the manifest already holds
Command behavior:
- `pin`: pin the ref already declared in the workflow
- `update`: move selected dependencies to the latest upstream version. By default it targets GitHub Actions; add `--cargo` or `--all` for cargo support
- `status`: report current tracked versions, latest upstream versions, and whether a change is needed for the selected target set
- `policy`: scan workflow files for explicit Bash/Python script usage and baseline workflow policy findings without modifying files
- `update` without `--create-pr`: apply changes locally in the checked-out repository, which is the equivalent of the original tool's stage mode
- `release`: bump the Cargo version from conventional commits, then create the release commit, tag, and GitHub Release through the API (see below)
Cargo update support currently manages registry-backed dependencies that declare a direct version requirement such as:
- `reqwest = "0.12.13"`
- `serde = { version = "^1.0.200", features = ["derive"] }`
- `regex = { version = "~1.10.0" }`
The updater preserves the existing requirement operator where possible and skips unsupported forms such as multi-range requirements, `path` dependencies, `git` dependencies, and `workspace = true` references.
Policy scanning reports:
- unpinned action references that do not use a full 40-character commit SHA (high)
- `permissions: write-all` (high) and other write-carrying shorthand values (medium)
- individual `<scope>: write` permission entries, with `id-token: write` treated as low
- workflows with no explicit top-level `permissions` block (medium)
- jobs with no `timeout-minutes` (medium)
Script scanning reports explicit Bash/sh and Python usage, both from `shell:` declarations and from interpreter invocations inside `run:` blocks. Both scans are read-only; pair them with `--fail-on-findings` to gate a pull request.
Remote update mode:
```bash
cargo run -- update \
--cargo \
--create-pr \
--owner ThreatFlux \
--repo-name githubWorkFlowChecker \
--token "$GITHUB_TOKEN"
```
Remote update mode will:
- validate the token before mutating repository state
- resolve the default branch when `--base-branch` is not provided
- create a tree/commit/branch through the GitHub API
- open a pull request and attach any requested labels
Release mode:
```bash
# Outside GitHub Actions, pass --owner/--repo-name (or set GITHUB_REPOSITORY).
cargo run -- release --dry-run \
--owner ThreatFlux \
--repo-name github_actions \
--token "$GITHUB_TOKEN"
cargo run -- release \
--owner ThreatFlux \
--repo-name github_actions \
--update-major-alias \
--token "$GITHUB_TOKEN"
```
`release` runs entirely through the GitHub REST API — no `git`, `gh`, or `cargo` binaries are needed at runtime — so it works inside minimal containers:
1. Reads the current version from `Cargo.toml` (`[package].version`, falling back to `[workspace.package].version`).
2. Finds the latest `--tag-prefix` semver tag and classifies the conventional commits since it (`feat:` → minor, `fix:` → patch, `!`/`BREAKING CHANGE` → major). Merge commits are skipped. When no commit warrants a release it exits successfully with `released=false`; `--bump major|minor|patch` forces a release.
3. Rewrites the version across workspace member manifests, internal dependency pins, and `Cargo.lock`.
4. Creates the release commit directly on the base branch (fast-forward only — if the branch advanced past the analyzed head, the run skips cleanly), the `vX.Y.Z` tag (annotated by default so provenance checks like `git cat-file -t` pass; `--tag-style lightweight` opts out), an optional moving major alias tag (`--update-major-alias`), and the GitHub Release with grouped release notes.
5. Writes release notes to `--notes-file` and `released`/`version`/`tag`/`commit`/`release-url`/`notes-file` outputs to `$GITHUB_OUTPUT` when set.
Release mode requires a token with `contents: write` on the target repository. The `workflow` scope is not required because release commits only touch Cargo manifests.
#### Splitting a release across two runs
`--phase` exists for one problem: an artifact that has to be built *from* the released version cannot be referenced *by* that release, because the version-tagged artifact only exists once the tag does. A container action pinning its own runtime image is the canonical case — and releasing in one pass is why `v0.7.0` shipped a `0.6.2` binary.
Splitting the release breaks the cycle:
1. `--phase bump` bumps the manifest and commits it, then stops. `released=false`, `version` holds the pending version, and `commit` holds the version commit. Nothing is tagged, so the version stays claimable.
2. Build and publish whatever has to carry that version. It is built from a tree whose manifest already reads the released version.
3. `--phase tag` releases the version the manifest already holds — no second bump — and stages `--extra-files` into the commit the tag points at, so a digest resolved in step 2 lands inside the tag.
The tagged tree and the artifact's source then differ only in the files staged in step 3. `--phase tag` skips when the manifest version is already tagged, so a re-run after a failure finishes the pending release instead of starting another. It also releases regardless of whether the commit range still warrants a bump, since the bump decision was already made in step 1. Neither phase combines with `--create-pr`, which separates the bump from the tag by its own means.
This repository's own `auto-release.yml` runs both phases; see [Release flow](#release-flow).
## Token Permissions
Remote PR mode requires a GitHub token with the equivalent of:
- `repo` or `public_repo`
- `workflow`
`pin`, `update --dry-run`, and `status` can run without a token. Authenticated requests are still recommended for GitHub-backed operations to raise API rate limits.
## Rate Limits
The GitHub client retries transient failures and rate-limited responses. The crates.io client also retries `429` and `5xx` responses with `Retry-After` handling.
GitHub handling includes:
- `429 Too Many Requests`
- `403 Forbidden` responses that carry rate-limit exhaustion headers
- server-side `5xx` responses
- connection and timeout errors from the HTTP client
When GitHub returns reset metadata, the client sleeps until the reset window instead of blindly retrying.
## GitHub Action Usage
One action ships every command. Reference it as `ThreatFlux/github_actions@<ref>`
(the root [`action.yml`](action.yml)) and select the behavior with `command`:
| `pin` | Rewrite floating action refs in workflow files to the commit SHA they resolve to today. |
| `update` | Move GitHub Actions and/or cargo dependencies to their latest upstream version, locally or on a pull request. |
| `status` | Report current versus latest versions without writing anything. |
| `policy` | Report script usage and baseline workflow policy findings without writing anything. |
| `release` | Bump the Cargo version from conventional commits and publish the release commit, tag, and GitHub Release — or stage them on a release pull request. |
The action is a Docker container action built from
[`runtime/Dockerfile`](runtime/Dockerfile), which pulls a digest-pinned
prebuilt image, so it starts in seconds instead of compiling from source.
### Inputs
| `command` | all | `pin` | Command to run: `pin`, `update`, `status`, `policy`, or `release`. |
| `token` | all | `${{ github.token }}` | GitHub token. Required for remote pull request creation and for `release`; recommended everywhere to raise API rate limits. |
| `owner` / `repo-name` | all | from `GITHUB_REPOSITORY` | Target repository coordinates. |
| `repo` | all | `.` | Path to the checked-out repository. |
| `base-branch` | `update`, `release` | repository default branch | Base of the dependency pull request, or the branch to release from. |
| `dry-run` | all | `false` | Analyze and report without writing files, commits, tags, releases, or pull requests. |
| `create-pr` | `update`, `release` | `false` | Open a dependency-update pull request, or stage the release on a release pull request instead of publishing directly. |
| `commit-message` | `update`, `release` | per command | `Update dependencies` for `update`; `chore: release v{version}` for `release`. |
| `workflows-path` | `pin`, `update`, `status`, `policy` | `.github/workflows` | Workflow directory relative to the repository root. |
| `github-actions` | `update`, `status` | `false` | Include GitHub Actions workflow updates. |
| `cargo` | `update`, `status` | `false` | Include cargo package dependency updates. |
| `all` | `update`, `status` | `false` | Include both GitHub Actions and cargo updates. |
| `branch-name` | `update` | generated | Branch name for the dependency-update pull request. |
| `labels` | `update` | `dependencies` | Comma-separated labels for the dependency-update pull request. |
| `title` | `update` | `Update dependencies` | Title for the dependency-update pull request. |
| `check-scripts` | `policy` | `true` | Report explicit Bash/sh and Python usage in `run:` and `shell:` blocks. |
| `check-policies` | `policy` | `true` | Report unpinned actions, permission, and job timeout findings. |
| `fail-on-findings` | `policy` | `false` | Fail the action when the scan reports any finding. |
| `bump` | `release` | `auto` | `auto`, `major`, `minor`, or `patch`. |
| `tag-prefix` | `release` | `v` | Prefix for release tags. |
| `tag-style` | `release` | `annotated` | `annotated` or `lightweight`. |
| `update-major-alias` | `release` | `false` | Also move the moving major alias tag (for example `v0`). |
| `notes-file` | `release` | `release_notes.md` | Where generated release notes are written, including on dry runs. |
| `release-branch` | `release` | `automation/release` | Automation-owned branch used with `create-pr`; must use the `automation/release` prefix. |
| `extra-files` | `release` | none | Comma-separated repository-relative files to stage into the release commit, for values that can only be resolved at release time. |
| `phase` | `release` | `all` | `all`, `bump` (commit the version bump without tagging), or `tag` (release the version the manifest already holds). |
### Outputs
Every output is set by `release` and is empty for the other commands.
| `released` | `true` when a release was created, otherwise `false`. |
| `version` | Released version without the tag prefix (also set on dry runs and tag-exists skips). |
| `tag` | Created release tag. |
| `commit` | SHA of the commit the release created; with `phase: bump` this is the version commit the `tag` phase builds on. |
| `release-url` | URL of the created GitHub Release. |
| `notes-file` | Path to the generated notes file, empty when no notes were generated. |
| `release-pr-number` | Number of the created or updated release pull request. |
| `release-pr-url` | URL of the created or updated release pull request. |
| `release-branch` | Branch used for the release pull request. |
### `command: pin`
```yaml
jobs:
pin:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Pin workflow action refs
uses: ThreatFlux/github_actions@v0 # pin to a SHA in production
with:
command: pin
token: ${{ secrets.GITHUB_TOKEN }}
```
`pin` rewrites the checked-out files in place; commit them yourself, or add
`dry-run: "true"` to report the rewrites without touching the working tree.
### `command: status`
```yaml
- name: Report dependency drift
uses: ThreatFlux/github_actions@v0 # pin to a SHA in production
with:
command: status
all: "true"
token: ${{ secrets.GITHUB_TOKEN }}
```
`status` never writes; `contents: read` is enough.
### `command: policy`
```yaml
- name: Scan workflow policy
uses: ThreatFlux/github_actions@v0 # pin to a SHA in production
with:
command: policy
fail-on-findings: "true"
```
`policy` reads only the checked-out workflow files, so it needs no token and
`contents: read` is enough. Narrow the scan with `check-scripts: "false"` or
`check-policies: "false"`; drop `fail-on-findings` to report without failing the
job.
### `command: update`
```yaml
jobs:
update:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Update workflow and cargo dependencies
uses: ThreatFlux/github_actions@v0 # pin to a SHA in production
with:
command: update
all: "true"
create-pr: "true"
token: ${{ secrets.DEPENDENCY_UPDATE_TOKEN }}
owner: ${{ github.repository_owner }}
repo-name: ${{ github.event.repository.name }}
```
`create-pr: "true"` publishes through the GitHub API rather than editing the
checkout, so the job itself needs no write permission — but the *token* does.
Updating files under `.github/workflows/` requires the `workflow` scope, which
the default `GITHUB_TOKEN` does not have; use a PAT or GitHub App token there.
Without `create-pr`, `update` edits the checked-out files and you commit them.
`create-pr` is one of the inputs affected by the
[version skew note](#version-skew-during-upgrades) below.
### `command: release`
Direct mode publishes the release commit, tag, and GitHub Release:
```yaml
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- id: release
uses: ThreatFlux/github_actions@v0 # pin to a SHA in production
with:
command: release
token: ${{ secrets.GITHUB_TOKEN }}
update-major-alias: "true"
- if: steps.release.outputs.released == 'true'
run: echo "Released ${{ steps.release.outputs.tag }} -> ${{ steps.release.outputs.release-url }}"
```
Release-pull-request mode stages the version bump on the automation-owned
branch and opens or refreshes one pull request instead of publishing:
```yaml
jobs:
release-pr:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- id: release
uses: ThreatFlux/github_actions@v0 # pin to a SHA in production
with:
command: release
token: ${{ secrets.GITHUB_TOKEN }}
create-pr: "true"
release-branch: automation/release
- name: Report the release pull request
run: |
echo "PR #${{ steps.release.outputs.release-pr-number }} on ${{ steps.release.outputs.release-branch }}"
echo "${{ steps.release.outputs.release-pr-url }}"
```
In pull-request mode no tag moves and no GitHub Release is published, so
`released` stays `false` and `release-pr-number`/`release-pr-url`/`release-branch`
carry the result; the tag and Release are cut by the follow-on release run
after the pull request merges.
That follow-on run tags the version the merged pull request left in the
manifest rather than bumping again. The release range is measured from the
latest tag, so a run that bumped a second time would keep proposing new
versions for commits the merged pull request already covered and never
publish any of them. Once the manifest version is tagged, later runs bump
from it as usual, and merges whose commits are only `chore:`/`docs:` produce
no release at all — the action exits successfully with `released=false`.
### Tokens and downstream pipelines
GitHub suppresses workflow triggers for events created with the default
`GITHUB_TOKEN`: the tag `release` creates will **not** start your tag-triggered
(`on: push: tags:`) workflows. Pick one of:
1. **Same-workflow chaining (no extra secrets):** gate follow-on jobs on
`steps.release.outputs.released == 'true'`, or dispatch the tag pipelines
explicitly — `workflow_dispatch` is exempt from the suppression rule. The
dispatching job needs `permissions: actions: write`.
2. **GitHub App or PAT token:** pass it as `token` and the created tag triggers
`on: push: tags:` workflows natively.
3. **No downstream pipelines:** the default `GITHUB_TOKEN` is all you need.
`release` needs a token with `contents: write` (plus `pull-requests: write` for
`create-pr`). The `workflow` scope is not required, because release commits
only touch Cargo manifests.
### Branch protection
Direct-mode `release` pushes the release commit to the base branch
(fast-forward only). On a protected branch, add a bypass for the identity the
token represents — for rulesets, add the GitHub Actions app or your own GitHub
App to the bypass list. `create-pr: "true"` is the alternative that respects
branch protection: it never writes to the protected branch directly.
The action pins its analysis to the branch head it first observes and publishes
with a fast-forward-only ref update, so a branch that advances mid-run makes the
run skip cleanly with `released=false` instead of releasing a stale commit. If
the computed tag already exists, the run skips as well. Run release jobs under a
`concurrency` group regardless.
### GitHub App authentication
To attribute release commits and pull requests to an App instead of
`github-actions[bot]`:
1. Create a GitHub App under your organization and generate a private key.
2. Grant the installation **Contents: Read and write**, **Pull requests: Read
and write**, and **Actions: Read and write**, then install it on every
repository that releases.
3. Add the App's numeric ID as the `RELEASE_APP_ID` repository or organization
variable.
4. Add the private-key PEM as the `RELEASE_APP_PRIVATE_KEY` secret. Never commit
the PEM or store it in a plain-text variable.
5. Pass `github-app-id` and the `github-app-private-key` secret to the reusable
workflow below, which mints the installation token with
`actions/create-github-app-token` and hands it to the action.
App authentication attributes API commits and pull requests to the App;
cryptographic commit signing still requires a separate signing-key policy.
Configure both values together — a half-configured App fails the workflow
instead of silently falling back.
## Release flow
This repository releases itself, and it is a container action, so its runtime
image has to be pinned to an image built from the release being cut. That is
circular in a single pass — `docker.yml` derives version tags from the git tag,
so `ghcr.io/threatflux/github_actions:0.7.2` cannot exist before `v0.7.2` does.
`v0.7.0` shipped a `0.6.2` binary for exactly this reason.
`auto-release.yml` therefore releases in two phases:
| 1 — bump | `CI`, `Security`, and `Docker` all green on the head | Pins the runtime to the head's image so this run executes a current binary, then `phase: bump` commits the version bump and stops. Dispatches `docker.yml` with `release-prep=true`. |
| 2 — tag | `docker.yml` hands off after build, scan, and sign | Resolves the new image's digest, **verifies its `--version` equals the manifest version**, gives the digest its `:X.Y.Z`, `:X.Y`, and `:X` tags, rewrites both Dockerfile pins, then `phase: tag` stages those pins into the commit it tags. |
Consequences worth knowing:
- **The tag ships a runtime that reports its own version.** The tagged tree and
the image's source differ only in the two Dockerfiles, which do not affect the
image.
- **A release takes as long as a multi-arch image build** (roughly 40–60 minutes
on QEMU) because phase 2 waits for the image. It costs no extra build:
`docker.yml` is no longer dispatched for the tag, since retagging the verified
digest already produces the version tags, and rebuilding would move them to a
digest the release does not pin.
- **The version commit is pushed with `GITHUB_TOKEN`, so it starts no
workflows.** Both the image build and the handoff back are explicit
dispatches, which are exempt from that suppression.
- **A committed-but-untagged version is the resumable state.** Phase detection
reads the manifest version at `main` and checks whether its tag exists, so any
later run — the weekly cron, a push, or a manual dispatch — finishes a release
that stalled after phase 1 rather than bumping again. `workflow_dispatch`
accepts an explicit `phase` to force either half.
- **Phase 2 is not gated on CI and Security runs** for its target, because no
workflow runs for a token-pushed commit. Its gate is the registry: the image
must exist and must report the version being tagged.
Consumer repositories do not need any of this. The reusable workflow below
releases in a single pass, which is correct for a repository that does not
package its own release into an image it then runs.
## Reusable Auto Release Workflow
For Cargo repositories that want the whole release gate — required-workflow
checks, optional GitHub App authentication, and downstream workflow dispatches
— call the reusable workflow instead of wiring the action yourself:
```yaml
name: Auto Release
on:
push:
branches: [main]
concurrency:
group: auto-release-${{ github.ref }}
permissions:
contents: read
actions: read
pull-requests: read
jobs:
release:
permissions:
contents: write
actions: write
pull-requests: write
uses: ThreatFlux/github_actions/.github/workflows/reusable-auto-release.yml@v0 # pin to a SHA in production
with:
bump: auto
```
The reusable workflow needs no separate action pin: it checks out and runs the
action at its own commit (`job.workflow_sha`), so the action version always
matches whatever workflow ref you pinned. Its inputs, outputs, and the
`required-workflows` / `dispatch-workflows` gate are documented in
[release/README.md](release/README.md#reusable-workflow-inputs).
## Migrating to the Unified Action
This repository used to ship two actions. It now ships one; `release/` is
deprecated.
| `uses: ThreatFlux/github_actions/release@v0` | `uses: ThreatFlux/github_actions@v0` plus `command: release` |
| `uses: ThreatFlux/github_actions@v0` (maintainer) | unchanged, but state `command:` explicitly — it defaults to `pin` |
Every `release/` input keeps its name and default on the unified action, and
all eight release outputs are unchanged, so migrating is the two-line edit
above. Root-action users who were already passing inputs such as
`workflows-path`, `all`, `labels`, or `title` should read the version-skew note
below: those inputs now travel to the binary as environment variables and need
runtime image 0.6.1 or newer to take effect.
### Version skew during upgrades
Only the flags every published binary accepts are passed as container
arguments (`command`, `repo`, `token`, `owner`, `repo-name`, `base-branch`,
`dry-run`). Every other input reaches the binary through the `INPUT_<NAME>`
environment variables GitHub sets for container actions. That keeps the action
working while [`runtime/Dockerfile`](runtime/Dockerfile) still pins a pre-0.6.0
image — but that older binary ignores `INPUT_*` variables entirely.
Until the first post-merge release (0.6.0) publishes and Dependabot bumps the
`/runtime` pin (yielding 0.6.1), these inputs silently fall back to their
built-in defaults:
- `pin`, `update`, `status`: `workflows-path`, `github-actions`, `cargo`,
`all`, `branch-name`, `labels`, `title`, `commit-message`, `create-pr`
- `release`: `bump`, `tag-prefix`, `tag-style`, `update-major-alias`,
`notes-file`, `release-branch`, `commit-message`, `create-pr`
Most notably, **`create-pr: "true"` on `release` performs a direct release
instead of opening a release pull request** during that window, and
`create-pr: "true"` on `update` rewrites the checkout instead of opening a
dependency pull request. Pin the action
to a ref whose `runtime/Dockerfile` holds 0.6.1 or newer before relying on any
of these inputs. The skew self-heals once that pin lands.
### Deprecation timeline
`ThreatFlux/github_actions/release@<ref>` still works and still takes the same
inputs, but it is deprecated as of the unified action and will be **removed in
the next major version**. New workflows should use `command: release` on the
root action; existing ones have the whole `v0` line to migrate.
Roadmap: the release engine is manifest-driven (`src/versioning.rs`), with Cargo supported today; npm (`package.json`) and Python (`pyproject.toml`) manifest adapters are planned next so the same action covers the whole ThreatFlux org.
## Development
```bash
make dev-setup
cargo fmt --all
cargo clippy --all-features --all-targets -- -D warnings
cargo test --all-features
```
## Architecture
The Go-based `githubWorkFlowChecker` concept was narrowed for the initial Rust implementation:
- keep the repo general-purpose for future GitHub Actions maintenance features
- ship secure pinning first, then add version-aware updates and authenticated PR publishing
- separate scanning, GitHub resolution, and rewrite orchestration into small modules
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the current design.