Development
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo test --workspace --locked
cargo build --release --locked
The Cargo workspace contains twelve crates:
| Crate | Responsibility |
|---|---|
qk-config | Workspace discovery, JSONC/YAML parsing, typed project configuration and normalization |
qk-graph | Workspace edges, project selection, reverse reachability and graph export |
qk-taskgraph | Dependency expansion, configuration selection and task DAG validation |
qk-executor | Command preparation, environment, argument interpolation, process groups and output capture |
qk-runner | Bounded task scheduling, dependency failure propagation and cancellation |
qk-lockfile | pnpm v9 lockfile parsing and what each importer installs |
qk-cache | Input hashing, cache entry storage, output restoration and log replay |
qk-affected | Git change analysis and project/task affected selection |
qk-history | SQLite run history, task records and cache-key explanations |
qk-input-analysis | File-access report schema, conservative suggestions and compatible observation unions |
qk-trace | The eBPF recorder: its kernel program, privileged helper and strace-format traces |
qk-cli | Argument parsing and output; produces the qk binary |
Fixture and CLI tests use self-contained workspaces.
Parity with Nx
tools/parity/parity.mjs measures qk against Nx. By default it uses the
synthetic workspace in tools/parity/fixture and the goldens committed in
tools/parity/goldens, captured with the Nx release pinned in
tools/parity/nx. It needs Node, and pnpm for the cases that run package
scripts:
node tools/parity/parity.mjs compare --qk target/release/qk
Each run builds a scratch git repository from the fixture. The cases in
tools/parity/cases.json compare task graphs, show projects --affected
for a change applied on a branch (the files in tools/parity/changes/<case>,
plus any deletions the case lists), with the affected tasks of
affectedTargets and the projects show projects --affected -t lists for
them, by default and with nx.json affected.legacy, and runs, which execute tasks without
cache and compare whether the run failed and which tasks ran. The comparison
requires byte-identical show projects --json, a graph --file equal after
normalisation, and --graph task graphs with the same tasks, with the same
target, root, outputs and cache, continuous and parallelism flags, and the
same dependencies and continuous dependencies. The differences it normalises away are listed at the
top of the script. An affected or run case that differs fails unless it
records the exact difference it accepts and why.
After changing the fixture or the pinned Nx, recapture and commit the goldens:
(cd tools/parity/nx && pnpm install)
node tools/parity/parity.mjs capture
capture --check captures into a scratch directory and fails when the
committed goldens differ. CI runs both compare and capture --check.
The harness can also point at any workspace with its own Nx installation
and git history, reading cases from <goldens>/parity.json, with affected
cases given as base and head revisions:
node tools/parity/parity.mjs capture --workspace <dir> --goldens <dir>
node tools/parity/parity.mjs compare --workspace <dir> --goldens <dir> --qk target/release/qk
For an external workspace, capture also records Nx’s median wall times, and compare prints them beside qk’s.
Releases
Releases are cut with cargo-release
(cargo install cargo-release --locked), configured under
[workspace.metadata.release] in the root Cargo.toml. From an up-to-date
main with a clean working tree:
cargo release <patch|minor|major> # dry run: shows the new version
cargo release <patch|minor|major> --execute
This bumps the version shared by every crate, commits it as chore: Release,
creates the single tag v<version> and pushes the commit and the tag. The tag
then triggers the release workflow below, which is the only thing that
publishes: the GitHub release, the npm packages and the crates.io crates.
Never run cargo publish by hand, and do not push the tag before the version
bump is on it, because the workflow rejects a tag that does not match the
workspace version.
Pushing a tag v<version> that matches the workspace version runs
.github/workflows/release.yml: it builds qk for Linux x64 and arm64, macOS
arm64 and Windows x64 and attaches the binaries to a GitHub release. When the
repository variable NPM_PUBLISH is true, the workflow publishes npm
packages generated by cargo-npm 0.1.4 using trusted publishing (OIDC).
Each package must trust the GitHub repository Pajn/qk and workflow
release.yml, with permission to publish directly and no environment
constraint. The npm job grants id-token: write; no npm token secret is needed.
The main package is
@runqk/cli, exposing the qk command. Its platform dependencies are:
@runqk/cli-linux-x64@runqk/cli-linux-arm64@runqk/cli-darwin-arm64@runqk/cli-win32-x64
The names and targets are configured in crates/qk-cli/Cargo.toml under
package.metadata.npm. Platform packages publish before the main package.
The workflow checks that all four are present before publishing. With
NPM_PUBLISH unset, GitHub binary releases still run and npm publishing is skipped.
The workflow also publishes every crate to crates.io through trusted publishing,
with tools/release/publish-crates.sh. It publishes only the crates whose
version crates.io does not hold yet, in dependency order. crates.io can be
slow to list a crate it has just accepted, which fails the publishing of the
crates depending on it, so the script tries again, publishing what is still
missing. If publishing still stops partway, run the workflow by hand with the
release’s tag (gh workflow run release.yml -f tag=v<version>): that run
publishes the release’s missing crates and nothing else.
To inspect packages locally after compiling the configured release targets:
cargo install cargo-npm --version 0.1.4 --locked
cargo npm generate -p qk-cli
npm bootstrap and trusted publishing
The five npm packages start at version 0.0.0 under the bootstrap tag.
These placeholders contain licensing and metadata but no native executable.
They allow configuring trust before the first functional binary release.
Their generator is separate from cargo-npm’s release packaging:
node tools/npm/bootstrap.mjs --repository Pajn/qk --out /tmp/qk-bootstrap
For initial setup, log in to npm locally with an account that owns the
packages and has two-factor authentication enabled. Publish the four platform
packages from that directory, then @runqk/cli, using npm publish <directory> --access public --tag bootstrap. Configure and verify trust for each package:
npm trust github <package> --repository Pajn/qk --file release.yml --allow-publish --yes
npm trust list <package>
These commands require npm 11.15.0 or newer and may request browser
verification. If the default user configuration is read-only, select a
writable login configuration with NPM_CONFIG_USERCONFIG=/path/to/.npmrc.
After verifying all five trust entries, set the GitHub repository variable
NPM_PUBLISH to true. Push the release workflow before creating a matching
version tag. See npm’s trusted publishing documentation.
crates.io releases
The release workflow publishes all ten workspace crates to crates.io through
OIDC, with qk-cli published after its internal dependencies. Install the CLI
with cargo install qk-cli --locked. Internal crate dependencies use exact
workspace versions so a release consistently uses the matching source.
CI verifies the packaged distribution with cargo publish --workspace --locked --dry-run, including compiling the extracted crates without workspace paths.
CI and release builds also run installation smoke tests on all four release platforms, including Linux arm64. They pack the host packages generated by cargo-npm and install their tarballs offline into a fresh npm prefix. The installed command shim runs from an independent workspace whose paths contain spaces. The same checks run against an executable extracted from a binary archive: version, project discovery, strict doctor, task execution, cache restoration after deleting outputs, and invalidation after changing inputs. No packages are published by these tests. To run them locally:
cargo npm generate -p qk-cli --target <host-triple>
node tools/npm/smoke.mjs --packages npm --binary target/<host-triple>/release/qk
Use qk.exe on Windows. The release build must already exist for the host triple.
Trusted publishing must be configured for every workspace crate, since the
short-lived token can only publish crates that trust this workflow. Each crate
must trust GitHub owner Pajn, repository qk, and workflow release.yml, with
no environment constraint. Configure these under each crate’s Settings →
Trusted Publishing on crates.io. See crates.io’s setup guide.
crates.io requires an initial local publication before trust can be configured.
qk-cli already has a metadata-only 0.0.0 reservation, whose isolated source
is retained in tools/crates-io/qk-cli. The nine dependency crates also need an
initial publication and trust configuration before the first workflow release.
Generate their metadata-only 0.0.0 reservations with
python3 tools/crates-io/bootstrap.py --out /tmp/qk-cargo-bootstrap, then publish
each generated manifest locally with cargo publish --manifest-path <manifest>.
These placeholders have no public API; functional releases use the workspace
source and version.
Subsequent releases need no long-lived Cargo token in GitHub secrets. The
workflow verifies the packages before requesting its short-lived token.
Benchmarks
tools/bench/bench.mjs generates a workspace of 300 projects, 3,600 files
and a 3,000-package pnpm lockfile, populates the cache, and measures with
hyperfine: show projects, graph, show projects --affected with no
changes, and a fully cached run-many, once with every output in place and
once with every output removed first, so all 9,300 are restored. Both cached
runs also run with .nxignore enabled to exercise filesystem walking and
ignored generated files. A separate restoration matrix uses the same graph with
both repeated and distinct blob contents, absent and stale complete output
directories, and partial restoration with an excluded sentinel. Every measured
restoration is followed by an untimed check of all output contents, stale-file
removal, exclusion preservation and build counters, so cache misses cannot pass
as restoration measurements. Preparation and verification are outside timing. Watch samples measure each source edit to the callback’s
timestamp, with Git ignore rules and with .nxignore, including native event
delivery, debounce, workspace reload and Node startup. Two initial samples are
discarded; results are observations, with no timing thresholds in CI. It needs
hyperfine, git and Node:
node tools/bench/bench.mjs --qk target/release/qk [--projects 300] [--runs 10] [--json out.json] [--markdown out.md]
CI runs it on every push, writing the table to the job summary and the numbers to an artifact.
GitHub Actions is configured for Linux, macOS and Windows. The lockfile is
checked in for reproducible dependency resolution. On Windows, C dependencies
are compiled with clang-cl (CC_x86_64_pc_windows_msvc=clang-cl), in CI and
for releases alike: tree-sitter-scss, which fallout parses stylesheets with,
passes its C compiler a GCC-style warning flag that cl.exe rejects.
See Maintaining the documentation for book builds and Pages setup.
The parity harness also captures isolated behavioral cases: direct/transitive
selective dependency artifacts, broad output globs, .nxignore negations,
stdin lists (including spaces and empty input), and watch callbacks queued
while a command runs. Capture uses the pinned Nx daemon only for the watch
oracle; qk remains daemon-free. Callback records live outside task inputs.
Distinct edit timestamps avoid coarse metadata-cache timing artifacts.
The Rust watch integration test runs on all three CI platforms; Windows uses
a private console so cancellation cannot signal parallel tests.