Skip to content

Latest commit

 

History

219 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FrostBuild dependency crystal

FrostBuild

Official site: hjosugi.github.io/frost-build

FrostBuild is a production-oriented Rust build engine for correct, low-latency incremental builds in large monorepos. C/C++ has native translation-unit rules; Rust, Go, Java, TypeScript and ecosystem tools use a language-neutral, directly executed command adapter. The core idea is to maximize work that never reaches execution: micro-partition pruning, constructive traces, dependency-narrowed inputs, early cutoff and content-addressed output restoration.

The normative architecture is DESIGN.md; the manifest specification is docs/06_manifest_spec.md, and the per-language definition of “win” is docs/18_polyglot_win_matrix.md. docs/README.md indexes every document by what it is for.

Quick start

FrostBuild builds itself. Cloned fresh, with no frost installed:

./frostw test --all     # the whole pre-PR gate, skipping what did not change
./frostw build binaries # frost and frostd, release
task --list             # the same entry points by name (https://taskfile.dev)

frostw is checked in beside .frost-version, the way gradlew and bazelisk are: it finds or downloads exactly that release, verifies it against the release's SHA256SUMS, and runs it. frost init writes one for a new workspace, and frost init --wrapper adds one to a workspace that already has a manifest. task bootstrap is the path for a machine with neither frost nor network — it is cargo build --release. See docs/22_developer_loop.md.

# In a directory that already has C/C++, Java, Rust, Go, TypeScript or Python
# sources:
frost init && frost build

# Polyglot trees require an explicit, reviewable choice:
frost init --language java

# Where does this configuration write? Ask instead of hardcoding the rule:
frost info bin_dir
frost info --json

cargo build --release --locked
./target/release/frost -C sample_c build
./sample_c/.frost/bin/debug/app                 # frost: 42 (.exe on Windows)
./target/release/frost -C sample_c build        # frost: up to date
./target/release/frost -C sample_c build --explain
./target/release/frost -C sample_c plan
./target/release/frost -C sample_c graph --dot

# sample_multi is the same idea with a shape: four packages, four target
# kinds, and a diamond, which is what the graph queries have to answer for.
./target/release/frost -C sample_multi build
./sample_multi/.frost/bin/debug/apps_cli_cli    # frost 1: 42 (.exe on Windows)
./target/release/frost -C sample_multi test --all

# Which targets must rebuild when this file changes?
./target/release/frost -C sample_multi query owners "core/src/*.c"
# Every route between two targets, not just one — cutting a dependency needs
# all of them.
./target/release/frost -C sample_multi query allpaths //apps/cli:cli //core:core
./target/release/frost -C sample_multi query rdeps //core:core --kind cc_test

# Multi-module Java, with Frost owning javac (needs a JDK and frost on PATH):
./target/release/frost -C sample_java build

# Spring Boot via Gradle, and Maven: the whole ecosystem build is one cached
# action with a declared boundary (needs Gradle/Maven and the network).
./target/release/frost -C sample_spring build
./target/release/frost -C sample_maven build

docs/29_sample_workspaces.md walks through all five, and through the choice they exist to illustrate: when to let Frost own the compiler, and when to wrap a tool that owns its own dependency graph.

Useful production workflows:

# Isolated debug/release trees
frost -C myrepo build --profile release -j 16

# Cross/device builds: declare [platform.aarch64] in frost.toml, then
frost -C myrepo build --platform aarch64 --profile release
frost -C myrepo build --all-platforms --profile release

# Build and cache test targets; select only affected tests
frost -C myrepo test --affected --explain
frost -C myrepo test --all --no-cache
# GCC C/C++: isolated instrumented tree and one lcov file per cc_test
frost -C myrepo test --coverage --explain

# Stop anything that hangs: tests carry a default limit, builds opt in
frost -C myrepo build --timeout 600
frost -C myrepo test --timeout 120

# Optional workspace sandbox and determinism audit
frost -C myrepo build --sandbox
frost -C myrepo build --check-determinism

