Skip to content

Releases: fallow-rs/fallow

v3.33.1: health and duplication baselines in CI, Vitest fixes

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 10 Oct 06:13
Immutable release. Only release title and notes can be modified.
v3.33.1
fccff88

Bug fixes

  • The GitHub Action and the GitLab template can baseline health and duplication on the default run. A project with existing complexity can now gate the default fallow run with a baseline. Set the health-baseline and dupes-baseline inputs of the action, or FALLOW_HEALTH_BASELINE and FALLOW_DUPES_BASELINE in the GitLab template. Create the files with fallow health --save-baseline and fallow dupes --save-baseline. Other commands ignore these inputs. Both integrations report a stale health or duplication baseline, or one that another command saved. fail-on-stale-baseline (FALLOW_FAIL_ON_STALE_BASELINE) judges these baselines too. It also judges a --health-baseline or --dupes-baseline flag that you pass through args (FALLOW_ARGS). (#3316)
  • The action and the GitLab template no longer count health findings that a baseline accepts. With a health baseline, the CLI passed, but the action and the GitLab template failed the job with fail-on-issues. A fallow health run also failed the job. The count now uses the findings that remain after the baseline. (#3316)
  • The job summary counts health findings after the baseline. With a loaded health baseline, fallow report --format github-summary shows the new findings and gives the accepted ones as context, for example "2 new functions exceed thresholds (74 in the baseline)". Without a baseline, the summary does not change. (#3316)
  • fallow --help names the baseline flags of the bare run. On bare fallow, --baseline and --save-baseline hold the dead-code baseline only. When a bare run gets a fallow health or fallow dupes baseline through --baseline, fallow now prints a note that tells you to use --health-baseline or --dupes-baseline. (#3316)
  • The Vitest JSX import source follows the Vitest project model. On Vitest 5, an inline test.projects entry inherits the JSX import source of its config unless it sets extends: false. On Vitest 4, it inherits only with extends: true. Fallow reads the Vitest version from node_modules/vitest, else from the declared range. Project and base include globs add up. Fallow also reads a config that extends names. It reads project configs with a custom name, such as vitest.e2e.config.ts, also when a glob entry names them. Fallow no longer reports these files as unused. (#3318)
  • Fallow reads test.exclude for the Vitest JSX import source. When test.exclude or a negated test.include entry removes a test file, Fallow no longer applies the JSX runtime of its project to that file. Without test.exclude, the Vitest default exclude applies. The globs are relative to test.dir, test.root or the Vite root when the config sets one. (#3318)
  • react is no longer an unused dependency when Vitest test files use JSX with the default runtime. When a Vitest config sets no JSX import source, Vite imports the react JSX runtime. Fallow now credits react when a test file of that config has JSX and no @jsxImportSource pragma. A TypeScript test file gives no credit when its tsconfig sets jsxImportSource, or a jsx mode without the automatic runtime. The credit applies only to the workspace of the test file. A vite.config.* next to a vitest.config.* gives no credit, because Vitest does not load it. (#3318)
  • The source-map warning of fallow coverage upload-source-maps tries the same paths as Fallow Cloud. The CLI dropped the first segments of a source such as webpack:///@scope/app/./src/x.ts, and the cloud did not. The warning could then check a path that the cloud never uses. A shared fixture of path cases now keeps the two implementations equal. (#3320)

Full Changelog: v3.33.0...v3.33.1

v3.33.0: fallow architecture, baseline pruning, faster musl binaries

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 09 Oct 15:18
Immutable release. Only release title and notes can be modified.
v3.33.0
278dbc6

Features

  • fallow architecture reports import cycles, boundary violations and rule-pack policy violations in one command. Use --cycles, --boundaries or --policy to select one kind. The command takes the same scope and output flags as fallow dead-code. Run fallow guard <files> before an edit and fallow architecture after it. Bare fallow shows these findings in an "Architecture" category after the other dead-code categories. It also shows the category with --quiet, --group-by and --summary. With --group-by, fallow architecture shows one "Architecture" heading per group.
  • fallow architecture --format json writes kind: "architecture" with its own schema_version (1). With --group-by, the kind is architecture-grouped. The arrays, finding ids, actions, exit codes, gate outcomes and severities are the same as on fallow dead-code. next_steps names fallow architecture. With --explain, _meta.docs points to the architecture page, and _meta.rules lists only the architecture rules. --save-baseline writes a dead-code baseline (kind: "dead-code"), so the two commands read each other's baselines. The JSON schema and the generated TypeScript types have the ArchitectureOutput and ArchitectureGroupedOutput types.
  • fallow report, MCP, the Node bindings and CI accept the architecture report.
    • fallow report --from renders a saved architecture or architecture-grouped file in every format, the same as the direct run. PR comments and reviews of fallow architecture have the title "Fallow architecture report".
    • The MCP server has a check_architecture tool, and Code Mode exposes it as checkArchitecture. The Node bindings have detectArchitecture, and fallow_api has run_architecture. Each returns the architecture output. detectCircularDependencies, detectBoundaryViolations and the MCP analyze tool keep the dead-code output.
    • Bare fallow accepts architecture in --only and --skip. --only architecture runs the dead-code analysis and reports only the architecture findings, and its Failed: line names architecture. --skip architecture removes these findings from the dead-code section. The health score and the --save-baseline file do not change.
    • The GitHub Action accepts command: architecture, and the GitLab template accepts FALLOW_COMMAND: architecture. For this command, issue-types and FALLOW_ISSUE_TYPES take cycles, boundaries and policy. Another value stops the job with exit code 2 and an error that names the valid values.
    • Telemetry records fallow architecture runs as the architecture workflow.
  • fallow baselines prune removes the baseline entries of fixed findings. The command reads the files that audit.deadCodeBaseline, audit.healthBaseline and audit.dupesBaseline name. It runs one whole-project analysis and removes each entry that --baseline no longer matches. It never adds an entry, so a new finding stays visible to the gates. After a prune, --fail-on-stale-baseline passes, and the baseline hides the same findings as before. --check writes nothing and exits 1 when an entry can be pruned. --dead-code-baseline, --health-baseline and --dupes-baseline override the config paths. --coverage gives the coverage input for CRAP scores. The command skips a file with an older key form or with fields from a newer version. It prints the command that saves that file again. The stale-baseline warning and the gate message now name the prune command. Thanks @BenMcGit for the request (#3281).
  • fallow hooks install --target git --prune-baselines prunes the baselines in the pre-commit hook. The hook runs fallow baselines prune before the audit and stages the baseline files that the prune wrote. The prune reads the working tree, but the commit uses the index. So the hook skips the prune when the working tree has unstaged or untracked changes. It also skips the prune for a commit with paths (git commit <path>). A failed prune never blocks the commit. The hints for Lefthook and for an existing hook include the same step.
  • fallow trace --dependency <package> reports how the code uses each imported name of a package. The report gives file and call counts, and sites with line and column. It follows one hop through project wrappers such as useSelector.withTypes(), and it counts each use that it cannot resolve. --sites, --specifier, --limit and --cursor page the sites. --callers --depth N adds the files that import the users. The MCP tool trace_dependency accepts the same options.
  • A JSX import source in a Vitest config now adds a module graph edge. With the automatic JSX runtime, oxc.jsx.importSource (or the older esbuild.jsxImportSource) makes each test file with JSX import <source>/jsx-dev-runtime. Fallow now adds this edge when the source is a path and the test file has JSX and no @jsxImportSource pragma. A package source only marks the package as used. A local JSX runtime no longer shows as unused, and --impact-closure on a runtime file now includes these test files.
    • The edge applies to the files that test.include matches. Fallow uses the root config when it has no test.projects, else each inline project config.
    • A project inherits the root source only with extends: true. The classic runtime adds no edge.
    • A relative source resolves from the test file first, then from the config directory. A source that does not resolve adds no edge and no unresolved import.

Changed

  • The Linux musl binaries are about 4 times faster. The malloc of musl is slow when many threads allocate, and fallow analyzes files in parallel. The musl binaries now use mimalloc. This applies to the linux-x64-musl and linux-arm64-musl npm packages and the musl release assets. On a 10-core aarch64 Alpine container, check, dupes and health took 77% less time over 15 fixture cases, with identical output. Peak memory is about a third higher. The Node addon keeps the default allocator. Glibc, macOS and Windows builds do not change.
  • Fallow hides the findings in an unused file by default. Fallow no longer lists an unused export, type, class member or enum member in a file that it reports as unused. Deleting the file removes these findings too. The JSON output has the count in cascade_hidden, and SARIF and markdown (grouped or not) show the count too. To list the findings, pass --show-cascade, set showCascade in the config, or use the show_cascade parameter of the MCP tools. The health vital signs still count them. When an issue-type filter leaves out unused-files, nothing is hidden.
  • fallow viz shows where to start and says what is wrong.
    • The Overview panel opens with the health grade and one card per lens. Each card shows the number of files, the number of high findings, and the worst file with its reason. The Overview map colors files that have findings in any lens. Folder labels show how many of their files have findings.
    • Each lens list opens with a short summary and a severity split. The list groups rows into high, medium and low. The full file name comes first, then a plain reason. For example, "File path built from input" replaces path-traversal, and "High complexity, no test coverage" replaces a CRAP score. Security lists each file once with all of its candidates.
    • Folders with no imports to or from other folders now show when the active lens has findings in them, for example unused files. Health colors only files above the review threshold, so high-risk files stand out. Folders without findings fade.
    • The Overview tab of the file panel opens with a list of the lenses that have findings in the file, each with its reason. The Health tab shows maintainability, change risk and importers. Then it lists each function to fix with its branches, lines, test coverage and next step. The treemap rings and names the selected file.
    • Folder and import-group labels have two lines (the name, then the size and findings). The labels stay over their cluster and keep clear of the controls and the legend. Import groups that span folders read as site/src + 5 more instead of (mixed). The status line shows the grouping or the path of the file in focus. The map hides disabled controls.
    • A click on a line between two folders lists the target files in use. Then it lists every import, with forbidden and cyclic imports first.
    • Hover tooltips use the same plain reasons as the panel. During a search, the legend explains the match and importer rings.
    • Below 700 px, the panel docks under the map as a sheet, so the map stays visible at full width. The legend becomes one line, crowded labels drop, and the file focus view fits both columns.
    • The map now follows the design of fallow.tools and fallow.cloud: paper and ink, Barlow type, hairline rules, square panels and flat controls. Day paper is the default. The night theme follows the system or the stored choice. The report embeds the fonts, so it looks the same offline.
    • On the graph, folders are octagons, the imports between them run at 0, 45 and 90 degrees, and flagged files have a ring. When you hover over an import line, the map picks the line under the pointer, gives it a pale blue halo and fades the others. Lines that are hidden at the current zoom no longer react. A hover over a file draws its imports the same way, in ink.
    • With a file open, the panel shows its place in the active lens ("3 of 61 in Security") with previous and next buttons. The j and k keys do the same.
    • High findings, sparse medium findings, and the copies of an open duplicated block get a halo on the graph.
    • The Architecture panel names the folders in an import loop that the map outlines, and says why the loop matters.
    • Folders with high findings get a label first. Labels no longer cover the dots of high findings. A label that had to mo...
Read more

v3.32.0: grouped health trends, markdown from saved runs, dead-code accuracy fixes

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 07 Oct 05:15
Immutable release. Only release title and notes can be modified.
v3.32.0
aaaec79

Features

  • fallow dead-code --absent-component-props reports optional props that callers never pass. The rule reports an optional component prop that no known reachable caller passes. It is off by default. It supports React, Preact, Solid, Qwik, Vue, Svelte, Astro, Angular, Lit and Ember components. Each finding shows the declaration, the callers, the framework and whether a default exists. Fallow gives no finding when it cannot see every caller of the component. Static analysis does not prove that a prop is unreachable at runtime. Review defaults and API intent manually. The rule has no automatic fix.
  • fallow health --group-by selects groups and tracks a trend per group. --group <KEY> keeps only the matching groups. It accepts exact keys, globs and ! negations. Each group gets severity and hotspot counts. --save-snapshot on a grouped run stores the group data (snapshot schema v11). --trend or the new --trend-from <PATH> then adds a trend per group. Markdown and the GitHub job summary get a ## Health by <mode> table and one findings block per group. --top N now applies to each group. The MCP check_health tool accepts group and trend_from.
  • fallow report --from <file> --format markdown renders saved JSON output. A saved dead-code, dupes, health or bare combined run gives the same markdown as the live run with the same flags. You can analyze once and write both a job summary and a markdown report from one JSON file. The saved output of a combined run does not record --group-by, so its markdown is not grouped. A saved audit, security or fix run, and a combined run with type-aware evidence, exit 2 with a reason.
  • fallow dupes marks symlinked clone instances. A clone instance under a symlink now has is_symlink: true in JSON, symlink=true in compact output and a (symlink) marker in human output. To report only duplication between real files, set duplicates.ignoreSymlinks: true or pass --ignore-symlinks. --no-ignore-symlinks overrides the config value. The MCP tools, the Node API, the LSP and the VS Code setting fallow.duplication.ignoreSymlinks accept the same option. Thanks @lzear for the report (#2961).
  • fallow agent install adds a native commit gate for Codex. The install writes a PreToolUse handler to .codex/hooks.json and the gate script to .codex/hooks/fallow-gate.sh. The handler runs the audit from the nearest install root, also from a subdirectory. The Claude Code handler now also finds the nearest install root. Run fallow agent install again to get the new handler. Codex runs a hook only after you trust it in /hooks. fallow agent status shows the gate as its own row, and fallow hooks status --format json adds a codex_gate entry. The AGENTS.md block is now routing guidance. fallow hooks install --target agent --agent codex --user now writes the user gate and no longer writes the project AGENTS.md block.
  • The new fallow-setup skill tells an agent how to add code-quality tools. The skill is for a JavaScript or TypeScript project. It tells the agent to detect the existing tools, run fallow recommend, install Fallow as a dev dependency and add a CI gate with a baseline. It gives each tool one job, and it removes a tool only after a parity check. fallow agent install writes it next to the fallow skill.
  • The gdp-proof-producer rule-pack kind restricts proof producers. The opt-in rule restricts @gdp-ts/core.defineProof calls to allowedFiles. proofKinds assigns literal proof labels to owner modules. The analysis follows static imports and unambiguous re-export chains.
  • Fallow has new built-in plugins.
    • release-it keeps each .release-it.* config form and reads the package.json key. It does not read the JSON5, YAML and TOML forms. It credits the plugins and extends packages.
    • eve makes agent modules entry points.
    • ag-ui credits the hooks of a class that extends AbstractAgent directly.
    • nestjs-trpc credits the methods that the module calls.
    • kibana reads kibana.jsonc plugin manifests and adds their entry points.
  • The health JSON names the sections that the run produced. The root array sections lists each section that the run computed. With --baseline, summary.baseline_staleness.remaining_findings gives the number of functions above a threshold that the baseline does not accept.
  • The CLI, MCP and the LSP report the same finding_id for a security finding. The VS Code "Copy Fallow finding id" quick fix now works on a security candidate (#3035).
  • Fallow can suggest its Claude Code plugin. Inside a Claude Code session, Fallow writes one hint line to stderr for human output. Fallow writes the hint only without --quiet, outside CI, and when no Fallow plugin or skill is installed. The hint has no effect until the official marketplace lists the Fallow plugin. Set FALLOW_CLAUDE_CODE_HINT=off or FALLOW_SUGGESTIONS=off to suppress it.

Changed

  • Tooling dependencies need evidence of use. Three kinds of devDependency no longer count as used by name alone:

    • A command-line tool from the tooling catalogue.
    • A tooling package of an active plugin.
    • A @types/X package.

    A tool counts as used when a script, a CI workflow or a git hook runs it. It also counts when its config file or its package.json key exists, or when a plugin credits it. Fallow now reads git hooks in .husky/, lefthook, simple-git-hooks and lint-staged configs. A @types/X package counts as used when the project declares or imports X, or names X in tsconfig types. A short list of global type packages, such as @types/node, always counts. --trace-dependency names each credit. Production dependencies still count as used by name.

  • Root dependencies follow the nearest manifest. Each import now counts for the nearest manifest that installs the package. Fallow reports a root declaration as unused when each import uses a workspace declaration. The finding has a declared_and_imported_in field that names those workspaces. Such an entry gets one finding, not a second test-only or type-only finding.

  • export default Name links to the local binding Name. Fallow now reports unused members of a class that a file exports this way. With ignoreExportsUsedInFile, such a default export is used when the file uses Name.

  • fallow health --group-by counts a clone in each group that holds an instance. The group duplicated_lines values now sum to the project value. Group scores can drop once after the upgrade.

  • CODEOWNERS matching follows GitHub. A pattern without a trailing / owns the directory contents. * does not match /, apps/ matches an apps directory at any depth, and /* owns only the files at the root. Owner groups can change.

  • Gate rows show a count. The health-findings and error-severity-findings entries in gate_outcomes now have observed and threshold_label. The PR decision and the PR comment show the count, for example 6 at or above error.

  • fallow hooks install --target agent selects Codex from a .codex/ directory, not from AGENTS.md alone.

Bug fixes

Dependencies

  • Fallow no longer reports an optional peer of a used dependency as unused. Fallow also no longer reports a devDependency that the same manifest lists as a peer. An optional peer that the project does not list no longer credits its own peers.
  • A peer-only entry no longer hides the ancestor workspace that installs a package. A workspace file can use a package that an ancestor manifest declares. This does not apply to production code in a publishable workspace. An ancestor declaration counts as used when a nested workspace uses it.
  • A postcss.config.* file credits the PostCSS plugins that it names, also without a declared postcss package.
  • A script binary credits every package that can provide it, for example run-p for npm-run-all2.
  • Fallow credits a runtime loader that a tool config needs, such as ts-node for a TypeScript Jest config. The Mocha plugin reads .mocharc files and the mocha key.
  • A tool config file in a workspace package credits the root devDependency of that tool.
  • require.resolve and import.meta.resolve with any subpath credit the package. require.resolve('pkg') reports an unlisted package. Thanks @DrJKL for the report (#3213).
  • import {} from 'pkg', a module augmentation declare module 'pkg', and a node_modules/.bin/<name> path in source credit the package.
  • Fallow credits Expo config plugins in the app config, Oxlint jsPlugins inside overrides, and packages in Starlight customCss.
  • React Router apps without entry.server no longer report isbot as unused.
  • An esbuild call in a test file no longer removes the credit that a dependent workspace gets for the packages of a private sibling. Fallow removes that credit only when a build file or a package script sets every package external.
  • --trace-dependency gives the same credits as the dependency report. It names each manifest that the report flags.

Entry points and plugins

  • The tsdown and tsup plugins read JSON configs, the package.json key and string or object entry values. They also read config arrays, configs in workspace packages and tsdown workspace globs. Thanks @moltar for the report (#3209).
  • Playwright reads every defineConfig argument and the static testDir and testMatch settings.
  • These files are now entry points: the test files of a bare node --test script, the tests of a vitest config that a script passes with --config, a file that a command substitution runs, and a file that a CI step runs with Bun.
  • The Vite root option moves the default entries. An electron-vite config sets the main and preload entries. Default electron-builder script configs stay used.
  • Vercel functions in `a...
Read more

v3.31.0: per-package changedSince baselines, package cycles, stable dead-code finding ids, shrink-only baselines

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 30 Sep 17:23
Immutable release. Only release title and notes can be modified.
v3.31.0
71369f8

Features

  • Per-package changedSince baselines for monorepos. Map a workspace root to its own Git ref, for example "workspaces": { "changedSince": { "packages/web": "main", "packages/legacy": "release/2024.10" } }. For a mapped package, check, dead-code, dupes and the editor report findings only in files that changed since its ref. A combined run applies the map to those sections too. Unlisted packages and root files stay in full scope. A global --changed-since replaces the map for one run. audit, health and security ignore the map. Write each key as fallow list --workspaces prints it. JSON reports list the applied refs in package_baselines, and the LSP sends the same rows as packageBaselines. request_outcomes gets the entry package-baselines, with the value applied or not-applied. The value is not-applied when a key names no workspace or Git cannot resolve a ref. The run then reports every package in full scope and prints a warning. A malformed key or ref exits with code 2. To disable the map, use --no-package-baselines for one run or FALLOW_PACKAGE_BASELINES=false for every run of a process. In the editor, use the VS Code setting fallow.packageBaselines. The Node API option noPackageBaselines and the MCP parameter no_package_baselines of analyze and find_dupes do the same. When you save a dead-code baseline under the map, Fallow prints a warning that the file is partial. The baseline records scope_reasons, and a later run without that narrowing warns before it compares. A run that the map narrowed reports package-baselines in baseline_staleness.scope_reasons. The GitHub Action and the GitLab template then add --no-package-baselines to their baseline re-read. The recheck-baseline next step also includes the flag. Thanks @M-Hassan-Raza for the contribution (#2969).
  • package-cycle reports dependency cycles between workspace packages. Each workspace package is a node, and each resolved import from one package into another is an edge. The check finds a cycle such as @repro/a -> @repro/b -> @repro/a also when the files form no file-level cycle. Packages in such a cycle cannot build in dependency order. Each finding in package_cycles lists packages and package_roots in cycle order, and one example import per hop in edges. The example is the first runtime import of the hop, or else the first type-only import. Type-only imports are edges, because declaration builds still need an order. A hop with only type-only imports has type_only: true. Declared package.json dependencies are not edges, and imports from test, spec, story, fixture and tooling config files do not count either. The rule is package-cycle (alias package-cycles), and the default is warn. --package-cycles shows only this finding. // fallow-ignore-next-line package-cycle removes one import or re-export statement from the package graph. // fallow-ignore-file package-cycle, or a per-file override to off, removes every import of that file. A cycle goes away when you remove every import on one hop. When two packages share a name, the label is name (root). The first entry of packages is the label that sorts first, so the baseline keys stay stable. A group of connected packages lists at most 20 cycles, or fewer on a very dense package graph. Each cycle in such a group has group_truncated: true, and every output format shows a note. --group-by, workspace scope, per-file severity, --changed-since and diff scope use the file of the example import. The circular-dependencies check does not change. The MCP analyze tool accepts issue_types: ["package-cycles"]. The new fallow://tools/analyze guide explains each group_by mode. Thanks @azu for the report (#2955).
  • Dead-code findings have a stable finding_id in JSON, LSP, MCP and SARIF output. Each dead-code finding, stale suppressions included, gets an id such as dc1:unused-export:81a349a3b9ea3b15. The id comes from the rule, the root-relative path and the symbol name. The line and the column are not inputs, so the id stays the same when you add lines, reformat a file or reorder declarations. A rename gives a new id. A second finding of one type with the same subject gets the suffix ~1. Workspace scope, --changed-since, ignoreFindings and baselines do not change the id of a finding that stays in the report. The field is optional in the JSON schema, so schema_version does not change. LSP dead-code diagnostics add the id as data.findingId. Security diagnostics do not have the key yet. The VS Code extension adds the quick fix "Copy Fallow finding id". The MCP analyze and check_changed descriptions name finding_id. They also say that an id absent from a scoped or differently configured run means unknown. The typed path, the CLI fallback and the Node bindings return the same ids. The per-flag detail of analyze moved into the fallow://tools/analyze guide resource. SARIF dead-code results add fallowFinding/v1 to partialFingerprints. tools.fallow.fingerprint/v1 and primaryLocationLineHash/v1 do not change, so GitHub code scanning keeps each open alert. Unlisted-dependency and duplicate-export results give one result per location and do not get the key. fallow report --from on an older report gives no key. Security SARIF keeps fallowSecurity/v2.
  • fallow dead-code --finding-id <id> reports only the findings you ask for. Repeat the flag or pass a comma-separated list. The filter runs after every other filter and after the baseline. JSON adds finding_id_query with requested, found, missing, filtered, conclusive and inconclusive_reasons. When conclusive is true, a missing id means that the finding is fixed, suppressed or ignored by config. These options make the answer not conclusive: a scope, --changed-since, a workspace, --file and an issue-type filter. Production mode, includeEntryExports, a baseline and a rule set to off do the same. filtered lists requested findings that still exist but that a filter removed. The answer also has analysis_fingerprint, a hash of the version, the config, the plugins, the detection options and the ignore files. The hash also covers the manifests, the tsconfig and jsconfig files and the plugin config files. Store the fingerprint with your decision. When a later fingerprint differs, treat a missing id as unknown. A malformed id exits with code 2. The MCP analyze tool (finding_ids), DeadCodeOptions::finding_ids and the Node bindings (findingIds) take the same option.
  • --fail-on-baseline-growth makes a committed baseline shrink-only. Before, a change could add a finding and save the baseline again in the same commit. --baseline and --fail-on-stale-baseline then passed. The new gate compares each loaded baseline with the same file at a base ref. It exits 1 when the baseline has a key that the base file does not have. It lists each new key per category on stderr. A renamed file gives a new key. In a dead-code baseline with line-free keys, one more occurrence of a key counts as growth, and a moved line does not. --baseline-base <ref> sets the base ref. Without it, the gate uses the fallow audit base: --changed-since or --base, then FALLOW_AUDIT_BASE, then the merge-base with the upstream or the remote default branch. A baseline that the base ref does not have is new, and the gate passes with a note. When Git cannot resolve the base ref, the gate exits 2, and the message names git fetch and fetch-depth: 0. The gate also exits 2 when the default base resolves to HEAD. In CI, pass --baseline-base origin/main. The gate applies to the dead-code, dupes and health baselines and to the three baselines of the bare run. It also applies to the fallow audit flags --dead-code-baseline, --health-baseline and --dupes-baseline. The result is in gate_outcomes["baseline-growth"], where observed is the number of new keys. health --report-only and the review brief do not run the gate and say so on stderr. A command that loads no baseline rejects the flags. Thanks @tmak for the report (#2938).
  • circularDependencies.ignoreLazyImports removes lazy edges from cycle detection. The option is off by default. When the option is on, cycle detection skips four kinds of lazy edge. These are an import() inside a function, a template import(), a lazy import.meta.glob and a worker URL. An edge that also has a static import stays. Fallow removes lazy edges before it counts the cycles of a group. Lazy cycles then can no longer fill the limit of 20 cycles and hide a static cycle. A top-level await import('./x') now loads eagerly in the module graph and counts in the startup import weight. A top-level await import() stays lazy in a Vue <script setup> block, a Svelte instance script or an Astro frontmatter. That code runs for each component instance. Thanks @tmak for the report (#2936).
  • A ! entry in ignorePatterns restores files that discovery skips. Before, no config could restore source in a directory that a built-in ignore matches, or in a hidden directory. Every detector skipped these files. "!src/policy/coverage/**" now overrides the built-in **/coverage/** for that subtree only. A top-level coverage/ output stays excluded. "!.config/**" adds the hidden directory .config to discovery, with only the files that an exception matches. Fallow applies the built-in defaults first, then your patterns, then the ! exceptions. A ! entry that names node_modules or .git is a config error (exit 2). The skipped-source-dotdir and excluded-by-default-ignore messages and the --explain-skipped note name the ! form as the fix. Thanks @tmak for the report (#2940, #2452).
  • ignoreDependencies accepts globs. An...
Read more

v3.30.0: flag retirement report, startup import weight, faster language server, Oxc 0.151

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 26 Sep 22:28
Immutable release. Only release title and notes can be modified.
v3.30.0
2b0f0b7

Features

  • fallow flags --retirement reports the flags that you can retire. Each row groups every site of one flag and lists the reasons to remove it. The reasons are single-read-site, test-only, literal-constant, identical-branches, empty-branch, guards-dead-code and defined-never-read. The literal-constant reason adds the new kind constant for a flag-prefixed module-level const with a literal value, such as const FEATURE_NEW_UI = true. A guard in the same module must test the flag. These flags are in the retirement report only, not in feature_flags[]. The defined-never-read reason also covers a Vercel flag() definition with an unused export, and an unused member of a flag registry enum. The reasons come from the code only, so they do not show the production state of the flag.
  • The retirement report works in every fallow flags format and gives each flag an age from git. JSON adds a top-level retirement object, and human and markdown output add a "Retirement candidates" section. Compact adds flag-retire: lines, SARIF adds the rule fallow/flag-retirement-candidate at level note, and CodeClimate adds fallow/flag-retirement issues. --flag-age blame (the default), --flag-age pickaxe and --flag-age off set how Fallow measures the age. --reason, --min-age, --sort age|sites|name and --top filter and order the rows. Fallow removes no code. Every action has auto_fixable: false. fallow explain flag-retirement describes the rule.
  • fallow flags --retirement --flag-state <FILE> compares the code with a vendor flag export. The file is a local JSON file with one vendor-neutral schema, and Fallow reads it offline without credentials or network calls. The export adds the reasons fully-rolled-out, archived-in-vendor, missing-in-vendor and vendor-only. The vendor-only rows do not count in summary.distinct_flags. The new flags.vendorKeyPrefix config key removes a prefix from each vendor key before the match. An invalid file, or a file larger than 16 MiB, exits with code 2 and the error code FALLOW_FLAG_STATE_INVALID.
  • fallow flags --retirement can gate CI on flag counts and flag age. --save-regression-baseline <PATH> writes a baseline with a new flags section. --fail-on-regression --regression-baseline <PATH> exits with code 1 when distinct_flags grows more than --tolerance. --max-flag-age <DAYS> exits with code 1 when a flag in scope is older than that limit. Each gate reports a status of pass, exceeded or skipped, and prints the result to stderr in every format. A run with --changed-since or --workspace skips the regression gate and saves no baseline. Without --retirement, the regression options have no effect on fallow flags, and the command prints a warning.
  • The MCP feature_flags tool and the Rust API can return the retirement report. The MCP tool adds the retirement, flag_state and flag_age parameters. FeatureFlagsOptions adds a retirement field. The CLI and the API build the report with the same engine function, so the retirement block is the same on both. The regression gates stay CLI only.
  • fallow flags finds more flag reads. The scan now reports import.meta.env.X reads, and SDK calls with a registry member as the name, such as useFlag(FLAGS.NewCheckout). It also finds flag reads inside a larger if or ternary test, such as if (process.env.FEATURE_X === 'true'). Flags in .js files with JSX now match custom sdkPatterns and envPrefixes. More flag reads now have a guard, so dead_code_overlap can find more unused exports.
  • fallow list --entry-weight reports the startup import weight of each runtime entry point. The report counts the project modules and the source bytes that load before the entry runs. It splits the modules into eager, deferred and out of thread, and lists the packages on the startup path. The unit is source bytes on disk, so the value is not a bundle size. The output is human or JSON (entry_weight in fallow list --format json). The health score does not change.
  • An opt-in regression gate for the startup import weight. fallow list --entry-weight --save-regression-baseline <PATH> writes the eager bytes, modules and packages of each entry into the baseline file. A later run with --regression-baseline <PATH> reports the change of each entry and the new eager packages. Add --fail-on-regression to exit 1 when an entry grows more than --tolerance. A new entry never fails the gate. fallow dead-code --save-regression-baseline <PATH> now keeps the entry weights in the same file.
  • fallow trace --path and fallow viz show dynamic imports. Each hop in the trace JSON has a new dynamic field, and the human output tags such a hop [dynamic]. With --eager-only, the trace follows static value imports only. A new edge flag bit (2) marks a lazy edge. The focus view draws it with a short dash.
  • Built-in Waku plugin. The waku plugin activates from the waku dependency and follows the managed-mode file router. Every module under <srcDir>/pages is an entry, except modules in _components, _hooks and _actions folders. The plugin reads srcDir from waku.config.*, with src as the default. Thanks @aheissenberger for the contribution (#2921).
  • The language server can parse the project before the first open. Set the initialization option prewarm to true. At initialized, the server then loads the project session and parses the files, but analyzes and publishes nothing. The option is off by default and needs kept project sessions and a workspace root with a package.json.
  • --performance reports exact work counts, the time outside the pipeline and a span tree. The dead-code timings gain a counters object with counts that do not change with the thread count or the machine. A standalone dead-code run adds a process object and a Process section with a WALL row. The new spans array gives the parent of each stage and marks concurrent spans. Health --performance adds git_log_bytes, and the parse cache load gets its own parse_cache_load_ms field.
  • Runtime hot paths show where speed work gives the largest gain. Each entry in runtime_coverage.hot_paths now has an optimization_target block. The block holds cost_score, cost_basis, inner_iterations_per_call, cognitive, cyclomatic and line_count. Compare cost_score only between hot paths with the same cost_basis. The human output, --explain and the MCP get_hot_paths tool show the same fields.

Performance

  • Import resolution, stylesheet scans and coverage remaps do less work. Each specifier in a file now resolves once, not once for each imported binding. A stylesheet is masked for comments one time, and the health --css token scans count lines in one pass. Runtime coverage indexes each source-mapped script one time and finds each offset with a binary search. The findings do not change.
  • The language server keeps its project session between saves. A save now parses only the files that changed, and the other modules come from memory. A change to a config input, such as package.json, a lockfile, a tsconfig file or a rule pack, loads the session again. On a platform without a file change time, such as Windows, each run reads the persisted parse cache as before. The server keeps sessions while their estimated memory is at most 512 MB in total. Set FALLOW_LSP_REUSE_SESSION=0 to load a new session on each run.
  • The language server does less work for each analysis run. It publishes only the diagnostics that changed, and converts diagnostic columns in linear time. It reads an open file only when the buffer can differ from disk. Under autosave, a newer event cancels the run in flight, and the run after a cancelled one always finishes and publishes.
  • Typed MCP tool calls share parsed modules. A later call on the same unchanged files takes the modules from memory. For the sequence analyze, find_dupes, check_health, trace_file, run two times, the parse passes go from 6 to 1. The answers do not change. The store keeps at most 4 file lists and about 512 MiB of parsed modules. It does not keep a project with more than about 40 MiB of source. To stop the reuse, set FALLOW_MCP_WARM_SESSION=0.
  • fallow flags parses each file once with a custom flags config. Before, sdkPatterns, envPrefixes or configObjectHeuristics made the command parse every file a second time. The command also matches guarded flags to unused exports by file and line. The dead_code_overlap output does not change.
  • Fallow starts fewer git processes. A run without --diff-file, --diff-stdin or FALLOW_DIFF_FILE no longer calls git rev-parse for the diff base directories. The Impact project identity reads the git common directory and the toplevel with one call in a work tree.
  • The VS Code extension no longer searches the workspace for manifests at startup. It starts when the workspace root has a package.json or a Fallow config file. It also starts when a JavaScript, TypeScript, Vue, Svelte, Astro or MDX file opens. A monorepo folder with no root package.json now starts the extension when the first source file opens.
  • fallow viz pages load and respond faster. The page parses only the file list, the edges and the summary at start. On the Fallow repository, the data parsed at start drops from 2.49 MB to 64 KB. The HTML file drops from 2.8 MB to 1.9 MB. A hover on the treemap now makes 3 draw calls, not one call per tile. The page shows its frame before the script runs, so the layout does not shift.

Changed

  • fallow flags gives medium confidence to generic SDK names without a flag import. The names isEnabled, getValue and useFeature also occur in libraries that are not flag SDKs. A call to one of these names now has `confiden...
Read more

v3.29.0: raw V8 coverage, parse-error gate, complexity rule levels, deprecated export consumers

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 26 Sep 05:04
Immutable release. Only release title and notes can be modified.
v3.29.0
08ce24a

Features

  • health --coverage reads raw V8 coverage. Give it a NODE_V8_COVERAGE directory or one V8 coverage JSON file, and CRAP scores use measured coverage without a conversion step. The dumps of all test processes add up. A transpiled script maps back to its sources through the source map in the dump. health.coverage, FALLOW_COVERAGE, fallow audit and the MCP tools take the same inputs. The new summary.coverage_input_format field is istanbul or v8 (#2906).
  • A run can fail when fallow cannot parse a source file. The new --fail-on-parse-error flag and the failOnParseError config key add a parse-error gate on dead-code, health, audit and bare fallow. The gate is off by default, and gate_outcomes["parse-error"] names each file with its parser error count. Thanks @fpresta0607 for the report (#2727).
  • Complexity findings can warn without failing the run. The new rules complexity-cyclomatic, complexity-cognitive and complexity-crap accept error (the default), warn or off, also per file through overrides[].rules. Each complexity finding in the JSON output gets an optional effective_severity. Thanks @jwenger-notion for the report (#2783).
  • fallow dead-code can list the files that still use @deprecated exports. The opt-in deprecated-export-in-use finding gives a consumer_count, a sample of up to 10 consumers and the deprecation message. Turn it on with "deprecated-exports-in-use": "warn" in rules or --deprecated-exports-in-use. An unused deprecated export now has deprecated: true on its unused-export finding (#2598).
  • The review brief shows how many CODEOWNERS owner groups a change touches. fallow review and fallow audit --brief get an ownership section when the project has a CODEOWNERS file. It gives owner group counts, unowned changed files and independent slices. The brief schema_version moves from 10 to 11 (#2599).
  • Module Federation credits shared packages and runtime remotes, and traces name the config. A package that shared names is no longer an unused dependency (#2794). Literal registerRemotes and loadRemote calls register their remotes in a file that imports them from @module-federation/runtime or @module-federation/enhanced/runtime (#2795). --trace-file and --trace-dependency name the Federation config and key in a new sources array (#2796).
  • --changed-since states how many analyzed files it kept. The changed-since entry of request_outcomes now has scope_size, so a change to a README only gives scope_size: 0. The GitHub Action, the GitLab template, the PR and MR comments and the MCP warning state the empty scope (#2800).
  • A foreign baseline names the command that wrote it. baseline_staleness now has saved_by next to unrecognised_format: true. The GitHub Action has a new baseline-saved-by output (#2801).
  • Security candidates record strict origin checks. fallow security records an origin-equality-guard control when a file on the import trace compares .origin strictly with a known string and exits on a mismatch. The control is a hint for verification, not proof. It does not suppress a candidate or lower its severity.

Changed

  • The rule, not the band, sets the CI level of a complexity finding. error gives ::error, SARIF error and CodeClimate major. warn gives ::warning, SARIF warning and CodeClimate minor. The band stays in the title and the message (#2783).
  • --fail-on-issues also raises warn complexity findings to error. This applies to fallow health and bare fallow, also through --ci. fallow audit keeps its own verdict (#2824).
  • JSON output always states whether the run passed. Bare fallow, dead-code, check, health, security and audit now always include gate_outcomes with the default rule of the command and its status. No exit code changes.
  • The GitHub Action flags the legacy summary renderer, and a failed native render no longer falls back to it. For fallow before 3.4.2, the step log shows a notice and the job summary shows a footnote. The legacy renderers get no new finding types. On 3.4.2 or later, a failed native render writes a warning.

Bug fixes

  • Dockerfiles with non-ASCII text no longer crash the run. A short line with a multi-byte character, for example in an embedded SQL seed script, now counts as an ordinary line. Thanks @ga-h-usuba for the report (#2896).
  • CI formats state the rule severity of each dead-code finding. A finding with rule error now gives ::error in github-annotations, and SARIF and CodeClimate read the per-file overrides[].rules severity. Error annotations come first, so the max-annotations cap keeps them. Thanks @jwenger-notion for the report (#2782).
  • Severity levels agree across commands and saved reports. fallow dead-code now uses the overrides[].rules entry for catalog and dependency-override findings, the same as fallow audit. Prop-drilling, thin-wrapper and duplicate-prop-shape findings show as SARIF warning at most, because they never fail the run (#2826). fallow report --from keeps the saved level of each finding (#2827).
  • Nuxt autoImports credits global components, local layers and OG image templates. Global components and @nuxt/content components are now entry points, also when a package layer such as docus registers @nuxt/content (#2847, #2851). Fallow now credits OG image templates that a string names (#2849). Local layers are part of the project, and auto-import names from #components namespace imports now credit their files (#2752). A name now credits only the files of the app that uses it and of its layers.
  • Module Federation reads more config shapes. Fallow now reads options behind an export const, a spread, Object.assign, a wrapper call or a relative import (#2757). An exposes target in a sibling workspace is an entry point, and fallow reads runtime calls in .vue and .svelte scripts (#2757, #2876). A shape that fallow cannot read records a plugin-config-unreadable diagnostic.
  • Bundler entries resolve like the bundler resolves them. A bare rollup, rolldown or vite input such as my-lib/client credits its package. An entry without an extension matches the file or the directory index (#2753). Fallow now reads webpack configs in config/, build/ or webpack/, and applies rspack context and rsbuild root to entries (#2753). Fallow reads an absolute config path under the project root as that path (#2806).
  • Storybook stories patterns resolve against the .storybook/ directory. A pattern such as ../src/**/*.mdx now matches, and @(ts|tsx) groups work (#2831). A central docs app now credits stories in a sibling workspace, and fallow now reads the object form { directory, files, titlePrefix } (#2842).
  • Save and report files stay inside allowed directories. The save flags, --output-file and --sarif-file exit 2 when the resolved file is outside the allowed directories (#2805, #2861). These are the project root, its Git work tree, the CI workspace, RUNNER_TEMP and the system temp directory. -o /dev/null still discards a report, and -o NUL now discards it on Windows (#2877). When .fallow resolves outside the project, the run does not use the cache and prints a note (#2861).
  • Flags that did nothing now exit 2. --baseline and --save-baseline on a subcommand without a baseline, such as fallow list, now exit 2 (#2802, #2807). dupes, health and a bare run without dead-code reject --sarif-file and point to --format sarif --output-file (#2861, #2877).
  • Bare fallow now applies dupes and health baselines. Use fallow --dupes-baseline <file> and fallow --health-baseline <file>. --fail-on-stale-baseline now checks all three baselines.
  • Export tags attach to the right export. A JSDoc block before a decorator now attaches to the decorated export (#2835). In code without semicolons, a tag such as @public no longer applies to later exports.
  • A re-export through an unresolved import counts as used when a file imports the name. Before, the re-exported name was also an unused export. A re-exported name that no file imports is still an unused export (#2870).
  • Inline suppressions work for component events. A fallow-ignore-next-line or fallow-ignore-file comment for unused-component-emit, unused-component-input, unused-component-output or unused-svelte-event now drops the finding.
  • Template complexity counts operators inside a template literal. In an Angular, Vue or Svelte template, a ternary, &&, || or ?? inside ${} now adds complexity (#2798).
  • fallow audit shows a dependency finding only when its manifest changed. The changeset must touch the package.json or the catalog file that declares the dependency. An edit that only removes a tag such as @expected-unused or @public now counts the finding as introduced. The MCP audit tool treats findings in renamed files as inherited, the same as the CLI.
  • The MCP tools and the Node API match the CLI. A bad FALLOW_DIFF_FILE or FALLOW_CHANGED_SINCE now gives a not-applied entry in request_outcomes, not isError (#2799). Clone groups across workspaces and hidden duplicate exports now follow the CLI. The four MCP tools that can write a baseline declare readOnlyHint: false, and code_execute refuses the save parameters (#2755).
  • Health reports cover more repository layouts. health --hotspots works when the project root is a subdirectory of the Git repository. A branch without commits gives a hotspots-skipped diagnostic with the cause no-commits (#2803). With --type-aware-require complete, gate_outcomes of health and `a...
Read more

v3.28.0: baselines carry a kind, request outcomes on every surface, Module Federation and Nuxt reads

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 22 Sep 06:27
Immutable release. Only release title and notes can be modified.
v3.28.0
bd8fca5

Features

  • Every saved baseline now says which command wrote it. --save-baseline writes a top-level kind field with dead-code, dupes or health. Older baselines without it still load, and an older fallow still loads a baseline saved by this release.
  • Pointing --baseline at the wrong command's file is no longer silent. The run prints a warning with both command names and the path, and the file suppresses nothing. In the JSON output, baseline_staleness.unrecognised_format is true. The same warning appears in the pull-request comment, the merge-request note and the Check Run. The GitHub job summary shows the file path through the new baseline-path output, and fallow audit prints one warning per baseline it cannot use.
  • The JSON output says what narrowed a run. baseline_staleness.scope_reasons lists the reasons, for example changed-since or production. A narrowed run with a baseline also gets a recheck-baseline entry in next_steps, and the bare fallow command gets it too.
  • request_outcomes is now in the output of fallow flags, fallow suppressions and fallow security. The first two report their changed-since entry, and fallow security --sarif-file gets a sarif-file entry.
  • An empty diff no longer looks like a clean project. When a diff filter applies, its request_outcomes entry has a scope_size with the number of added lines. A diff with zero added lines reports scope_size: 0. The Action, the GitLab template, the MCP tools and the pull-request comment then all say that the clean result covers nothing.
  • hotspots-skipped now has a cause: not-a-repository, invalid-since or churn-file-unreadable.
  • A config that a plugin cannot read shows up in workspace_diagnostics. When a Module Federation exposes or remotes is not a static object literal, the run records plugin-config-unreadable with the plugin, the key and the reason. It sets degrades_analysis, so the Action and the GitLab template print it in their degraded-inputs warning. Nuxt has a related case: a components: or imports: shape that fallow cannot model. Such a project records plugin-effect-not-modeled, and that entry is silent.
  • The JSON output and both CI integrations report a run that widened to the whole project. They also report a --sarif-file that could not be written, and a format that cannot group results with --group-by.
  • fallow reads Module Federation exposes and remotes. It reads module-federation.config.* and inline plugin options in webpack, rspack, rsbuild, vite and Next.js configs. The plugin call can sit anywhere in the config: in a nested plugin array, in a variable, under tools.rspack.plugins or in a webpack(config) hook. Options held in a const in the same file are read, and so is the array form of exposes. Each exposes target becomes a runtime entry point, and each remotes alias counts as a provided dependency for the code under that config's directory.
  • Nuxt autoImports credits a global/ or islands/ component under the name Nuxt gives it. Each workspace root now gets its own verdict from its own config. components: true, imports: {} and imports: { dirs: [] } count as the Nuxt default. A name imported or re-exported from #components or #imports marks its file as used, and #layers/<name>/ works as a path alias.

Changed

  • fallow dead-code no longer exits 2 when the baseline belongs to another command. It warns and continues. --fail-on-stale-baseline fails the run on such a file. Saving over a baseline of another kind is refused with exit 2.
  • The GitHub Action and the GitLab template log why a JSON read failed. The cause goes to the debug log, and a green run gets no extra line.
  • fallow security --base warns when it cannot map the analysis root into the base worktree.

Bug fixes

  • A package name in a bundler entry is credited as a dependency. A value like react-hot-loader/patch or webpack-hot-middleware/client?reload=true in a webpack, rspack or rsbuild entry used to become an entry pattern that matched no file. The package then showed up as unused.
  • The MCP audit and decision_surface tools detect the base ref again. This regressed in 3.1.0. Thanks @codingthat for the report and the reproduction (#2699).
  • autoImports: true works in a Nuxt project that turns the scan off. components: false, imports: { scan: false } and the other scan-off shapes no longer count as a custom layout, so unused convention files are reported. Thanks @Tsuyoshi84 for the report (#2695).

Upgrade notes

  • A CI job that points --baseline at another command's file now fails when --fail-on-stale-baseline is set. Give each command its own baseline file.
  • A Nuxt project with autoImports: true and one of the scan-off shapes will see new unused-file findings. Add a file to entry when it is used in a way fallow cannot see.
  • If you switch on workspace_diagnostics[].kind, handle the two new kinds. Every new JSON field is optional.

Full Changelog: v3.27.0...v3.28.0

v3.27.0: every CI gate can fail the job, baseline staleness in CI, gate results in MCP

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 17 Sep 21:32
Immutable release. Only release title and notes can be modified.
v3.27.0
adff2c9

Features

  • An armed gate reports its result in the JSON output. A run that arms a gate gets gate_outcomes, an object keyed by gate name. Each entry has status (pass, warn, fail or skipped) and enforced. Where a comparison happened, you also get observed, threshold and threshold_label. Only the gates the run armed appear in the object. A dead-code run can exit 1 on the default severity rules with no object at all. The key set is open, and a gate name you do not recognise means "some gate". (#2680, #2681, #2683, #2685)
  • The JSON output reports a stale baseline, and CI can fail on it. 3.26.0 printed the stale-baseline warning and --fail-on-stale-baseline on stderr only, and --quiet removes that output. The GitHub Action and the GitLab template both run with --quiet. dead-code / check, the bare run, dupes and health report one baseline_staleness object, in grouped output too. It holds the entry counts, change_scoped, the warning result and gate_trips. Both integrations show a stale baseline as a warning and in the job summary. On a pull request the main run covers changed files only. The integrations therefore re-read the baseline once over the whole project. That re-read took 0.11s with a cache and 0.24s with type-aware analysis on an 870-file TypeScript project. Whether a stale baseline fails the job depends on the new fail-on-stale-baseline input and the FALLOW_FAIL_ON_STALE_BASELINE variable. The pull-request comment and the merge-request note do not include the warning yet. Thanks @cloud-walker for the report, which traced the gap through the action scripts. (#2673)
  • min-score is an Action input and a GitLab variable. Before this release you could set it only through args: / FALLOW_ARGS. --min-score implies --score, so the integrations add --complexity when no health section input is set. That keeps the annotations, the SARIF upload and the pull-request comment populated. The CLI turns its own findings rule off for such a run, and the integrations do the same, which leaves the decision to the score. min-score and min-severity apply to command: health and exit 2 elsewhere. (#2682)
  • A run that analyzed no source file reports it. The run prints a warning and passes by default. fail-on-empty-analysis: true (FALLOW_FAIL_ON_EMPTY_ANALYSIS on GitLab) makes it fail. The JSON output has a no-source-files-analyzed diagnostic and the new workspace_diagnostics[].degrades_analysis field. When findings cover less than the whole project, the integrations print one combined warning with the diagnostic kinds and their counts. (#2686)
  • An MCP tool result reports what each gate concluded. The tools run the CLI with --quiet and turn exit 1 into a successful result, which hid every gate result from the agent. The root warnings array reports a stale baseline together with the re-save remedy. It also lists every gate that concluded fail or warn, with its numbers. A run that covered less than the whole project gets one entry too. The subprocess, Code Mode and typed routes all do this. find_dupes with a threshold takes the route that can evaluate it. A response with nothing to report is unchanged. (#2676)
  • fallow report --from shows the gate results in CI. It prints them as a notice annotation and as a line in the job summary, the pull-request comment and the merge-request note. The line is informational and never fails a step. (#2684)

Changed

  • security-gate fails the job independently of fail-on-issues. In both integrations the security branch sat inside the fail-on-issues conditional, and fail-on-issues: false meant that branch never ran.
  • The Action's inline Check threshold step is gone. Its logic moved into the analyze step, and the gates-failed output names the gates that decided the result.
  • In combined mode the duplication threshold does not fail the run, and the JSON output has enforced: false for it. Standalone dupes exits 1 as before. The default GitLab job forwards the threshold in combined mode, and that pipeline prints a warning with the reason.
  • --fail-on-stale-baseline changes one field in the output. That field is gate_outcomes["stale-baseline"].enforced. Nothing in baseline_staleness depends on the flag, not even gate_trips.
  • health --report-only sets enforced: false on every gate it evaluated.
  • One stale baseline can produce two lines for a pull request. The Action's warning comes from its whole-project re-read. The gate line from fallow report describes the scoped run, where the gate fails nothing.
  • No schema_version in the JSON output changed. gate_outcomes and workspace_diagnostics[].degrades_analysis are additive and optional.

Bug fixes

  • fail-on-regression, threshold, min-severity and the security gate fail the job. All four were documented as gates. Each one reported its result on stderr, which --quiet removes. Both integrations also drop the exit code when stdout parses as JSON. The integrations read gate_outcomes instead. A gate fails the build when its status is fail and enforced is true, and only when the input that owns it was set. A flag passed through args: prints a warning and cannot override fail-on-issues: false. (#2680, #2681, #2683, #2685)
  • Every failing gate is reported before the step exits. Both integrations stopped at the first failure, and a run with a tripped gate and findings reported one of the two. They print every reason, write the outputs and artifacts, and exit once. The security gate keeps exit 8, which outranks the generic 1.
  • The duplication threshold applies to the bare command on GitHub. The Action forwarded it on command: dupes only.

Upgrade notes

  • If you set fail-on-regression, threshold, min-severity or a security gate and relied on the job passing, expect failures. With security-gate and fail-on-issues: false, unset security-gate to keep the old behaviour.
  • Remove --fail-on-stale-baseline from args / FALLOW_ARGS, delete any separate unscoped gate step added as a workaround, and set the fail-on-stale-baseline input.
  • fail-on-stale-baseline with no baseline set, or on fix or security, exits 2.
  • Do not point baseline and save-baseline at the same file. The run saves before it compares, and such a baseline can never have a stale entry. The integrations warn about it.
  • If a workflow references the Check threshold step by name (continue-on-error, steps.*.outcome), point it at the analyze step and the gates-failed output.
  • With a pinned fallow older than 3.27.0, the integrations use the fields that version writes. A gate with no field in that version passes, and the integrations print one warning.
  • If you import npm/fallow/types in TypeScript, HealthBaselineStaleness is now BaselineStaleness. The old name remains as a deprecated alias.

Full Changelog: v3.26.0...v3.27.0

v3.26.0: stale-baseline gate, built-in exclusion diagnostics, rule overrides everywhere

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 16 Sep 00:06
Immutable release. Only release title and notes can be modified.
v3.26.0
4f3bd97

Features

  • --fail-on-stale-baseline turns a rotting baseline into a failing build. The new global flag exits 1 when a loaded --baseline has an entry that matched nothing this run. It works on dead-code / check, the bare run, dupes and health, in every output format. A run that cannot judge the baseline skips the flag and says so on stderr. That covers a scoped run, health --report-only, audit and decision-surface. JSON output is unchanged. (#2637)
  • dead-code --baseline warns when the saved baseline has gone stale. The wording matches what health --baseline has printed since 3.12.0. When a quarter or more of the entries match no current issue, the run says so and points at the re-save command. Exit codes are untouched. Thanks @cloud-walker for the report. (#2627)
  • A run can say which built-in ignore pattern removed source files. The walk assigns every excluded candidate to the pattern that removed it. It records one excluded-by-default-ignore entry per pattern in workspace_diagnostics[]. The entry has the glob, an exact file count, the matched-directory count and a project-relative anchor. --explain-skipped prints the breakdown on check, dead-code, audit and the default run. Without the flag, only a run that discovered no source files at all prints a two-fact warning that points at the flag. (#2638)
  • Oxfmt is a built-in plugin. The plugin marks oxfmt.config.ts and its siblings always-used, and credits static imports from those configs. Thanks @uzosrc for the request. (#2614)
  • fallow now recognizes the Expo Router route exports SuspenseFallback, getNavOptions and generateMetadata. Thanks @tilgovi for the SuspenseFallback patch (#2618).

Changed

  • The built-in build exclusion now matches at any depth (**/build/**), which lines up with the dist and coverage defaults. Nested build output in a monorepo no longer produces unused-file, unused-export, duplication and health findings. The changelog states five consequences with the remedy for each. Among them, the walk now skips hand-written source in a nested build/ directory. Thanks @michalius for the report. (#2622)
  • Unused- and unlisted-dependency checks are faster on large workspace monorepos. The run resolves the owning workspace of each file once per analysis. On a repository with about 18,000 files and 800 workspaces the check went from roughly 30 seconds to 6 seconds. Thanks @Freakazo for the patch (#2624).

Bug fixes

  • Rule severity, including per-path overrides[].rules, applies everywhere fallow reports. Inline editor diagnostics now match the result set fallow dead-code reports. The programmatic runtime behind the MCP tools and the Node bindings matches it, and so do the decision surface and --type-aware CLI runs. The language server also watches every config file name the loader accepts, so editing .fallowrc.json refreshes diagnostics. Thanks @JasonHassold for the report. (#2621, #2636)
  • fallow audit no longer hangs on a sparse checkout of a large monorepo. The audit filters the committed tree through the sparse cone before it touches any blob. A blobless partial clone therefore no longer fetches every out-of-cone blob. Thanks @AndranikSimonian for the report and the reproduction. (#2615)
  • Istanbul statement coverage no longer charges a nested function body to the function that contains it. fallow assigns each statement to the innermost function whose body encloses it. For a function that lexically contains another function, coverage_pct, coverage_tier and crap now describe its own body. Thanks @ntdkhang for the precise reproduction. (#2620)
  • The config JSON Schema declares the JSONC dialect fallow parses (allowComments, allowTrailingCommas). An editor built on the JSON language service stops reporting trailing commas in .fallowrc.json. Thanks @michalius. (#2623)
  • dupes --baseline warns about a partially stale baseline. A project with nothing left to compare gets no baseline warning. A scoped dead-code --baseline run no longer advises a re-save that would gut the baseline. With health --production --baseline, a fresh baseline no longer counts as stale. (#2627, #2637)
  • The graph cache is reused again on a project where a dynamic-import pattern matches no files. Thanks @Freakazo for the patch (#2626).

Upgrade notes

  • If a nested directory named build holds hand-written source, rename or move it, or analyze it as its own project with fallow --root <dir>. ignorePatterns has no negation.
  • Agent-facing JSON from the MCP tools and the Node bindings now applies default-off rules such as private-type-leaks. That matches what the CLI has always shown.

Full Changelog: v3.25.0...v3.26.0

v3.25.0: positional path scope, runtime coverage joins callbacks and object members

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 11 Sep 08:48
Immutable release. Only release title and notes can be modified.
v3.25.0
30167b4

Scope any file command to a path

Bare fallow, check, dupes, health, audit, security, fix, list and
similar-code now take an optional positional path:

npx fallow src/components
npx fallow health src/api/client.ts
npx fallow fix src/legacy

fallow still builds the whole-project graph, and every cross-file fact stays
sound. Only the reported findings are limited to the path. fix plans and
applies only the fixes that touch scoped files.

Resolution is root-first for a bare relative path. fallow reads ./ and ../
as claims about the current directory. A missing path, or one outside the root,
gets an exit-2 error that says what to do. The run stops there instead of
analysing the whole project. The scope acts as one more
workspace root next to --workspace, and it intersects with --changed-since
and --diff-file. audit limits the set of changed files it considers, which
keeps its result and its base attribution consistent. Its base pass stays
unscoped, because that pass runs in another worktree.

Runtime coverage joins far more of your code

fallow coverage analyze --cloud matches cloud runtime rows against a static
index. That index came from the health and complexity pass, which lists
declarations and bindings and nothing else. The runtime instrumenter names more
shapes than that. An arrow passed to a call takes the name of its callee:
rows.map(...), sqliteTable("t", {}, (table) => [...]),
.references(() => ...). An object-literal method, a getter or setter, and a
function assigned to a member get their own names too. For all of those, the
index held nothing to match. Their rows went into
cloud_functions_unmatched and never appeared in findings or hot_paths. In a
typical service they are the highest-traffic functions, and the top of the
hot-path list was whatever declaration the pass happened to list.

The index now holds every function the instrumenter would name. It resolves them
through the same walker the static inventory upload uses, which makes the
identity match the stable_id the cloud stores. A function known only by the
callee it was passed to is marked as a callback. The text fallow prints for it
names the call site ("Callback passed to map; ...") instead of a declaration
that does not exist.

The static function inventory got the same fix. An object-literal method, a
function-valued property, and a getter or setter kept the (anonymous_N)
placeholder. So did a function assigned to a member expression, and an anonymous
export default. The instrumenter names those functions run, execute,
get closed, rollback and default. Both sides now agree, and an
uploaded inventory entry and the runtime row for one function share a single
identity.

The inventory blob reports its own size guard

fallow coverage upload-inventory --with-callers has always capped the
importer-edge map per callee. The cap keeps a pathological fan-in from bloating
the upload, and it shortened the list silently. Nobody reading the body could
tell a function that genuinely has as many importers as the cap from one that
lost some.

The version 3 body has a callerEdgeLimits header next to callerEdges. The
header holds maxSitesPerFunction, maxSymbolsPerSite and truncatedFunctions.
The command prints a warning that says how many functions lost importer sites.
The header appears only when callerEdges does, and a version 1 or version 2
body keeps exactly the shape it had.

Windows paths read correctly in check and health

Both human renderers printed the separator of the platform. A Windows user read
src\a.ts while dupes, list, fix and every JSON output said src/a.ts.
The split between the dimmed directory and the bold filename keys on /, so the
whole path lost that emphasis as well. Every path those two renderers put on
screen normalises the way the rest of the CLI already did. Path handling on disk
is untouched.

Install

npx fallow@3.25.0
npm install --save-dev fallow@3.25.0
cargo install fallow-cli@3.25.0

Full Changelog: v3.24.1...v3.25.0