* refactor(watcher,wrkflw): finish draining god-files into modules
The last round of extractions (event_kind, ignore, paths, setup,
trigger_cache, debouncer, shutdown) already peeled the leaves off
`watcher.rs`, but the orchestration core — the 430-line `run()`
body, the git-state cache, the per-cycle `evaluate_and_execute` —
was still sitting there, interleaved with the struct definitions
and hiding under 2071 lines of mostly-test file. `main.rs` had the
same problem on the CLI side: the `Run` and `Watch` match arms
were long inline blocks, and the `pull_request + no base-branch`
rejection was *literally copy-pasted* between the run arm's
`apply_base_branch` helper and the watch arm's inline block.
Having the same load-bearing error string in two places is not my
idea of redundancy.
Lift the TTL-bounded git-state cache into a new `git_state.rs` as
a proper `GitStateCache` type. The `Mutex<Option<CachedGitState>>`
field on `WorkflowWatcher` was a hint that this wanted its own
home. Move the whole main loop body into a new `reactor.rs`, and
while at it drag `evaluate_and_execute`, `refresh_trigger_cache_async`
and `canonicalize_changed_paths` with it — they're private, nobody
outside the reactor calls them, and leaving them on
`WorkflowWatcher` was just more clutter. Keep a `#[cfg(test)]`
shim for `cached_git_state` because two tests pin staleness
behavior through the method; changing the tests would have been
scope creep.
On the `wrkflw` side, extract `prefilter.rs` (holds
`PrefilterDecision`, `PrefilterRequest`, `run_trigger_prefilter`,
`build_event_context`, `apply_base_branch`,
`effective_strict_filter`, plus the seven prefilter unit tests),
then `run_workflow_cmd.rs` and `watch_cmd.rs` for the two command
bodies. Collapse the duplicated pull_request rejection into a new
shared `validate_event_requires_base_branch` helper so both hosts
render identical diagnostics. That's the whole reason the
prefilter pattern exists — to put the flag-matrix logic in one
testable place — and letting the watch command keep its own copy
was how we got here in the first place.
While at it, fix a silent-skip hole at the top of the reactor
loop. The workflow-rescan failure at the old `watcher.rs:628-637`
was logged at `debug` level, directly under a comment that called
the fallback "the exact silent-skip pattern the rest of this PR
has been plugging." Someone saw the hazard and then muted it.
Bump it to `warning` so a persistently-failing rescan (chmod 000,
flaking NFS mount, missing parent) actually surfaces to the user
instead of leaving them staring at a session-long "0 triggered"
stream. Non-verbose users would otherwise never know their new
workflow file was being ignored.
Numbers: `watcher.rs` 2071 → 1218 (~300 non-test), `main.rs` 2039
→ 937. New files: `git_state.rs` (171), `reactor.rs` (819),
`prefilter.rs` (814), `run_workflow_cmd.rs` (221), `watch_cmd.rs`
(248). Public API unchanged. All 46 watcher tests + 7 prefilter
tests still pass. \`cargo clippy -D warnings\` and \`cargo fmt
--check\` are both clean.
* fix(watcher,wrkflw): address review findings on god-file drain
Three things from the review on the refactor PR, none of them
structural but all three worth fixing before merge.
First, the unified `validate_event_requires_base_branch` helper had
hardcoded "simulating" in both the strict error and the non-strict
warning. Reads fine from `wrkflw run` — the run command *is*
simulating — but `wrkflw watch` is not simulating anything, it's
watching. The old inline watch-arm check used "Watching
pull_request" for a reason, and collapsing both sites onto a
run-flavored literal was a UX regression for watch users. The PR
body also claimed the two sites were "literally copy-pasted"; they
were similar but not textually identical. Reword the helper
host-neutral ("event `pull_request` without --base-branch..."),
drop the "simulating"/"watching" verbs entirely, and pin it with
three new tests: the non-PR passthrough pair, the
`pull_request_target` strict-mode rejection, and a regression pin
that fails the build if either host-specific verb ever sneaks back
in.
Second, the escalation of the per-cycle rescan-failure log from
`debug` to `warning` was the right direction — `debug` was
invisible to non-verbose users staring at a session-long "0
triggered" stream — but the same function wires up
`supervisor_warned_at_threshold` as a one-shot latch right above
the rescan code, and the rescan branch was left unlatched. Under
`chmod 000 .github/workflows` plus active file churn the new
warning would fire on every debounced cycle for the rest of the
session, which is the diagnostic-flood failure mode on the other
side of the silent-skip hole. Same pattern as the supervisor: warn
once per failing spell, reset the latch on the first healthy
rescan, log a matching info line on recovery so the operator sees
the transition.
Third, `SUPERVISOR_WARN_THRESHOLD` and `SUPERVISOR_HARD_CAP` were
declared `pub(crate)` on `watcher.rs` but read only from
`reactor.rs`. Move them next to the loop that enforces them, drag
the compile-time `const _: () = { assert!(...) }` invariant block
along, and leave a breadcrumb in `watcher.rs` so `git blame`
doesn't have to chase it.
46/46 watcher tests pass, prefilter tests go 7 → 10, clippy and
fmt both clean.
WRKFLW
A command-line tool for validating and executing GitHub Actions workflows locally. Test your workflows on your machine before pushing to GitHub.
Features
- TUI interface — interactive terminal UI for browsing, running, and monitoring workflows
- Workflow validation — syntax checks, structural validation, and composite action input cross-checking with CI/CD-friendly exit codes
- Local execution — run workflows using Docker, Podman, or emulation mode (no containers)
- Job selection — run individual jobs with
--jobflag or via TUI job selection mode - Job dependency resolution — automatic ordering based on
needswith parallel execution of independent jobs - Action support — Docker container actions, JavaScript actions, composite actions, and local actions
- Reusable workflows — execute caller jobs via
jobs.<id>.uses(local orowner/repo/path@ref) - GitHub context emulation — environment variables,
GITHUB_OUTPUT,GITHUB_ENV,GITHUB_PATH,GITHUB_STEP_SUMMARY - Matrix builds — full support for
include,exclude,max-parallel, andfail-fast - Secrets management — multiple providers (env, file, Vault, AWS, Azure, GCP) with masking and encryption
- Remote triggering — trigger
workflow_dispatchruns on GitHub or GitLab pipelines - GitLab support — validate and trigger GitLab CI pipelines
Installation
cargo install wrkflw
Or build from source:
git clone https://github.com/bahdotsh/wrkflw.git
cd wrkflw
cargo build --release
Quick Start
# Launch the TUI (auto-detects .github/workflows)
wrkflw
# Validate workflows
wrkflw validate
# Run a workflow
wrkflw run .github/workflows/ci.yml
Usage
Validation
# Validate all workflows in .github/workflows
wrkflw validate
# Validate specific files or directories
wrkflw validate path/to/workflow.yml
wrkflw validate path/to/workflows/
# Validate multiple paths
wrkflw validate flow-1.yml flow-2.yml path/to/workflows/
# GitLab pipelines
wrkflw validate .gitlab-ci.yml --gitlab
# Verbose output
wrkflw validate --verbose path/to/workflow.yml
Exit codes: 0 = all valid, 1 = validation failures, 2 = usage error. Use --no-exit-code to disable.
Execution
# Run with Docker (default)
wrkflw run .github/workflows/ci.yml
# Run with Podman
wrkflw run --runtime podman .github/workflows/ci.yml
# Run in emulation mode (no containers)
wrkflw run --runtime emulation .github/workflows/ci.yml
# Run a specific job
wrkflw run --job build .github/workflows/ci.yml
# List jobs in a workflow
wrkflw run --jobs .github/workflows/ci.yml
# Preserve failed containers for debugging
wrkflw run --preserve-containers-on-failure .github/workflows/ci.yml
TUI
# Open TUI with default directory
wrkflw tui
# Open with specific runtime
wrkflw tui --runtime podman
Controls:
| Key | Action |
|---|---|
Tab / 1-4 |
Switch tabs (Workflows, Execution, Logs, Help) |
Up/Down or j/k |
Navigate |
Space |
Toggle selection |
Enter |
Run / View details |
r |
Run selected workflows |
a / n |
Select all / Deselect all |
e |
Cycle runtime (Docker / Podman / Emulation) |
v |
Toggle Execution / Validation mode |
t |
Trigger remote workflow |
q / Esc |
Quit / Back |
Remote Triggering
Trigger workflow_dispatch events on GitHub or GitLab.
# GitHub (requires GITHUB_TOKEN env var)
wrkflw trigger workflow-name --branch main --input key=value
# GitLab (requires GITLAB_TOKEN env var)
wrkflw trigger-gitlab --branch main --variable key=value
Runtime Modes
| Mode | Description | Best for |
|---|---|---|
| Docker (default) | Full container isolation, closest to GitHub runners | Production, CI/CD |
| Podman | Rootless containers, no daemon required | Security-conscious environments |
| Emulation | Runs directly on host, no containers needed | Quick local testing |
Reusable Workflows
jobs:
call-local:
uses: ./.github/workflows/shared.yml
call-remote:
uses: my-org/my-repo/.github/workflows/shared.yml@v1
with:
foo: bar
secrets:
token: ${{ secrets.MY_TOKEN }}
- Local refs resolve relative to the working directory
- Remote refs are shallow-cloned at the specified
@ref with:entries becomeINPUT_<KEY>env vars;secrets:becomeSECRET_<KEY>
Limitations: outputs from called workflows are not propagated back; secrets: inherit is not supported; private repos for remote uses: are not yet supported.
Secrets Management
WRKFLW supports GitHub Actions-compatible ${{ secrets.* }} syntax with multiple providers:
# Environment variables (simplest)
export GITHUB_TOKEN="ghp_..."
wrkflw run .github/workflows/ci.yml
# File-based secrets (JSON, YAML, or .env format)
# Configure in ~/.wrkflw/secrets.yml
Supported providers: environment variables, file-based, HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, Google Cloud Secret Manager. See the secrets demo for detailed examples.
Limitations
Supported
- Workflow syntax validation with exit codes
- Job dependency resolution and parallel execution
- Matrix builds, environment variables, GitHub context
- Container, JavaScript, composite, and local actions
- Reusable workflows (caller jobs)
- Environment files (
GITHUB_OUTPUT,GITHUB_ENV,GITHUB_PATH,GITHUB_STEP_SUMMARY) - TUI and CLI interfaces
- Container cleanup (even on Ctrl+C)
Not Supported
- GitHub encrypted secrets and fine-grained permissions
actions/cache(no persistent cache between runs)- Artifact upload/download between jobs
- Event triggers other than
workflow_dispatch - Windows and macOS runners
- Job/step timeouts, concurrency, and cancellation
- Service containers in emulation mode
- Reusable workflow output propagation (
needs.<id>.outputs.*)
Project Structure
WRKFLW is organized as a Cargo workspace with focused crates:
| Crate | Purpose |
|---|---|
wrkflw |
CLI binary and library entry point |
wrkflw-executor |
Workflow execution engine |
wrkflw-parser |
Workflow file parsing and schema validation |
wrkflw-evaluator |
Structural evaluation of workflow files |
wrkflw-validators |
Validation rules for jobs, steps, triggers |
wrkflw-runtime |
Container and emulation runtime abstractions |
wrkflw-ui |
Terminal user interface |
wrkflw-models |
Shared data structures |
wrkflw-matrix |
Matrix expansion utilities |
wrkflw-secrets |
Secrets management with multiple providers |
wrkflw-github |
GitHub API integration |
wrkflw-gitlab |
GitLab API integration |
wrkflw-logging |
In-memory logging for TUI/CLI |
wrkflw-utils |
Shared helpers |
License
MIT License - see LICENSE for details.