# Materialize root [fetch.NAME] pins explicitly; builds never use the network
frost -C myrepo fetch
frost -C myrepo fetch zlib --offline

# Which scheduling strategy suits this graph? (plans, never builds)
frost -C myrepo simulate --jobs 1,4,16
frost -C myrepo build --stats -j 16          # then calibrate against a real run

# TTY builds use a live dashboard; force stable line output when needed
frost -C myrepo build --no-tui

# Rebuild on edits; restart a direct-argv dev process only after success
frost -C myrepo watch app --run .frost/bin/debug/app  # append .exe on Windows

# Or infer that artifact automatically
frost -C myrepo dev app -- --example-argument

# Build and run without remembering the configured output path
frost -C myrepo run app -- --example-argument

# Launch GDB/LLDB, jdb, Node inspect or Python pdb based on the artifact
frost -C myrepo debug app -- --example-argument

# Generate non-overwriting VS Code build/launch configuration
frost -C myrepo ide app --dry-run
frost -C myrepo ide app

# Diagnose required tools separately from optional developer integrations
frost -C myrepo doctor
frost -C myrepo doctor --json

# Ask where a configuration writes instead of hardcoding the layout
frost -C myrepo info                                  # whole table
frost -C myrepo info output_dir --platform aarch64    # one bare value
frost -C myrepo info --json

# Workspace-aware target/profile/platform completion and optional fzf picker
source <(COMPLETE=bash frost)             # Bash (Zsh/Fish/PowerShell/Elvish too)
frost pick                                # TAB selects several targets
frost pick --tests

# Deterministic compressed Java archive (also usable as a command step)
frost -C myrepo pack-jar --input .frost/tmp/debug/classes \
  --output .frost/out/debug/app.jar --main-class com.example.Main

# Deterministic standards-compliant pure-Python wheel
frost -C myrepo pack-wheel --input src \
  --distribution my-package --version 1.2.3 \
  --output dist/my_package-1.2.3-py3-none-any.whl

# Graph queries: what does a change affect?
frost -C myrepo query rdeps util
frost -C myrepo query deps app --json
frost -C myrepo query somepath app //libs/util:util

# Shared cache across machines/CI: verified, optional, never load-bearing
frost -C myrepo build --remote-cache http://cache.internal/frost
frost -C myrepo build --remote-cache /mnt/shared/frost-cache --remote-upload

# IDE, trace and persistent service
frost -C myrepo compdb
frost -C myrepo build --trace trace.json
frost -C myrepo build --daemon
frost -C myrepo daemon status
frost -C myrepo cache stats

# Preview a conservative Bazel native-C/C++ migration (never overwrites)
frost -C bazelrepo import-bazel --dry-run

# Keep Bazel authoritative while adding success-only hot restart
frost -C bazelrepo bazel-dev //apps/server:server -- --port 3000

