0.11.0 2026-05-19

Added

  • Task chaining for runner run and runner install. New -s / --sequential and -p / --parallel flags turn the trailing positionals into a chain: runner run -s build test lint runs the three tasks in order; runner run -p test:unit test:e2e fans them out concurrently. runner install build test chains install → build → test (install head is always sequential; -p is rejected on install).
  • Failure policies for chains. Default is fail-fast (sequential stops on first non-zero; parallel lets running siblings finish, doesn't start new ones). -k / --keep-going runs every task to completion regardless of failures, with the chain's final exit code reflecting the first failure. --kill-on-fail (parallel only) terminates siblings immediately when one fails. -k and --kill-on-fail are mutually exclusive across CLI, env, and config — conflicting layers surface ResolveError::ConflictingFailurePolicy with the offending source named.
  • [chain] section in runner.toml plus RUNNER_KEEP_GOING / RUNNER_KILL_ON_FAIL env-var mirrors. Same resolver-chain precedence as the rest of the policy knobs: CLI > env > config. Env layer is presence-authoritative — RUNNER_KEEP_GOING=0 overrides [chain].keep_going = true in config, not just the default.
  • Line-prefix multiplexer for parallel chain output. Each task's piped stdout/stderr is captured by a reader thread, prefixed with [<task-name>] (right-padded to the longest name, colored from an 8-slot deterministic palette), and forwarded to the parent's stdout/stderr. Honors NO_COLOR and non-TTY parents.
  • Resolver-warning deduplication across chain dispatch. Per-task warnings collect into a shared HashSet, then emit once at the end sorted by Display so output order is stable across runs.

Changed

  • runner install --frozen <tasks> now propagates the --frozen flag into the synthetic install head of the chain, so the install step runs lockfile-only when the flag is set. Previous behavior silently dropped the flag in chain mode.
  • Root single-binary Go task name now derives from the module path in go.mod (last segment, with a /vN major-version suffix dropped to match how Go names the built binary) instead of the project directory name, so cloning a repo into a differently-named directory no longer changes the task name. Falls back to the directory name only when go.mod is absent or has no parseable module line.

Fixed

  • Node script discovery is no longer gated on a *detected* package manager. A package.json with scripts but no lockfile and no packageManager / devEngines field (a typical pnpm-workspace member directory) reported "No project detected" and runner run build fell through to a bogus bun build. Manifest presence is now the Node signal; *which* PM dispatches scripts is the resolver's runtime job. A manifest-less subdirectory still lists ancestor scripts, but only when it provably sits inside a JS monorepo (workspace-root-aware, VCS-bounded), so an unrelated outer project's package.json is never silently adopted. https://github.com/kjanat/runner/pull/32
  • Detection now mirrors the resolver's package-manager chain (packageManagerdevEngines.packageManager → enclosing-workspace lockfile/manifest), so runner info / runner install from a workspace member target the workspace's tool instead of resolving nothing. Corepack semantics preserved: a present-but-unparseable legacy packageManager still warns and is not silently superseded by devEngines.

0.10.0 2026-05-14

Added

  • mise task extraction and dispatch. mise was previously detection-only — runner listed it under "Task Runners" but its tasks were invisible to runner list and runner run <task>. New TaskSource::MiseToml makes mise a first-class source: tasks declared in mise.toml / .mise.toml (and the *.local.toml, mise/config.toml, .config/mise.toml companions in mise's documented precedence) appear in listings, participate in the selection priority, and dispatch via mise run <task>.
  • Bacon-style two-tier extraction for mise. Primary path shells out to mise tasks --json — authoritative across mise's config layering and file-based tasks (mise-tasks/*); fallback parses the first project-local config when mise isn't on $PATH. Both paths exclude hidden tasks (hide = true), underscore-prefixed names, and tasks whose source lives outside the project root (so global / ~/.config/mise/* tasks don't pollute the project's task list). Empty or whitespace-only description = "" values are treated as missing so the renderer falls through to the run body or file reference instead of showing a blank column. Aliases come through as separate entries pointing at their target, mirroring the justfile shape.

Fixed

  • Resolver no longer dispatches through a Node package manager in projects with no Node-ecosystem evidence (https://github.com/kjanat/runner/issues/23). runner run <unknown-task> in a Go-only repo with bun installed used to warn "no node signals matched" and then run bun <task> anyway; the FallbackPolicy::Probe PATH probe now requires a package.json (or equivalent manifest) somewhere upward of ctx.root before it considers the canonical Node order. Without that evidence the resolver returns the existing soft NoSignalsFound sentinel and cmd::run::run_pm_exec_fallback spawns the target directly on $PATH — no more wrong-ecosystem dispatch.

0.9.0 2026-05-13

Added

  • Unified package-manager resolution chain. runner run now follows a documented 8-step precedence — qualified syntax → --pm / --runnerRUNNER_PM / RUNNER_RUNNERrunner.tomlpackage.json (packageManager then devEngines.packageManager) → lockfile → PATH probe → terminal error — making toolchain selection predictable across Corepack, antfu/ni, mise, and pnpm v11+ conventions. New src/resolver/ module owns the chain end-to-end.
  • --pm / --runner global flags with RUNNER_PM / RUNNER_RUNNER env-var mirrors. Cross-ecosystem overrides like --pm cargo against a Node project fall through to detection rather than hijacking dispatch. CLI wins over env; empty env strings are treated as unset.
  • runner.toml project config ([pm], [task_runner], [resolution], [sources.*]). Per-ecosystem PM overrides apply only when the named PM matches the requested ecosystem; [resolution].fallback controls the no-signal behavior.
  • devEngines.packageManager parsing from package.json (OpenJS proposal, npm 10.9+). Single-object and array forms supported with per-entry onFail (ignore/warn/error). download collapses to warn since runner is not an installer. The legacy packageManager field still wins when both are present; manifest declarations win over lockfile signals (Corepack semantics) and emit a package.json warning when they disagree.
  • Semver enforcement on devEngines.version. When the declared range doesn't match the installed PM's --version, onFail=warn emits a warning and onFail=error bails. Unparseable ranges or missing --version output skip the check silently so a partially-broken environment never blocks dispatch.
  • --fallback policy (probe default | npm legacy | error) with RUNNER_FALLBACK env mirror. The default replaces the silent npm fallback with a canonical-order PATH probe (bun > pnpm > yarn > npm); when nothing matches, the user sees an actionable error listing every source that was checked.
  • --explain flag (RUNNER_EXPLAIN env). Emits a one-line trace describing which chain step produced the PM decision: · runner resolved: pnpm via package.json "packageManager".
  • runner doctor subcommand. Dumps every signal the resolver considers: detected PMs/runners, override sources in effect (with origin attribution), manifest declarations, lockfile presence, PATH probe results for each Node PM, the final decision, and any warnings. --json emits schema-versioned output for jq/scripts/bug reports.
  • runner why <task> subcommand. Walks the source-selection chain for a single task: lists every candidate source with its (priority, depth, display_order, alias) tuple, names the winner, and renders the PM resolution trace when a package.json script is picked. --json available.
  • Passthrough-wrapper detection generalized to every supported task runner. A package.json script like "build": "just build" is now recognized as a thin wrapper around just and deduped from completion candidates when the underlying runner exposes a same- named task. Recognized runners: turbo, just, make, task (go-task), nx, bacon, mise.
  • Ecosystem enum (Node/Deno/Python/Rust/Go/Ruby/Php) formalizing the PM-to-ecosystem mapping used by override scoping.

Changed

  • Task.passthrough_to_turbo: bool replaced by Task.passthrough_to: Option<TaskRunner> so wrappers around any runner — not just turbo — can be attributed at detection time and used by completion.
  • cmd::run::run signature now takes a &ResolutionOverrides so the resolver-chosen PM also flows through the no-task fallback paths (bun test special case, npx-style exec). --pm npm against a Bun-detected project now correctly suppresses the bun-test fallback.
  • Task selection unified into a single sort key (source_priority, source_depth, display_order, alias_last). Replaces the Deno-only depth path with a generic tiebreak that applies to every source. Every non-workspace-aware source now walks ancestors upward when computing source_depth so the tiebreak actually distinguishes nested Makefile/Justfile/Taskfile/bacon.toml configs from workspace-root ones.
  • Resolver::resolve_node_pm returns Result<ResolvedPm> so manifest onFail=error and --fallback error can bubble up structured errors instead of bailing inside the resolver.

Fixed

  • package.json packageManager: "deno@…" projects no longer require a deno.json alongside to be recognized as a Deno project.
  • Stale doc comment on detect::push_package_json_tasks updated to reflect that passthrough detection covers every known runner, not just turbo.

0.8.1 2026-05-12

Fixed

  • Error: program not found on Windows when runner run <script> dispatches through npm / yarn / pnpm (issue #20). Bare-name spawns now walk PATH × PATHEXT so .cmd / .bat shims (npm.cmd, yarn.cmd, pnpm.cmd) resolve the same way they do under cmd.exe / PowerShell. Same fix covers every other Windows-shimmed tool runner dispatches: turbo, deno, make, task, just, composer, poetry, pipenv, uv, bundle, go, bacon, and the runner run <bin> arbitrary-target fallback. Non-Windows targets are unchanged.

0.8.0 2026-05-10

Added

  • bacon.toml as a runner task source. Jobs surface in runner list / runner info, dispatch via runner run <job> / run <job>, and resolve under the bacon.toml:<job> qualified syntax. Bacon is detected as a task runner alongside just / make / go-task. Jobs whose names start with _ are treated as private and hidden. When the bacon CLI is on PATH, extraction shells out to bacon --list-jobs so bacon's built-in jobs (check, clippy, test, …) merge into the listing alongside whatever bacon.toml declares — same view bacon itself presents. Falls back to TOML parsing when bacon isn't installed. Job arguments forward through bacon's -- separator (runner run test -- --ignoredbacon test -- --ignored) so they reach the underlying job intact.
  • Project-local bacon.toml defining lint, test-all, and bins jobs for the runner crate, mirroring the cargo l / cargo t aliases.
  • cargo binstall runner-run support via [package.metadata.binstall] in Cargo.toml. cargo-binstall now downloads the prebuilt binary from the matching GitHub release asset (runner-v{version}-{target}.tar.gz) instead of building from source — same archives taiki-e/upload-rust-binary-action uploads from release.yml. Both runner and run install side by side, no toolchain required.

Changed

  • Release pipeline reordered. crates-release now triggers on push: tags: ['v*'] instead of release: published, so cargo publish fires in parallel with binary builds and no longer waits on the npm publish chain to complete first. release.yml gains a final publish-release job that flips the draft GitHub release to published once binaries and the npm-dist artifact land — this is now the natural pivot of the release lifecycle and drives npm-release.yml via release: published. npm-release.yml drops its workflow_run trigger (and the draft-flip side job that was hidden in it), resolving the build run-id for cross-workflow artifact download via gh run list instead. Net effect: tag push alone ships crates.io immediately, and the GH release auto-publishes once binaries are ready — no more manual draft-flipping.
  • npm/facade/README.md updates the install fallback instructions to cargo install runner-run (crates.io) instead of the git-source form, matching the 0.7.1 README/landing-page change.
  • npm/facade/package.json template no longer carries a version field. The build script (npm/scripts/build-packages.ts) injects the version from cargo metadata at build time and the template value was always overwritten. Single source of truth is now Cargo.toml.

Fixed

  • runner completions $SHELL no longer fails when $SHELL expands to a full path (e.g. /usr/bin/zsh). The explicit <shell> arg now accepts either a bare name (zsh) or a full path, mirroring the bare-arg fallback that already file-stems $SHELL. Previously the stock clap ValueEnum parser rejected anything but the bare names, making the explicit and implicit paths inconsistent.
  • pwsh surfaces in runner completions --help and the rejection error alongside powershell. The internal mapping has accepted both since shell_from_path was written; only the user-facing message lagged.

0.7.1 2026-05-10

Fixed

  • Turborepo configs resolve under both turbo.json and turbo.jsonc filenames, and either is parsed as JSONC (line/block comments and trailing commas), matching Turborepo v2's own parser. Detection previously hardcoded turbo.json and the parser was strict JSON, so .jsonc files were invisible to detection and any JSONC syntax surfaced parse errors. The qualified-task syntax also accepts turbo.jsonc:task (and deno.jsonc:task, fixed in the same line for parity). Fixes #10.
  • Root Tasks in turbo.json — entries written with the //#name prefix, invoked via turbo run name against the workspace root — now surface in runner list under their bare name. Workspace-scoped entries (pkg#task) remain filtered, and the result set is deduplicated when both name and //#name are defined. Fixes #11.

Changed

  • crates-release CI workflow uses crates.io trusted publishing via OIDC. Replaces the long-lived CARGO_REGISTRY_TOKEN secret with a short-lived token minted per run by rust-lang/crates-io-auth-action; the secret-presence preflight is gone.
  • README and the landing page promote cargo install runner-run (from crates.io) as the primary cargo install command, with the git and local-checkout forms remaining as fallbacks for unreleased commits and development work.
  • README adds a crates.io shields badge alongside the existing npm one; the landing page footer adds crates.io and npm registry links next to the source and changelog references.

0.7.0 2026-05-10

Added

  • crates.io publishing: new crates-release workflow publishes the crate to crates.io when a maintainer publishes the GitHub release that release.yml cuts as a draft. Verifies the tag matches Cargo.toml, runs cargo publish --locked --dry-run first, then publishes via CARGO_REGISTRY_TOKEN under the crates-io environment. workflow_dispatch is preserved for manual republishes.

Changed

  • Renamed the published package from runner to runner-run (the bare runner name is taken on crates.io). The library crate name is pinned to runner via [lib] so existing runner::… imports in src/main.rs and src/bin/run.rs keep working unchanged.
  • GitHub composite action: uses: kjanat/runner@vX.Y.Z installs the runner and run binaries on PATH in CI. Pinned tag refs make zero API calls (tag resolved from github.action_ref); version: latest triggers a single releases/latest lookup. Parallel curl of archive + sha256 with --retry-all-errors --retry 5, sha256 verification before extract, actions/cache@v4 keyed on tag + triple (cache hits ~250 ms, cold install 600–900 ms), and a runner --version smoke test on every install so a bad cache or missing asset surfaces here, not at the consumer's first task.
  • Cargo aliases as a runner task source. The hierarchical .cargo/config.toml chain (cwd up to filesystem root, then $CARGO_HOME/config{,.toml}) is merged with cargo's precedence rules, recursive alias chains are expanded so runner list shows the fully-resolved command (l → clippy --all-targets --all-features -- -D warnings, recursive_example → run --release --example recursions), and built-ins (b/c/d/t/r/rm) always surface even in projects without a user config. User attempts to redefine a built-in are silently ignored to match cargo's own rule. runner run <alias> shells out to cargo <alias> <args...> so cargo's runtime resolution stays authoritative.
  • Landing page at https://runner.kjanat.com, deployed to Cloudflare Workers Assets from site/. Single static page, dark mode via prefers-color-scheme, click-to-copy install commands with polite ARIA live-region announcements, tab-completion section, custom 404 styled as a fake runner <path> error line. Page weight squeezed under TCP IW10 (~14.5 KB uncompressed, ~3.6 KB brotli) so the whole first response lands in a single round-trip; _headers ships strict CSP, HSTS, and edge cache.
  • Templated site build (site/build.ts + site/dev.ts): Bun bundles index.html / 404.html from src/ into dist/ and substitutes {{version}}, {{repo}}, {{authorName}} from Cargo.toml, so the site and the crate share one source of truth for metadata. dev.ts serves dist/, watches src/ / public/ / Cargo.toml with an 80 ms debounce, and injects a WebSocket live-reload snippet into served HTML.
  • site/build.ts returns the emitted file list with bytes; the CLI prints a sorted raw / gzip / br table at quality 9/11 to mirror what a CDN actually serves, surfacing budget regressions next to the build. HTML post-processing reads from Bun.build outputs in memory instead of re-reading dist/, and copyTree returns the bytes it copied so public/ files land in the same DistFile[] the summary walks.
  • BuildOptions.dir ("relative" | "full") toggles the size-summary path column between dist/-relative and absolute, surfaced via the FULL env in the local build script. Public files respect the toggle too so the rendered table is consistent across sources.
  • GitHub Pages-aware publicPath: under GitHub Actions the asset prefix derives to https://<owner>.github.io/<repo>/ from GITHUB_REPOSITORY, so PR previews of forks load their own assets without a config flag. PUBLIC_PATH env still overrides everything.
  • CF_BEACON_TOKEN env overrides the inlined Cloudflare Web Analytics token; the literal stays as a fallback so production builds without the env still report correctly.
  • External sourcemaps emit when SENTRY_DSN is set; otherwise the build skips them entirely so the published site stays one-round-trip-sized for end users.
  • README links the landing page and npm package, and adds shields.io badges for the runner-run npm version and the MIT licence.

Changed

  • Cloudflare Web Analytics beacon now only injects in CI / GitHub Actions builds. Local bun run build and library imports leave the snippet out so dev previews don't phone home, and a missing </body> warns instead of throwing so partial HTML fragments don't fail the build.
  • npm/scripts/build-packages.ts swaps the deprecated cargo read-manifest for cargo metadata --no-deps --format-version 1, picking the workspace's workspace_default_members[0] by id (falls back to the first package for non-virtual single-member workspaces). maxBuffer bumped to 64 MiB so large workspaces don't trip Node's 1 MiB default.
  • .github/scripts/publish/npm.sh derives REQUIRED_PLATFORMS and OPTIONAL_PLATFORMS from npm/targets.json at runtime via jq instead of hardcoding two parallel lists; npm-release.yml sparse-checkout adds npm/targets.json so the script can read it. Optional == experimental: true, matching the workflow's existing continue-on-error semantic. One source of truth for the platform matrix.
  • Drop the unused NODE_AUTH_TOKEN env from the npm publish step; auth flows through the OIDC token via id-token: write and npm publish --provenance, not a long-lived NPM_TOKEN.
  • Move the landing-page primary domain from runner.kjanat.com to runner.kjanat.dev across the root README, site docs, site package metadata, and the page canonical URL so published links and metadata point at the new host.
  • Add runner.kjanat.dev as a Cloudflare custom-domain route while keeping the existing .com route active during the transition.
  • Reflow the npm facade README intro for cleaner package-page rendering.
  • Set Cargo.toml [package].homepage and npm/facade/package.json homepage to https://runner.kjanat.dev (was the GitHub README URL), so the published crate and npm package surface the landing page on registry pages.

Fixed

  • site/build.ts publicPath precedence: the original env["PUBLIC_PATH"] || isCI ? X : Y parsed as (... || ...) ? X : Y, so a literal PUBLIC_PATH value never reached Bun.build — it acted as a boolean toggle. The hardcoded runner.kjanat.com/ fallback also leaked into Cloudflare Workers preview deploys (*.workers.dev) and tripped CSP 'self', blocking every asset on every PR preview. Replaced with env["PUBLIC_PATH"] || githubPagesUrl() || "/": explicit override wins, GitHub Pages still gets its /<repo>/ prefix, everything else stays same-origin.
  • public/_headers drops the dead /favicon.ico Content-Type override block; the icon ships as an SVG via <link rel="icon">, so nothing routes through /favicon.ico to need it.
  • .github/scripts/build/package-release-asset.sh writes checksum files as <basename>.sha256 (not <basename>.tar.gz.sha256), matching taiki-e/upload-rust-binary-action's convention and what verify-checksum.sh enforces — the previous mismatch would have broken the npm pipeline's checksum verification on release.
  • npm/scripts/build-packages.ts: Target.build union now covers all five schema enum values (previously only cargo | cross, missing the three variants added when the BSD build paths landed) so type narrowing matches reality and a stray build-tool name fails the build instead of silently shipping.
  • Remove the stale openbsd-x64 entry from npm.sh's OPTIONAL_PLATFORMS and the matching matrix-gen comments in release.yml / release-dryrun.yml; the openbsd build path was scrapped earlier and the residue would have failed optionalDependencies validation if the platform was ever re-expected.
  • Tab completion for turborepo monorepos no longer triple-emits the same task. A package.json script is classified as a turbo passthrough at detection time when its command body literally invokes turbo run <name> (or the shorthand turbo <name>) for a same-named target, optionally followed by flag tokens (--filter web, --concurrency=4) or — after a bare -- end-of-options separator (POSIX/getopt convention) — args forwarded to the underlying task; the full bash control set (&&, ||, ;, ;;, ;&, ;;&, |, |&, &, !, {, }, (, )), fd-style redirects (bare >/</>>/<<<, combined-fd &>/>&, fd-prefixed 2>, composite 2>&1, 2>/dev/null, &>file.log), shell expansion (parameter $X/${X}/${X:-def}/${X//a/b}, special vars $@/$*/ $#/$?, command substitution $(cmd) and backtick `cmd`, arithmetic $((expr)), double-quoted forms with embedded expansion "${X}") — including those positioned after a value-expecting flag or after -- — and extra positional targets all reject the match so scripts that do real work beyond dispatching to turbo stay visible. Only thin passthroughs are dropped from completion when a same-named turbo.json task also exists. Real scripts like "build": "vite build" keep their qualified form even when they happen to share a name with a turbo task. runner list still surfaces both sources for transparency, runner build already dispatched through turbo per source_priority, and a third source (e.g. Makefile) keeps its qualified form alongside turbo.json:build for disambiguation.

0.6.1 2026-05-08

Added

  • GitHub composite action: uses: kjanat/runner@vX.Y.Z installs the runner and run binaries on PATH in CI. Pinned tag refs make zero API calls (tag resolved from github.action_ref); version: latest triggers a single releases/latest lookup. Parallel curl of archive + sha256 with --retry-all-errors --retry 5, sha256 verification before extract, actions/cache@v4 keyed on tag + triple (cache hits ~250 ms, cold install 600–900 ms), and a runner --version smoke test on every install so a bad cache or missing asset surfaces here, not at the consumer's first task.
  • Cargo aliases as a runner task source. The hierarchical .cargo/config.toml chain (cwd up to filesystem root, then $CARGO_HOME/config{,.toml}) is merged with cargo's precedence rules, recursive alias chains are expanded so runner list shows the fully-resolved command (l → clippy --all-targets --all-features -- -D warnings, recursive_example → run --release --example recursions), and built-ins (b/c/d/t/r/rm) always surface even in projects without a user config. User attempts to redefine a built-in are silently ignored to match cargo's own rule. runner run <alias> shells out to cargo <alias> <args...> so cargo's runtime resolution stays authoritative.
  • Landing page at https://runner.kjanat.com, deployed to Cloudflare Workers Assets from site/. Single static page, dark mode via prefers-color-scheme, click-to-copy install commands with polite ARIA live-region announcements, tab-completion section, custom 404 styled as a fake runner <path> error line. Page weight squeezed under TCP IW10 (~14.5 KB uncompressed, ~3.6 KB brotli) so the whole first response lands in a single round-trip; _headers ships strict CSP, HSTS, and edge cache.
  • Templated site build (site/build.ts + site/dev.ts): Bun bundles index.html / 404.html from src/ into dist/ and substitutes {{version}}, {{repo}}, {{authorName}} from Cargo.toml, so the site and the crate share one source of truth for metadata. dev.ts serves dist/, watches src/ / public/ / Cargo.toml with an 80 ms debounce, and injects a WebSocket live-reload snippet into served HTML.
  • site/build.ts returns the emitted file list with bytes; the CLI prints a sorted raw / gzip / br table at quality 9/11 to mirror what a CDN actually serves, surfacing budget regressions next to the build. HTML post-processing reads from Bun.build outputs in memory instead of re-reading dist/, and copyTree returns the bytes it copied so public/ files land in the same DistFile[] the summary walks.
  • BuildOptions.dir ("relative" | "full") toggles the size-summary path column between dist/-relative and absolute, surfaced via the FULL env in the local build script. Public files respect the toggle too so the rendered table is consistent across sources.
  • GitHub Pages-aware publicPath: under GitHub Actions the asset prefix derives to https://<owner>.github.io/<repo>/ from GITHUB_REPOSITORY, so PR previews of forks load their own assets without a config flag. PUBLIC_PATH env still overrides everything.
  • CF_BEACON_TOKEN env overrides the inlined Cloudflare Web Analytics token; the literal stays as a fallback so production builds without the env still report correctly.
  • External sourcemaps emit when SENTRY_DSN is set; otherwise the build skips them entirely so the published site stays one-round-trip-sized for end users.
  • README links the landing page and npm package, and adds shields.io badges for the runner-run npm version and the MIT licence.

Changed

  • Cloudflare Web Analytics beacon now only injects in CI / GitHub Actions builds. Local bun run build and library imports leave the snippet out so dev previews don't phone home, and a missing </body> warns instead of throwing so partial HTML fragments don't fail the build.
  • npm/scripts/build-packages.ts swaps the deprecated cargo read-manifest for cargo metadata --no-deps --format-version 1, picking the workspace's workspace_default_members[0] by id (falls back to the first package for non-virtual single-member workspaces). maxBuffer bumped to 64 MiB so large workspaces don't trip Node's 1 MiB default.
  • .github/scripts/publish/npm.sh derives REQUIRED_PLATFORMS and OPTIONAL_PLATFORMS from npm/targets.json at runtime via jq instead of hardcoding two parallel lists; npm-release.yml sparse-checkout adds npm/targets.json so the script can read it. Optional == experimental: true, matching the workflow's existing continue-on-error semantic. One source of truth for the platform matrix.
  • Drop the unused NODE_AUTH_TOKEN env from the npm publish step; auth flows through the OIDC token via id-token: write and npm publish --provenance, not a long-lived NPM_TOKEN.

Fixed

  • site/build.ts publicPath precedence: the original env["PUBLIC_PATH"] || isCI ? X : Y parsed as (... || ...) ? X : Y, so a literal PUBLIC_PATH value never reached Bun.build — it acted as a boolean toggle. The hardcoded runner.kjanat.com/ fallback also leaked into Cloudflare Workers preview deploys (*.workers.dev) and tripped CSP 'self', blocking every asset on every PR preview. Replaced with env["PUBLIC_PATH"] || githubPagesUrl() || "/": explicit override wins, GitHub Pages still gets its /<repo>/ prefix, everything else stays same-origin.
  • public/_headers drops the dead /favicon.ico Content-Type override block; the icon ships as an SVG via <link rel="icon">, so nothing routes through /favicon.ico to need it.
  • .github/scripts/build/package-release-asset.sh writes checksum files as <basename>.sha256 (not <basename>.tar.gz.sha256), matching taiki-e/upload-rust-binary-action's convention and what verify-checksum.sh enforces — the previous mismatch would have broken the npm pipeline's checksum verification on release.
  • npm/scripts/build-packages.ts: Target.build union now covers all five schema enum values (previously only cargo | cross, missing the three variants added when the BSD build paths landed) so type narrowing matches reality and a stray build-tool name fails the build instead of silently shipping.
  • Remove the stale openbsd-x64 entry from npm.sh's OPTIONAL_PLATFORMS and the matching matrix-gen comments in release.yml / release-dryrun.yml; the openbsd build path was scrapped earlier and the residue would have failed optionalDependencies validation if the platform was ever re-expected.
  • Tab completion for turborepo monorepos no longer triple-emits the same task. A package.json script is classified as a turbo passthrough at detection time when its command body literally invokes turbo run <name> (or the shorthand turbo <name>) for a same-named target, optionally followed by flag tokens (--filter web, --concurrency=4) or — after a bare -- end-of-options separator (POSIX/getopt convention) — args forwarded to the underlying task; the full bash control set (&&, ||, ;, ;;, ;&, ;;&, |, |&, &, !, {, }, (, )), fd-style redirects (bare >/</>>/<<<, combined-fd &>/>&, fd-prefixed 2>, composite 2>&1, 2>/dev/null, &>file.log), shell expansion (parameter $X/${X}/${X:-def}/${X//a/b}, special vars $@/$*/ $#/$?, command substitution $(cmd) and backtick `cmd`, arithmetic $((expr)), double-quoted forms with embedded expansion "${X}") — including those positioned after a value-expecting flag or after -- — and extra positional targets all reject the match so scripts that do real work beyond dispatching to turbo stay visible. Only thin passthroughs are dropped from completion when a same-named turbo.json task also exists. Real scripts like "build": "vite build" keep their qualified form even when they happen to share a name with a turbo task. runner list still surfaces both sources for transparency, runner build already dispatched through turbo per source_priority, and a third source (e.g. Makefile) keeps its qualified form alongside turbo.json:build for disambiguation.

0.6.0 2026-05-05

Added

  • npm distribution: install prebuilt binaries via npm install -g runner-run (or pnpm/yarn/bun). The façade package (runner-run) declares one @runner-run/<platform>-<arch>[-<libc>] package per supported target in optionalDependencies; npm/pnpm/yarn filter at install time using each sub-package's os / cpu / libc fields, so only the matching binary is fetched. No postinstall script and no network access during install. Façade shims (bin/runner.cjs, bin/run.cjs) resolve the platform sub-package via require.resolve() at runtime through lib/resolve.cjs and lib/launch.cjs, with helpful diagnostics when no matching sub-package is installed.
  • Release matrix expanded from Linux musl x86_64/aarch64 to 13 targets across Linux (gnu/musl × x64/arm64 + armv7), macOS (x64, arm64), Windows (x64, arm64, ia32), FreeBSD (x64, arm64), and NetBSD x64. Tier-3 BSD targets are marked experimental: true and do not block the release. Per-target runner / build-tool selection is data-driven from npm/targets.json (validated by npm/targets.schema.json).
  • New npm-release workflow downloads the GitHub Release tarballs, verifies SHA-256 checksums, generates per-platform packages from npm/targets.json, and publishes to npm with provenance, optional dry-run, and configurable dist-tag.
  • build.rs build script reads [[package.metadata.authors]] from Cargo.toml and exposes the primary author as compile-time env vars RUNNER_AUTHOR_NAME (always) and RUNNER_AUTHOR_EMAIL (when set), consumed by the help byline via env! / option_env!.
  • Pin Rust toolchain via rust-toolchain.toml (channel 1.95, components rustfmt / clippy / rust-analyzer, profile minimal).
  • Add justfile with developer recipes for building both bins, generating per-target npm packages (build-packages), and end-to-end-testing the façade resolution against the host triple (test-release).
  • README documents the npm install path and the façade pattern (per-platform sub-package via optionalDependencies, install-time filtering, no postinstall, no network).
  • .github/scripts/build/ helpers: build-npm-packages.sh, derive-dist-dry.sh, download-release-archives.sh, verify-checksum.sh, plus .github/scripts/publish/npm.sh for the publish path.

Changed

  • Move authors metadata from package.authors to a structured [[package.metadata.authors]] table (with name / email fields) consumed by the new build script; src/lib.rs drops the runtime primary_author / authors regex-style parser in favour of env!("RUNNER_AUTHOR_NAME") / option_env!("RUNNER_AUTHOR_EMAIL"). help_byline(stdout_is_terminal: bool) -> String replaces the prior Option<String>-returning helper and is now part of the public API along with requests_version.
  • Breaking (Cargo features): rename feature run-aliasrun; the run binary's required-features follows. Builds passing --features run-alias no longer enable the alias.
  • Bump MSRV from 1.88 to 1.95 (matches the pinned toolchain).
  • Add build = "build.rs" and a [package.metadata.npm] block (name, subpkgscope, bugs, repository, engines) consumed by the npm build pipeline so target naming / scope live in one place.
  • Promote colored, json5, serde_json, shlex, and yaml-rust2 into a single [dependencies] table; add serde + toml as [build-dependencies] for the build script.
  • Migrate npm build/publish scripts from .mjs to TypeScript (npm/scripts/build-packages.ts, npm/scripts/publish.ts); add tsconfig.json. Build-script type narrowing (narrowRepository / narrowBugs / narrowAuthor) now throws on wrong-typed optional fields instead of silently dropping them, so Cargo metadata drift fails the build instead of shipping a façade with missing fields.
  • Release workflow archive layout updated to package per-target artifacts consumable by the npm build, and adds an experimental flag so tier-3 BSD targets do not block a release.
  • Editor / formatting config: .dprint.json plugin update, .gitattributes added for line-ending consistency, .gitignore ignores generated npm artefacts, .zed/settings.json checked in.

Fixed

  • Help-byline rendering no longer depends on parsing clap::crate_authors!() at runtime; the email-aware OSC-8 hyperlink path is driven by option_env!("RUNNER_AUTHOR_EMAIL") set at compile time, removing the runtime string-split fallback.

Security

  • Harden the npm release pipeline with five fail-loud input-validation guards (commit b404098): - derive-dist-dry.sh validates INPUT_DIST_TAG against ^[A-Za-z][A-Za-z0-9._-]*$ so a malformed override cannot smuggle flag-like or whitespace values into npm publish --tag, and normalises INPUT_DRY_RUN to strict true / false (previously True / 1 / yes fell through as dry_run=false, i.e. a real publish disguised as a dry run). - release.yml smoke-tests the packaged linux-x64-gnu binary with --version before uploading the npm/dist artefact, catching a broken bin at build time instead of as ENOENT post-publish. - npm.sh validates optionalDependencies in publish_allowed: the façade must list every required platform under the scope at exactly EXPECTED_VERSION, and platform sub-packages must declare none — closing a vector where a tampered platform package could smuggle attacker-controlled transitive deps. - npm view and npm publish are wrapped in timeout 120s with explicit 124 handling so a hung registry cannot burn the full job budget.

0.5.0 2026-04-21

Added

  • Detect justfile aliases (alias b := build) and surface them as first-class tasks in runner list and runner <alias>. Private aliases (prefixed _, tagged [private], or pointing at a private recipe) are hidden. Works via just --dump-format json when just is on PATH and via the regex fallback parser otherwise.
  • Render justfile aliases distinctly from recipes in runner list and shell tab completions: aliases appear under a dedicated justfile (aliases) group with name → target annotations instead of duplicating the target recipe's description.

0.4.1 2026-04-21

Fixed

  • Stop zsh completion from leaking unmatched glob patterns into the user's prompt. The completer function now scopes NULL_GLOB via emulate -L zsh -o NULL_GLOB, so globs evaluated by _files internals or user zstyles (e.g. specs tagged globbed-files) silently drop when they match nothing — fixing both the no matches found: *:globbed-files error under the default NOMATCH, and the subsequent *(/) / *(-/) residue that would otherwise appear on the command line under NO_NOMATCH when completing a directory-typed flag in a directory with no subdirs.
  • Actually stop the *(-/) / *(/) glob-pattern residue that --dir <TAB> in an empty directory would type into the prompt. The initial switch to NULL_GLOB was the right idea but was defeated by a function-scoped setopt noglob sitting on top of the _files call: that option disabled globbing inside _path_files as well, so its internal tmp1=( $~tmp1 ) never expanded *(-/) and the literal pattern was handed to compadd as a candidate. Replace the option with a noglob *precommand modifier* on the _files call so globs like *(*) still reach _files unexpanded while its internals run with NULL_GLOB semantics as intended.
  • Also enable EXTENDED_GLOB in the completer's emulate -L zsh scope. zsh's own _files builds qualifier patterns like *(#q-/) and uses (#b) backreferences internally; emulate -L zsh resets to plain-zsh defaults (extended glob off), so _files -/ would emit bad pattern: *(#q-/):globbed-files on every TAB even once the residue bug above was fixed.

0.4.0 2026-04-17

Added

  • Add shfmt for shell script formatting via dprint command integration.
  • runner run <target> now falls back to executing <target> through the detected package manager (npx / pnpm exec / bunx / uv run / …) when no matching task is defined, unifying task execution and ad-hoc command execution under one entrypoint.
  • Shorthand runner <name> prefers a same-named task when one exists in the project, so tasks called clean, install, list, info, or completions are no longer shadowed by built-in subcommands. Passing any built-in-specific flag (--frozen, -y, --include-framework, --raw, the completions shell positional) keeps the built-in path as an escape hatch; runner i / runner ls aliases always hit the built-in.
  • run alias binary now uses a dedicated parser (no subcommands), so run clean, run install, and friends always run the task/command even when the name matches a runner built-in.
  • runner completions <shell> emits registration scripts for both runner and run in one invocation, so a single eval "$(runner completions zsh)" registers completion for both CLIs.
  • runner completions now accepts --output <PATH> (-o) to write the script directly to a file instead of stdout. Parent directories are not auto-created; an existing file is overwritten; a stderr confirmation line (wrote completion script to <PATH>) is printed on success.
  • Zsh completion honours path hints: --dir <TAB> (and any arg carrying ValueHint::DirPath / FilePath / AnyPath / ExecutablePath) delegates to zsh's native _files so ~/, ~named-dir/, globs, and cdpath all work.
  • Declare MSRV rust-version = "1.88" in Cargo.toml (matches the let-chain syntax used throughout the crate).

Changed

  • Make install.sh accept both X.Y.Z and vX.Y.Z version arguments and environment overrides.
  • Quiet installer downloads and checksum verification, and switch install output to a more compact structured summary with the installed version.
  • Streamline install docs around the installer script and custom destination override details.
  • runner's shorthand for a name that matches a detected task is now preferred over the built-in subcommand when no built-in flag is set. Breaking for projects that relied on runner install / runner clean always hitting the built-in while also defining a same-named task.
  • run alias binary now parses positionals unconditionally (no built-in subcommands); previously it inherited runner's parser, so positional names that matched a built-in would dispatch there.
  • Added clap::ValueHint::DirPath to the --dir flag on both CLIs so shell completion knows to offer directories.

Removed

  • Breaking: runner exec <cmd> is gone. Use runner run <cmd> (which now falls through to the package manager when no task matches) or the run alias binary.
  • Remove the tool::deno::exec_cmd and tool::cargo_pm::exec_cmd helpers: deno run <target> treats the target as a local script, and cargo <target> dispatches to a cargo subcommand/plugin — neither runs arbitrary package binaries like npx does. runner run <target> in a Deno- or Cargo-only project now spawns <target> directly via PATH.

Fixed

  • Stop zsh completion from leaking caller-side XTRACE / alias / word-split state into the prompt: the completer function now starts with emulate -L zsh.
  • runner run <name> in a Deno project no longer fires off deno run <name> (which would misinterpret <name> as a script path).
  • Scope completion flag-hint lookup to the active subcommand chain instead of recursing through every subcommand: a sibling subcommand's --flag with a different ValueHint no longer bleeds into an unrelated context, and a subcommand-local boolean --flag correctly shadows an ancestor's value-taking definition.

0.3.1 2026-04-15

Added

  • Help output now includes a quiet by Kaj Kowalski attribution line, with an OSC8 mailto: link when rendered to a terminal.

Changed

  • Enable clap's cargo, env, and wrap_help features, and use clap cargo macros for package description/version metadata.
  • Shorten help copy for runner completions and --dir, and show RUNNER_DIR directly in --help output.
  • Make clap value parsers explicit for --dir <PATH> and the optional completions shell argument.

Fixed

  • Resolve Deno tasks, deno.json(c) configs, and package.json scripts from the nearest applicable ancestor config, while stopping at VCS boundaries and ignoring workspace roots that do not list the current path as a member.
  • Prefer the nearest Deno task source when duplicate task names exist across ancestor configs.

0.3.0 2026-04-15

Added

  • Add global --dir <PATH> and RUNNER_DIR overrides to scan and run tasks against another project directory.

Fixed

  • Resolve Deno tasks and configs from the nearest applicable ancestor config, while stopping at VCS boundaries and ignoring workspace roots that do not list the current path as a member.
  • Limit task source OSC8 hyperlinks to visible filename text so alignment padding is not clickable.
  • Add repo and release-tag hyperlinks to runner --version and the runner info header version display.

0.2.1 2026-04-15

Changed

  • Bump clap_complete to 4.6.2.

Fixed

  • Detect Deno projects from packageManager: "deno@..." and deno.lock instead of defaulting those repos to npm.
  • Keep package.json tasks available for Deno projects during task discovery.

0.2.0 2026-03-29

Added

  • Add install.sh convenience installer for Linux release assets, including latest/pinned version resolution, checksum verification, and arch selection.
  • Dynamic shell completions with live task candidates, source tags, and descriptions instead of static subcommand lists.
  • Auto-detect shell from $SHELL when no completion argument is given.
  • description field on Task, threaded from justfile doc comments and go-task desc fields into completion candidates.
  • Tag-grouped zsh completions — candidates render under section headers (e.g. -- justfile --, -- Commands --) via custom _describe adapter.

Changed

  • Make installer destination fallback explicit as nested precedence: RUNNER_INSTALL_DIR -> XDG_BIN_HOME -> ~/.local/bin.
  • Extract zsh completion script to standalone grouped.zsh file, embedded via include_str! for syntax highlighting and linting support.

Fixed

  • Correct checksum filename in installer.

0.1.0 2026-03-27

Added

  • Initial runner CLI release for unified project task execution.
  • Auto-detection for package managers and task sources across ecosystems.
  • run alias binary for shorter invocation.
  • Unified commands for task run/list, dependency install, clean, and exec.