Repository navigation
v3.35.0: worsened complexity in audit, Vite+ and Egg.js plugins, mutation progress
Features
-
fallow auditreports inherited complexity findings that got worse. A complexity finding from the base snapshot is nowworsenedwhen its cyclomatic, cognitive, CRAP or line count value is higher than at the base. CRAP counts only when both sides have a score. The line count must grow by at least 5 lines, so a short comment or a formatter rewrap keeps a finding inherited. A function that grows from 80 to 300 lines with the same complexity values is nowworsened. The JSON output addsattributionto each complexity finding,worsened_metricsto each finding with a grown value, andattribution.worsenedandattribution.complexity_worsened. The human, compact and markdown output add a "Worse than base" section. SARIF and CodeClimate show the values, and the GitHub summary marks each finding asworsened. The--worsenedflag and theaudit.worsenedconfig key select the effect:warn(default, exit code 0),error(counts like an introduced finding) oroff(only growth of an exceeded metric is worsened). The GitHub Action has aworsenedinput, the GitLab template hasFALLOW_AUDIT_WORSENED, and the MCPaudittool has aworsenedparameter. -
The VS Code extension has a
fallow.audit.worsenedsetting. The values arewarn,errorandoff, the same asfallow audit --worsened. The defaultautousesaudit.worsenedfrom the project config. The extension sends the flag only to a CLI that supports it. With an older CLI, the audit runs without the flag and the output channel shows a note. -
A human
fallow mutationrun shows phase progress on stderr. The lines name the runner setup, the baseline run and the mutant runs with done and total counts. They also name the retries, the final worktree check and the report. A long phase prints a line at a fixed interval, also in non-TTY output, so a slow run does not look stalled.--quietand--format jsonprint no progress lines, and stdout does not change. The programmatic API hasrun_mutation_with_progresswithMutationProgressevents. (#3599) -
fallow mutation --runner-config <path>selects the Vitest config of a run. A project that keeps its integration, UI or browser tests in a separate Vitest config can now run mutants against those tests. Themutation.runnerConfigconfig key sets the same file, and the flag wins. The file must exist inside the project root, and only the Vitest runner accepts it. A static test file that the selected config does not include stays visible as atest-files-not-discoverederror.--configstill selects the Fallow config. (#3598) -
Mutation JSON shows the edit of each mutant and the history of each retry. Each mutant in the
mutationreport and themutation-plandocument has achangeobject, for example{"from": "??", "to": "&&"}. The values come from a fixed vocabulary of operator tokens, literals and placeholders, so the report still holds no source text (#3658). A retried mutant has a newattemptsarray with the status, the duration and the killing test, failure kind or runtime-error reason of each run. You can now see if aflakyresult wastimeout -> survivedorsurvived -> killed(#3656). -
fallow mutationmutates fluent chains in module-levelconstinitializers. The newremoved-chain-linkoperator removes one call link of a chain. For example,z.number().int().positive()gives a mutant without.int()and a mutant without.positive(). A validation schema with no literal or operator now has mutants. The operator is on by default, and--operatorsandmutation.operatorsaccept its name. (#3662) -
Fallow reads Vite+ projects. A
vite-plusdependency, or a rootvite.config.*that importsvite-plus, enables the newvite-plusplugin. Thepack.entryvalues are entry points, and thelint.jsPlugins,lint.overrides[].jsPluginsandlint.extendspackages count as used. Thevite-plusdependency also enables the Vitest plugin. Thevp,vprandvpxbinaries creditvite-plus. Fallow readsvp test -c <file>,vp exec <binary>andvp run <task>. It also reads the commands of thestagedandrun.tasksblocks and the.vite-hooks/scripts. The file targets ofvp lintandvp fmtare not entry points. Avi.mockcall throughvite-plus/testcounts as a Vitest mock. -
Fallow detects Egg.js and tegg projects. The new
eggplugin activates onegg,@eggjs/coreor@eggjs/tegg, or on anegg,eggPluginoreggModulekey inpackage.json. Entry points are the files that the Egg loader loads by convention:app.js,agent.js, the router, controllers, services, middleware, extensions, schedules and config files, also in nested example and test fixture apps. Every file of a tegg module is an entry point. The plugin credits the packages thatconfig/plugin.*names, theegg.frameworkpackage and the defaulteggframework to thepackage.jsonthat owns the app. -
Fallow reads the entry files of a JavaScript GitHub Action. When
action.ymloraction.yamlsetsruns.usingto anoderuntime, theruns.pre,runs.mainandruns.postfiles are entry points. A path indist/maps to its source file, with the same rules as apackage.jsonmainfield. A composite or Docker action adds no entry points. -
A dead-code baseline records if
node_modulesexisted. A--save-baselinerun writesanalysis_identity.node_modulesaspresentormissing. A later run in the other install state adds abaseline-install-state-mismatchentry toworkspace_diagnosticsand prints a warning, also under--quiet. Findings that need installed packages can differ between the two states, so such a run can report baseline findings as new. -
External plugins can opt a config pattern out of the
defaultexport credit. List the pattern inconfigDefaultExportOptOutwhen the tool reads named exports, or when the pattern also matches ordinary source files. -
Fallow reports a missing workspace directory and analyzes nested pnpm projects. A positive workspace pattern whose directory does not exist now gets the
workspace-pattern-dir-missingdiagnostic, withdegrades_analysis: true. While such a pattern exists, the run does not reportunused-catalog-entriesfor any catalog, because the missing packages can use catalog entries. This also applies to a pattern for a directory that does not exist yet, such aspackages/*in a template. A directory that exists but holds no package does not turn off the check. Create the directory before the run or remove the pattern to get the check back. A directory with its ownpackage.jsonandpnpm-workspace.yamlthat no root pattern declares is an independent pnpm project. Fallow analyzes it as a workspace and reports the information-onlynested-project-rootdiagnostic. (#3710) -
Each release publishes a CycloneDX SBOM for every target. The files are named
fallow-<target>.cdx.json..vex/openvex.jsonstates why fallow is not affected by each advisory that the repository ignores.SECURITY.mdlists the supported versions and the code scanning policy.
Changed
-
fallow audit --gate new-onlyreports growth of an already exceeded complexity metric asworsened. Before, such a finding wasintroduced. A function that exceeded a threshold on the base and grows a little in the PR no longer fails the audit by default. With the defaultaudit.worsened: warn, the verdict iswarnand the exit code is 0. The finding staysintroducedwhen the growth moves it to a higher severity tier. It also staysintroducedwhen it now exceeds a metric that it did not exceed on the base, and that metric grew. The exceeded flags and the tier come from the base run. A threshold override in the same PR does not make an old finding introduced. -
fallow audit --gate new-onlyno longer fails on a CRAP score from estimated coverage alone. Without a coverage map, fallow estimates CRAP from static test reachability, and this estimate does not measure the change. A finding that exceeds only the CRAP threshold with estimated coverage now haseffective_severity: warnand SARIF levelwarning. A finding that also exceeds the cyclomatic or cognitive threshold still gates. CRAP from measured coverage still gates, and--gate alldoes not change. -
fallow auditno longer fails on a complexity finding in a test file. A longit(...)ordescribe(...)callback often exceeds the cognitive threshold. Such a finding now haseffective_severity: warnand the new fieldgate_exemption: "test-file", with a verdict of at leastwarnand exit code 0. This applies to--gate new-onlyand--gate all. The human, markdown and compact views list these findings under "Test files, not gated".fallow healthdoes not change. (#3753) -
The GitHub Action requests the branded token only when it posts. The token step now runs only on a
pull_requestevent withcomment: trueorreview-comments: true. Thebranded-tokeninput now defaults toauto.autoposts as the Fallow app when the workflow grantsid-token: write. Without that permission, it falls back togithub-tokenwithout a log line. An explicitbranded-token: truekeeps one short fallback line, andfalsebehaves as before. (#3755) -
--type-awareclass-member refinement is faster on large projects. The symbol-use scan now checks the name first and keeps one alias answer for each symbol. On a large TypeScript project, the scan takes 4 to 12 s instead of 35 to 70 s, with the same decisions. The scan now also sends only the names that a query can reach to the checker for TypeScript files. JavaScript files keep the full scan, becauserequirebindings also create aliases. (#3765) -
fallow mutationwith Vitestisolate: falseno longer repeats the setup of unrelated test files. Each static mutant runs in a new runner process, and that process now dry-runs only the test files that cover the mutant. A covering test that fails its dry run is still a baseline failure. (#3650) -
The
fallowskill shows the review loop that fallow checks. One short example runsfallow audit, reads the walkthrough guide, writes a judgment, and validates it with--walkthrough-file. The example saves the guide and the judgment in the temporary folder, because a new file in the project tree makes the judgment stale.
Bug fixes
-
fallow mutationmakes fewer equivalent mutants. No test can kill these mutants, so they no longer appear:- an empty-string or swapped-boolean
caselabel with the same body asdefault, only when all labels are literals and both bodies exit unconditionally (#3639); i != Nfor the test of a countedforloop with an integerletcounter and no other write (#3649);- a zero comparison on the length of a known array, such as
items.length > 0toitems.length != 0(#3651); - the constant
truefor an optional lookup before.filter(Boolean), such as["base", align ? alignment[align] : ""].filter(Boolean)(#3667).
- an empty-string or swapped-boolean
-
fallow mutationruns the tests that can kill a mutant. A mutant in module-level code now runs the tests of static importers when another test also loads the module throughimport()(#3652). The Vitest setup file serves the mutant to acreateRequire(import.meta.url)load on Node 22.15 and later (#3653). Withnode:test, a Node child process of a test (spawn,exec,execFileorfork) loads the active mutant (#3680). Thenode-testandbun-testrunners now run a mapped test file in a dot directory such as.github/(#3678). -
Fewer false survivors in
fallow mutation, and Vitest inline projects stop at the first failure. The retry of a survivor now runs each test of the files of its covering tests. A test that reads a cached value can now kill the mutant. Such a kill iskilledwhen the same tests pass in a control run with no active mutant (#3670). Each Vitest inline project intest.projectsnow stops at the first failed test, and a control run still runs the full selection (#3701). -
fallow mutationmarks undiscovered test files, keeps time for retries, and shows the assertion of a failed control run. A run in which the runner does not list a static test file is nowpartialwith the reasontest-files-not-discovered(#3596). With--time-budget, the first pass keeps time for the retries of its survivors and timeouts, and the cache stores the confirmed results. A repeat run continues with the next mutants (#3665). When a test fails in a control run, theFALLOW_MUTATION_BASELINE_FAILEDerror and the human footer show the assertion.baseline_failuresalso shows the project stack frames (#3690). -
Writes through an ORM or a query builder are now side-effect sinks in
fallow mutation. A safe write such asdb.insert(records).values({ value })was not a side-effect sink, so a run could write to a real database. Database write method names, SQL text that starts with a write verb, and prepared statement runs are now sinks. These sinks apply only in a module that loads a database client package, or that loads a project module that loads one. Examples aredrizzle-orm,@prisma/client,kysely,knex,mongodbandpg. A preparedSELECTand a route registration such asapp.delete("/items/:id", handler)are not sinks.--allow-side-effectskeeps these mutants. (#3641) -
fallow audit --gate new-onlyno longer reads a missing base CRAP score as growth. A matched finding with a head CRAP score and no base CRAP score is nowworsenedorinherited, notintroduced. A CRAP score from estimated coverage also no longer makes a matched finding introduced. -
The
defaultexport of a tool config file is no longer an unused export underincludeEntryExports. The rule applies to each config pattern of an active plugin, built-in or external, for examplepostcss.config.*,tailwind.config.js,jest.config.jsandplaywright.config.ts. The credit now also covers Capacitor, Sanity, Nitro, SVGO, SVGR, Kysely, OpenAPI TS, Remix, i18next, VitePress, CSpell,@sanity/pkg-utils, Electron Forge and electron-builder configs. A rootcodegen*and a rootserverless.*file keep theirdefaultexport, and cucumber-js and@vercel/confignamed exports count as used. Gatsbygatsby-node,gatsby-browserandgatsby-ssrand Vercelvercel.*patterns get nodefaultexport credit. Named exports of a config file stay reportable. Output withoutincludeEntryExportsdoes not change. Thanks to @osazemeu for the report (#3691) and the PostCSS patch (#3694). -
Serverless handler exports and
.tsxand.jsxSanity configs are entry points.fallow dead-code --include-entry-exportsno longer reports the export that a Serverless handler names, such asmainforsrc/users.main. A nestedserverless.tsorserverless.jsmodule, such assrc/lib/serverless.ts, is an ordinary source file again. Fallow now treatssanity.config.tsxandsanity.config.jsxas Sanity entry points, so the modules that they import are no longer unused. For a.tsxor.jsxSanity config, the output changes in all modes. -
Fallow finds the test files of more Vitest setups. A Vitest config below the project root, for example a fixture config, now applies its
test.includeglobs only below its own directory. Fallow readstest.root,test.dirand the Viteroot, and resolvessetupFiles,globalSetupand reporter paths from the Vitest root. A project file such aspackages/a/vitest.unit.config.tsintest.projectsis the root of its project. An inline project gets the values that it merges from its base config. The default test patterns match.mts,.cts,.mjsand.cjsfiles. -
Vitest bench files and reporter paths are used files. A config that sets
test.includeno longer makes bench files unused. Fallow readstest.benchmark.include, and the default bench pattern now matches.benchmark.files and the.mtsand.ctsextensions. Areportersentry such as'./scripts/ci-reporter.ts'marks that file as used, also inside a spread array. The config reader of the other plugins now also reads spread arrays and both branches of a condition. -
The napi-rs plugin works in a source repository. The plugin activates on the
napifield and itstargets, withoutoptionalDependencies. In a generated loader, the platform packages and@napi-rs/wasm-runtimeare no longer unlisted, and the.wasi.cjs,.wasi-browser.jsand.nodefiles are no longer unresolved imports. The generated loader andbrowser.jsare support entry points, so a hand-written wrapper is no longer a duplicate export. Thenapiandnapi-rawbinaries credit@napi-rs/cli. You can remove theignorePatternsandignoreDependenciesentries for these files. -
Fallow credits more dependencies. These cases no longer give unused, unlisted or unresolved findings:
- a resolve specifier from a conditional const with
?:,||or??and string literal branches; - a
<workspace-package>/package.jsonimport withoutnode_modules; require.resolveon a function-scopedcreateRequire(import.meta.url)orcreateRequire(__filename);require('..')andrequire('.')whenmainpoints at missing build output;- a script that calls a binary from the
binfield of a workspace package, withoutnode_modules; - the
taplo,oxnode,utandutxbinaries; - a peer of a package that a script runs or that a plugin credits, for example
@emnapi/runtimenext to@napi-rs/cli; - the
vite.optimizeDeps,vite.ssr.externalandvite.ssr.noExternalpackages of a VitePress config, and a nested ViteoptimizeDepsentry such as'mermaid > @braintree/sanitize-url'.
- a resolve specifier from a conditional const with
-
GitHub Actions steps resolve file arguments in their
working-directory. Fallow reads theworking-directoryof the step and thedefaults.run.working-directoryof the workflow and the job. Paths that start with$GITHUB_WORKSPACE/,${{ github.workspace }}/or GitLab$CI_PROJECT_DIR/resolve from the project root. Fallow now also reads the run steps of composite actions in.github/actions/and the rootaction.yml. -
css-broken-referenceno longer suggests a "did you mean" fix for a defined class. Fallow skips tokens with the shape of a Tailwind utility, such asw-full, when a stylesheet loads Tailwind or the rootpackage.jsondeclares it. As a result, an authored class that starts with a utility root, such astext-orlist-, gets no typo suggestion. A class that an Astro, Vue or Svelte<style>block or a CSS module defines only through:globalnow counts as defined. A<style>block in an.htmlfile now adds its classes. Thanks to @zaphir for the report. (#3588) -
A
<in template text no longer hides a later component invocation. The caller scan for Vue, Svelte, Angular and Ember templates read each<as a tag start. In<p>{{ a < b }}<Button size="lg" /></p>, theButtoninvocation was lost. The scan now skips text expressions as a unit and reads a<as a tag start only before a letter,/,!or?. -
Fallow now reads a JSON or JSONC config file that ends with a
//line comment and no final newline. Before, the parse failed. For example, atsconfig.jsonlost itsextendspackage, and an.oxlintrc.jsonclost itsjsPluginspackages, which fallow then reported as unused. -
Glob expansion on disk works when the project path contains
[,*or?. Under a root such as~/work/app [1], pluginfileExistsdetection,fallow plugin-checkentry seeds and workspace globs matched nothing. A plugin did not activate, and fallow reported the files of a workspace package as unused. -
A module that imports
Xunder an alias and exports its ownXis no longer a duplicate export. Such a module is an adapter, for exampleimport { configureAuth as core } from "../ci/auth"followed byexport function configureAuth(). Fallow still reports two modules that importXfrom each other under aliases. -
A nested
package.jsonwithout dependency fields no longer causes unlisted dependencies. A metadata-only manifest, such as a tegg module with{ "name": "x", "eggModule": {} }, now inherits the dependency declarations of its ancestor manifests. A manifest with an explicit empty field, such as"dependencies": {}, stays the owner of its files. -
--traceno longer calls an export unused when a framework plugin marks it as used.--traceand--trace-filelist these credits in a newplugin_creditsfield with the plugin and its file pattern. The human output shows the same credit. -
ignoreFindingscovers the parse errors of the files that it hides. A syntax-error test fixture thatignoreFindingscovers no longer reportssource-parse-degraded, prints a parse warning, or fails theparse-errorgate. Its partial parse no longer adds theincomplete-import-graphcaveat to other findings. -
Warnings on stderr have color codes only on a terminal.
NO_COLORturns them off, andCLICOLOR_FORCEturns them on.--quiet,--ciandFALLOW_QUIEThide the analysis warnings that repeat aworkspace_diagnosticsentry. SetRUST_LOG=warnto show them again. Config warnings still print. -
The setup hints name the current commands. A bare run names
fallow initandfallow agent install --dry-run, and the JSONsetupnext step usesfallow agent install --dry-run.fallow inittells you to runfallownext. A dry run offallow agent installthat plans writes putsagent-install-applyfirst innext_actions[], with the flags that change the plan. The command repeats a detected harness as--harness. The review focus readsfan-in (1 importer)for one importer. -
An older fallow no longer drops data from an Impact store that a newer fallow wrote. Every write path now leaves such a store unchanged.
fallow impact enable,fallow impact disableandfallow init --declineexit with code 2 and name the store schema and the fallow version of the newest record. Reads stay available. (#3597) -
Nested pnpm projects and catalogs give correct dependency findings. A nested independent pnpm project owns its files and dependencies, and its
catalog:references are not checked against the root catalog. Apackage.jsonwith no name and no dependencies, such as one with only{"type":"module"}, is no longer an undeclared workspace. A pnpm override with no version selector now takes precedence over acatalog:reference. Underaudit --gate new-only, catalog findings that the base snapshot could not check are inherited, not introduced. A new unused catalog entry in such a run is also inherited. (#3710) -
The
fallow-setupskill detects all Oxlint and Oxfmt config file names. The table now also lists.oxlintrc.jsonc,oxlint.config.{ts,mts},.oxfmtrc.jsoncandoxfmt.config.*. -
Fallow links patched rustls-webpki and webpki-roots, and the Docker image pins its base images. Fallow and the similar-code sidecar link rustls-webpki 0.103.16 (RUSTSEC-2026-0382) and webpki-roots 1.0.10 (RUSTSEC-2026-0381). The Docker image runs on Node.js 26.12.1, pins each base image by its multi-platform digest, and installs corepack from a lockfile with an integrity hash.
Upgrade notes
-
fallow auditverdicts can change. An inherited complexity finding that got worse now gives awarnverdict, with exit code 0. Growth of an exceeded metric under--gate new-onlyno longer fails the audit. To keep the earlier gate, setaudit.worsened: "error"or pass--worsened error. Withaudit.worsened: off, growth of an exceeded metric now also gives awarnverdict. Fallow does not reuse cached base snapshots of earlier versions. -
A missing workspace directory turns off the unused catalog entry check for all catalogs. Create the directory before the run, or remove the pattern, to get the check back.
-
fallow dead-codecan report new unused files after the Vitest change. Files that only a fixture Vitest config marked as used can now show up. You can remove anentryworkaround for test files that Vitest finds. -
Mutation scores can change. Projects with ORM writes can see more
prunedmutants and a different score. -
Save the dead-code baseline again to get the
node_modulesinstall state check. A baseline without the key loads as before. -
The first run after the upgrade parses each file again. The extraction and template scan cache versions changed.
Full Changelog: v3.34.1...v3.35.0