Implemented engine

  • native C/C++ compilation, libraries, binaries and cc_test; shell or named-tool direct-argv tests; genrules; and a direct-argv adapter for any declared compiler/build tool
  • deterministic glob expansion and multi-package //package:target labels
  • debug/release/custom profiles with independent output/cache identities
  • [platform.*] cross/device toolchains with per-platform trees and caches, plus one-command host-and-device builds via --all-platforms
  • dynamic GCC/Clang depfile ingestion and generated-file order-only edges; depfile_format also accepts a plain path list or cl.exe /showIncludes notes read from captured output
  • output_dirs: a command target may own whole directories when the tool names its outputs after their content (bundlers, tsc --outDir). The tree is scanned after execution, recorded and published like any output, restored exactly on a hit, and represented in the graph by a content stamp, so dependents get real edges and early cutoff
  • parallel critical-path scheduler with CPU/RAM/exclusive admission, test-specific concurrency, captured diagnostics and --keep-going
  • interactive TTY progress with job slots, cache/timing state, critical path, scrollable logs, automatic plain CI/pipe output and --no-tui
  • stat cache, parallel BLAKE3 hashing and toolchain closure fingerprinting
  • append-only crash-tolerant binary journal with per-action flush
  • immutable local CAS, digest-verified copy materialization and bounded GC; blobs over 2 MiB also receive Bazel-compatible FastCDC chunk manifests for verified chunk-level restoration, positional previous-version zstd residual deltas and persistent exact/delta reuse accounting; independent chunk publication and positioned private-file restoration use the bounded Rayon pool without weakening the final digest gate
  • optional shared cache (--remote-cache, --remote-upload) over a shared directory or plain HTTP: keyed by declared inputs with the producing run's discovered inputs recorded as a verified trace, so a workspace with no journal reuses compiles whose real inputs it has never read. Every response is digest verified, executable mode is recovered from the digest, and any miss, corruption or transport failure falls back to building locally
  • early cutoff, affected test selection and opt-in determinism checking; frost test --coverage adds a separate GCC/gcov configuration with content-keyed raw counters and deterministic per-test lcov output
  • mmap/versioned graph cache, plan, explain, Chrome trace and compdb
  • init safely auto-detects native C/C++ or plain Java; Java becomes one direct javac batch followed by a deterministic runnable/library JAR. Mixed native/Java trees require --language, and existing Gradle/Maven markers stop auto-detection rather than silently bypassing their dependencies or plugins. Explicit command targets cover Rust, Go, TypeScript, Gradle, Maven, npm and other tools without a shell intermediary
  • deterministic compressed JAR packing with optional Main-Class, avoiding a second JVM while publishing one stable cacheable Java artifact
  • deterministic pure-Python wheel packing with normalized filename, METADATA/WHEEL, and complete SHA-256/size RECORD
  • static completion generation for Bash, Zsh, Fish, PowerShell, Elvish and Nushell; dynamic completion resolves workspace targets, profiles and platforms
  • pick provides multi-target fuzzy selection when fzf is installed
  • query deps/rdeps/somepath over the target graph with JSON output
  • simulate: deterministic scheduler and declared-resource comparison from journal durations, and build --stats to calibrate it against a real run
  • per-workspace daemon (Unix socket or Windows loopback endpoint) with an in-process verified no-op path; a daemon build applies the invoking client's environment, so --daemon produces the same bytes as the same command without it, plus watch: recursive native filesystem events, debounce, self-write filtering and success-only process restart
  • dev: the same success-only restart loop with target artifact/runtime inference, so native/JAR/JavaScript/Python paths do not have to be repeated
  • bazel-dev: native Bazel incremental build + success-only bazel run process-tree restart, keeping the last healthy target alive on build failure
  • run: target-to-artifact discovery plus direct native/Java/JavaScript/Python execution, with explicit cross-platform runner support
  • debug: symbol-profile validation and direct GDB/LLDB, jdb, Node inspector or Python pdb launch; native init scaffolds explicit -O0 -g debug and optimized release profiles
  • ide: artifact-aware VS Code task/launch generation for native, Java, JavaScript and Python, with dry-run preview and strict no-overwrite behavior
  • doctor: graph/toolchain readiness plus optional debugger/runtime/fzf/ sandbox/Graphviz diagnostics, with matching machine-readable JSON
  • opt-in Linux bubblewrap sandbox and process-group cancellation
  • Ninja importer, conservative Bazel-query native C/C++ migration importer, and reproducible Ninja/Make/Frost/Bazel benchmark harness

Remote execution remains v2 adapter work; the local model is deliberately REAPI-translatable and has passed an external BuildGrid/BuildBox certificate covering Merkle inputs, platform properties, missing blobs, output trees and Action Cache reuse. See remote cache and remote execution.

Repository layout

