v3.15.0: near-miss clone detection, diagnosable threshold overrides, community bug fixes
Highlights
Near-miss clone detection and smarter duplicate triage
fallow dupes --near (config duplicates.near) now also detects function-scoped clones with small structural edits, reported with a similarity score. Every clone-group finding carries a new spread field, and --top ranking boosts clones scattered across distant files above local ones, so the duplicates worth consolidating first rise to the top. A new duplicates.ignoredClones list ("dup:<fingerprint>:<instance_count>") silences clone groups a reviewer has accepted; fingerprints hash the normalized token sequence, so formatting-only edits keep a reviewed clone recognized.
Threshold overrides are finally diagnosable (health schema 10)
A health.thresholdOverrides entry that did not silence a finding now tells you why: every override row carries a required dimension (complexity or crap), a new insufficient status marks an override that raises a ceiling the code still exceeds, and an optional outstanding[] lists the dimensions a matched unit still breaches. Overrides now also apply to synthetic <template> findings in Svelte, Vue, and Astro single-file components, and those findings get a framework-correct suppression hint (<!-- fallow-ignore-next-line complexity --> on the line above the reported line) instead of Angular decorator advice. A follow-up pass made the override rows agree with their findings everywhere: the <component> rollup consults the override resolver, outstanding no longer swaps dimensions between same-named units, status is scored across ceilings, and CodeClimate plus GitHub annotations quote the effective threshold instead of the run-global one. Standalone health and combined output move to schema_version 10; audit output does not embed the health contract and stays at 9. Thanks @lcestou for the report.
Bug fixes
- Type-aware analysis respects an explicit
"private-type-leaks": "off". EnablingtypeAwareforce-enabled the opt-in rule regardless of config, which also made the api-surface sidecar capability mandatory. The explicit setting now wins; unset configs keep the default-on behavior. Thanks @thewirv. cacheMaxAgeDaysandFALLOW_AUDIT_CACHE_MAX_AGE_DAYSactually reclaim audit caches now. The base-snapshot cache GC only visited entries under the current repo's hash prefix, so caches from linked worktrees accumulated forever. Abandoned entries from other repo identities now age out, live owners are skipped (a repo's own0cannot be defeated from outside), and invalid env values warn instead of silently falling back. Thanks @EvanAgee.--gate new-onlyno longer fails clone-removal refactors. Extracting a shared helper and deleting duplicate instances shifted the clone group's attribution key and made the surviving duplication look introduced. Groups whose instances contain no added line are now demoted to inherited, while a genuinely pasted clone still fails the gate. Thanks @devmax128.- Framework template findings show which conditions caused them. A
<template>complexity finding in Vue, Angular, Svelte, or Astro now reports each decision point at its own line and column with the weight it added, so the VS Code inline breakdown andhealth --complexity-breakdownwork in templates too. Thanks @Ericlm. - Windows: added-line attribution now sees untracked files. Native backslash paths passed to the untracked-file diff meant every added-line check on an untracked file silently missed on Windows.
- Svelte await-block complexity labels match the source.
{#await}/{:then}contributions were labeledif; they now use explicitawaitandthenkinds, including inlinethen/catchcontinuations. - Malformed config shapes fail loud. A malformed
extendsvalue, an unknown key in anoverridesentry, or an unknown key in anignoreExportsrule now fails config load with a named error instead of silently disabling the rule it was meant to configure. Runfallow configbefore upgrading to surface rejected shapes ahead of CI. - Resolver fixes:
.jsspecifiers resolve to declaration-only.d.tsmodules;ignoreUnresolvedImportsentries with a leading./match again; multi-binding re-exports from a missing module produce one finding anchored on the specifier instead of one per binding. - Plus:
fallow --helplists every subcommand again,fallow setup-hookscan no longer duplicate a hand-edited managed block, audit read failures no longer fabricate weakening signals, a malformedpnpm-workspace.yamlwarns instead of silently disabling catalog resolution, MCP workspace comma lists work on API-backed tool paths, untrusted values are escaped ingithub-summarytables,fallow-ignore-next-lineinside multi-line block comments anchors correctly, VS Code binary downloads can no longer hang LSP startup, and LSP analysis no longer blocks the dispatch loop.
Performance
Module resolution and graph build got faster: resolve fallback paths reuse the session canonicalize cache, dynamic-import pattern matching memoizes compiled glob matchers, and reference attachment on high-fan-in modules replaces two quadratic rescans with indexed lookups.
Compatibility
- Health and combined JSON output move to
schema_version10 (requireddimensiononthreshold_overrides[]rows, newinsufficientstatus, optionaloutstanding[]). Audit output stays at 9. - Two
--format compacthealth line grammars gained fields (exceeded=onhigh-complexity:, a dimension segment and optionaloutstanding=onthreshold-override:); key=value consumers are unaffected. - Three previously-tolerated malformed config shapes are now load errors (see above).
- TypeScript consumers: each output field references its own exact version alias (
CheckSchemaVersion,HealthSchemaVersion, ...); the legacySchemaVersionexport remains an alias ofCheckSchemaVersion.
Full Changelog: v3.14.0...v3.15.0