Releases: fallow-rs/fallow
Release list
v3.3.0: verifiable plugin authoring, config recommendations, and hardened agent surfaces
Agent-facing plugin authoring, config recommendations, and a blast-radius tool
fallow plugin-check: a read-only dry-run for external-plugin authoring. It reports per plugin whether it activated (and which detection requirement was unmet when it did not), and formanifestEntriesrules the matched manifests,when-gate results, seeded entries withpath_exists, and typedwarnings[]. Deterministic output, always exit 0.manifestEntriesfor external plugins: afallow-plugin-*.jsonccan now derive entry points from framework manifest files (recursive glob + JSON/JSONC parse + dotted-fieldwhengate +${dotted.field}interpolation), making manifest-driven monorepo frameworks self-serviceable without a built-in plugin.fallow recommend: a read-only, project-tailored config recommendation for cold-start onboarding; detects frameworks living in workspace packages too.impact_closureMCP tool: the pre-edit blast radius as a one-call primitive. Wrapsfallow dead-code --impact-closure <path> --format json(reverse dependencies, re-export chains, coordination gaps) so agents no longer pay for the fullinspect_targetbundle to answer "what breaks if I change this file?".- Self-documenting capability surface:
fallow schemanow enumerates the security-candidate category ids (with aninclude_requiredflag), publishes each rule's default severity and opt-in status, and everyconfig-schemakey carries a description.
Detection accuracy
- Class-member crediting got three structural upgrades: member access through interface- or type-alias-typed property hops now resolves same-file and cross-module (thanks @martijnwalraven for the report in #1785); options objects typed by a local, unexported class credit the members of their imported property types (#1788); and Playwright Page Object methods used through a function-wrapped fixture (
export function appTest() { return base.extend(...) }) are credited (thanks @committedpazz for the report in #1791). - Iteration bindings credit class members in three more shapes: Vue
v-forover aref.value/store.<field>source, Angular@for/*ngForin an externaltemplateUrl, and function-local array receivers (#1716, #1717, #1718). - React Router and Remix route loaders now feed
unused-load-data-keywith framework-scoped harvesting, andfallow health --cssreportsnear_duplicate_css_in_js_tokensalongside the existing theme-token near-duplicates.
Reliability
- The npm wrapper exits non-zero when the binary dies by a signal (previously a SIGSEGV or OOM kill read as exit 0 to CI gates); signal deaths map to 128 plus the signal number with the name on stderr, and the Linux fallback prefers the statically linked musl build when libc cannot be detected.
- A panic inside
@fallow/nodeno longer aborts the host Node process: the addon ships with a dedicated unwinding profile and catches panics at the FFI boundary, surfacing a structuredFallowNodeErrorwith codeFALLOW_PANIC. - The LSP emits UTF-16 column positions as the protocol negotiates, so diagnostics, hover, and code lenses land on the right characters on lines containing non-ASCII text, and the remove-export quick fix no longer corrupts lines with Unicode indentation.
fallow health --hotspotsreports churn-fetch failures on stderr instead of emitting a second JSON document.
Performance
fallow auditprecomputes its focus-map signals in one graph pass, removing an O(changed files x references) cost on large PRs.fallow health --cssparses each CSS-in-JS consumer once per run instead of once per token definer.
Internal
- The duplication detector, trace, churn, and cross-reference engines are now owned solely by
fallow-engine(about 20k duplicated lines deleted); CLI, JSON, LSP, and MCP surfaces are unchanged. - The MCP server migrated to rmcp 2.x.
Full Changelog: v3.2.0...v3.3.0
v3.2.0: configurable large-function threshold, surfaced in the health summary
Health: a configurable "function too big" threshold, surfaced in the summary
This release makes the unit-size (large-function) check configurable and reports the effective ceiling in the health output.
health.maxUnitSize: raise the large-function bar instead of switching it off
The line count at which a function is reported as an oversized "large function" was hardcoded to 60 LOC. That made test suites noisy: a describe() callback spans hundreds of lines, and each large it() body trips the threshold too. The only escape was health.ignore, which drops every health signal (complexity, CRAP, hotspots) for those files, so you lost complexity checking on your test code as well.
You can now raise the bar rather than turning it off:
- Set a global
health.maxUnitSize(default 60). - Or scope it to a glob with a per-file
thresholdOverridesentry, for example{ "files": ["**/*.test.*"], "maxUnitSize": 500 }. Leavefunctionsempty so the override covers both thedescribe()wrapper and the individualit()blocks.
Complexity, cognitive, and CRAP findings on those files are unchanged. Like the existing maxCyclomatic / maxCognitive / maxCrap overrides, this filters the reported "large functions" list; the descriptive unit-size profile and the health score still reflect raw sizes (use health.ignore to remove a file from the score entirely). Resolved thresholds are inspectable via fallow config. Thanks @digulla for the request. (Closes #1731)
The health JSON summary reports the effective unit-size threshold
The summary block on fallow health --format json already carried max_cyclomatic_threshold, max_cognitive_threshold, and max_crap_threshold, but not a unit-size sibling, so a consumer reading the summary to learn which thresholds a run uses saw only three of the four. It now also carries max_unit_size_threshold (the effective global health.maxUnitSize, default 60). The human report's "Large functions" section reflects the configured global instead of a static "60" when health.maxUnitSize is raised project-wide. This is an additive-required field matching the existing max*Threshold siblings; no change to the unit-size check itself. (Closes #1750)
Other changes
- Reuse the audit analysis context in the programmatic API path for less redundant work per run.
- Benchmark-harness coverage and CI sharding improvements (internal).
Full Changelog: v3.1.0...v3.2.0
v3.1.0: dev-dependency-in-production rule, member tracing, conditional dynamic imports
Features
New rule: dev-dependency-in-production
The promote-side mirror of test-only-dependency and type-only-dependency. A package in devDependencies that a production (non-test, non-config) file imports at runtime is flagged so you can promote it to dependencies, because pnpm install --prod omits devDependencies and the import breaks in production.
Only imports from files reachable from a runtime entry point count as evidence, so repo tooling (scripts/, benchmarks, rollup-config chains) does not trigger it, and workspace-owned files are governed by their own manifest. Type-only production imports are not flagged, and a package also present in dependencies / peerDependencies / optionalDependencies is left alone. Defaults to warn; suppress a package via ignoreDependencies. Surfaces in human, JSON, SARIF, Code Climate, compact, and markdown output, in fallow explain, and as an LSP diagnostic.
Thanks @CallumHoward for the implementation.
Trace tooling now covers class, enum, and store members
fallow dead-code --trace FILE:MEMBER previously errored export 'X' not found when pointed at a class member, so a member finding could not be debugged from the trace tool. On an export miss the trace now falls back to a member trace that names the owning class, reports its reachability and usage, lists who imports it, and points at the right --unused-class-members inspection command. The MCP trace_export tool and Code Mode return the same member trace in-process, so AI agents get parity with the CLI. Trace not-found errors across trace_export / trace_file / trace_clone now carry an actionable help pointer to the right discovery tool.
Bug fixes
Conditional and logical dynamic imports are now traced
import(cond ? './a' : './b') and import(x || './b') previously produced no module-graph edge, so every file reachable only through such an import (and its whole transitive subtree) was falsely reported as unused-files / unused-exports. Extraction now emits one edge per statically-resolvable branch, including parenthesized and no-substitution template forms, across all dynamic-import shapes: bare expressions, const x = await import(...) declarations, .then() callbacks, React.lazy / next/dynamic arrow wrappers, and Angular/Vue route loadComponent / component callbacks. Genuinely runtime arguments (import(someVar)) still yield no edge, and repeated literals across branches deduplicate to a single edge.
Thanks @Jerc92 for the contribution.
fallow health no longer exits 1 when the config contains a rules key
A bare #[serde(default)] on a rule-severity field resolved to error when a rules object was present (even {"rules": {}}), diverging from the documented default. This promoted coverage-gaps and eight warn-default component / store / inject / server-action rules to error, wrongly gating CI on otherwise-clean Vue / Svelte / Angular projects. The nine affected defaults are now pinned to their documented values.
Thanks @lightsound for the report.
unused-class-member false positives through return-type-annotated factories
A factory or hook whose body has no new value proof but is explicitly typed : SomeController now credits member reads on const c = useController(); c.method() across the module boundary, for both the function-declaration and arrow forms. A genuinely-unused method on the returned class still reports.
Thanks @prosky for the report.
Full Changelog: v3.0.0...v3.1.0
v3.0.0: new brand, styling audit, rule packs, and architecture guard
Fallow 3.0
Your code-health gate now reviews your styles. Same PR, same JSON, no new tool.
Fallow 3.0 is a major release for one reason: fallow audit, the gate that already reviews your TypeScript and JavaScript, now reviews your CSS and CSS-in-JS in the same PR, in the same JSON stream, with no separate tool and no extra config. That is the change that crossed the version to 3.0.
Alongside it: policy-as-code with rule packs, a pre-edit architecture guard, better PR and MR reporting, and a new brand mark.
No breaking changes. CLI flags, configuration, and JSON output contracts are all unchanged. The major bump marks the platform milestone (styling in audit), not a breaking API change. GitHub Action users on @v2 should move to @v3 to keep receiving releases.
npm install -g fallow@3.0.0
# or
npx fallow@3.0.0Styling analysis, now inside fallow audit
Run fallow audit on your next PR and styling feedback lands next to your JS and TS findings, in the same JSON. No new command, no new config, no second tool. Findings are verdict-neutral by default: they report, they do not fail the gate. What you get:
- Design-system drift and near-duplicate theme tokens
- Duplicate CSS blocks and selector-complexity hotspots
- Dead styling surface and broken references
- Raw one-off values that bypass your tokens
The deep pass scans the project-wide styling surface, then narrows cross-file results back to the anchors the PR touched. A two-file change gets styling feedback scoped to those two files, not a repo-wide dump.
Two new findings, css-token-drift and raw-style-value, flag introduced raw values on design-system axes: colors, font sizes, line heights, radii, and shadows. They are low-confidence and verify-first, so they stay off the gate until you opt in:
// gate on drift once you trust it
"rules": { "css-token-drift": "error" }Dialing it back:
--no-css(oraudit.css: false) disables styling analysis entirely.--no-css-deep(oraudit.cssDeep: false) keeps the local styling checks and skips only the project-wide reachability pass.
Styling actions are report-only (auto_fixable: false). Agents surface the finding and let you verify and edit by hand, rather than rewriting your styles for you.
Custom rules: policy as code
Encode your architecture in declarative rule packs, enforce them in the same gate, and let agents check the rules before writing a line.
- Authoring.
fallow rule-pack init | list | test | schemascaffolds, inspects, and validates policy packs.initdrops in a starter or architecture-oriented pack and can wire it straight into your config.listshows loaded packs with their sources, severities, matchers, and messages (human or JSON).testandschemavalidate a pack before you ship it. - V2 matchers. Scope rules to boundary
zones, ban direct exports withbanned-export, and use a trailing/*banned-import specifier for subpath-only deep-import bans. Everything reports under one stablepolicy-violationfamily, so existing suppressions and CI keep working untouched. - A guard that runs before the edit.
fallow guard <files>tells you which rules govern a file before a line is written: its boundary zone, allowed import zones, forbidden call patterns, rule-pack rules, effective severities, and suppression tokens. It works on files that do not exist yet, and it is exposed as the read-only MCPguardtool, so an agent can ask "what rules apply here?" and stay inside the lines from the first draft.
Improved PR and MR reporting
The bundled GitHub Actions and GitLab CI integrations now render sticky summary comments from typed Rust output: a gate table, an attention banner, top fixes, and sidecar artifacts for the full drilldown. Clean runs no longer spawn a fresh comment, and an existing Fallow comment is updated in place so stale warnings disappear. GitHub Actions posts a native Fallow Check Run when checks: write is available. Inline review comments and MR discussions skip empty envelopes, so a dead-code-only job no longer leaves "0 inline findings" noise.
New FALLOW_PR_COMMENT_LAYOUT (default | compact | gate-only | details) controls the sticky summary layout.
Other changes
- Callback arguments get real names. A function passed as a call or
newargument is no longer surfaced as anonymous. It takes the callee's name (arr.map(cb), route handlers,.references(() => ...)), matching the runtime instrumenter so static inventory and runtime coverage agree on the same name. - New f-wing brand mark, across the icon, the light and dark wordmark lockups, the VS Code sidebar icon, and the docs.
- Engine and registry architecture split completed (internal). Command, API, MCP, and editor flows now route through typed engine and API boundaries, and combined and audit runs reuse retained project artifacts instead of repeating discovery, parse, and graph work. The security catalogue moved into a dedicated
fallow-securitycrate. CLI wire contracts, config, and output formats are unchanged.
Thanks
Thanks @revazi for adding repo-scoped agent skills (#1727).
Full Changelog: v2.104.0...v3.0.0
v2.104.0: CSS-in-JS intelligence, styling health, token blast-radius
Highlights
This release is heavy on CSS intelligence. fallow health --css now understands CSS-in-JS (styled-components, emotion, linaria, vanilla-extract, StyleX, Panda) as first-class, ships a second styling-health quality axis, and adds a design-token blast-radius index. Plus a staged human review walkthrough, an opt-in unused-prop exemption, and a batch of framework false-positive fixes.
CSS intelligence (CSS program, Phases 3-4)
- CSS-in-JS is first-class in
fallow health --css. styled-components / emotion / linaria (tagged templates) and vanilla-extract / StyleX / Panda / emotion-object (object notation) previously producednullcss_analytics. A lexical lifter now extracts the CSS body from both forms and feeds it through the same structural analytics and styling-health pipeline, so a CSS-in-JS app gets real duplicate-block, structural, and token-sprawl signals. Dep-gated on a declared CSS-in-JS library, so non-CSS-in-JS projects are byte-unchanged. - Styling-health: a second CSS-quality axis, confidence-aware.
fallow health --cssreports a separatestyling_healthscore (0-100) and A-F grade with aDeductions:breakdown across five capped penalty categories. It carries aconfidencemarker (high/low) so a thin authored-CSS surface (utility-first Tailwind app) renders dimmed with a caveat instead of an authoritative grade. Descriptive-only: no exit code, badge, or CI gating. - Formula v3 weights value drift over exact repetition. Research is clear that exact CSS duplication is the least-harmful pattern while design-token inconsistency is the real maintenance harm, so the exact-block penalty is down-weighted to a soft hint and token-erosion gains a hardcoded-value-sprawl drift term (distinct un-tokenized
box-shadow/border-radius/line-heightvalues).STYLING_HEALTH_FORMULA_VERSIONbumps to 3. If you diffstyling_health.score/gradeover time, re-baseline or gate onformula_version; the one-time step-change at this boundary is expected. - Design-token blast-radius (
token_consumers) for Tailwind v4 AND CSS-in-JS tokens.fallow health --css --format jsonnow carries a reverse index of where each design token is consumed. Change--color-brand(Tailwind@theme) or a StyleXdefineVars/ vanilla-extractcreateThemetoken and see aconsumer_countplus locatedconsumers[]before touching it.consumer_countis a static lower bound (descriptive context, not a deletion gate); the authoritative dead-token finding staysunused_theme_tokens. - New
get_token_blast_radiusMCP tool. A focused, read-only tool that surfaces the token blast-radius directly without the agent needing to know the data hides insidecss_analytics. - Fuzzy CSS clones via value canonicalization.
fallow dupesnow canonicalizes CSS values on the stylesheet path (a zero-with-unit collapses to bare0, a hex color expands to its long lowercased form), so value-drifted clones (0pxvs0,#fffvs#ffffff) hash equal and the same shadow / gradient / transition recipe re-implemented with drift finally matches. Scoped to CSS-family files and SFC/Astro<style>regions; JS/TS clone detection is unchanged.
Review and configuration
fallow review --walkthrough: a staged terminal tour. Renders the review walkthrough guide as an ordered, human-readable tour (Review Focus header, staged sections, per-file one-line facts and grounded badges, a collapsed "cleared" panel).--format markdownemits a paste-into-PR artifact;--format jsonis byte-identical to--walkthrough-guide. Per-file viewed state persists locally (--mark-viewed) and tolerates a moved tree. Always exits 0.unusedComponentProps.ignorePattern: exempt intentionally-unused props. Set"unusedComponentProps": { "ignorePattern": "^_" }to exempt props whose local destructure binding matches the regex (the leading-underscore convention that TSnoUnusedParametersand ESLintvarsIgnorePatternhonor). Applies to Vue, Svelte, Astro, and React/Preact. Opt-in; default behavior is unchanged. Thanks @hniedner for the request. (Closes #1648)
CI, coverage, and architecture
- The GitLab CI template can reuse a pre-installed fallow binary. Set
FALLOW_SKIP_INSTALL: "true"to skipnpm install -g fallowand run afallowalready onPATH(for example a version pinned through a pnpm catalog), so CI runs the same binary as your local lint gate. The job fails fast when nofallowis found. Thanks @Jerc92 for the patch in #1662. - Coverage upload enrichment.
fallow coverage --with-callersuploads importer edges, and the inventory upload now emits per-function complexity and per-file churn. - Typed architecture boundaries.
fallow-engine,fallow-output, andfallow-apinow own the command-neutral analysis runners, output contracts, and programmatic Rust boundary; LSP, MCP, and NAPI callers consume typed results and serialize JSON only at protocol boundaries. The oldfallow-programmatic-clicompatibility crate has been removed.
Bug fixes
- Iterating a typed class array no longer false-flags the class members as unused. A cluster of
unused-class-memberfalse positives where the class is only used through an iteration loop variable is now fixed across array-method callbacks (.map/.forEach/.filter/ ...),for...of, React/Preact JSX.map, Svelte{#each}, Vuev-for(includingprops.<field>sources), Angular@for/*ngForinline templates, and Astro template.map. Over-credit only: a genuinely unused member still reports. Thanks @Ericlm for the report and minimal reproduction. (Closes #1707, #1711, #1712, #1713) unused-filesno longer false-flags a Next.jspage.mdxwhennext.configwraps its config object.export default withMDX(nextConfig)(the official@next/mdxidiom),module.exports = createJestConfig(cfg), and nested/curried wrappers now resolve, which also fixes the same class for any wrapped Vite / Webpack / Jest config. Thanks @AlonMiz for the report. (Closes #1642)unused-filesno longer false-flags acommit-and-tag-versionupdater script. A new plugin (legacy enablerstandard-version) credits eachbumpFiles[]/packageFiles[]updatermodule andfilenametarget, from both the package.json key and standalone.versionrcconfigs, gated on the file existing on disk. Thanks @rbalet for the report. (Closes #1640)unused-class-membersno longer false-flags framework-dispatched OpenLayers methods or a coercion-onlytoString. AhandleEventon anol/interaction/*subclass and atoStringused only through string coercion (template interpolation,String(...),+) are now credited, with tight gating so genuinely-dead members still report. (Closes #1638)- Telemetry
findings_presentis recorded again forfallow flags,fallow watch, and thesecuritysurvivors/blind-spotssubcommands. A debug-build invariant now fails fast if any finding-surfacing workflow records an event without noting its find-state, preventing the whole regression class. No change to the telemetry payload shape. (Closes #1650)
Full Changelog: v2.103.0...v2.104.0
v2.103.0: typed output contracts, runtime trust-output, false-positive fixes
Runtime coverage trust-output
coverage analyze --format json now mirrors the cloud runtime trust-output contract on the local report, so an agent can reproduce a verdict instead of re-deriving it:
- Actionability + provenance. Each report carries
actionable,actionability_reason, andactionability_verdict(a capture with no tracked functions is a first-classinsufficient_evidenceverdict, never silently read as cold), plus aprovenanceblock (data_source,freshness_days,untracked_ratio,unresolved_ratio,stale,stale_after_days). The block is context only: it never gates a positive verdict or a confidence score. - Confidence discriminators. Every finding now carries a
discriminatorsblock exposing the inputs behind its verdict:tracking_state(called / never_called / untracked),invocation_ratio, thelow_traffic_thresholdandmin_observation_volumein effect, andtrace_countwithmeets_observation_volume. - Source-map upload hint. When
coverage analyze --cloudcannot map runtime positions to source and built source maps exist on disk, fallow prints the exactfallow coverage upload-source-maps --dir <dir>command. Human output only; JSON consumers already get the structuredcoverage_unresolvedwarning.
All three additions are additive and backwards-compatible.
Typed output contracts
The engine, output, API, and programmatic-CLI boundaries are now explicit: typed engine results feed the CLI, LSP, NAPI, MCP, and programmatic consumers through shared contracts instead of CLI rendering being the implicit API surface.
As part of this, workspace_diagnostics is now a typed WorkspaceDiagnostic array on CheckOutput and DupesOutput (and the combined + audit envelopes), matching WorkspacesOutput. docs/output-schema.json and the generated npm / VS Code .d.ts now describe it precisely instead of as an opaque value. Thanks @riker-wamf for flagging it (#1635).
MCP
get_blast_radius and get_importance now state the augment-not-gate rule in their tool descriptions: both return review context (caller counts, risk bands, importance scores) that must not gate a safe_to_delete decision or a confidence score. Only the three-state runtime tracking signal can issue a deletion verdict.
Changed
fallow dupesnow ignores test and mock files by default. Duplicate-code analysis skips*.test.*,*.spec.*,__tests__, and__mocks__paths out of the box, reducing first-run noise. Setduplicates.ignoreDefaults: falseto restore the previous corpus.
Bug fixes
unused-component-propsno longer false-flags Sveltebind:/style:/class:directive shorthands or Vue value-lessv-bindsame-name shorthands. A value-less directive (bind:open,style:height) or a Vue 3.4+:openshorthand references the prop itself, and<style> v-bind(accent)references bind script/prop values into CSS. All three are now credited. Thanks @hniedner for the report (#1641).unused-store-membersno longer false-flags a Pinia store member reached through indirection, including inlineuseFooStore().member, a store passed as aReturnType<typeof useFooStore>param, and member usage in.tsfiles. Thanks @Jerc92 and @Ericlm for the reports (#1489, #1488).unused-class-membersno longer false-flags a member reached through a factory or composable return value, including when the factory's return type is inferred rather than annotated. Thanks @Jerc92 (#1441).unused-component-propsno longer aborts on a spaced</template >closing tag, which previously reported every prop and emit in that SFC as unused. Thanks @Jerc92 (#1439).ignorePatternsnow accepts a leading./. Entries such as./src/generated/**match the same files assrc/generated/**(also applies toignoreUnresolvedImports).
Full Changelog: v2.102.0...v2.103.0
v2.102.0: code review brief, decision surface, and symbol-level trace
Code review for changed code
This release adds a toolkit for reviewing changed code, designed for both human reviewers and AI agents.
Review brief (fallow review, or fallow audit --brief)
A new advisory orientation mode over changed code. It runs the same dead-code + complexity + duplication analysis as fallow audit but answers "where do I look?" instead of "will CI block this?": it ALWAYS exits 0 (the verdict is carried informationally), so a reviewer or agent can read it regardless of the gate outcome. The brief renders a ranked decision surface, a weighted focus map, and change-impact context. --format is orthogonal to --brief.
Decision surface (fallow decision-surface and the decision_surface MCP tool)
Surfaces the consequential structural decisions a change embeds: a ranked, capped (3 to 5) set of coupling/boundary, public-API/contract, and dependency decisions, each framed as a judgment question with the routed expert to ask. Each decision carries an honest count of how many internal consumers it affects plus an explicit trade-off clause, so the reader sees the cost as well as the call. It is separable and cheap, advisory (always exits 0), and every decision is suppressible with // fallow-ignore. Use --base / --changed-since to pick the comparison point, exactly like fallow audit.
Symbol-level call chains (fallow trace <FILE:SYMBOL>)
Walks callers up (modules that import the symbol) and callees down (import-symbol edges plus intra-module call sites) through the module graph, bounded by --depth (default 2). Use --callers / --callees to scope the direction; both are walked by default. Best-effort and syntactic per ADR-001: resolved-vs-unresolved callees are reported honestly, never silently dropped.
fallow trace src/utils.ts:formatDateAgent-contract walkthrough loop
For agents that review changed code, fallow audit --walkthrough-guide emits a deterministic digest (the brief, the decision surface, the review direction, the JSON schema the agent must return, and a graph-snapshot hash) built from the graph only, so PR prose is never folded in and the digest is injection-resistant. The agent produces judgment JSON and reopens with --walkthrough-file, which post-validates it against the LIVE graph: it rejects any judgment whose signal_id fallow did not emit (anti-hallucination) and refuses the whole payload as stale when the snapshot hash no longer matches. The verifier is the graph, not a second model. Both flags always exit 0.
A weighted focus map ranks changed units by review weight and collapses the de-prioritized tail by default; --show-deprioritized re-expands it (the deprioritized list is always present in --format json).
Framework health
- Astro framework-health detection.
.astrocomponents now participate in the same health suite as Vue/Svelte/Angular/React: a reachable component rendered in no template surfaces asunrendered-component, aninterface Propsfield read nowhere surfaces asunused-component-prop, andfallow healthnow scores.astrocomplexity. A zero-false-positive abstain ladder protects public surfaces. No new rules or severities. - Lit / web-component framework-health detection. A custom element registered via
@customElement/customElements.definebut rendered as a tag in nohtmltemplate surfaces asunrendered-component, and a@state()reactive property read nowhere surfaces asunused-class-member.@property(the public attribute API) is never flagged. Gated on alit/lit-element/@lit/reactive-elementdependency. - Deeper React prop coverage for
unused-component-prop. The React arm now harvests props from same-file typed interfaces and genericforwardRefcomponents, not only inline destructure, while still abstaining on imported prop interfaces and exported public-API components.
Editor
- React component intelligence. The LSP surfaces ambient React/Preact context with no new rule, finding, or severity. A code lens above each component summarizes render count, props, and hooks; a per-prop hover shows where a prop is read and passed from; a forwarded prop shows its forwarding chain. Editor-only context:
fallow/audit/--format jsonoutput is unchanged. - VS Code: clearer tree badges, hardened health spawn, and de-duplicated diagnostics.
Security
- LLM-call prompt-injection candidate. A new
llm-call-injectioncategory (CWE-1427) in thefallow securitytainted-sink catalogue. It fires only when an untrusted source flows into the prompt/messages argument of a known LLM-call sink (a taint path into the call, not every LLM call). Like allfallow securityoutput it is a candidate for verification, not a verified vulnerability, and never appears under barefallowor theauditgate.
Fixed
- Merged namespace values imported through star barrels are no longer falsely reported as unused. A value export sharing its name with an
export declare namespaceand consumed throughexport *now receives the same named-import credit as a direct import. Thanks @TeoVezza95 for the report. (#1373) - VS Code now resolves the native fallow binary from platform packages when the binary is reached through a
.cmd/.ps1launcher shim onPATH, so LSP-backed diagnostics start reliably. Thanks @ivan-palatov for the report. (#1359) - TanStack Router: custom
routeFileIgnorePrefixis honored, so files using a configured ignore prefix are no longer flagged as dead code. Thanks @Spiralis for the report. (#1358) fallow auditbase-snapshot worktree paths are unique per call, so concurrent audit runs no longer collide.- More precise telemetry failure classification when telemetry is enabled.
- Vendored GitLab CI now bundles
gitlab_common.sh, sofallow ci-template gitlab --vendorpipelines run without reaching out toraw.githubusercontent.com.
Performance
- Faster command-only surfaces (schema and template printers and similar metadata surfaces).
Full Changelog: v2.101.0...v2.102.0
v2.101.0: faster duplicate detection, stylesheet resolution, and namespace-component fixes
A performance and bug-fix release: faster duplicate detection, stylesheet resolution, and plugin detection on large repositories, plus false-positive fixes for Vue namespace components, Varlock, and the GitLab CI template.
Performance
- Faster duplicate detection on large repositories. Clone detection now builds its suffix array with a linear-time SA-IS construction instead of the previous prefix-doubling approach, cutting the duplication stage of
fallow dupes(and of barefallowandfallow audit) on large codebases. Reported clones are unchanged. - Faster repeated stylesheet resolution. Resolving external (
node_modules) stylesheet@import/@usechains now reuses a single resolver session across lookups instead of rebuilding the resolver and re-reading every workspacepackage.jsonper stylesheet. On a 41-workspace monorepo this removes roughly 80 manifest reads and 80 path-canonicalization calls per external stylesheet. - Faster plugin and config detection on large repositories. Plugin config files are collected during the existing project file scan instead of a second filesystem walk, and workspace file bucketing runs in parallel. On a 21k-file, 41-workspace project this roughly halves the plugin detection stage. Analysis output is byte-identical across the benchmark suite.
Bug fixes
- Vue components exposed as namespaces are no longer falsely reported as unused. A design system that re-exports compound components through namespace barrels (
export * as List from "./components/List") and renders their members via dotted tags had every such member reported byunrendered-componentas "reachable but rendered nowhere". The render-usage walk now follows namespace re-export edges back to the underlying.vuefiles, for both the named-import form (import { List };<List.Root>) and the whole-namespace-import form (import * as DS;<DS.List.Root>), including barrels nested through furtherexport */export * asre-exports. A component re-exported through a namespace that nothing renders is still reported. Thanks @Smrtnyk for the report (#1351). - Varlock now activates from a nested
.env.schema, not just a root-level one. A project with a.env.schemain a subdirectory and novarlockdependency previously left the plugin inactive, so packages declared via@plugin(...)in that schema were reported as unused. They are now credited. fallow ci-template gitlabnow bundles the sharedgitlab_common.shhelper. The vendored GitLab CI template sourcesgitlab_common.shfrom its comment and review steps, but the file was missing from the vendored output, so a generated pipeline failed at runtime. The template now includes it.
Full Changelog: v2.100.0...v2.101.0
v2.100.0: agent inspect bundles, security verifier workflows, web-format duplicate detection
fallow v2.100.0 bundles new agent and security workflows, broader duplicate detection, framework-aware health, and a batch of accuracy fixes.
Features
fallow inspect for one-shot evidence bundles. A new read-only inspect command composes trace, dead-code, duplication, complexity, and security evidence for a single file or exported symbol into one typed JSON result, without a new analysis pass. It is the CLI and editor equivalent of the MCP inspect_target tool, so an agent can gather context before editing without starting an MCP session:
fallow inspect --file src/api.ts --format json --quiet
fallow inspect --symbol src/api.ts:fetchUser --format json --quietSymbol targets add precise trace_export identity plus file-scoped evidence for the analyses that do not yet map to an enclosing symbol. The VS Code inspect command saves dirty files before running.
Security verifier workflows. Two read-only fallow security subcommands close the loop between fallow's candidates and an external verifier:
fallow security survivorsjoins rawfallow security --format jsoncandidates with a verdict file, reporting confirmed survivors without rewriting candidate output. It surfacessummary.unverdicted, separates verifier dispositions from unreviewed candidates, and offers--require-verdict-for-each-candidateas a strict complete-verdict CI gate.fallow security blind-spotsgroups unresolved security callees and accepts--filebefore or after the subcommand.
The unverified-candidate framing (candidates, not proven vulnerabilities) is preserved throughout, and the new contracts are in the schema and generated TypeScript types.
Duplicate detection now covers web formats. fallow dupes tokenizes .css, .scss, .sass, and .less files plus Vue, Svelte, and Astro template and style regions. Section boundaries are respected, so a clone candidate can never be stitched together from script, markup, and style tokens, and same-format namespacing keeps JS, style, and markup regions from forming cross-format clone groups. Warm duplicate-token caches refresh on upgrade.
Framework detector coverage in health JSON. fallow health --format json gains an optional framework_health block listing detected framework ids and per-detector status (active, disabled, abstained, or not-checked) when the run already has the dead-code analysis it needs, so it is visible why a framework-specific finding did or did not surface.
unused-component-prop now covers Svelte 5 $props(). A Svelte prop declared via $props() and read nowhere now reports through the existing rule, with the same output shape, severity, action, and suppression token as Vue and React. It stays conservative, requiring a declared svelte / @sveltejs/kit dependency and abstaining on rest, computed, nested, or whole-object destructures.
Rule packs can ban catalogue-derived effect classes. New banned-effect rules let teams forbid calls whose callee matches an internal security-catalogue effect such as network, storage, shell, crypto, randomness, dom, or database, resolved through the same written and import-resolved callee matching as banned-call. Findings report as policy-violation with scoped suppression via <pack>/<rule-id>.
Bug fixes
audit --gate new-onlyno longer flags pre-existing clones as introduced after a line shift. Editing a large file so an unchanged duplicate block moved to new line numbers could fail the gate. Clone attribution now matches inherited clones by content fingerprint independent of line position. Thanks @dotmaster for the report.- Local-only value exports report consistently as unused. A value export referenced only by another export in the same file is again treated as unused public surface by default, so
dead-code,trace, and stale-suppression detection agree. The fix action still removes only the export modifier. fallow dupes --format compactemits traceable clone lines with thecode-duplicationtag and the stabledup:<id>fingerprint, so agents can jump straight tofallow dupes --trace dup:<id>.
Performance
- SARIF file output streams to disk. Writing SARIF with
--output-file/-o(or--sarif-file) streams to the file instead of buffering the whole document, lowering peak memory on large result sets. Output content is unchanged.
Documentation
- MCP server setup now documents project devDependency installs (
npx/pnpm exec/yarn/bunx) alongside the global form, fixing anENOENTstartup failure. Thanks @wouterkroes. - Refreshed the duplicate-detection benchmark comparison numbers against current upstream releases. Thanks @kucherenko.
Full Changelog: v2.99.0...v2.100.0
v2.99.0: opt-in suppression reasons
This release adds opt-in suppression hygiene and fixes three reported issues across detection accuracy, the editor extension, and catalog support.
Features
Require a documented reason on suppressions
A new opt-in require-suppression-reason rule lets teams enforce that every suppression carries a written justification. Suppression comments and @expected-unused JSDoc tags can now take a trailing -- <reason>:
// fallow-ignore-next-line unused-export -- public compatibility export
export const legacyHelper = () => {};
// fallow-ignore-file -- generated route mapThe reason text is parsed, cached, and carried through to suppression hygiene reporting. The default is off, so existing suppressions are unaffected. Set rules.require-suppression-reason to "warn" or "error" and any fallow-ignore-* comment or @expected-unused tag without a reason surfaces as a missing-suppression-reason finding so the team can backfill it. Reported across human, JSON, SARIF, CodeClimate, audit, and baseline output plus the LSP and editors. Thanks @codingthat for the request. (Closes #1302)
Bug fixes
-
Nested same-file schema values no longer false-positive as unused. When an exported value is composed into another reachable exported schema in the same file (for example
Schema.Array(Foo)), the child value is now credited through the parent, while unrelated unused sibling schemas still report. Thanks @danielo515 for the report. (Closes #1304) -
The VS Code / VS Codium extension no longer shows inflated totals on startup. On a cold start the extension could consume the LSP's first workspace analysis before any document had opened, leaving a stale, too-high count until the next edit. Startup analysis now waits for the first opened document, and a save-triggered analysis queues behind an in-flight startup run instead of being dropped. Thanks @codingthat for the report. (Closes #1303)
-
Catalog rules now read Bun catalogs from the root
package.json. Bun workspaces that declare catalog entries underworkspaces.catalog/workspaces.catalogs(or Bun's top-levelcatalog/catalogsform) now get the sameunresolved-catalog-references,unused-catalog-entries, andempty-catalog-groupscoverage as pnpm workspaces. Fallow still preferspnpm-workspace.yamlwhen present. Thanks @codingthat for the report. (Closes #1301)
Full Changelog: v2.98.0...v2.99.0