crates/frostbuild-core/     manifest, graph, hashing, journal, graph store, CAS
crates/frostbuild-store/    stable persistence facade
crates/frostbuild-exec/     scheduler, executor, sandbox, cancellation
crates/frostbuild-daemon/   watcher, framed socket protocol, frostd
crates/frostbuild-cli/      frost command and end-to-end correctness tests
crates/frostbuild-bench/    benchmark binary entry point
sample_c/                   real compiler sample workspace
sample_multi/               multi-package sample: four packages, a diamond
sample_java/                multi-module Java, Frost owning javac
sample_spring/              Spring Boot via Gradle, wrapped as one cached action
sample_maven/               the same wrapping with Maven
bench/                      checked-in benchmark baselines
docs/                       design decisions and research outcomes
.github/                    CI, security and release automation
frost.py, sample/           Python reference model and synthetic comparison data
zig_skeleton/               historical, superseded implementation sketch

The Rust workspace is authoritative. frost.py is retained only as a reference model for algorithm/benchmark comparison and retires from user-facing workflows once the Rust correctness suite covers every reference scenario. The Zig skeleton is historical and is not an implementation choice.

Manifest sketch

[workspace]
default_targets = ["app"]

[toolchain]
cc = "cc"
cxx = "c++"
cflags = ["-Wall"]

[profile.release]
cflags = ["-O3", "-DNDEBUG"]

[target.util]
kind = "cc_library"
srcs = ["src/**/*.c"]
includes = ["include"]

[target.app]
kind = "cc_binary"
srcs = ["src/main.cpp"]
deps = ["util"]

Package manifests below a root [workspace] use package-relative paths and may depend on labels such as //libs/util:util, :local, or a local bare name.

For a non-C/C++ tool, declare it once and invoke it without shell parsing:

[toolchain.tools]
rustc = "rustc"

[target.hello]
kind = "command"
tool = "rustc"
inputs = ["src/main.rs"]
outputs = [".frost/out/${config}/hello"]
args = ["src/main.rs", "-o", "${out}"]

The same rule shape is exercised end to end with real rustc, go, javac, Python and Node installations. Package-manager adapters deliberately leave Cargo/Go/npm/Gradle/Maven's internal incremental cache authoritative; Frost partitions their project/task graph and keys declared boundary artifacts. See docs/10_language_adapters.md.

frost init writes that manifest for a directory that has none. It detects C/C++, Java, Rust, Go, TypeScript and Python, and it stops where another tool already owns the build: Cargo dependencies and build scripts, Go module requirements, npm dependencies/scripts, Python runtime dependencies, and Gradle/Maven project markers each produce a refusal that names the responsible file and a deliberate --language override. Native projects with Bazel or Ninja markers are directed to the corresponding importer instead of silently bypassing the existing graph.

frost init                      # auto-detect, or refuse and say why
frost init --language rust      # deliberate direct-tool override
frost init --dry-run            # review the manifest without writing it

Generated manifests explain the direct action they chose and include a Next: frost build comment. Rust calls rustc on one crate root, Go builds one package main, TypeScript gives tsc a Frost-owned output tree, and Python packs a deterministic pure-Python wheel. Package/dependency resolution stays with the ecosystem tool.

Completion and interactive selection

Dynamic completion asks the current frost binary for candidates, so targets, profiles and platforms follow the frost.toml selected by -C:

# Bash / Zsh (dynamic targets, profiles and platforms)
source <(COMPLETE=bash frost)
source <(COMPLETE=zsh frost)

# Fish
COMPLETE=fish frost | source

# PowerShell
$env:COMPLETE = "powershell"; frost | Out-String | Invoke-Expression

frost completions --install writes that hook into the startup file of the shell it detects from $SHELL (or the one named on the command line). It is idempotent, --dry-run shows the exact lines first, and it refuses rather than guesses: a hand-written hook is left alone, and PowerShell/Nushell profiles — whose location depends on the host — print the snippet to paste instead.

