Skip to content

Releases: fallow-rs/fallow

v3.3.0: verifiable plugin authoring, config recommendations, and hardened agent surfaces

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 09 Jul 11:34
v3.3.0
c9326d9

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 for manifestEntries rules the matched manifests, when-gate results, seeded entries with path_exists, and typed warnings[]. Deterministic output, always exit 0.
  • manifestEntries for external plugins: a fallow-plugin-*.jsonc can now derive entry points from framework manifest files (recursive glob + JSON/JSONC parse + dotted-field when gate + ${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_closure MCP tool: the pre-edit blast radius as a one-call primitive. Wraps fallow dead-code --impact-closure <path> --format json (reverse dependencies, re-export chains, coordination gaps) so agents no longer pay for the full inspect_target bundle to answer "what breaks if I change this file?".
  • Self-documenting capability surface: fallow schema now enumerates the security-candidate category ids (with an include_required flag), publishes each rule's default severity and opt-in status, and every config-schema key 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-for over a ref.value / store.<field> source, Angular @for / *ngFor in an external templateUrl, and function-local array receivers (#1716, #1717, #1718).
  • React Router and Remix route loaders now feed unused-load-data-key with framework-scoped harvesting, and fallow health --css reports near_duplicate_css_in_js_tokens alongside 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/node no longer aborts the host Node process: the addon ships with a dedicated unwinding profile and catches panics at the FFI boundary, surfacing a structured FallowNodeError with code FALLOW_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 --hotspots reports churn-fetch failures on stderr instead of emitting a second JSON document.

Performance

  • fallow audit precomputes its focus-map signals in one graph pass, removing an O(changed files x references) cost on large PRs.
  • fallow health --css parses 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

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 05 Jul 21:34
v3.2.0
0d56827

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 thresholdOverrides entry, for example { "files": ["**/*.test.*"], "maxUnitSize": 500 }. Leave functions empty so the override covers both the describe() wrapper and the individual it() 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

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 05 Jul 11:09
v3.1.0
71410c4

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

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 04 Jul 09:17
v3.0.0
630722b

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.0

Styling 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 (or audit.css: false) disables styling analysis entirely.
  • --no-css-deep (or audit.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 | schema scaffolds, inspects, and validates policy packs. init drops in a starter or architecture-oriented pack and can wire it straight into your config. list shows loaded packs with their sources, severities, matchers, and messages (human or JSON). test and schema validate a pack before you ship it.
  • V2 matchers. Scope rules to boundary zones, ban direct exports with banned-export, and use a trailing /* banned-import specifier for subpath-only deep-import bans. Everything reports under one stable policy-violation family, 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 MCP guard tool, 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 new argument 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-security crate. 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

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 01 Jul 19:01
v2.104.0
e36026d

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 produced null css_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 --css reports a separate styling_health score (0-100) and A-F grade with a Deductions: breakdown across five capped penalty categories. It carries a confidence marker (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-height values). STYLING_HEALTH_FORMULA_VERSION bumps to 3. If you diff styling_health.score/grade over time, re-baseline or gate on formula_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 json now carries a reverse index of where each design token is consumed. Change --color-brand (Tailwind @theme) or a StyleX defineVars / vanilla-extract createTheme token and see a consumer_count plus located consumers[] before touching it. consumer_count is a static lower bound (descriptive context, not a deletion gate); the authoritative dead-token finding stays unused_theme_tokens.
  • New get_token_blast_radius MCP tool. A focused, read-only tool that surfaces the token blast-radius directly without the agent needing to know the data hides inside css_analytics.
  • Fuzzy CSS clones via value canonicalization. fallow dupes now canonicalizes CSS values on the stylesheet path (a zero-with-unit collapses to bare 0, a hex color expands to its long lowercased form), so value-drifted clones (0px vs 0, #fff vs #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 markdown emits a paste-into-PR artifact; --format json is 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 TS noUnusedParameters and ESLint varsIgnorePattern honor). 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 skip npm install -g fallow and run a fallow already on PATH (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 no fallow is found. Thanks @Jerc92 for the patch in #1662.
  • Coverage upload enrichment. fallow coverage --with-callers uploads importer edges, and the inventory upload now emits per-function complexity and per-file churn.
  • Typed architecture boundaries. fallow-engine, fallow-output, and fallow-api now 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 old fallow-programmatic-cli compatibility 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-member false 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}, Vue v-for (including props.<field> sources), Angular @for / *ngFor inline 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-files no longer false-flags a Next.js page.mdx when next.config wraps its config object. export default withMDX(nextConfig) (the official @next/mdx idiom), 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-files no longer false-flags a commit-and-tag-version updater script. A new plugin (legacy enabler standard-version) credits each bumpFiles[] / packageFiles[] updater module and filename target, from both the package.json key and standalone .versionrc configs, gated on the file existing on disk. Thanks @rbalet for the report. (Closes #1640)
  • unused-class-members no longer false-flags framework-dispatched OpenLayers methods or a coercion-only toString. A handleEvent on an ol/interaction/* subclass and a toString used only through string coercion (template interpolation, String(...), +) are now credited, with tight gating so genuinely-dead members still report. (Closes #1638)
  • Telemetry findings_present is recorded again for fallow flags, fallow watch, and the security survivors / blind-spots subcommands. 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

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 28 Jun 06:35
v2.103.0
ac68a1a

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, and actionability_verdict (a capture with no tracked functions is a first-class insufficient_evidence verdict, never silently read as cold), plus a provenance block (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 discriminators block exposing the inputs behind its verdict: tracking_state (called / never_called / untracked), invocation_ratio, the low_traffic_threshold and min_observation_volume in effect, and trace_count with meets_observation_volume.
  • Source-map upload hint. When coverage analyze --cloud cannot map runtime positions to source and built source maps exist on disk, fallow prints the exact fallow coverage upload-source-maps --dir <dir> command. Human output only; JSON consumers already get the structured coverage_unresolved warning.

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 dupes now 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. Set duplicates.ignoreDefaults: false to restore the previous corpus.

Bug fixes

  • unused-component-props no longer false-flags Svelte bind:/style:/class: directive shorthands or Vue value-less v-bind same-name shorthands. A value-less directive (bind:open, style:height) or a Vue 3.4+ :open shorthand 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-members no longer false-flags a Pinia store member reached through indirection, including inline useFooStore().member, a store passed as a ReturnType<typeof useFooStore> param, and member usage in .ts files. Thanks @Jerc92 and @Ericlm for the reports (#1489, #1488).
  • unused-class-members no 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-props no longer aborts on a spaced </template > closing tag, which previously reported every prop and emit in that SFC as unused. Thanks @Jerc92 (#1439).
  • ignorePatterns now accepts a leading ./. Entries such as ./src/generated/** match the same files as src/generated/** (also applies to ignoreUnresolvedImports).

Full Changelog: v2.102.0...v2.103.0

v2.102.0: code review brief, decision surface, and symbol-level trace

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 23 Jun 19:59
v2.102.0
8a83dc0

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:formatDate

Agent-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. .astro components now participate in the same health suite as Vue/Svelte/Angular/React: a reachable component rendered in no template surfaces as unrendered-component, an interface Props field read nowhere surfaces as unused-component-prop, and fallow health now scores .astro complexity. 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.define but rendered as a tag in no html template surfaces as unrendered-component, and a @state() reactive property read nowhere surfaces as unused-class-member. @property (the public attribute API) is never flagged. Gated on a lit / lit-element / @lit/reactive-element dependency.
  • Deeper React prop coverage for unused-component-prop. The React arm now harvests props from same-file typed interfaces and generic forwardRef components, 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 json output is unchanged.
  • VS Code: clearer tree badges, hardened health spawn, and de-duplicated diagnostics.

Security

  • LLM-call prompt-injection candidate. A new llm-call-injection category (CWE-1427) in the fallow security tainted-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 all fallow security output it is a candidate for verification, not a verified vulnerability, and never appears under bare fallow or the audit gate.

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 namespace and consumed through export * 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 / .ps1 launcher shim on PATH, so LSP-backed diagnostics start reliably. Thanks @ivan-palatov for the report. (#1359)
  • TanStack Router: custom routeFileIgnorePrefix is honored, so files using a configured ignore prefix are no longer flagged as dead code. Thanks @Spiralis for the report. (#1358)
  • fallow audit base-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, so fallow ci-template gitlab --vendor pipelines run without reaching out to raw.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

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 20 Jun 22:13
v2.101.0
5da5e73

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 bare fallow and fallow audit) on large codebases. Reported clones are unchanged.
  • Faster repeated stylesheet resolution. Resolving external (node_modules) stylesheet @import / @use chains now reuses a single resolver session across lookups instead of rebuilding the resolver and re-reading every workspace package.json per 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 by unrendered-component as "reachable but rendered nowhere". The render-usage walk now follows namespace re-export edges back to the underlying .vue files, 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 further export * / export * as re-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.schema in a subdirectory and no varlock dependency previously left the plugin inactive, so packages declared via @plugin(...) in that schema were reported as unused. They are now credited.
  • fallow ci-template gitlab now bundles the shared gitlab_common.sh helper. The vendored GitLab CI template sources gitlab_common.sh from 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

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 19 Jun 10:49
v2.100.0
eaeb063

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 --quiet

Symbol 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 survivors joins raw fallow security --format json candidates with a verdict file, reporting confirmed survivors without rewriting candidate output. It surfaces summary.unverdicted, separates verifier dispositions from unreviewed candidates, and offers --require-verdict-for-each-candidate as a strict complete-verdict CI gate.
  • fallow security blind-spots groups unresolved security callees and accepts --file before 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-only no 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 compact emits traceable clone lines with the code-duplication tag and the stable dup:<id> fingerprint, so agents can jump straight to fallow 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 an ENOENT startup 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

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 18 Jun 06:34
v2.99.0
c2be0d5

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 map

The 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 under workspaces.catalog / workspaces.catalogs (or Bun's top-level catalog / catalogs form) now get the same unresolved-catalog-references, unused-catalog-entries, and empty-catalog-groups coverage as pnpm workspaces. Fallow still prefers pnpm-workspace.yaml when present. Thanks @codingthat for the report. (Closes #1301)

Full Changelog: v2.98.0...v2.99.0