CLI flags reference
Generated from
packages/lang/src/spec-data.ts
(this table used to live in README.md — it moved here as part of the docs-site cluster). For the full prose description of each subcommand, see
SPEC.md §12.
Every subcommand the shipped tflw binary dispatches has a section on this page, and
verify-docs.mjs fails the build if one is added without one.
npx tflw run --env staging --parallel 4 --seed 42 --now 2026-01-01T00:00:00.000Z --no-colortflw run
Section titled “tflw run”tflw run [--env <name>] [--tag <name>[,<name>...]] [--only <name>] [--kind <kind>[,<kind>...]] [--parallel <n>] [--workers <n>] [--skip-workload] [--seed <n>] [--now <iso>] [--no-color] [--verbose] [--forbid-insecure] [--evidence <level>] [--teardown <level>] [--allow-public-target <origin>] [--failed] [--shard i/n] [--bail] [--no-keep] [--format ndjson] [--no-timestamps] [--log-file <path>] [--browser <engine>] [--headed] [--trace] [--update-snapshots] [--no-helpers] [--log-output <dest>] [--log-level <level>] [--fail-on <severity>] [--baseline <file>] [--baseline-write <file>] [--probe-seeded <n>]--env <name>- selects a named
envblock fromtflw.configinstead of thedefaultone — e.g. run the same suite againststaging --tag <name>[,<name>...]- only runs tests carrying any of the listed
@names (comma-separated OR; combines with--onlyas AND); a!excludes —--tag !slowruns everything not tagged@slow, exclusions AND together and beat an inclusion --only <name>- runs a single test by its exact declared name (composes with
--tag's OR-list as AND) --kind <kind>[,<kind>...]- only runs the tests and crawls of the listed kinds —
api,browser,load,scan— as their own statements classify them (comma-separated OR; combines with--tag/--only/--failedas AND). A test is of every kind whose statements it carries, so one with a request and a page is bothapiandbrowser --parallel <n>- runs up to
nfiles concurrently in this process (default:tflw.config'sworkerskey) — distinct from--workersbelow, which scales one workload-bearing test's own load generation across processes, not files --workers <n>- forks
ngenerator processes to produce one file's workload-bearing test(s)' load — each an equal striped share of the target population/rate, merged back into one report; a no-op warning on a file with no workload-bearing tests; default 1, no forking --skip-workload- skips every workload-bearing test (any
testcontaining aramp/hold/step/spike/run … iterationsline), regardless of whichparallel/sequentialbatch it's in — for fast iteration on the functional tests alone --seed <n>- fixes every
random-family value for the run, so a failure is reproducible byte-for-byte --now <iso>- pins the run's notion of "now" to an exact instant (combine with
--seedto reproduce a run's exact absolute generated values) --no-color- disables ANSI color in CLI output — useful for CI logs or piping to a file
--verbose- additionally prints one line per step (pass or fail); buffered per-file under
--parallel > 1so concurrent files never interleave (--workersis the unrelated load-generation axis and has no effect here) --forbid-insecure- CI policy gate — fails before any test runs if
insecure trueis active for the env actually running --evidence <level>- overrides
tflw.config'sevidencekey (full/headers-only/none) for this run only --teardown <level>- overrides
tflw.config'steardownkey (always/on-success/never) for this run only — hyphenated here, two bare words in the config --allow-public-target <origin>- affirms that an originating scan (
authorization violations) may point at a host outside the private address ranges —TF065. Repeatable, one origin each (scheme + host + port), and it must match anauthorized targetthis env declares. Has notflw.configkey by design: the declaration layer lives in config, and this is the layer that makes it impossible for a committed config to send CI at the internet on its own. Asecurity violationsscan needs no flag — it only inspects a response the suite already asked for --failed- replays the tests that are failing — each test's newest verdict in the kept runs (
report/runs/); falls back to the full suite with a note if none is --shard i/n- runs every nth file of the sorted discovered suite, starting at the ith — the n shards are disjoint and together the whole suite, so a CI matrix of n jobs runs everything once;
--tag/--only/--failedthen narrow within the shard, and every shard still validates the whole suite first --bail- stops after the first failing test's final (post-retry) verdict; under
--parallel > 1, in-flight files still finish (the file pool stops pulling new work, it does not abort a running file) --no-keep- does not copy this run into
report/runs/<id>/— every other run is kept there, bounded byruns keep N(default 50), for the history the summary and the page read;report/itself is written as always --format ndjson- streams the event log as one JSON object per line to stdout (plus
report/events.ndjson) instead of human text; always full detail regardless of--verbose --no-timestamps- omits the
HH:MM:SS.mmmprefix every console line otherwise gets by default --log-file <path>- duplicates console output to a file, always plain text (ANSI stripped) regardless of stdout's own color state
--browser <engine>- switches every browser step to one engine — chromium/firefox/webkit (default chromium)
--headed- shows the browser window instead of running headless (local debugging only)
--trace- keeps the Playwright trace of every browser test, even when everything passed (needs evidence
full); open it withnpx playwright show-trace --update-snapshots- writes/overwrites
matches snapshotbaselines instead of just comparing against them --no-helpers- refuses every
usebefore the first request, whatevertflw.config'shelpersallows — for a run that must not execute a JS/TS module (TF083) --log-output <dest>- overrides
tflw.config'slog destinationkey (console/html/both/none) for this run's barelog "…"calls only — alog … to …statement's own destination always wins --log-level <level>- overrides
tflw.config'slog levelkey (debug/info/warn/error) — the minimum level alogstep must clear to be rendered in console output/report.html(never affects whether it's recorded inresults.json/ndjson) --fail-on <severity>- security findings below the given severity (
minor/moderate/serious/critical) are reported and do not fail the build. The gate can only relax, never tighten: a test that wrote its own floor (has no serious security violations) keeps it, and where the two disagree the stricter wins — a command-line flag that could turn a green suite red would produce a failure nobody can locate from the source. Never applies to the negatednot has no …form, where a finding is what makes the assertion succeed. A withheld finding still renders, badged, and the passing line names the count --baseline <file>- accepted findings, matched on the 16-hex fingerprint alone — the
rule/endpointbeside each entry are for the human reading the file, since matching on a name would let a renamed rule silently un-accept every entry that mentioned it. Listed findings still render, badged known/accepted, and do not fail the build; delete a line to un-accept it. A malformed file is refused rather than degraded to "accepted nothing", which would look exactly like a codebase that fixed everything --baseline-write <file>- writes this run's findings out as the accepted set, sorted and deduplicated — it ships with
--baselinerather than after it because fingerprints are hashes and hand-transcribing forty of them is not an adoption path. Entries the run did not reproduce are reported and never removed: a--tagrun legitimately produces a subset of the suite's findings, so pruning on absence would delete acceptances the next full run still needs --probe-seeded <n>- adds
ngenerated mutation payloads per already-granted class to the input-handling scan's fixed corpus. It cannot widen whatauthorized targetpermitted — seeding is a capability of the run and a mutation class is a claim in the config. Its findings are reported and never gate: a generated payload has no stable fingerprint, appearing under one seed and vanishing under the next, so gating on it would churn a baseline or fail a build on a coin flip. Each renders with its payload and seed under promote this payload into the corpus. Capped at 64 per class, because probes are strictly sequential and this is wall-clock one assertion pays
tflw run drives functional and workload-bearing tests alike in one pass — a test becomes
workload-bearing the moment it contains a ramp/hold/step/spike/run … iterations line (see
the load testing guide); there's no separate load command (folded into
run). parallel/sequential (a test-header modifier, not a flag) controls which tests in
a file run concurrently with each other; --workers N is the unrelated, workload-only axis above —
it scales one workload-bearing test's own generated load across N forked processes, never files.
Every run with at least one workload-bearing test also self-diagnoses its own generator process's
event-loop lag/CPU and warns if tflw itself was the bottleneck. A live ~1Hz console line tracks
iterations/rps/error-rate for the workload-bearing tests currently in flight. The first Ctrl-C is a
graceful stop for any run: no new file, test or iteration starts, the tests in flight finish, and a
partial report marked aborted is written and kept instead of losing the run; a second Ctrl-C
quits at once. The page's Cancel is the same stop. Everything — functional and
workload-bearing test results alike — renders into the one report/report.html/junit.xml/
results.json, in file-declaration order; there are no separate load-* artifacts. Exit 0
= every test passed and every threshold was met (or none declared), 1 = a test failed or a
threshold was breached, 2 = usage error, 3 = inconclusive (a workload-bearing test ran and
tflw's own generator process saturated — the numbers describe tflw contending with itself, not the
system under test; every threshold's junit <testcase> comes back skipped, not passed/failed),
130 = aborted via Ctrl-C (the standard "died from SIGINT" code).
tflw check
Section titled “tflw check”tflw check [--format json] [--allow-public-target <origin>] [--env <name>] [--no-color]--format json- prints one
{ file, diagnostics }entry per file checked as JSON instead of text — for editor and CI integrations --allow-public-target <origin>- the same affirmation
runtakes, accepted here becausecheckanswers will this run?, and the answer depends on it — without it a suite legitimately scanning a public staging host could never get a cleantflw check.checkstill sends nothing: the flag only changes which diagnostics it reports --env <name>- selects a named
envblock fromtflw.configinstead of thedefaultone — decides which env-scoped checks (service names,insecure) run --no-color- disables ANSI color in CLI output
Validate-only: the same parse + checker pipeline run executes before it does anything, with no
HTTP traffic and no secrets required. Exit 0 when every file is clean, 2 otherwise.
tflw init
Section titled “tflw init”tflw init [--load] [--scan] [--example] [--force]--load- also scaffolds a
load.tflw— a workload-bearingtestin the open (rps) model, runnable with plaintflw run --scan- also scaffolds a
scan.tflwand atflw.configcarrying a commented-outauthorized target— the scan refuses (TF060) until a person uncomments it and writes the reason, because that declaration is an affirmation only its author can make; the config points at a real host rather than the demo service, whose scheme has no origin to authorize --example- writes the Coffee Shelf instead of the one-test scaffold: a stdlib
node:httpshop (npm run shop), its order page, 37 tests across every kind, and atflw.configwhose URLs readSHOP_URL --force- with
--exampleonly: overwrites files of the same name instead of refusing
Scaffolds tflw.config, example.tflw, .env.example, and a .gitignore covering .env and
report/ — appending to an existing .gitignore rather than duplicating entries.
tflw init --example writes the Coffee Shelf instead: a small shop (server.mjs, started with
npm run shop), its order page, and twelve test files covering every kind tflw runs. It is the
project the walkthrough uses. Its tflw.config reads SHOP_URL for every
address, and so does the shop, so one variable moves both off port 4720. It refuses to write over
any file already there unless --force, and adds the shop script to an existing package.json
rather than replacing the file.
tflw install-browsers
Section titled “tflw install-browsers”tflw install-browsers [--browser <engine>]--browser <engine>- downloads chromium/firefox/webkit (default chromium) — runs the
playwrightCLI inside the optional peer dependency, resolved from the consuming project; refuses if that peer is absent rather than fetching one
One-time browser binary download for UI steps — playwright is an optional peer, so this only
works once the consuming project installs it.
tflw pick <url>
Section titled “tflw pick <url>”tflw pick <url> [--browser <engine>] [--cdp-port <n>]--browser <engine>- the engine to launch: chromium (default), firefox or webkit
--cdp-port <n>- opens the browser's DevTools endpoint on
127.0.0.1:<n>, announced on stderr, so a script can drive the window with Playwright'sconnectOverCDP; chromium only, and never on any address but loopback
Opens a real, visible browser at <url> and prints one verified locator per click; runs until the
window is closed or Ctrl+C. <url> is an absolute URL, or a path (/order.html) opened against
tflw.config's web for the default env, joined the way a test's open joins it.
tflw record <url>
Section titled “tflw record <url>”tflw record <url> [--browser <engine>] [--cdp-port <n>]--browser <engine>- the engine to launch: chromium (default), firefox or webkit
--cdp-port <n>- opens the browser's DevTools endpoint on
127.0.0.1:<n>, announced on stderr, so a script can drive the window with Playwright'sconnectOverCDP; chromium only, and never on any address but loopback
Opens a real, visible browser at <url> and prints one tflw step per action taken in it — a click,
a filled field, a ticked box, a chosen option, a key press. pick's sibling, and the difference is
that nothing is prevented: a click navigates and a form submits, because a recording is a
sequence and a sequence needs the page to advance.
A field's keystrokes are coalesced into one fill rather than written per character; a checkbox
prints tick or untick; a key press carries the control it was typed into. Actions only —
a recorder can see that a page changed but not what about the change mattered, so expectations are
yours to add afterwards. Runs until the window is closed or Ctrl+C. <url> is an absolute URL, or a
path opened against tflw.config's web, as pick's is.
Every tab the session opens is recorded, not just the one it started in. A click that opens a tab is written as the block that wraps it, because the runtime has to be listening before the click happens; coming back to a tab you left prints the switch that takes you there.
click button "Track parcel"switch to new tab click text "View receipt"click button "Print"switch to tab 1Names that match more than one thing
Section titled “Names that match more than one thing”A page with twelve Add to cart buttons has no unambiguous name for any of them. Rather than fall back to a selector naming the position the row happened to be in, the recorder scopes the statement to the nearest ancestor that narrows the name to one and has a name of its own:
within css "[aria-label=\"Product 2\"]" click button "Add to cart"An ancestor that narrows the name but cannot be named is skipped — a within naming a position is
no better than the selector it would replace. When nothing qualifies, a CSS path is still written.
Driving the window from a script
Section titled “Driving the window from a script”--cdp-port <n> opens the browser's DevTools endpoint on 127.0.0.1:<n> and says so on stderr.
Anything that speaks the Chrome DevTools Protocol can then act in the window, and every action is
recorded exactly as a person's would be — which is how a recorded session can be checked in CI
against a golden file:
import { chromium } from 'playwright';
const browser = await chromium.connectOverCDP('http://127.0.0.1:9333');const [page] = browser.contexts()[0].pages();await page.getByRole('link', { name: 'Shop' }).click();Chromium only, and only ever on loopback. pick takes the same flag.
What is not recorded
Section titled “What is not recorded”Clicks the browser synthesised. Pressing Enter in a text field submits the form, and the browser
does that by dispatching a click at the submit button — recorded, that click makes a replay submit
twice. Activating a focused control from the keyboard is a gesture and is still recorded.
Redirecting it to a file
Section titled “Redirecting it to a file”Steps go to stdout and everything else — the opening banner, and the word it writes when it has to
skip an action — goes to stderr. So tflw record http://localhost:3000 > steps.tflw gives you a
file whose every line is a step, ready to paste under a test declaration.
tflw watch
Section titled “tflw watch”tflw watch [--env <name>] [--seed <n>] [--browser <engine>] [--no-color]--env <name>- selects a named
envblock fromtflw.configinstead of thedefaultone --seed <n>- fixes the seed reused by every run for the whole watch session (else one is freshly minted at startup)
--browser <engine>- switches every browser step to one engine — chromium/firefox/webkit (default chromium)
--no-color- disables ANSI color in CLI output
tflw watch [files] [--env E] [--seed S] [--browser engine] [--no-color] re-runs headed on every
save, one shared browser window for the whole session; saving tflw.config re-runs everything.
Runs until Ctrl+C.
tflw migrate [files]
Section titled “tflw migrate [files]”tflw migrate [files] [--env <name>] [--no-color]--env <name>- selects a named
envblock fromtflw.configinstead of thedefaultone — deprecations are checker diagnostics, so this only affects which env-scoped checks run --no-color- disables ANSI color in CLI output
Mechanically rewrites checker-flagged deprecations in place, then prints which files changed. Three
rules carry a rewrite today — scenario → test, think → pause, uncheck → untick.
How to tell whether migrate can fix something: the diagnostic says so. Any diagnostic that ends
with = fix: run tflw migrate to apply this automatically is one it can act on. That line is
derived from the rewrite itself rather than written next to each rule, so the offer and the
capability cannot drift apart.
Bare check <locator> is deliberately not one of them. It has two honest readings —
tick field "…" (the old click) and check field "…" is checked (the assertion) — and guessing
wrong turns an assertion into a mutation in a test that keeps passing. Migrate reports each one and
leaves it to you; on the real migration this was built against, that was 5 of 7 sites.
It works on files that do not parse, which is the only kind it exists for. It splices what it
can, writes, re-checks the rewritten source, and prints whatever remains against the new line
numbers — repeating until a pass finds nothing left, since a think nested inside a scenario is
invisible to the parser until the scenario is fixed. Exit 0 if the suite is clean afterwards,
2 if errors remain, including when it did rewrite something and the file still fails: migrate's
job is the rewrite, not the verdict.
It rewrites keywords, not prose. A migrated file can be entirely correct code and still name the
old keyword in its comments and test "…" names — this is not a rename-symbol refactor.
tflw fmt [paths]
Section titled “tflw fmt [paths]”tflw fmt [paths] [--check]--check- writes nothing; lists every file that would change and exits 1 if any would — the CI form. Without it, files are rewritten in place and each one that changed is named
Formats .tflw files in place. A path may be a file or a directory; a directory is walked for
.tflw files (node_modules/, .git/ and report/ are skipped — a repro tflw wrote into
report/ is not something you meant to format); no path means the current directory. Each file
that changed is named, then one summary line.
What it decides, and what it never touches. Blocks indent two spaces per level. On a line,
tokens are separated by one space, with none before , : ) ] and none after ( [; objects
are padded { a: 1 }, arrays are [1, 2], an interpolation {name} stays tight (that is the
language's own rule: { IDENT } is an interpolation and an object always has a key:). A
multi-line body { … } indents its content one level past the line that opened it and puts the
closer back at that line's indent. Data tables align by column. Blank lines collapse to one,
trailing whitespace goes, the file ends in exactly one newline. Comments never move: a comment
line stays a line, at the indentation of the code that follows it (or of the block it closes, when
it sits at the end of one); a trailing comment gets two spaces before its #; comment text is
never edited. Strings, numbers, paths and everything else inside a token are emitted byte for byte.
Three spaces are kept as you wrote them, because the same two tokens mean two things and only the
parser tells them apart: the space after a -/+ (price: -1 against today - 10 days), the
space between a number and the word after it (4s is a duration, 3 seconds is one too, 4 s is
an error), and the space between a name and a [ (body[0] is an index, equals [1, 2] is a
list).
A file that does not lex is not formatted. It is named on stderr with the diagnostic, left
byte-for-byte alone, and the command exits 1 — a formatter that silently skips a broken file is a
check that says "clean" about a file it never read. Fix the file (tflw check says how), then
format it.
The guarantee. For every file it writes, the formatted text lexes to the same tokens in the
same order (indentation and comments excluded), keeps every comment in text and order, keeps the
block structure, and formats to itself on a second pass. That property is what lets the rules be
this opinionated: nothing the formatter does can change what a file means. The same function
answers the editor's Format Document through tflw lsp, so format-on-save in VS Code needs no
setting beyond the extension.
tflw export
Section titled “tflw export”tflw export otlp [report-dir] --endpoint <url> [--header <name=value>]--endpoint <url>- where
tflw export otlpPOSTs the trace — a collector's OTLP/HTTP traces address, e.g.http://localhost:4318/v1/traces; required --header <name=value>- a header sent with the trace, for a collector's auth; repeatable
Sends a finished run to an OpenTelemetry collector as one trace — a span for the run, each file,
each test and each step — over OTLP/HTTP's JSON encoding. It reads results.json from the report
directory (report/ when none is named). A report records the run's start and every duration but
not when each test started, so the spans are laid end to end and each carries tflw.timing = "reconstructed". Exits 0 when the collector answers 2xx, 1 when it answers anything else, and 2
for a usage problem. See CI, reporting & safety.
tflw merge <report-dir>...
Section titled “tflw merge <report-dir>...”tflw merge <report-dir>... --out <dir> [--no-color]--out <dir>- where
tflw mergewrites the mergedreport.html,junit.xml,results.jsonandfindings.sarif; required, and never one of the inputs --no-color- plain text for the merged summary, as
run --no-color
Joins finished runs — the shards of a sharded CI job, or a sweep's groups — into one report. Tests
keep their own files and appear in the order the directories were given; the counts are re-derived
from them, findings are deduplicated by fingerprint so SARIF uploads each once, and the verdict is
the one tflw run would give (an aborted input makes the merge not ok). env names every input's
env and mergedFrom in results.json lists the inputs. Each input's events.ndjson is joined in
order, and assets/ is copied across. Exits 0 when the merged run passed, 1 when it did not, and 2
for a usage problem. See splitting a suite across CI jobs.
tflw doctor
Section titled “tflw doctor”tflw doctor [files...] [--env <name> | --all-envs] [--browser <engine>] [--allow-public-target <origin>] [--forbid-insecure] [--no-helpers] [--json]--env <name>- which env block
tflw doctorresolves — default asrun:TFLW_ENV, then the block markeddefault; an undeclared name is a usage error --json- the same facts as one JSON object —
ok,node,config,services,proxy,tls,secrets,suite,checks,browsers,problems,warnings— for a CI step or a support ticket --all-envs- one verdict per env the config declares —
✓, or✗with the first problem — exiting 1 if any env cannot run; not beside--env --browser <engine>- the engine
run --browserwould launch, which must be downloaded when the suite has browser tests (default chromium, asrun) --allow-public-target <origin>- the affirmation
runtakes, so the checks doctor makes are the ones that run makes (TF065); repeatable --forbid-insecure- fails doctor as it fails
runwhen the env hasinsecure true --no-helpers- judges every
useasrun --no-helpersdoes (TF083)
What this machine and this project will run with, on one screen: tflw and Node versions, the env
and every api base it resolves to, the proxy variables and whether anything reads them, TLS
(certificate verification, a client certificate and whether it is on disk), the suite's size and
how much of it drives a browser, which Playwright browsers are downloaded, and the secrets the env
requires — by name, and whether each is set, never the value. Read-only and offline — it
resolves the config (with the project's .env, as run does) and asks Playwright where its
browsers are; it sends no request. Exits 1 exactly when tflw run with the same files and flags
would refuse before its first test: no tflw.config; Node older than 22; a tflw check error
under the env (the first one is quoted, with the tflw check command that lists the rest); browser
tests when the engine run will launch is not downloaded; --forbid-insecure beside insecure true; or a required secret not set. Warnings, marked ⚠, never change the exit code: a .env git
would commit, the warnings tflw check reports, insecure true. --all-envs prints one verdict per
env. See installing tflw.
tflw ui [dir]
Section titled “tflw ui [dir]”tflw ui [dir] [--port <n>] [--no-open]--port <n>- the loopback port the page is served on (default 4141);
0lets the OS pick and the chosen port is printed --no-open- do not open the page in a browser after the server starts — the URL is printed either way, which is the form a tunnel (
ssh -L) or a script wants
Serves the page for a directory — the one holding tflw.config, the current one by default — on
127.0.0.1 and prints the URL. A directory with no tflw.config opens too, as a blank
project: the page says so and offers to create one, which runs tflw init for you. What is
refused is a path that is not a directory at all, because that is a typo rather than a new
project. The page is a projection of the project and of its report
directory, never a second copy of either: it lists the discovered files and the tests in them, it
starts a run as tflw run --format ndjson (the same command, the same files, the same report
directory a terminal run writes) and relays the stream as it arrives, and it opens every report
directory the project holds — the current one, and each run started from the page, which is kept
aside as report/runs/<id>/ when it ends.
The page can also author a test, through one write route with one call site: a form per statement
previews the exact bytes it is about to write and tflw check judges them before the write, the
file on disk is the only truth, and tflw.config is not reachable through that route. The forms
and what each kind scaffolds are documented under the UI.
The project's configuration is edited in its own tab, through its own route. The header's
Config panel is a plain editor over tflw.config — the bytes you type are the bytes written, and
nothing reformats them — refusing only text that does not parse, and writing under the version the
page read so a file changed by a terminal in the meantime is reported rather than overwritten. It
is a separate capability from the one above and not a widening of it: the test-writing route still
refuses this file.
That tab is multi-document once the config declares one. A baseline declaration names a second
file the project owns, so the tab carries a switcher — tflw.config first, then one entry per
declared document, labelled with its path and the block that declares it — and each document is
addressed in the URL, which is what lets a link point at one line of one of them. They are the same
editor under the same rules, with one difference the file itself dictates: a declared baseline that
has not been written yet opens as an empty document rather than an error, because that is where
every project adopting triage starts, and the config's diagnostics are not applied to a file that is
not in the config dialect. Beside it, Auth reads what that configuration means for the file you are
looking at — which sessions its tests run as and what each adds to a request, the built-in
anonymous principal, and every authorized target in force with its reason and what each
probe opt-in grants.
What a browser test shows is what the report holds. At evidence full the screenshots a run
took are on the page and a failed test's Playwright trace opens in Playwright's own trace viewer,
which tflw ui serves under /trace/ from the project's playwright-core — nothing is spawned
and the archive never leaves the machine. Below full there is no screenshot and no trace, by
decision, and the page says so under each browser test rather than leaving a gap.
A workload test is its metrics, charted. The figures are results.json's own — iterations,
failures, error rate, the all-iterations and the successful-only percentiles, every threshold with
its actual, one row per endpoint — in the units the console and report.html print. The four
charts are the report's per-second timeline (latency p50/p95/p99, throughput, error rate) and its
bucketed histogram; hover a chart and the legend reads that second's own numbers. Two runs compare
by opening two report directories: pick one under compare with and every table gains the other
run's column and the signed difference, and every chart its dashed series.
A security scan is its findings, grouped by rule. The block above the tests is
results.json's findings in the order the console and report.html use — gating first, then
withheld, worst severity first — each with its endpoint, its source line, its fingerprint, the
gate's verdict (known/accepted for a baselined finding, which stays on the page rather than
vanishing) and the same possible fixes entry report.html carries. Which rules ran lists what
applied and what stood down, with the reasons. With a second run open under compare with, each
finding says whether the other run had it and with what verdict, and the other run's findings this
one lacks are listed — the baseline diff, drawn from two reports.
A finding can be accepted from the page, and accepting it writes nothing. Each gating finding
that carries a fingerprint has an [accept] link: it resolves which baseline document a run under
this report's env grades against — the env's own block, or the defaults one it falls back to —
splices the entry into that document in the shape --baseline-write emits, and opens the Config
tab on the line it added. The document is then unsaved text in an editor, and stays that way until
somebody saves it. An acceptance is an affirmation about the application under test that only its
author can make, so the page stages it and shows it; it does not make it. A document it cannot
splice honestly — not JSON, no accepted array, or already carrying this fingerprint — is returned
unchanged and opened anyway, and an env with no baseline declared is refused with the reason. See
Findings, baselines & the gate for the workflow this belongs to.
A run's exit is stated where its report cannot state it. tflw run's exit codes 0, 1, 3 and
130 are the report's own verdicts (passed, a failure, inconclusive, aborted) and the header carries
them; anything else — a process that died after writing its report, a signal, a cancel from the
page — is written above the report with the run's stderr, and a run that wrote no report at all
(a refused argument, an unmet require env) keeps a row of its own in the run list rather than
disappearing. The environment a page-started run sees is the server's: start tflw ui where a
terminal run would work, with the same .env beside the config.
Loopback only. The bind address is not configurable: the page can start a run, and a run reads
the project's .env. To reach it from another machine, tunnel it (ssh -L 4141:127.0.0.1:4141
to that machine) rather than exposing it.
tflw docs [topic] [section]
Section titled “tflw docs [topic] [section]”Prints a page of these docs in the terminal: the Guide and Reference pages, grouped as this site's
sidebar groups them. With no topic it lists them; tflw docs cli prints this page, with every flag
table filled in, and tflw docs cli run prints one section of it. A word that is not a topic is
looked for among every page's section titles, so tflw docs unique prints unique vs. random.
Links to another printed page read (tflw docs <topic>); links anywhere else read as a URL. Takes
no flags. The pages are rendered into the CLI at build time, so it needs no network.
tflw spec [--json]
Section titled “tflw spec [--json]”Prints the construct manifest of this build: every declaration, step keyword, matcher, generator,
locator, tflw.config word and diagnostic code the parser dispatches, followed by a build stamp —
version, commit and build time.
It exists for conformance testing. A suite that wants to prove it exercises the whole language has
to be able to ask what the whole language is, and reading it out of @tflw/lang is not available to
a consumer who installed the published tflw package: that ships one self-contained bundle with no
@tflw/* packages to import. --json is the form such a gate reads.
The manifest lists what this binary dispatches, and only that. A construct SPEC marks 🔮 planned is absent rather than listed as planned, so building one makes it appear — which is what lets a downstream coverage gate go red on its own the day it ships, with nobody having to remember.
Declarations are the outermost family: the seven words a file can begin a top-level block with
(test, crawl, action, import, use, before, after) and the five clauses a test header
takes (tags, with each, as, retry, parallel/sequential). A hook's two scopes are one
construct with two forms, the same way switch to new tab and switch to tab N are one switch.
The build stamp is the other half. commit is a short sha, or null where there was no git to ask
(a published tarball, a vendored checkout) — never invented, because an invented one would be
believed. dirty says whether the working tree had uncommitted changes, and is null when there is
no commit for it to be relative to. Together they answer which tflw just produced this output,
which is the question a stale vendored copy makes unanswerable.
tflw spec [--json] [--json]--json- emits the construct manifest as JSON instead of the human listing — the form a conformance check reads. Spelled as a boolean rather than
--format json:spechas one machine format and one human one, so there is no open set of renderings to name
tflw lsp
Section titled “tflw lsp”Runs the language server over stdio. Takes no flags and is not meant to be typed by
hand: stdin and stdout carry the LSP wire protocol, so an editor spawns it. packages/vscode does
exactly this; any other LSP-capable editor can point at npx tflw lsp. Diagnostics come from the
same checkProgram pass list tflw check runs, so an editor squiggle and a CI failure are the
same computation.
tflw refactor apply <id>
Section titled “tflw refactor apply <id>”Extracts one reuse-pass hint (an RF0xx id from tflw check's output) into a shared action,
writing a new file and rewriting every matched call site. Takes exactly one positional argument —
no flags — and always scans the whole default suite (no [files]/--env selection, since the
hint ids it consumes come from that same whole-suite scan). Re-run tflw check/tflw run
afterward to confirm the rewritten suite is still clean and green.
Global
Section titled “Global”--version,-v- print the installed version
--help,-h- print usage