Beyond targets, profiles and platforms, --remote-cache completes the file:///http:///https:// schemes and directories, import-npm --script reads the script names out of package.json, frost info completes its key names, and every path argument declares whether it wants a file, a directory or an executable. A unit test walks the whole command tree and fails when an argument that takes a value declares no candidates, no value hint and is not on the explicit free-text list, so a new flag cannot silently complete as nothing.

Elvish uses the same dynamic COMPLETE=elvish protocol. Nushell and startup files use the complete static command tree from frost completions bash|zsh|fish|powershell|elvish|nushell; Nushell's current generator does not provide the dynamic callback protocol. frost pick adds an optional fzf UI; --print makes it useful in scripts and --tests restricts the list to test targets. Completion has no fzf dependency.

Configuration

frost.toml says what to build. .frostrc says how — the flags you would otherwise retype or hide in a shell alias.

# <workspace>/.frostrc, or ~/.config/frost/frostrc
[common]
jobs = 16

[build]
profile = "release"

[config.ci]
sandbox = true
remote-cache = "http://cache.internal/frost"
frost build --config ci        # apply a named set
frost build --no-frostrc       # ignore both files
frost doctor                   # every setting in effect, with file and line

Precedence runs built-in default < user file < workspace file < named section < what you typed. --config may be repeated and applies in the order given; it does not nest.

Keys are the long option names, and one no subcommand accepts is refused at startup with the file, the line, the key and a suggestion — checked against the real argument tree, so an option works in a config file the moment it exists on the command line.

A flag from a file is a flag. It is spliced ahead of the real command line and parsed by the same code, so it validates identically and reaches the action key identically. Whether an option is key material is a property of the option, never of where its value came from: changing profile rebuilds and changing sandbox does not, exactly as on the command line.

Deliberately absent: conditional syntax like build:linux --foo (platform differences belong in [platform.*]) and sections that reference other sections.

Verification and benchmarks

cargo test --workspace --all-targets --locked
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo fmt --all -- --check
python3 -m unittest discover -s tests

./frost-bench run --suite standard \
  --tools frost,ninja,make --sizes 1000,10000 --iterations 5 --jobs 8

BAZEL_BIN=/path/to/bazel scripts/compare_bazel.sh

FROST_BIN=target/release/frost GRADLE_BIN=/path/to/gradle \
  ./frost-bench java --tools frost-unit,frost-batch,gradle,maven \
  --size 100 --iterations 3 --jobs 8

FROST_BIN=target/release/frost GRADLE_BIN=/path/to/gradle \
  ./frost-bench java --tools frost-jar,gradle-jar,maven-jar \
  --size 100 --iterations 7 --jobs 8

FROST_BIN=target/release/frost ./frost-bench rust \
  --tools frost,cargo --size 100 --iterations 7 --jobs 8

FROST_BIN=target/release/frost ./frost-bench go \
  --tools frost-native,frost-go,go \
  --size 100 --iterations 7 --jobs 8

TSC_BIN=/path/to/typescript-7/native/tsc NODE_BIN=/path/to/node \
FROST_BIN=target/release/frost ./frost-bench typescript \
  --size 100 --iterations 7 --jobs 8 \
  --checkers 1 --frost-checkers 4 --tsc-checkers 2

TSC_BIN=/path/to/typescript-7/native/tsc NODE_BIN=/path/to/node \
FROST_BIN=target/release/frost ./frost-bench typescript-projects \
  --projects 8 --modules 25 --iterations 7 --jobs 8 \
  --frost-checkers 1 --tsc-checkers 2

FROST_BIN=target/release/frost ./frost-bench python \
  --tools frost,python-build,uv \
  --size 100 --iterations 7 --jobs 4

cargo run --release --locked -p frostbuild-bench --bin frost-bench-rs -- \
  daemon-noop --frost "$PWD/target/release/frost" --iterations 31

cargo run --release --locked -p frostbuild-bench --bin frost-bench-rs -- \
  daemon-graph --frost "$PWD/target/release/frost" \
  --targets 10000 --iterations 31 \
  --out bench/baselines/<date>-<host>-daemon-10k.json

