- --max-jobs / --node-ram-gb pass through to estimate(); shown in report header
and JSON.
- --timeline recomputes the recommended shape's schedule and renders a compact
per-node text Gantt (also emitted under 'timeline' in --json).
- --provision [CLASS] (+ --provision-name) renders the EphemeralBuilder YAML to
stdout while the human/JSON report goes to stderr, so it pipes to kubectl.
- new 'mine' subcommand: nix-estimate mine <installables...> [--repeat N] [-o]
runs mine_history, merges repeats via merge_histories, writes {name: minutes}.
Default estimate path preserved: 'mine' is dispatched on the leading token,
so 'nix-estimate <attr> [flags]' is unchanged.
- scale_to_cores now annotates cores as float (eff_cores = cores/max_jobs is
a float; silences pyright).
- schedule_for() returns the concrete (makespan, per-lane assignments) for a
single (machines x cores) shape, reusing the grid's durations, for --timeline.
Probe --json support via nix build --help and parse structured output
when available (robust across Nix versions and locales). Falls back to
the #5 text scraper on older Nix or unexpected payloads, feeding the
same tri-state contract.
_recommend assumes sorted, deduped grids (core_grid[-1] = biggest, knee
sweeps run low->high). The CLI parsed --nodes/--cores in raw order, so
e.g. --cores 32,8,16 yielded nonsense. Sort+dedupe in cli.py and reject
non-positive/non-integer values; also normalize defensively inside
estimate() so the function is correct for any caller. Add estimate/cli
tests.
Refactor dry-run text parsing into parse_dry_run_text(text, returncode)
so an empty set (warm cache) is distinct from None (unparseable). Only
None triggers the cold-cache whole-closure fallback in cli.py; an empty
set now correctly reports 'nothing to build'.
#1 --cold/--full forces the whole-closure cold-cache estimate, so a
fully-built target no longer dead-ends at 'nothing to build'.
#2 cost NixOS assembly-glue derivations (units, activation, /etc, the
system closure) at ~0 instead of the flat 1min DEFAULT, so a deep glue
chain stops fabricating the span. Critical-path report now shows the
heaviest derivations on the path (not the terminal glue), and the
recommendation note branches on whether the span is one heavy drv or a
long cheap chain. Adds tests/test_costmodel.py.
Nix >=2.34 wraps the closure under {version, derivations}, uses bare drv
keys, and moved edges from inputDrvs to inputs.drvs. dry-run still emits
full store paths, so normalize every drv path to basename for a
schema-agnostic join. Fixes closure collapsing to 2 derivations.
Reads the to-build derivation DAG from Nix (derivation show -r + --dry-run),
applies a heuristic/history build-cost model with Amdahl core-scaling, and
computes critical-path / peak-concurrency / list-scheduled makespan across a
node×core grid to recommend a remote-builder shape.
- schedule.py: pure critical-path, peak-concurrency, p-machine list scheduler
- costmodel.py: heuristic table + --history override + core re-weighting
- graph.py: DAG extraction via nix
- estimate.py/cli.py: sweep + knee recommendation + report
- tests: scheduler validated on toy DAGs (all pass)
Prior-art gap documented (Hubble, nixbuild.net) in README.