Skip to content

v3.34.0: fallow mutation, segment-safe config globs, audit sub-analysis selection

Latest

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 17 Oct 16:50
· 12 commits to main since this release
Immutable release. Only release title and notes can be modified.
v3.34.0
2823b65

Changed

  • A single * in a config path glob now stays within one path segment. Only ** crosses directories. Before, src/plugins/*/index.js in entry also matched src/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 to entry and dynamicallyLoaded (#3513, closes #3490). It now applies to every config path glob (#3541):

    • ignorePatterns, ignoreFindings, overrides[].files, ignoreExports[].file and ignoreCatalogReferences[].consumer,
    • duplicates.ignore, similarCode.ignore, health.ignore and health.thresholdOverrides[].files,
    • the boundaries zone and coverage globs, and the rule-pack file globs,
    • the framework[] fields usedExports[].pattern, manifestEntries[].manifests and detection.pattern.

    To keep the old match, change the single * to **. For example, change src/generated/* to src/generated/**, and *.test.ts to **/*.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. --quiet hides 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 .gitignore can match fewer files in ignorePatterns. Globs that match package names or import specifiers, such as ignoreDependencies and ignoreUnresolvedImports, 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 to warn. Archived JSON reports with effective_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 for import.meta.glob.

    • Storybook: before, only @(..) matched. Now ../src/**/!(*.dev).stories.@(js|ts) matches, and !(*.dev) excludes the files that match *.dev.
    • Playwright: before, a testMatch glob with a group made every script below testDir an entry point. Now only the matched files are entry points.
    • Vitest: before, a test.include glob 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 example a.test.jsx and a.test.tsx for *.test.+(ts|js). An exclude glob still matches the group once 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* ...) gives Users.create, and it("renders", () => ...) gives it("renders"). A generator that a named function returns gives that name, for example load for const 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_modules for a Vitest, Jest or Rstest project glob. Since 3.33.1, a test.projects glob such as apps/**/vitest.config.ts read the full dependency store of a pnpm monorepo once per workspace. Plugin detection then took minutes. The walk now skips node_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 imports takes 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.
  • The build uses one oxc version. oxc_coverage_instrument moves to 0.14, which uses oxc 0.152 like the rest of fallow. cargo deny now fails when two versions of the core oxc crates enter the dependency graph.

  • The MCP audit tool has a fallow://tools/audit guide. The guide names the five diff-risk rules and holds the styling, coverage, baseline, grouping, runtime-coverage and FALLOW_DIFF_FILE detail. 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 mutation measures 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. --all and file paths widen or narrow it.
    • --plan selects 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 --report names. A changed file gives a stale status.
    • The runner is Vitest (>=4.1.0 <6.0.0), node:test of Node.js 22.18 or later, or bun test (>=1.4.0 <2.0.0). The default mutation.runner: auto detects it. An unsupported version exits with code 2 and FALLOW_MUTATION_RUNNER_UNSUPPORTED (#3407).
    • The node:test runner takes the module loading flags of the node --test command in the project scripts, such as --experimental-transform-types and --import (#3487). A borp script is a node:test run, 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. With node:test and bun test, the plan leaves component files out with the skip reason runner-cannot-load (#3470).
    • The plan prunes the mutants in the default export of a tool config file, such as vite.config.ts, with the reason framework-config (#3483). A mutant inside a function of the config stays.
    • The regex-literal operator removes a ^ or $ anchor, negates a character class, or swaps the case of \d, \w or \s.
    • A run refuses a dirty git worktree unless --allow-dirty is set. The plan prunes mutants near file system, process, network or database calls unless --allow-side-effects is 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-score or mutation.minScore makes 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 flaky and leaves the score. retried: true marks a status from both runs.
    • A type test (*.test-d.ts, *.spec-d.ts, or a TypeScript file below test-d/) is not a test file of the plan.
    • --typecheck or mutation.typecheck type-checks each survivor through the --type-aware companion. A survivor that adds a type error gets compile_error and leaves the score. Without a companion, the run exits with 2 before the tests start. A Vue, Svelte or Astro survivor stays survived with the skip reason unsupported-file (#3471).
    • Each no_coverage mutant has a test_reach object with the class mocked-only, none, none-file, transitive or direct.
    • 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.json extends chain. Dynamic imports that the module graph does not resolve, and files that a test reads, are not in the key. --no-cache runs 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 mutation config section sets the runner, the operators, the minimum score and the concurrency. --setup-timeout sets how long each runner process can take to start, finish its dry run or reload (default 2m).
  • fallow audit takes --only and --skip. The values are the same as for bare fallow: dead-code, architecture, dupes and health. The config keys audit.deadCode, audit.health and audit.dupes set the default selection. A skipped sub-analysis is skipped on the head and on the base, so it leaves the verdict, summary and attribution. A warn-only gate for complexity and duplication is now fallow audit --skip dead-code with the complexity rules at warn.

    • The JSON report omits the block of a skipped sub-analysis and sets summary.dead_code_skipped, complexity_skipped or duplication_skipped.
    • architecture narrows 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-root and --runtime-coverage print 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 only and skip inputs, the GitLab FALLOW_ONLY and FALLOW_SKIP variables and the MCP audit tool now apply to audit too. The GitHub step summary now has the Styling row.

    Thanks @cr101 for the request (#3381).

  • fallow audit reports risky changes as rules. Five new rules compare the base and head text of each changed file.

    • weakened-tests reports a new skip or focus marker, removed test cases, fewer assertions, or a less strict coverage threshold.
    • added-suppression reports a new lint, type-check, coverage or fallow suppression comment.
    • test-only-branch reports a new process.env.NODE_ENV === 'test' style check in source code. A Vitest in-source test block is not reported.
    • diverged-clone-sibling reports an edit to one copy of duplicated code when the other copies did not change.
    • added-dependency reports a new package.json dependency. package.json cannot hold a suppression comment, so set the rule to off for a path in an overrides entry.

    diverged-clone-sibling and added-dependency are off by default. The other three rules are warn by default. The findings are in the new diff_risk JSON section and in SARIF, CodeClimate, compact and markdown output. GitHub annotations, GitHub job summaries and the MCP audit tool 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 --brief and fallow review add a test_reach section. For each changed value export, it states direct, transitive, mocked-only, none, none-file or framework. The JSON lists at most 25 exports that need attention, with the mocking tests of a mocked-only export. 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. Each direct_references[] entry of fallow dead-code --trace FILE:NAME now has line, search_visibility (found, renamed, missed), reason and search_as. A file that only forwards the export is found with reason pass-through. --impact-closure coordination gaps have search_visibility, reason, search_as and search_symbol. fallow audit --brief adds impact_closure.search_gap, also for changed .vue, .svelte, .astro and .mdx files. The MCP trace_export and impact_closure tools return these fields too (#3371, #3372).

  • fallow health reports 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 new change_coupling section 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.changeCoupling changes the thresholds.

    • Each hotspot gets median_files_per_commit and median_lines_per_commit.
    • A strong pair adds the hidden_coupling factor to refactoring targets. A file with no other target becomes a resolve_hidden_coupling target.
    • fallow audit --brief shows a co_change_gap fact 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/v1 import accepts an optional commit field.
  • Projects that declare effect get three dead-code rules. Each rule is warn by default.

    • unused-effect-layer reports a layer that only unused layers compose.
    • unused-tagged-error reports a Data.TaggedError or Schema.TaggedError class that types or catchTag handlers reference, but that no code constructs, fails with or decodes.
    • unproduced-catch-tag reports a catchTag handler for a project tag that no code produces, when no unused-tagged-error finding 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-drilling to error to enforce chains, or to warn to 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, railway and textlint read their configs and credit the packages and scripts that tasks, commands and rules name.
    • jasmine and rstest make their spec, helper, setup and project files test entry points.
    • serverless-framework and osls make each function handler an entry point and credit each package in plugins.
    • flue makes the app, database, Cloudflare and agent modules of a Flue project entry points.
    • typescript-content-mapper runs the exec script of package.json#typescript.contentMapper as an entry point.
    • alchemy makes each alchemy.run.* file an entry point. The file that a resource names in main stays reachable, with its default export and its Durable Object, Workflow and Container classes.
    • opentelemetry credits the methods of a class that implements an SDK interface such as Sampler or SpanExporter. Other members of the class stay reportable.
  • fallow migrate recognizes more plugin sections and config keys. A section for expo-router, varlock, turbo or one of the new plugins gets the "auto-detected" message. The root keys cycles, ignoreGlobalBinaries, preprocessor, preprocessorOptions and asyncCompilers get 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 is killed only when a control run without the mutant passes. A test that fails alone, without a mutant, goes into baseline_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 a Message line with the first line of the first failure. Vitest tests now run under NODE_ENV=test, as the vitest command does.
  • Scores and limits count the right mutants. --max-mutants N now runs up to N covered mutants, and an uncovered mutant does not use the limit. A survivor whose retry times out stays survived, not flaky. The plan also prunes the config object of plugin config files such as .vitepress/config.ts and nitro.config.ts.
  • Vue components that call defineExpose no 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>/mutation and 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 and FALLOW_MUTATION_UNSAFE_CACHE_DIR. A write to an ignored file such as .env is a worktree-changed error.
  • 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.glob patterns 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.glob entries such as '!**/*.test.ts' now exclude files, as in Vite.
    • Patterns that start with ../ now resolve from the directory of the importing file. This covers import.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. An import() whose value escapes, such as register(import('./x')), now credits every export of the target. A .then((m) => m.start()) callback credits start. A local loader function now finds a return import('./x') inside switch, if, try and loop blocks.
  • export default x and export = x of an imported binding now forward the import. The importers of the forwarding file now show in --trace and --impact-closure. An export = x never shows as an unused export, and fallow fix leaves an export default x forward in place (closes #3392).
  • A namespace that holds only types is now a type export. An unused one reports as unused-type, not as unused-export. A namespace with a value statement, a const enum or 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. ssrLoadModule results wrapped in as, satisfies or 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 Map value in for (const [, v] of map.entries()) or map.forEach,
    • an object-factory property, a callback wrapper or a Vue computed value,
    • const { a } = await factory().
  • Members that a package or the runtime calls are no longer unused. An override member of a class whose base comes from a package or a global constructor counts. message, stack, cause and toString on a subclass of Error count 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 from playwright/test. A project that depends only on playwright and has a playwright.config.ts or playwright.config.js activates the plugin. Thanks @vethman for the report (closes #3462).
  • Test runner commands credit their test files. bun test src/, deno test, node --test test/ and node --test scripts/*.test.mjs now give test entry points. The code that they import gets the static coverage estimate, not tier none. A reporter, a loader or a --test-coverage-include glob gets no test role.
  • Nuxt projects get the right entry points. Fallow now reads nuxt.config.mjs, .cjs, .mts and .cts. plugins/, middleware/ and modules/ count one level deep, as Nuxt scans them. So an unused helper such as plugins/foo/helper.ts now shows.
  • An electron-vite renderer without an input keeps its files, and three more config shapes work. An electron-vite renderer without build.rollupOptions.input keeps src/renderer/index.html and its files. The tsup and tsdown plugins read the entries of a function config. ESLint configs credit the plugins of a shared config behind an exports subpath pattern. A Jest projects glob works when the project path has a [.

Vue, Pinia and CSS

  • ignoreExportsUsedInFile now applies to Vue single-file components, and Pinia store members get more credit. Thanks @Smrtnyk for the reports.
    • ignoreExportsUsedInFile now applies to Vue single-file components, for example a type that defineProps<T>() uses (closes #3347).
    • unused-store-member credits Pinia members that a helper reads through ReturnType<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.value in a computed, credits that member.
  • CSS health credits more class and keyframe uses. A keyframe name in an animation shorthand with var() now counts (closes #3349). The classes of Vue <Transition> and <TransitionGroup>, such as fade-enter-active, now count (closes #3350). Thanks @Smrtnyk for the reports.
  • fallow dupes reports clones inside an object that a call receives. An example is the props of defineComponent({ ... }). 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 audit no 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 --base or FALLOW_AUDIT_BASE keeps the single-base comparison. The diff-risk rules still read the diff against the primary base. With their default warn severity, such a merge gives warn, not fail.
  • fallow audit reports 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 --brief no 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 prune reports and removes them, as entries with pass: "snapshot".
  • The diff-risk rules report less noise. weakened-tests reads only JavaScript-family test files. It ignores tests that went away with the code that they name. added-suppression skips fixture and generated directories, and diverged-clone-sibling skips story copies. The brief weakening section 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 post resolves stale threads also when an inline comment fails to post.
  • The review brief labels an edited export modified. An export is new only when the file did not export the name at the base. The JSON test_reach object adds e2e_runner: true for 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 generated fallow-gate.sh then 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 a GIT_WORK_TREE or GIT_DIR prefix. The gate blocks when one audit fails. A quoted command such as bash -c 'git commit' now also counts as a git write.
  • diffFile in the Rust API and the Node binding places patch paths as --diff-file does. Foreign or ambiguous patch paths now give the full report with a not-applied outcome.

Health and coverage

  • Test reach counts only value references. An import type in 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 complexity comment now applies to the CRAP file score. File scores get crap_suppressed, crap_unsuppressed_max and crap_unsuppressed_effective_threshold. The coverage-gap note names exports that only mocking tests reference.
  • Config and recommendations use valid keys. fallow recommend uses health.maxCyclomatic, health.maxCognitive and health.maxCrap. The config schema accepts extends as a single string. Thanks @Smrtnyk for the report (closes #3351). The schema now describes warn correctly.

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/cancelled now stops a direct tool call and a code_execute snippet. 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, the audit config section and a type-aware config. When the typed route cannot apply a setting, the tool runs the CLI. trace_export runs the CLI when type-aware analysis is on.
  • fallow_api and the Node bindings refuse silent mismatches. They return FALLOW_TYPE_AWARE_CONFIG_UNSUPPORTED when the config turns type-aware analysis on. run_audit returns FALLOW_AUDIT_BASELINE_UNSUPPORTED for 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.json or tsconfig.json now rebuilds the cached module graph.
  • fallow fix keeps the package.json formatting 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-since and fallow dupes --baseline now order clone families by file set. fallow dupes, the MCP tools and fallow_api already 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 plugin fileExists pattern such as .*/marker.json also found marker.json in the parent of the project root. The same applied to fallow 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 projects entries 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 project extends path and a script --config symlink 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 audit no 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, change src/generated/* to src/generated/**, and *.test.ts to **/*.test.ts. The stderr warning names each pattern to change.
  • To report prop-drilling chains 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_coupling category moves schema_version to 12 for health, 13 for audit, 14 for the combined report and 13 for the audit brief.
  • diverged-clone-sibling is off by default. To review every diverged copy, set it to warn or error.
  • A fallow-ignore-next-line unused-export comment on a types-only namespace no longer matches. Change the comment to unused-type. Baselines that list such a namespace as an unused export can change.
  • To run type-aware analysis through fallow_api or the Node bindings, set typeAware.enabled in the call options. To keep a call syntactic, set FALLOW_TYPE_AWARE=0. TypeAwareOptions.enabled is now an Option<bool>.
  • A tool that filters the AddTestCoverage factor on metric: "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 agent again.
  • RUST_LOG still reads target=level and level directives. It now skips span and field directives.

Full Changelog: v3.33.1...v3.34.0