Repository navigation
Changed
-
A single
*in a config path glob now stays within one path segment. Only**crosses directories. Before,src/plugins/*/index.jsinentryalso matchedsrc/plugins/alpha/components/button/index.js. Each nested match became an entry point, so fallow did not report it as an unused file. The rule first applied toentryanddynamicallyLoaded(#3513, closes #3490). It now applies to every config path glob (#3541):ignorePatterns,ignoreFindings,overrides[].files,ignoreExports[].fileandignoreCatalogReferences[].consumer,duplicates.ignore,similarCode.ignore,health.ignoreandhealth.thresholdOverrides[].files,- the
boundarieszone and coverage globs, and the rule-pack file globs, - the
framework[]fieldsusedExports[].pattern,manifestEntries[].manifestsanddetection.pattern.
To keep the old match, change the single
*to**. For example, changesrc/generated/*tosrc/generated/**, and*.test.tsto**/*.test.ts. For each pattern that now matches fewer files, fallow prints a warning on stderr. The warning names the config field, the pattern, the count of files that only the old rule matched, and the rewrite.--quiethides the warning, and the JSON output does not change. A narrower glob can show new findings. Fallow matches a glob against each file path, not against each directory. Thus a line that you copy from.gitignorecan match fewer files inignorePatterns. Globs that match package names or import specifiers, such asignoreDependenciesandignoreUnresolvedImports, do not change. Thanks @jleifeld for the report. -
An existing
prop-drilling: "error"setting now enforces CI. To report chains without an error, change the rule towarn. Archived JSON reports witheffective_severity: "error"also render as errors. A config change does not rewrite those reports. -
Storybook, Vitest, Rstest, Jasmine and Playwright globs now accept every extglob group. One shared rewrite reads
@(..),?(..),+(..),*(..)and!(..)for these plugins and forimport.meta.glob.- Storybook: before, only
@(..)matched. Now../src/**/!(*.dev).stories.@(js|ts)matches, and!(*.dev)excludes the files that match*.dev. - Playwright: before, a
testMatchglob with a group made every script belowtestDiran entry point. Now only the matched files are entry points. - Vitest: before, a
test.includeglob with any group made no file an entry point. So fallow reported every test file as unused. A plain(a|b)group still matches one alternative. - Vitest, Rstest and Jasmine:
!(..)and*(..)now match. - A
+(..)group in an include glob now matches one or more repetitions. Before, Vitest, Rstest and Jasmine matched the group once only. The rewrite can also make a few more files entry points, for examplea.test.jsxanda.test.tsxfor*.test.+(ts|js). An exclude glob still matches the group once only.
- Storybook: before, only
-
Complexity findings name more callback bodies. A function that got
<anonymous>or<arrow>now takes a name from the code around it.Effect.fn("Users.create")(function* ...)givesUsers.create, andit("renders", () => ...)givesit("renders"). A generator that a named function returns gives that name, for exampleloadforconst load = () => Effect.gen(function* ...). Scores and existing names do not change. A renamed finding gets a new CodeClimate fingerprint one time. An identity baseline gives the allowance of an<anonymous>or<arrow>bucket to the new names in the same file. A file move no longer moves an<arrow>bucket. -
Runs are faster in five places.
- Plugin detection no longer walks
node_modulesfor a Vitest, Jest or Rstest project glob. Since 3.33.1, atest.projectsglob such asapps/**/vitest.config.tsread the full dependency store of a pnpm monorepo once per workspace. Plugin detection then took minutes. The walk now skipsnode_modules,.git, gitignored paths and symlinks that point out of the project root. Thanks @wouterkroes for the report (closes #3532). - Import resolution is fast again in monorepos whose packages extend a root tsconfig that references them. Since 3.20.0, each source file checked about 760 configs in place of about 30. On a monorepo with 28 references and 4,200 modules,
resolve importstakes 0.5 s in place of 10.1 s, with the same findings. Thanks @thymikee for the report and for the patch in #3504 (closes #3503). - A run with a restored CI cache no longer rewrites every cache entry. An entry whose content did not change gets only its new timestamps. On a large monorepo, such a run is about a fifth faster. The dead-code run also frees its parsed modules on a helper thread.
- An import through a barrel that does
export *of many packages is now checked only against the packages that export its name. Findings do not change. - Each glob compile is about three times cheaper. A dead-code run takes about 5 to 7 percent fewer instructions on a mid-size project, and the binary is smaller.
- Plugin detection no longer walks
-
The build uses one oxc version.
oxc_coverage_instrumentmoves to 0.14, which uses oxc 0.152 like the rest of fallow.cargo denynow fails when two versions of the core oxc crates enter the dependency graph. -
The MCP
audittool has afallow://tools/auditguide. The guide names the five diff-risk rules and holds the styling, coverage, baseline, grouping, runtime-coverage andFALLOW_DIFF_FILEdetail. The tool description is now a short summary that points to the guide (#3369). -
The guidance for workspace dependency declarations is clearer. Root imports of npm workspace packages need a dependency declaration, although npm links resolve them. The guidance covers private and tooling ancestor exceptions, self-references and explicit opt-outs. The policy does not change. Thanks @VariableVince for the question (closes #3325).
Features
-
fallow mutationmeasures how well the tests detect code changes. The command makes small changes (mutants) in the changed code and runs the project tests against each one. A mutant that no test fails on survives. The report gives the mutation score over the tested mutants and the survivors by file with their covering tests. It also gives the share of mutants that a test reaches.- The default scope is the changed code against the base ref, as for
audit.--alland file paths widen or narrow it. --planselects and ranks the mutants and runs no code.fallow mutation inspect <mutant_id>shows one mutant against the current file. It reads the report of the last run, or the file that--reportnames. A changed file gives astalestatus.- The runner is Vitest (
>=4.1.0 <6.0.0),node:testof Node.js 22.18 or later, orbun test(>=1.4.0 <2.0.0). The defaultmutation.runner: autodetects it. An unsupported version exits with code 2 andFALLOW_MUTATION_RUNNER_UNSUPPORTED(#3407). - The
node:testrunner takes the module loading flags of thenode --testcommand in the project scripts, such as--experimental-transform-typesand--import(#3487). Aborpscript is anode:testrun, and its globs give the test entry points (#3482). - Vue, Svelte and Astro components get mutants in their script blocks. Code that the framework compiler moves out of its block gives no mutant, for example the arguments of
defineProps. An expression that holds a Svelte rune call gets no mutant, but the arguments of a rune still get mutants. Withnode:testandbun test, the plan leaves component files out with the skip reasonrunner-cannot-load(#3470). - The plan prunes the mutants in the default export of a tool config file, such as
vite.config.ts, with the reasonframework-config(#3483). A mutant inside a function of the config stays. - The
regex-literaloperator removes a^or$anchor, negates a character class, or swaps the case of\d,\wor\s. - A run refuses a dirty git worktree unless
--allow-dirtyis set. The plan prunes mutants near file system, process, network or database calls unless--allow-side-effectsis set. Fallow does not write to project files, and the report lists any path that the tests changed.-o <file>writes the report after the run, and the worktree check leaves that file out. - With
--max-mutants, the coverage counts the planned mutants that did not run. The dry run decides their coverage. A capped run therefore reports the coverage of the full run. - The exit code is 0 by default.
--min-scoreormutation.minScoremakes a lower score exit with 1.--verdicts <file>marks equivalent mutants, which leave the score. - A survived or timed-out mutant runs once more, so host load alone does not count as detection. A changed result is
flakyand leaves the score.retried: truemarks a status from both runs. - A type test (
*.test-d.ts,*.spec-d.ts, or a TypeScript file belowtest-d/) is not a test file of the plan. --typecheckormutation.typechecktype-checks each survivor through the--type-awarecompanion. A survivor that adds a type error getscompile_errorand leaves the score. Without a companion, the run exits with 2 before the tests start. A Vue, Svelte or Astro survivor stayssurvivedwith the skip reasonunsupported-file(#3471).- Each
no_coveragemutant has atest_reachobject with the classmocked-only,none,none-file,transitiveordirect. - A run reuses the killed and survived results of an earlier run from the fallow cache. The key covers the mutant, its covering tests and their imports, the runner setup and config, the lockfile, and the
tsconfig.jsonextends chain. Dynamic imports that the module graph does not resolve, and files that a test reads, are not in the key.--no-cacheruns each mutant. fallow health --mutation-report <file>adds the mutation score of each complexity finding next to its CRAP score. The score never changes CRAP or the health score.- The new
mutationconfig section sets the runner, the operators, the minimum score and the concurrency.--setup-timeoutsets how long each runner process can take to start, finish its dry run or reload (default2m).
- The default scope is the changed code against the base ref, as for
-
fallow audittakes--onlyand--skip. The values are the same as for barefallow:dead-code,architecture,dupesandhealth. The config keysaudit.deadCode,audit.healthandaudit.dupesset the default selection. A skipped sub-analysis is skipped on the head and on the base, so it leaves theverdict,summaryandattribution. A warn-only gate for complexity and duplication is nowfallow audit --skip dead-codewith the complexity rules atwarn.- The JSON report omits the block of a skipped sub-analysis and sets
summary.dead_code_skipped,complexity_skippedorduplication_skipped. architecturenarrows the dead-code findings to, or away from, cycles, boundary violations and policy violations.- A baseline flag for a skipped sub-analysis exits 2, and a configured baseline for it is ignored.
--max-crap,--coverage,--coverage-rootand--runtime-coverageprint a note when the health sub-analysis is skipped. - A selection that leaves no sub-analysis exits 2, also for bare
fallow --skip dead-code,dupes,health. - The review brief (
--brief,fallow review) keeps every sub-analysis. - The GitHub Action
onlyandskipinputs, the GitLabFALLOW_ONLYandFALLOW_SKIPvariables and the MCPaudittool now apply toaudittoo. The GitHub step summary now has theStylingrow.
- The JSON report omits the block of a skipped sub-analysis and sets
-
fallow auditreports risky changes as rules. Five new rules compare the base and head text of each changed file.weakened-testsreports a new skip or focus marker, removed test cases, fewer assertions, or a less strict coverage threshold.added-suppressionreports a new lint, type-check, coverage or fallow suppression comment.test-only-branchreports a newprocess.env.NODE_ENV === 'test'style check in source code. A Vitest in-source test block is not reported.diverged-clone-siblingreports an edit to one copy of duplicated code when the other copies did not change.added-dependencyreports a newpackage.jsondependency.package.jsoncannot hold a suppression comment, so set the rule toofffor a path in anoverridesentry.
diverged-clone-siblingandadded-dependencyareoffby default. The other three rules arewarnby default. The findings are in the newdiff_riskJSON section and in SARIF, CodeClimate, compact and markdown output. GitHub annotations, GitHub job summaries and the MCPaudittool show them too. The human status line, the issue count of the GitHub Action and the GitLab template, and the VS Code audit status bar count them. -
The review brief reports test reach for changed exports.
fallow audit --briefandfallow reviewadd atest_reachsection. For each changed value export, it statesdirect,transitive,mocked-only,none,none-fileorframework. The JSON lists at most 25 exports that need attention, with the mocking tests of amocked-onlyexport. The section never changes the exit code. -
Traces name the files that use an export but that a name search does not find. A default import under another name, a barrel alias or a whole-namespace use hides such a file from
grep -w. Eachdirect_references[]entry offallow dead-code --trace FILE:NAMEnow hasline,search_visibility(found,renamed,missed),reasonandsearch_as. A file that only forwards the export isfoundwithreasonpass-through.--impact-closurecoordination gaps havesearch_visibility,reason,search_asandsearch_symbol.fallow audit --briefaddsimpact_closure.search_gap, also for changed.vue,.svelte,.astroand.mdxfiles. The MCPtrace_exportandimpact_closuretools return these fields too (#3371, #3372). -
fallow healthreports hidden change coupling and change dispersion. A hidden pair is two files that change together in most commits with no import or re-export chain between them. The newchange_couplingsection lists these pairs. The defaults are 5 commits per file, 5 shared commits and a coupling degree of at least 30%. Fallow skips commits with more than 30 analyzed files.health.changeCouplingchanges the thresholds.- Each hotspot gets
median_files_per_commitandmedian_lines_per_commit. - A strong pair adds the
hidden_couplingfactor to refactoring targets. A file with no other target becomes aresolve_hidden_couplingtarget. fallow audit --briefshows aco_change_gapfact that never changes the exit code. It shows when a diff changes one file of a hidden pair and not the other.- The health score does not change. The
fallow-churn/v1import accepts an optionalcommitfield.
- Each hotspot gets
-
Projects that declare
effectget three dead-code rules. Each rule iswarnby default.unused-effect-layerreports a layer that only unused layers compose.unused-tagged-errorreports aData.TaggedErrororSchema.TaggedErrorclass that types orcatchTaghandlers reference, but that no code constructs, fails with or decodes.unproduced-catch-tagreports acatchTaghandler for a project tag that no code produces, when nounused-tagged-errorfinding covers it.
Any value reference to an error class counts as a producer, so errors that a schema decodes are not reported. Tags that no project class declares, and Effect built-in tags, are never reported.
-
Prop-drilling findings can gate CI. Set
rules.prop-drillingtoerrorto enforce chains, or towarnto report chains without an error, also under--fail-on-issues. Source-hop overrides and suppressions control each chain. Baselines, audit attribution and changed-hop scopes support incremental adoption, and CI reports show the source location. The rule stays off by default, and editor diagnostics stay hints. Thanks @miky-mfw for the request (closes #3327). -
Built-in plugins for eleven more tools. Before, fallow reported the config files, entry files and packages of these tools, or their SDK callback members, as unused.
mise,railwayandtextlintread their configs and credit the packages and scripts that tasks, commands and rules name.jasmineandrstestmake their spec, helper, setup and project files test entry points.serverless-frameworkandoslsmake each functionhandleran entry point and credit each package inplugins.fluemakes the app, database, Cloudflare and agent modules of a Flue project entry points.typescript-content-mapperruns theexecscript ofpackage.json#typescript.contentMapperas an entry point.alchemymakes eachalchemy.run.*file an entry point. The file that a resource names inmainstays reachable, with itsdefaultexport and its Durable Object, Workflow and Container classes.opentelemetrycredits the methods of a class that implements an SDK interface such asSamplerorSpanExporter. Other members of the class stay reportable.
-
fallow migraterecognizes more plugin sections and config keys. A section forexpo-router,varlock,turboor one of the new plugins gets the "auto-detected" message. The root keyscycles,ignoreGlobalBinaries,preprocessor,preprocessorOptionsandasyncCompilersget a message with the closest fallow alternative.
Bug fixes
fallow mutation
- A run no longer reports a false 100% score. Before the first mutant job, a self-check runs the tests of one covered mutant with no active mutant. When that run executes no tests, the run exits with code 2 and
FALLOW_MUTATION_RUNNER_FAILED. A mutant run that executes no test iskilledonly when a control run without the mutant passes. A test that fails alone, without a mutant, goes intobaseline_failures. - Baseline failures give a clear result. A run in which baseline failures stop every mutant run now exits with code 2 and
FALLOW_MUTATION_BASELINE_FAILED. The human report shows aMessageline with the first line of the first failure. Vitest tests now run underNODE_ENV=test, as thevitestcommand does. - Scores and limits count the right mutants.
--max-mutants Nnow runs up to N covered mutants, and an uncovered mutant does not use the limit. A survivor whose retry times out stayssurvived, notflaky. The plan also prunes the config object of plugin config files such as.vitepress/config.tsandnitro.config.ts. - Vue components that call
defineExposeno longer break. A Vue macro call and its arguments give no mutant. - The worktree check sees more stray writes. The check now leaves out only
<cache>/mutationand the output files. A run refuses a cache directory that is the project root, a directory above it, or a directory with a planned file. It exits with code 2 andFALLOW_MUTATION_UNSAFE_CACHE_DIR. A write to an ignored file such as.envis aworktree-changederror. - Mutation companions no longer outlive fallow. A companion that loses its stdin kills its process group and exits. Fallow also ends the companions on SIGHUP, SIGQUIT and a panic.
Dynamic imports and the module graph
- Dynamic import patterns with extglob groups, negative entries or a leading
../now find their files.import.meta.globpatterns with extglob groups, such as./**/!(*.spec).{j,t}s, now match files. A pattern that is not a valid glob gives a warning. Thanks @jleifeld for the report (closes #3489).- Negative
import.meta.globentries such as'!**/*.test.ts'now exclude files, as in Vite. - Patterns that start with
../now resolve from the directory of the importing file. This coversimport.meta.glob,require.context, template literals and string concatenation. - The literal text of a pattern now matches only itself. For example,
import(`./[id]/${name}.js`)matches./[id]/view.js.
- A dynamic
import()credits the exports that the code reads. Animport()whose value escapes, such asregister(import('./x')), now credits every export of the target. A.then((m) => m.start())callback creditsstart. A local loader function now finds areturn import('./x')insideswitch,if,tryand loop blocks. export default xandexport = xof an imported binding now forward the import. The importers of the forwarding file now show in--traceand--impact-closure. Anexport = xnever shows as an unused export, andfallow fixleaves anexport default xforward in place (closes #3392).- A namespace that holds only types is now a type export. An unused one reports as
unused-type, not asunused-export. A namespace with a value statement, aconst enumor a merged value stays a value. - Named exports returned through higher-order import loaders stay in use. Default-only credit now needs a proven component loader import with no local shadowing.
- Vite SSR modules keep their consumed exports through TypeScript assertions.
ssrLoadModuleresults wrapped inas,satisfiesor a non-null assertion keep their bindings. Literal./src/...entry URLs resolve from the Vite root. - A re-export inside a webpack asset loader resource no longer keeps its source in use. A module that only asset loader edges load forwards nothing.
- Dependency checks are more precise. Runtime peers of production dependencies stay in production dependencies. Dependencies of a standalone nested package are now recognized under an explicit workspace list. Thanks @VariableVince for the report (closes #3324).
Class members
- Fallow credits more class member reads. A class member now counts when code reads it through:
- a namespace, such as
Discord.ApiClient.layer, also through re-export barrels, - a local subclass of a base from a namespace import,
- a typed class array field or constructor parameter. Thanks @VariableVince for the reproduction (closes #3326),
- a known array or tuple element,
- a
Mapvalue infor (const [, v] of map.entries())ormap.forEach, - an object-factory property, a callback wrapper or a Vue
computedvalue, const { a } = await factory().
- a namespace, such as
- Members that a package or the runtime calls are no longer unused. An
overridemember of a class whose base comes from a package or a global constructor counts.message,stack,causeandtoStringon a subclass ofErrorcount too.
Frameworks and plugins
- Playwright fixtures credit the page-object members that tests use. This now works for
mergeTests(...).extend(...), for.extend<T>(...)on an imported test, and for imports fromplaywright/test. A project that depends only onplaywrightand has aplaywright.config.tsorplaywright.config.jsactivates the plugin. Thanks @vethman for the report (closes #3462). - Test runner commands credit their test files.
bun test src/,deno test,node --test test/andnode --test scripts/*.test.mjsnow give test entry points. The code that they import gets the static coverage estimate, not tiernone. A reporter, a loader or a--test-coverage-includeglob gets no test role. - Nuxt projects get the right entry points. Fallow now reads
nuxt.config.mjs,.cjs,.mtsand.cts.plugins/,middleware/andmodules/count one level deep, as Nuxt scans them. So an unused helper such asplugins/foo/helper.tsnow shows. - An electron-vite renderer without an
inputkeeps its files, and three more config shapes work. An electron-vite renderer withoutbuild.rollupOptions.inputkeepssrc/renderer/index.htmland its files. The tsup and tsdown plugins read the entries of a function config. ESLint configs credit the plugins of a shared config behind anexportssubpath pattern. A Jestprojectsglob works when the project path has a[.
Vue, Pinia and CSS
ignoreExportsUsedInFilenow applies to Vue single-file components, and Pinia store members get more credit. Thanks @Smrtnyk for the reports.ignoreExportsUsedInFilenow applies to Vue single-file components, for example a type thatdefineProps<T>()uses (closes #3347).unused-store-membercredits Pinia members that a helper reads throughReturnType<typeof useCounterStore>or an alias of it, also from another file (closes #3348, #3357).- A setup store that reads returned state in its own body, such as
count.valuein acomputed, credits that member.
- CSS health credits more class and keyframe uses. A keyframe name in an
animationshorthand withvar()now counts (closes #3349). The classes of Vue<Transition>and<TransitionGroup>, such asfade-enter-active, now count (closes #3350). Thanks @Smrtnyk for the reports. fallow dupesreports clones inside an object that a call receives. An example is thepropsofdefineComponent({ ... }). A method chain is no longer filtered when an earlier call has a callback, so chained code can show new clone groups. Thanks @jleifeld for the report (closes #3491).
Audit, review brief and gates
fallow auditno longer blocks a merge of the default branch. With an auto-detected base, audit also compares with the merged default-branch commits. A finding that these commits have is inherited. An explicit--baseorFALLOW_AUDIT_BASEkeeps the single-base comparison. The diff-risk rules still read the diff against the primary base. With their defaultwarnseverity, such a merge giveswarn, notfail.fallow auditreports a new error in a changed template or regex literal as introduced. Before, audit reused the current findings as the baseline, so the audit could pass.- The audit caches behave correctly.
fallow audit --briefno longer reports every export as new public API after a run without--brief. The base-snapshot cache now removes old files and keeps the newest 32.fallow audit-cache prunereports and removes them, as entries withpass: "snapshot". - The diff-risk rules report less noise.
weakened-testsreads only JavaScript-family test files. It ignores tests that went away with the code that they name.added-suppressionskips fixture and generated directories, anddiverged-clone-siblingskips story copies. The briefweakeningsection now reads the same findings (#3368, closes #3367). - CI reports keep blocking findings visible. GitHub summaries sort errors before warnings and put introduced findings first under
new-only. The GitLab template counts styling findings like the GitHub Action (#3370).fallow ci review postresolves stale threads also when an inline comment fails to post. - The review brief labels an edited export
modified. An export isnewonly when the file did not export the name at the base. The JSONtest_reachobject addse2e_runner: truefor Playwright and Cypress projects. - The agent gate audits the work tree that a git command names. The command must have a strict form, such as
git -C <dir> commit. The generatedfallow-gate.shthen audits only the target, when git reports another work tree for it. A push from another linked worktree audits the session tree and the target, because linked worktrees share refs. Any other command audits the session tree as before. It also audits each directory that the command names when git reports another work tree for it, also through aGIT_WORK_TREEorGIT_DIRprefix. The gate blocks when one audit fails. A quoted command such asbash -c 'git commit'now also counts as a git write. diffFilein the Rust API and the Node binding places patch paths as--diff-filedoes. Foreign or ambiguous patch paths now give the full report with anot-appliedoutcome.
Health and coverage
- Test reach counts only value references. An
import typein a test no longer makes a file test-reachable. A test that uses a value export only in type positions no longer counts as test reach for it (closes #3400). - Coverage gaps and CRAP scores read suppressions and mocks. A
fallow-ignore-next-line complexitycomment now applies to the CRAP file score. File scores getcrap_suppressed,crap_unsuppressed_maxandcrap_unsuppressed_effective_threshold. The coverage-gap note names exports that only mocking tests reference. - Config and recommendations use valid keys.
fallow recommenduseshealth.maxCyclomatic,health.maxCognitiveandhealth.maxCrap. The config schema acceptsextendsas a single string. Thanks @Smrtnyk for the report (closes #3351). The schema now describeswarncorrectly.
MCP and programmatic API
- The MCP routes apply the same safety rules. Code Mode refuses an incomplete type-aware run that requires complete evidence. A typed result over 16 MiB gets
FALLOW_MCP_SUBPROCESS_OUTPUT_LIMIT. A Code Mode subprocess reads stdin from the null device. Fallow refuses a cloud repository name of only dots (.or..). - MCP calls stop on a cancel or a timeout. A client
notifications/cancellednow stops a direct tool call and acode_executesnippet. A subprocess call stops its complete process tree. - The typed and CLI routes of an MCP tool read the same settings. The routes now apply
production: false,type_aware: false, theauditconfig section and a type-aware config. When the typed route cannot apply a setting, the tool runs the CLI.trace_exportruns the CLI when type-aware analysis is on. fallow_apiand the Node bindings refuse silent mismatches. They returnFALLOW_TYPE_AWARE_CONFIG_UNSUPPORTEDwhen the config turns type-aware analysis on.run_auditreturnsFALLOW_AUDIT_BASELINE_UNSUPPORTEDfor configured audit baselines.
Caches and output
- Caches stay correct under concurrent runs and config edits. Each cache save now writes its own temp file. Eviction removes first the entries that recent runs did not discover. An edit to
package.json,deno.jsonortsconfig.jsonnow rebuilds the cached module graph. fallow fixkeeps thepackage.jsonformatting when it removes dependencies. It keeps indentation, line endings, property order and the final newline. Thanks @motlin for the report (closes #3323).- Output order and saved reports are stable.
fallow audit,fallow dupes --changed-sinceandfallow dupes --baselinenow order clone families by file set.fallow dupes, the MCP tools andfallow_apialready use this order. Fallow rejects a saved failed-run report with its original message, code, help and exit code. Legacy SARIF and CodeClimate reports keep canonical rule IDs.
Security
- A glob segment such as
.*no longer matches the parent directory. A pluginfileExistspattern such as.*/marker.jsonalso foundmarker.jsonin the parent of the project root. The same applied tofallow plugin-check, tooling config detection, and workspace globs. Fallow now rewrites a leading.in such a segment to[.]. The pattern still matches hidden files inside the project root. - Jest and Vitest
projectsentries no longer read files outside the project root. An absolute path, a..path, a glob or a symlink could lead fallow to a config file outside the root. A Vitest inline projectextendspath and a script--configsymlink had the same effect. Fallow now reads a match only when it is inside the project root. An in-root entry such as<rootDir>/../packages/*still works. fallow auditno longer follows a symlinked context file on Linux arm64. The reusable audit cache hashes context files such as lockfiles, and opens them with a flag that refuses symlinks. On Linux arm64 the code used the x86-64 value of that flag. So the open followed a symlink and hashed the target, also a target outside the project. The flag values now come from the platform headers (#3539).- Fallow and the similar-code sidecar link rustls 0.23.46. Version 0.23.45 has the advisories RUSTSEC-2026-0346, RUSTSEC-2026-0347, RUSTSEC-2026-0348 and RUSTSEC-2026-0349.
- The similar-code sidecar still links zip 8.6.0, but its advisories do not apply. RUSTSEC-2026-0367, RUSTSEC-2026-0368 and RUSTSEC-2026-0369 affect only the extraction of a ZIP archive to disk. The sidecar loads only safetensors model files and never extracts a ZIP archive. The release audit accepts the three advisories until the next candle-core release moves to zip 9.
Upgrade notes
- To keep the old match of a single
*that crossed directories, change it to**. For example, changesrc/generated/*tosrc/generated/**, and*.test.tsto**/*.test.ts. The stderr warning names each pattern to change. - To report
prop-drillingchains without an error, change"error"to"warn". - The extraction, parse, graph, duplication and churn caches change version, so the first run after the upgrade parses and rebuilds once. The audit base snapshot and mutation result caches are not reused.
- The new
resolve_hidden_couplingcategory movesschema_versionto 12 for health, 13 for audit, 14 for the combined report and 13 for the audit brief. diverged-clone-siblingisoffby default. To review every diverged copy, set it towarnorerror.- A
fallow-ignore-next-line unused-exportcomment on a types-only namespace no longer matches. Change the comment tounused-type. Baselines that list such a namespace as an unused export can change. - To run type-aware analysis through
fallow_apior the Node bindings, settypeAware.enabledin the call options. To keep a call syntactic, setFALLOW_TYPE_AWARE=0.TypeAwareOptions.enabledis now anOption<bool>. - A tool that filters the
AddTestCoveragefactor onmetric: "crap_max"must also accept"crap_unsuppressed_max". - To record the new complexity finding names, save the identity baseline again.
- A styling finding from a saved or legacy report gets a new SARIF rule ID and CodeClimate fingerprint one time. So code scanning and the merge request widget show it as new once.
- To update an installed agent gate script, run
fallow hooks install --target agentagain. RUST_LOGstill readstarget=levelandleveldirectives. It now skips span and field directives.
Full Changelog: v3.33.1...v3.34.0