cargo run --release --locked -p frostbuild-bench --bin frost-bench-rs -- \
  cas --size-mib 64 --iterations 7

Results include host/load metadata and medians. Existing baseline JSON is in bench/baselines/; scripts/reproduce.sh reproduces the published runs. The checked-in real Bazel 9.1.0 comparison uses the same generated 1,000-action linear graph for both tools and verifies its target and dependency-edge sets before timing. On the recorded E14 run, Frost was 3.17x faster for no-op and 2.89x faster for a leaf-only rebuild. This is a workload-specific local result, not a universal speed claim; the report also records a high starting load average and that Bazel had no external CAS configured.

The checked one-target warm no-op report rotates all paths over 31 samples: standalone CLI measured 2.043 ms, end-to-end daemon CLI 1.711 ms and the daemon socket roundtrip 0.238 ms. Both local 5-ms gates pass, while the separate 10k standalone graph remains 15.620 ms; these workloads are intentionally not conflated.

The checked 10k-target daemon graph uses the same linear genrules, commands and declared inputs for Frost and Ninja. Its median-of-31 no-op result was 2.271 ms through frost build --daemon, 0.203 ms over the direct socket and 58.556 ms for Ninja: the end-to-end daemon path passed the 5-ms gate and was 25.78x faster than Ninja on this workload. A leaf-source change was not a win: Frost took 411.679 ms versus Ninja's 63.338 ms. The daemon shortcut is enabled only after a complete certificate validation proves that every file is a normal, existing path inside the watched workspace. Each hit drains a watcher barrier; workspace/output events, toolchain or environment changes, symlink/external or missing evidence, watcher errors and barrier timeouts all retain the complete validation/fallback path.

The checked Java comparison uses byte-identical outputs from the same 100-source set and records clean/no-op/one-source-change timings for Frost source-unit and batch layouts, Gradle and Maven. In an alternating-order median-of-15 comparison, Frost batch was 1.13x faster than Gradle clean, 1.13x faster after one source change and 269x faster no-op. Micro-partitions still made the changed-source case faster, but one javac JVM per source destroyed clean-build performance; they need a persistent worker or adaptive batching. These are workload-specific results, not a universal fastest claim. A separate semantic JAR comparison uses Frost's deterministic built-in packer; its median-of-7 result beat Gradle 9.3.1 clean, changed-source and no-op by 1.12x, 1.07x and 247x while using a 16-line manifest. See docs/17_java_gradle_maven_comparison.md.

The checked Rust comparison uses the same dependency-free 100-module crate, the same rustc and equivalent incremental dev settings. Frost measured 282.877 ms clean, 3.575 ms no-op and 204.870 ms after one module change versus Cargo's 417.192, 32.455 and 237.924 ms. A focused median-of-15 run confirmed the close changed-module result at 209.125 versus 243.408 ms. Every timed build ran the binary and checked exact stdout. This wins the simple crate contract, not Cargo's dependency/build-script/test ecosystem; see docs/19_rust_cargo_comparison.md.

The checked Go comparison separates a go build wrapper from a native package compiler/linker boundary. Frost native measured 112.492 ms after one file change and 3.720 ms no-op versus go build at 156.880 and 137.847 ms. The three-tool seven-sample clean result slightly favored Go; the required two-tool median-of-15 confirmation measured Frost at 151.074 ms and Go at 160.333 ms. Execution and normalized go version -m metadata match after every sample. This wins one dependency-free package, not the multi-package, cgo/embed/test ecosystem, and Go's three-line configuration is still much simpler. See docs/20_go_build_comparison.md.

