Skip to content

v2.89.0: editor intelligence, churn import, and detection fixes

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 05 Jun 16:25
· 190 commits to main since this release
v2.89.0
647fe2d

fallow 2.89.0 is an editor-intelligence release. The VS Code extension now surfaces health, complexity, security candidates, runtime coverage, the audit verdict, license management, and a monorepo workspace picker, and it explains complexity inline in the editor. The language server gets a large memory-leak fix, single-pass monorepo analysis, per-file circular-dependency diagnostics, and a references code-lens fix. Alongside the editor work: churn import for non-git VCS, sharper telemetry, more security-candidate categories, and a batch of dependency-detection accuracy fixes.

Telemetry stays opt-in, allowlisted, and off by default.

Editor: VS Code + language server

  • Inline complexity, line by line. fallow health --complexity-breakdown adds a contributions[] array to each complexity finding (one entry per decision point: each else if, nested if, && / || / ??, loop, case, catch, ternary, optional-chain), with its source line, the metric it adds to, its weight, and nesting depth. The extension renders a dim +N after each contributing line, a hover with the per-kind breakdown, and a CRAP explanation on the function signature (for example CRAP 420: cyclomatic 20, untested (0% covered). Full test coverage would bring CRAP down to 20.). New settings: fallow.complexity.breakdownEnabled, fallow.complexity.afterText, fallow.complexity.decorationCap. The MCP check_health tool gains a matching complexity_breakdown param. Off by default in CLI/CI output, computed in the existing complexity pass at no measurable cost.
  • More of fallow inside the editor. New sidebar views surface health and complexity, security candidates, runtime coverage, and the audit verdict, plus a monorepo workspace picker and in-editor license management.
  • A scannable sidebar. Duplicate clones are ordered by impact (total duplicated lines) and labeled by their dominant repeated identifier instead of an opaque Clone #N. Health rows lead with the file for a consistent column, complexity rows show <file>:<line> then the function and its metrics, and file paths middle-truncate so the basename:line stays visible (full path on hover). The complexity critical glyph is the section's flame icon, not an alarming red error mark, because these are heuristic candidates.
  • All duplication knobs are exposed. The settings page adds fallow.duplication.minTokens, minLines, skipLocal, crossLanguage, and ignoreImports, and the bare fallow command gains matching combined-mode flags so the extension applies them without a config-file edit.
  • Clearer all-clear. A clean run now says the all-clear applies to analyzed JS/TS files, records a short dead-code and duplication summary in the Fallow output channel, and offers to open it. Duplicate-code-only findings no longer collapse into the all-clear path, and the sidebar exposes the diagnostic mute manager from the view title bars.
  • License indicator only when relevant. The status-bar license badge is created only when license material is actually present (active, in-grace, or expired, found via $FALLOW_LICENSE, $FALLOW_LICENSE_PATH, or ~/.fallow/license.jwt, checked locally with no fallow call on startup). A machine that never had a license shows nothing. Activating or deactivating a license updates the badge without a reload.
  • References code lens fixed. Clicking a "N references" lens no longer throws "argument does not match one of these constraints". The lens now routes through a fallow.showReferences command that converts the language server's JSON arguments into the Uri / Position / Location values VS Code expects.
  • Every file in a cycle gets its own squiggly. Each participating file is marked at the exact import that points to the next file, the message rotates to read from the file you are in (standing in b of a → b → c → a reads Circular dependency (3 files): b → c → a → b), and related links jump to each real hop. Every per-file marker carries a shared cycle id in Diagnostic.data so editors and agents can fold the markers back into one cycle. fallow ... --format json gains an additive, optional edges[] array on each circular_dependencies[] entry (one { path, line, col } per hop); always emitted, not required by the schema, so existing consumers and baseline files are unaffected.

The editor improvements that depend on new diagnostics (references lens, per-file circular deps) require the matching fallow-lsp build shipped here.

Performance

  • Language server memory leak on dynamic-import patterns fixed. A file with import(`./${x}`) or require(`./${x}`) style imports used to credit every matched target once per pattern, so a file with many such patterns over a large tree accumulated graph symbols proportional to patterns times files, driving fallow-lsp into tens of GB on large React Native / Expo codebases. Each distinct target is now credited at most once per importing file; reachability and output are unchanged. Thanks @ReallyFloppyPenguin for the detailed report.
  • Monorepos analyzed once, not once per package. fallow-lsp previously re-ran the full pipeline for the workspace root and again for every sub-package. The single root pass already walks the whole tree and is workspace-aware, so total work no longer scales with the package count. This also matches the CLI and removes a class of false-positive unused-export findings that came from analyzing a sub-package in isolation. A sub-package's own .fallowrc.json still applies when that folder is opened directly.

