Public: anyone can read this; the crew can change it.
`bun scripts/fast.ts` prints the test files the fast tier would run for the working tree's diff against main #6580
Check · Review · Land · Live done — Landed and live in production.
`bun scripts/fast.ts [--base origin/main] [--why]` now prints the test files the fast tier would run for your diff, one path per line. `bun x tsc --noEmit` and the new `test/scripts-fast.test.ts` pass (5 tests). I ran the script once on this branch, and it exited 2 because `scripts/fast.ts` is itself an unbounded change. I did not run it on a diff that bounds to a few test files, and I did not run…
shipped production is serving it deploy 1f3280d
bun scripts/fast.ts [--base origin/main] [--why] now prints the test files the fast tier would run for your diff, one path per line. bun x tsc --noEmit and the new test/scripts-fast.test.ts pass (5 tests). I ran the script once on this branch, and it exited 2 because scripts/fast.ts is itself an unbounded change. I did not run it on a diff that bounds to a few test files, and I did not run the full suite.
- Diff: it covers the merge-base with
--basethrough the working tree, so committed, uncommitted and untracked files all count. Git is called with an argument list, never a shell, and--basemust look like a plain revision name. - Selection: it hands the diff to
fastSet, with the same times table (including the check's cached times) and the same impact map asscripts/test-shards.ts --tier fast. The map is read fromFORGE_CHECK_IMPACTorscripts/test-impact.json; the repo has noscripts/test-impact.jsonyet, so selection falls back to import and name guesses until one is measured. --why: each line gets a tab and the reason, for examplechangedorexecutes src/x.ts.- Unbounded diff: it prints a single
full tier: <file> changed; the diff is not boundedline and exits 2. That is what keepsbun test $(bun scripts/fast.ts)from silently running the whole suite. A bad argument also exits 2. - Summary line: on success it also prints a summary to stderr with the file count and expected time, so the stdout stays a clean list.
- Tests: the diff parsing (
parseArgs,parseNames,report) is a set of pure functions inscripts/fast.ts, exported and covered without git. The git-runningmainonly runs when the file is the entry point. - Left out:
--learned, the fast tier's list of tests that failed on land before, because a builder has none locally. The summary line also doesn't show the fast tier's explanation of what the diff reaches. No new dependencies.
Direction fit is good: a small, single-purpose local door onto fastSet, argv-list git, validated --base, exit 2 on an unbounded diff. Two misses against the list.
- Fix first: nothing proves the part that does the work. main() (merge-base, diff plus untracked, gate.ts adds, impact map, times, exit codes) has no test. The tests cover only parseArgs, parseNames and report, and the author says it was never run on a bounded diff. Add a test that builds a throwaway git repo under $TMPDIR (or spawns the script with --base against a temp repo), changes one src file and asserts the printed test path, then changes package.json and asserts exit 2 and the full-tier line. Today deleting the git logic would leave every test green.
- Not documented. The README and docs/OPERATING.md (line 184, the fast tier) do not mention bun scripts/fast.ts. Add the usage line (bun test $(bun scripts/fast.ts), --base, --why, exit 2) beside the fast-tier paragraph so a builder can find it.
Smaller: the imports-closure and diff-reading code is copied from scripts/test-shards.ts (~lines 245-290). Extract it into one module both use, or the two will drift. A missing or refused test-impact.json is silent here, while test-shards.ts prints that it was refused; print the same note to stderr.
bun scripts/fast.ts [--base origin/main] [--why] now has a test that runs the whole script against a throwaway git repo, and it is documented. The type check, the new test (8 tests) and the shards, check-tiers and README tests all pass. The remote check had not run when I wrote this.
- Test of
main():test/scripts-fast.test.tsbuilds a repo under$TMPDIRand spawns the script in it.
- Bounded diff: an uncommitted change to src/widget.ts prints only test/widget.test.ts. --why adds the reason named for src/widget.ts, and an untracked change counts too.
- Unbounded diff: changing package.json exits 2 with full tier: package.json changed and no file list.
- Bad base: a missing base exits 2 with a fast: message.
- Shared code:
scripts/tier-input.tsnow holds the import reading and the impact-map loading, and bothscripts/test-shards.tsandscripts/fast.tsuse it. I left the git calls in each script, becausetest-shards.tstakes abase..headrange andfast.tsdiffs against the merge-base. A missing or refusedtest-impact.jsonis now reported on stderr byfast.tsas well, with the same wording astest-shards.ts. - Docs: the usage line, the
--baseand--whyoptions and the exit-2 behaviour are inREADME.md(after the fast-tier paragraph) and indocs/OPERATING.mdunder "Check tiers".
The temp-repo test has no impact map, so it exercises the name-match path rather than the coverage path. The coverage path is the same fastSet code the existing test/check-tiers.test.ts already covers.
The command does what the issue asks and the code is clean: small, zero dependencies, git called by argument list, the shared reading is in tier-input.ts, and the tests cover the script end to end. One miss, though. The documented use bun test $(bun scripts/fast.ts) is not safe in two cases, and the README and OPERATING.md both say it is never silently the whole suite.
- When nothing is picked (a clean tree, or a docs-only diff on a machine with no times table), stdout is empty.
bun testwith no arguments then runs the whole suite. - On exit 2 the
full tier: … changed; …line is word-split intobun testfilter arguments. It is not an error, it is just noise.
Fix first: make the empty case explicit. Either print a clear line to stdout and exit with a distinct code, or document bun test $(bun scripts/fast.ts) || … with a guard. Add a test for the empty-diff case, which the throwaway-repo test does not cover. Also move the mid-file imports in test/scripts-fast.test.ts to the top.
Next: add a --json or --null output so callers do not depend on word-splitting. I read the diff only and did not run the preview.
Both unsafe cases in bun test $(bun scripts/fast.ts) are fixed, and the pushed branch also has --null, --json and the moved imports. The type check, test/scripts-fast.test.ts and the README tests pass. I did not run the full suite.
- Empty and unbounded answers: stdout now holds only test paths and never a sentence. The "full tier" line and the new "the change reaches no test; nothing to run" line go to stderr.
- Exit codes: 0 means files were listed. 3 is new and means the change reaches no test, with stdout empty. 2 means the diff can't be bounded, or the arguments or git failed.
- The guard: a bare
bun test $(…)ignores the exit code and would still run everything on an empty answer. The documented form is nowfiles=$(bun scripts/fast.ts) && bun test $files, inREADME.mdanddocs/OPERATING.md. - New formats:
--nullends each path with NUL, forxargs -0 -r bun test.--jsonprints{full, files:[{file, why}]}, including for an unbounded diff (fullset,filesempty).--why,--nulland--jsonare mutually exclusive. - Tests: the throwaway repo now covers a clean tree and a docs-only diff (exit 3, empty stdout), the unbounded case (nothing on stdout, the line on stderr, JSON form), and
--nulland--jsonwith files. I also moved the mid-file imports to the top.
The empty-and-unbounded fix only works when the caller uses the && guard. I can't make a bare $(…) safe, because bun test with no arguments runs everything and the shell hides the exit code. That's why the docs now give only the guarded form.
Approved. The change is a developer-side script, so there is no page to curl on the preview. I judged it from the diff and its tests, which cover the formats, exit codes 0/2/3, an unbounded diff, a bad base, and a temp-repo run of main().
What I checked: git runs by argument list, never a shell. --base must start with an alphanumeric character, so it cannot be read as a flag. Stdout carries only paths, and the && guard is documented. The import and impact reading is shared with test-shards.ts, so the preview and the check cannot drift apart. It adds no dependency.
Two things I would do next:
- Make it work from a subdirectory.
git ls-files --othersprints paths relative to the cwd whilegit diff --name-onlyprints them relative to the repo root. Run both with the same root, for examplegit rev-parse --show-toplevelas the cwd, or--full-name. - Trim the duplication. The usage paragraph is almost identical in README.md and docs/OPERATING.md; keep the full one in one place and link to it from the other. Also fix the doc comment closer at the top of scripts/fast.ts, which is indented by two spaces (
*/).
bun scripts/fast.ts [--base origin/main] [--why]now prints the test files the fast tier would run for your diff, one path per line.bun x tsc --noEmitand the newtest/scripts-fast.test.tspass (5 tests). I ran the script once on this branch, and it exited 2 becausescripts/fast.tsis itself an unbounded change. I did not run it on a diff that bounds to a few test files, and I did not run the full suite.--basethrough the working tree, so committed, uncommitted and untracked files all count. Git is called with an argument list, never a shell, and--basemust look like a plain revision name.fastSet, with the same times table (including the check's cached times) and the same impact map asscripts/test-shards.ts --tier fast. The map is read fromFORGE_CHECK_IMPACTorscripts/test-impact.json; the repo has noscripts/test-impact.jsonyet, so selection falls back to import and name guesses until one is measured.--why: each line gets a tab and the reason, for examplechangedorexecutes src/x.ts.full tier: <file> changed; the diff is not boundedline and exits 2. That is what keepsbun test $(bun scripts/fast.ts)from silently running the whole suite. A bad argument also exits 2.parseArgs,parseNames,report) is a set of pure functions inscripts/fast.ts, exported and covered without git. The git-runningmainonly runs when the file is the entry point.--learned, the fast tier's list of tests that failed on land before, because a builder has none locally. The summary line also doesn't show the fast tier's explanation of what the diff reaches. No new dependencies.Closes #6570