v2.89.0: editor intelligence, churn import, and detection fixes
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-breakdownadds acontributions[]array to each complexity finding (one entry per decision point: eachelse if, nestedif,&&/||/??, 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+Nafter each contributing line, a hover with the per-kind breakdown, and a CRAP explanation on the function signature (for exampleCRAP 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 MCPcheck_healthtool gains a matchingcomplexity_breakdownparam. 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 thebasename:linestays visible (full path on hover). The complexitycriticalglyph 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, andignoreImports, and the barefallowcommand 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 nofallowcall 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.showReferencescommand that converts the language server's JSON arguments into theUri/Position/Locationvalues 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
bofa → b → c → areadsCircular dependency (3 files): b → c → a → b), and related links jump to each real hop. Every per-file marker carries a shared cycle id inDiagnostic.dataso editors and agents can fold the markers back into one cycle.fallow ... --format jsongains an additive, optionaledges[]array on eachcircular_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}`)orrequire(`./${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, drivingfallow-lspinto 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-lsppreviously 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-positiveunused-exportfindings that came from analyzing a sub-package in isolation. A sub-package's own.fallowrc.jsonstill applies when that folder is opened directly.
Health: change history without git
--churn-fileimports 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 normalizedfallow-churn/v1JSON document, and--hotspots,--ownership, and--targetsthen 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 MCPcheck_healthtool aschurn_file. The import is authoritative for the analysis window. Scope: this powers the churn-backed health signals only;audit,impact, and--changed-sincestill need git for the base tree. Thanks @albion9919 for the request.
Security candidates
- More dangerous-sink categories. The
tainted-sinkcatalogue gained candidate categories surfaced underfallow 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, unsafeBuffer.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 ...}, Vuev-html="...", and Angular[innerHTML]="..."bindings now feed the existingdangerous-htmlcandidate 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")orrequire.resolve(`${packageName}/package.json`)with static values no longer reports those packages unused. : thisfluent chains. Constructor-rooted chains whose intermediate methods declare TypeScript's polymorphicthisreturn type no longer mis-report later methods asunused-class-members.- Bun default test files (
*.test,*_test,*.spec,*_spec) are now test entry points when the Bun plugin is active, scoped by[test].rootwhen present. - TanStack Router route files no longer surface as duplicate
Routeexports; the contract export is skipped only for real route files or files referenced byrouteTree.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.commandtemplate literals such aspnpm build && pnpm exec srvx --port ${PORT}now creditpnpm execCLI packages.
Telemetry (opt-in, off by default)
findings_presentreports whether an analysis surfaced any findings, decoupled from the exit-code gate. Previouslyfallow dupesalways reportedoutcome: "success"under its default config, so duplication was invisible in aggregate.- Workflow split.
fallow impact,security,fix, andexplaineach emit a distinctworkflowvalue instead of collapsing intounknown. - MCP surface tagging. MCP tool calls are attributed to the
mcpsurface with a per-toolmcp_tooldimension instead of looking like any othercli_jsonrun. 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 auditnow reclaims base-snapshot worktree caches whose directory was deleted out from under git (a$TMPDIRreaper, 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 byDO_NOT_TRACK,FALLOW_TELEMETRY_DISABLED, orFALLOW_UPDATE_CHECK=off. - Clone
suggested_name.fallow dupes --format json(and thedupesblock infallow/fallow audit) adds a best-effortsuggested_nameto eachclone_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