Repository navigation
v2.72.0: minOccurrences filter, --top fix, monorepo dep ownership, Cursor/Windsurf install fix
This release ships a noisy-clone filter for fallow dupes, a --top ranking fix, a workspace dep-ownership fix for monorepos, a Cursor/Windsurf install fix for the VS Code extension, and a static-factory member-tracking fix.
Features
fallow dupes --min-occurrences N (config: duplicates.minOccurrences)
Two-instance clones often reflect context-sensitive parallel code (similar reducer pairs, mirrored controller handlers) that's not worth extracting into a shared helper. Raising minOccurrences to 3 or higher tells fallow to report only widespread copy-paste, the kind that's actually worth refactoring.
fallow dupes --min-occurrences 3Threaded through fallow dupes, fallow audit, the bare fallow pipeline, the MCP find_dupes / trace_clone tools, and the napi binding (DuplicationOptions.min_occurrences). The default stays at 2 for backwards compatibility; fallow init writes minOccurrences: 3 into freshly-generated config files. Validation rejects < 2 at both the config-load and CLI-parse layers.
The filter runs AFTER line-level // fallow-ignore-* code-duplication suppressions, so a suppressed instance correctly reduces the group's effective size for the threshold. A new stats.clone_groups_below_min_occurrences JSON field discloses how many groups were hidden so consumers can lower the threshold without re-running. duplication_percentage keeps reflecting the FULL corpus (computed before the filter) so threshold gates and trend lines stay stable when the filter changes.
Refs #342. Thanks @danielo515 for the report.
Bug fixes
Workspace dependency ownership scopes per package.json
Monorepo unlisted-dependency detection now matches per-workspace ownership instead of the root. Previously a dependency declared only in the root package.json (or any workspace's @types/X) globally satisfied bare imports from any workspace, hiding real unlisted-dependency findings inside individual workspaces. The most-specific owning workspace's package.json is now the source of truth for files inside that workspace; root deps only count for files outside every workspace.
Upgrade note: monorepo users may see new unlisted-dependencies findings where root-declared deps are imported from workspaces that do not redeclare them. The new findings are correct semantics, the prior behavior was too permissive.
Closes #343. Thanks @M-Hassan-Raza for the patch.
fallow dupes --top N returns the N MOST-DUPLICATED clone groups
The truncation step ran against a path-sorted list, so --top 20 effectively returned twenty arbitrary groups whose file paths sorted earliest. Real-world impact on a 494-group svelte run: the previous behavior put cosmetic test-fixture pairs at positions 1 through 20 and buried the actual 33-instance hotspot at position 487.
After this fix, --top first sorts by instance count descending (tiebreak: line count descending, then deterministic path/line) and only then truncates, so users see the widest-impact clones first. stats.clone_groups and stats.clone_instances are also recomputed to match the truncated array length so JSON consumers iterating clone_groups[] see the same count as the stats block. CI baselines that captured --top N output may show different groups after upgrade.
Static factory methods no longer false-flag instance methods as unused
class MyClass { static getInstance() { return new this(); } getData() {...} } followed by const x = MyClass.getInstance(); x.getData(); in another file used to report MyClass.getData as unused. The call result was untyped and fallow's instance-binding tracking only fired on direct new ClassName() shapes.
Static methods whose body's last top-level statement is return new this() or return new <SameClassName>() now carry an is_instance_returning_static flag on their MemberInfo; a new analyze-layer pass walks const <local> = <ID>.<method>() bindings (cross-file via imports, same-file via local class declarations, plus barrel re-export chains) and credits accesses on <local> against the resolved class export.
Conservative on purpose: chained .build() calls, ternary returns, and named-target factories (Wrapper.getBuilder() { return new MyClass(); }) stay out of scope to avoid wrong-class crediting. Real-world delta on the next.js fixture: 3 false positives removed (Batcher.create(), HeadersAdapter.from()). Cache version bumped from 75 to 76.
Closes #346. Thanks @robbtraister for the report.
VS Code extension installs on Cursor and Windsurf again
engines.vscode was ^1.116.0 and Windsurf 1.110.1 (and other VS Code forks tracking older bases) rejected install with "not compatible with Windsurf '1.110.1'". The 1.116 floor came in incidentally when dependabot bumped @types/vscode; vsce demands engines.vscode >= @types/vscode, so the engine floor followed. No code in the extension actually used any VS Code API newer than 1.65, so the floor was unnecessary.
The floor now matches the December 2024 baseline (^1.96.0) that Cursor, Windsurf, and current VS Code can all satisfy. Dependabot now ignores @types/vscode in editors/vscode so the floor cannot drift again without a deliberate bump.
Refs #287. Thanks @FunctionDJ for the install-failure report.
Dependencies
Routine bumps for the underlying toolchain: napi-derive 3.5.4 -> 3.5.5, napi 3.8.5 -> 3.8.6, tokio 1.52.1 -> 1.52.2, rmcp 1.5.0 -> 1.6.0, oxc_coverage_instrument (oxc group), github/codeql-action 4.35.2 -> 4.35.3. VS Code extension dev deps: rolldown, @tanstack/intent.
Full Changelog: v2.71.1...v2.72.0