Health: change history without git

  • --churn-file imports change history. Projects on a non-git VCS (Arc, Mercurial, Perforce) used to see "hotspot analysis skipped: no git repository found". A new global --churn-file <path> flag accepts a normalized fallow-churn/v1 JSON document, and --hotspots, --ownership, and --targets then run their usual recency-weighting, trend, and ownership logic on the imported events. The flag resolves relative to --root, wins over git when both are present, and is exposed on the MCP check_health tool as churn_file. The import is authoritative for the analysis window. Scope: this powers the churn-backed health signals only; audit, impact, and --changed-since still need git for the base tree. Thanks @albion9919 for the request.

Security candidates

  • More dangerous-sink categories. The tainted-sink catalogue gained candidate categories surfaced under fallow security: dynamic module loading, file-system path traversal, HTTP response header injection, more raw-SQL escape hatches (Prisma unsafe raw, Knex raw, sequelize.literal), DOM navigation, source-backed mass assignment, additional SSRF clients, insecure randomness, deprecated cipher constructors, template escape bypass, XPath injection, unsafe Buffer.allocUnsafe, and react-native-webview injected scripts. Each fires only on a non-literal argument; fully-literal calls never fire, and every finding stays a candidate for verification, not a proven vulnerability.
  • Framework template HTML injection sinks. Non-literal Svelte {@html ...}, Vue v-html="...", and Angular [innerHTML]="..." bindings now feed the existing dangerous-html candidate flow with source spans. Literal bindings stay quiet; no new schema.

Dependency detection accuracy

  • Pino transport targets such as pino({ transport: { target: 'pino-pretty' } }) are credited as runtime references instead of reported unused. Thanks @MathieuSchaff for the report.
  • Package-root resolution such as resolveModuleDir("ffmpeg-static") or require.resolve(`${packageName}/package.json`) with static values no longer reports those packages unused.
  • : this fluent chains. Constructor-rooted chains whose intermediate methods declare TypeScript's polymorphic this return type no longer mis-report later methods as unused-class-members.
  • Bun default test files (*.test, *_test, *.spec, *_spec) are now test entry points when the Bun plugin is active, scoped by [test].root when present.
  • TanStack Router route files no longer surface as duplicate Route exports; the contract export is skipped only for real route files or files referenced by routeTree.gen.*.
  • Nuxt UI script-side icon strings like icon: 'i-simple-icons-github' credit their @iconify-json/* collection package via longest-prefix matching.
  • Playwright webServer.command template literals such as pnpm build && pnpm exec srvx --port ${PORT} now credit pnpm exec CLI packages.

Telemetry (opt-in, off by default)

  • findings_present reports whether an analysis surfaced any findings, decoupled from the exit-code gate. Previously fallow dupes always reported outcome: "success" under its default config, so duplication was invisible in aggregate.
  • Workflow split. fallow impact, security, fix, and explain each emit a distinct workflow value instead of collapsing into unknown.
  • MCP surface tagging. MCP tool calls are attributed to the mcp surface with a per-tool mcp_tool dimension instead of looking like any other cli_json run. The new fields are optional and additive, with the same privacy posture and consent model. In-process surfaces (LSP, VS Code, N-API, programmatic) still emit nothing.

Other fixes and additions

  • fallow audit now reclaims base-snapshot worktree caches whose directory was deleted out from under git (a $TMPDIR reaper, container restart, or CI cache eviction), even when age-based GC is disabled.
  • Stale-install nudge. On a successful human TTY run, fallow prints one concise stderr hint when the running stable version is behind, naming its inline opt-out (FALLOW_UPDATE_CHECK=off). Machine formats, quiet runs, CI, and non-TTY agent paths stay byte-identical. The background check is best-effort, throttled, and suppressed by DO_NOT_TRACK, FALLOW_TELEMETRY_DISABLED, or FALLOW_UPDATE_CHECK=off.
  • Clone suggested_name. fallow dupes --format json (and the dupes block in fallow / fallow audit) adds a best-effort suggested_name to each clone_groups[] entry: the dominant repeated identifier, or absent when there is no clear name. Additive and optional.

One-time note: the incremental cache is invalidated on upgrade (cache version bump), so the first run after upgrading re-parses the project once.

Full Changelog: v2.88.3...v2.89.0