The checked TypeScript comparison uses native TypeScript 7 for both frontends, byte-compares all 101 emitted JavaScript files and executes the entrypoint after every sample. With independently swept checker counts, Frost measured 259.409 ms clean / 49.391 ms one-module change / 2.468 ms no-op versus direct tsc at 228.080 / 42.467 / 41.318 ms. Frost therefore wins only the unchanged project boundary (16.7x); direct tsc remains faster whenever the compiler runs. In the eight-project-reference report Frost also wins no-op (3.200 vs 6.556 ms), but native tsc --build remains faster clean and after one project change. frost import-npm discovers npm workspaces and selected validation gates; --vite-builds adds only conservatively recognized production builds with profile-specific owned output trees. Generic frost watch/process restart ships, while browser HMR remains framework-owned and node_modules remains an explicit npm-owned non-hermetic boundary. A read-only iroha-pdf production trial passed seven validation gates, built a 6.26 MB Vite tree, returned a 0 ms no-op and restored the byte-identical tree from CAS in 10 ms. See docs/21_typescript_tsc_comparison.md and docs/27_npm_production_adoption.md.

The checked Python packaging comparison builds the same 101-source pure wheel through Frost's standards-compliant packer, python -m build and uv build. Frost measured 21.295 ms clean / 2.600 ms unchanged / 7.806 ms after one file change, versus uv's 326.911 / 290.841 / 290.786 ms. Every timed wheel had matching source bytes and identity/tag metadata, a fully verified RECORD, and exact execution after extraction. This wins the minimal pure-wheel contract, not arbitrary PEP 517 backends, extensions or pytest. See docs/24_python_wheel_comparison.md.

Installation and versioning

Before 1.0, FrostBuild follows SemVer with breaking changes allowed in minor versions. The POSIX installer selects the latest release for the host, verifies its archive against that release's SHA256SUMS, smoke-tests it, and only then publishes it under ~/.local:

curl --proto '=https' --tlsv1.2 -fsSL \
  https://raw.githubusercontent.com/hjosugi/frost-build/main/install.sh | sh
# Reproducible/pinned installation:
curl --proto '=https' --tlsv1.2 -fsSL \
  https://raw.githubusercontent.com/hjosugi/frost-build/main/install.sh | \
  sh -s -- --version 0.12.0

Alternatively build locally with Cargo, use cargo install --locked --path crates/frostbuild-cli, or download a checksummed Linux, macOS or Windows archive from a tagged GitHub release. Each archive contains frost, the optional frostd daemon, all generated man pages and static completions for six shells. Every release also publishes a tap-ready Homebrew formula and Scoop manifest generated from the archive checksums.

An archive/script installation can be updated only when explicitly requested: frost self-update --check performs a metadata-only check and frost self-update verifies and atomically replaces the executable. A binary under Cargo's install root is refused with the corresponding cargo install command; Frost never enables background updates or sends telemetry. Complete paths, package-manager commands, archive layout and Winget/AUR maintainer steps are in docs/30_distribution.md.

That covers which frost is on a machine. Which frost a repository requires is the other half, and it is answered by committing .frost-version and frostw next to the manifest: the wrapper runs exactly that release, fetching and verifying it once if this machine does not have it. Because breaking changes are allowed in minor versions before 1.0, a manifest written against one and built with another otherwise fails with an error that is correct and never mentions the version difference. Running frost directly still works and warns once, naming both versions.

Which surfaces a release promises not to break — the manifest grammar, the CLI names and exit codes, the --json schemas — and which are explicitly implementation (everything under .frost/, the action key, the internal crates) is enumerated in docs/28_compatibility_contract.md. State written by another version is detected and rebuilt, never misread.

Contributions follow CONTRIBUTING.md. Research decisions for predictive selection, learned scheduling, platform support and language adapters live in docs/ and keep safe behavior as the default.

What the action key covers, and the gaps it does not, are enumerated in docs/16_action_key_audit.md. What was learned in the 20 July 2026 investigation — including two performance hypotheses that were wrong and a benchmark that was not measuring what it claimed — is in docs/17_session_log_2026-07.md.

The issue implementation matrix maps the roadmap to code and identifies acceptance gates needing external evidence.

About

FrostBuild is a production-oriented Rust build engine for correct, low-latency incremental builds in large monorepos.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages