fallow 2.73.0 → 2.75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -75,7 +75,7 @@ fallow dupes --save-baseline # Save current duplication as baseline
75
75
 
76
76
  ## Framework support
77
77
 
78
- 94 built-in plugins covering Next.js, Nuxt, Remix, Qwik, SvelteKit, Gatsby, Astro, Angular, NestJS, Expo Router, Vite, Webpack, Vitest, Jest, Playwright, Cypress, Storybook, ESLint, TypeScript, Tailwind, UnoCSS, Prisma, Drizzle, Convex, Turborepo, Hardhat, and many more. Auto-detected from your `package.json`.
78
+ 95 built-in plugins covering Next.js, Nuxt, Remix, Qwik, SvelteKit, Gatsby, Astro, Angular, NestJS, AdonisJS, Expo Router, Vite, Webpack, Vitest, Jest, Playwright, Cypress, Storybook, ESLint, TypeScript, Tailwind, UnoCSS, Prisma, Drizzle, Convex, Turborepo, Hardhat, and many more. Auto-detected from your `package.json`.
79
79
 
80
80
  ## Configuration
81
81
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "2.73.0",
3
+ "version": "2.75.0",
4
4
  "description": "Codebase intelligence for TypeScript and JavaScript. Finds unused code, duplication, circular dependencies, complexity hotspots, and architecture drift. Optional runtime intelligence layer (Fallow Runtime) adds production execution evidence. Rust-native, sub-second, 94 framework plugins.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -73,13 +73,13 @@
73
73
  "@tanstack/intent": "0.0.40"
74
74
  },
75
75
  "optionalDependencies": {
76
- "@fallow-cli/darwin-arm64": "2.73.0",
77
- "@fallow-cli/darwin-x64": "2.73.0",
78
- "@fallow-cli/linux-x64-gnu": "2.73.0",
79
- "@fallow-cli/linux-arm64-gnu": "2.73.0",
80
- "@fallow-cli/linux-x64-musl": "2.73.0",
81
- "@fallow-cli/linux-arm64-musl": "2.73.0",
82
- "@fallow-cli/win32-arm64-msvc": "2.73.0",
83
- "@fallow-cli/win32-x64-msvc": "2.73.0"
76
+ "@fallow-cli/darwin-arm64": "2.75.0",
77
+ "@fallow-cli/darwin-x64": "2.75.0",
78
+ "@fallow-cli/linux-x64-gnu": "2.75.0",
79
+ "@fallow-cli/linux-arm64-gnu": "2.75.0",
80
+ "@fallow-cli/linux-x64-musl": "2.75.0",
81
+ "@fallow-cli/linux-arm64-musl": "2.75.0",
82
+ "@fallow-cli/win32-arm64-msvc": "2.75.0",
83
+ "@fallow-cli/win32-x64-msvc": "2.75.0"
84
84
  }
85
85
  }
package/schema.json CHANGED
@@ -165,6 +165,7 @@
165
165
  "feature-flags": "off",
166
166
  "stale-suppressions": "warn",
167
167
  "unused-catalog-entries": "warn",
168
+ "empty-catalog-groups": "warn",
168
169
  "unresolved-catalog-references": "error",
169
170
  "unused-dependency-overrides": "warn",
170
171
  "misconfigured-dependency-overrides": "error"
@@ -953,6 +954,10 @@
953
954
  "$ref": "#/$defs/Severity",
954
955
  "default": "warn"
955
956
  },
957
+ "empty-catalog-groups": {
958
+ "$ref": "#/$defs/Severity",
959
+ "default": "warn"
960
+ },
956
961
  "unresolved-catalog-references": {
957
962
  "$ref": "#/$defs/Severity",
958
963
  "default": "error"
@@ -1039,7 +1044,7 @@
1039
1044
  "const": "feature-sliced"
1040
1045
  },
1041
1046
  {
1042
- "description": "Bulletproof React: app → features → shared + server.\nFeature modules are isolated from each other; shared utilities and server\ninfrastructure form the base layers.",
1047
+ "description": "Bulletproof React: app → features → shared + server.\nFeature modules are isolated from each other via `autoDiscover`: every\nimmediate child of `src/features/` becomes its own `features/<name>` zone,\nand cross-feature imports are reported as boundary violations.\n\n**Trade-off (intentional):** top-level files in `src/features/` (e.g.\n`src/features/index.ts` barrel, `src/features/types.ts`) do NOT match any\nchild pattern and are unclassified, meaning they are unrestricted by the\npreset. This is deliberate so feature barrels can re-export children\nwithout producing false-positive `features → features/<child>` violations.\nTo classify top-level files strictly, override the `features` zone with\nan explicit user definition that includes a `patterns` field.",
1043
1048
  "type": "string",
1044
1049
  "const": "bulletproof"
1045
1050
  }
@@ -1060,6 +1065,13 @@
1060
1065
  "type": "string"
1061
1066
  }
1062
1067
  },
1068
+ "autoDiscover": {
1069
+ "description": "Directories whose immediate child directories should become separate\nzones under this logical group.\n\nFor example, `{ \"name\": \"features\", \"autoDiscover\": [\"src/features\"] }`\ncreates zones such as `features/auth` and `features/billing`, each with\na pattern for its own subtree. Rules that reference `features` expand to\nevery discovered child zone. If `patterns` is also set, the parent zone\nremains as a fallback after discovered child zones.",
1070
+ "type": "array",
1071
+ "items": {
1072
+ "type": "string"
1073
+ }
1074
+ },
1063
1075
  "root": {
1064
1076
  "description": "Optional subtree scope for monorepo per-package boundaries.\n\nWhen set, the zone's `patterns` are matched against paths *relative*\nto this directory rather than the project root. At classification\ntime, fallow checks that a candidate path starts with `root` and\nstrips that prefix before glob-matching the patterns against the\nremainder. Files outside the subtree never match the zone.\n\nUseful for monorepos where each package has the same internal\ndirectory layout: instead of writing `packages/app/src/**` and\n`packages/core/src/**` (which collide on shared zone names), set\n`root: \"packages/app/\"` and `patterns: [\"src/**\"]` per package.\n\nTrailing slash and leading `./` are normalized; backslashes are\nconverted to forward slashes. Patterns must NOT redundantly include\nthe root prefix: `root: \"packages/app/\"` with\n`patterns: [\"packages/app/src/**\"]` is rejected with\n`FALLOW-BOUNDARY-ROOT-REDUNDANT-PREFIX` because patterns are\nresolved relative to the root.",
1065
1077
  "type": [
@@ -1069,8 +1081,7 @@
1069
1081
  }
1070
1082
  },
1071
1083
  "required": [
1072
- "name",
1073
- "patterns"
1084
+ "name"
1074
1085
  ]
1075
1086
  },
1076
1087
  "BoundaryRule": {
@@ -1419,6 +1430,16 @@
1419
1430
  }
1420
1431
  ]
1421
1432
  },
1433
+ "empty-catalog-groups": {
1434
+ "anyOf": [
1435
+ {
1436
+ "$ref": "#/$defs/Severity"
1437
+ },
1438
+ {
1439
+ "type": "null"
1440
+ }
1441
+ ]
1442
+ },
1422
1443
  "unresolved-catalog-references": {
1423
1444
  "anyOf": [
1424
1445
  {
@@ -95,11 +95,14 @@ cargo install fallow-cli # build from source
95
95
  | Unlisted dependencies | `--unlisted-deps` | Used packages missing from package.json. In monorepos, importing a workspace package from a workspace whose own `package.json` does not list it is reported here too; self-references stay allowed without requiring a package to depend on itself. |
96
96
  | Duplicate exports | `--duplicate-exports` | Same symbol exported from multiple modules |
97
97
  | Circular dependencies | `--circular-deps` | Import cycles in the module graph |
98
- | Boundary violations | `--boundary-violations` | Imports crossing architecture zone boundaries. Presets: `layered`, `hexagonal`, `feature-sliced`, `bulletproof` |
98
+ | Boundary violations | `--boundary-violations` | Imports crossing architecture zone boundaries. Presets: `layered`, `hexagonal`, `feature-sliced`, `bulletproof`; `autoDiscover` can create one zone per feature directory |
99
99
  | Stale suppressions | `--stale-suppressions` | `fallow-ignore` comments or `@expected-unused` JSDoc tags that no longer match any issue |
100
100
  | Test-only dependencies | n/a | Production deps only imported from test files (should be devDependencies) |
101
101
  | Unused pnpm catalog entries | `--unused-catalog-entries` | `pnpm-workspace.yaml` entries no workspace package.json references via `catalog:` (default `warn`) |
102
+ | Empty pnpm catalog groups | `--empty-catalog-groups` | Named `catalogs.<name>:` groups in `pnpm-workspace.yaml` with no entries. Top-level `catalog:` placeholders are ignored. Default `warn`. |
102
103
  | Unresolved pnpm catalog references | `--unresolved-catalog-references` | `package.json` references to `catalog:` / `catalog:<name>` whose catalog does not declare the package; `pnpm install` would fail. Default `error`. Suppress via `ignoreCatalogReferences: [{ package, catalog?, consumer? }]` in fallow config (package.json has no comment syntax). |
104
+ | Unused pnpm dependency overrides | `--unused-dependency-overrides` | `pnpm-workspace.yaml#overrides` / `package.json#pnpm.overrides` entries whose target package is not declared by any workspace `package.json` and is not present in `pnpm-lock.yaml`. Default `warn`. When the lockfile is missing or unreadable the check degrades to a manifest-only fallback and every finding carries a `hint` reminding consumers to verify before removal. Suppress via `ignoreDependencyOverrides: [{ package, source? }]` in fallow config. |
105
+ | Misconfigured pnpm dependency overrides | `--misconfigured-dependency-overrides` | `pnpm.overrides` entries whose key is unparsable (empty, dangling separators, malformed selectors) or value is missing/empty. `pnpm install` would fail. Default `error`. Suppression: same `ignoreDependencyOverrides` config rule. |
103
106
 
104
107
  ## MCP Tools
105
108
 
@@ -72,6 +72,11 @@ Analyzes the project for unused files, exports, dependencies, types, members, an
72
72
  | `--circular-deps` | Circular dependencies |
73
73
  | `--boundary-violations` | Boundary violations (imports crossing architecture zone boundaries) |
74
74
  | `--stale-suppressions` | Stale suppression comments or `@expected-unused` JSDoc tags |
75
+ | `--unused-catalog-entries` | Unused pnpm catalog entries |
76
+ | `--empty-catalog-groups` | Empty named pnpm catalog groups |
77
+ | `--unresolved-catalog-references` | Package references to missing pnpm catalog entries |
78
+ | `--unused-dependency-overrides` | Unused pnpm dependency overrides |
79
+ | `--misconfigured-dependency-overrides` | Malformed pnpm dependency overrides |
75
80
 
76
81
  ### Examples
77
82
 
@@ -192,7 +197,7 @@ fallow dupes --format json --quiet --baseline fallow-baselines/dupes.json --thre
192
197
 
193
198
  ## `fix`: Auto-Remove Unused Code
194
199
 
195
- Auto-removes unused exports and dependencies.
200
+ Auto-removes unused exports, dependencies, enum members, and pnpm catalog entries.
196
201
 
197
202
  ### Flags
198
203
 
@@ -204,6 +209,13 @@ Auto-removes unused exports and dependencies.
204
209
  | `--format` | `human\|json` | `human` | Output format |
205
210
  | `--quiet` | bool | `false` | Suppress progress bars |
206
211
 
212
+ ### What gets fixed
213
+
214
+ - Unused exports (removes the `export` keyword; whole-enum block when every member is unused)
215
+ - Unused dependencies (removed from `package.json`)
216
+ - Unused enum members (removed from the declaration)
217
+ - Unused pnpm catalog entries (removed from `pnpm-workspace.yaml` by line-aware deletion; comments preserved). When the last entry of a catalog group is removed, the header is rewritten to `catalog: {}` / `<name>: {}` so pnpm doesn't reject the resulting null value. Entries with non-empty `hardcoded_consumers` are skipped to avoid breaking `pnpm install`; the skip is surfaced in the JSON fix output as `{"type": "remove_catalog_entry", "applied": false, "skipped": true, "skip_reason": "hardcoded_consumers", "consumers": [...]}`. After a successful catalog edit the CLI emits a one-line `Run pnpm install to refresh pnpm-lock.yaml` reminder. The JSON envelope carries a top-level `"skipped"` count alongside `"total_fixed"` for partial-fix gating.
218
+
207
219
  ### Examples
208
220
 
209
221
  ```bash
@@ -425,7 +437,7 @@ fallow health --format json --quiet --trend
425
437
  ```json
426
438
  {
427
439
  "schema_version": 3,
428
- "version": "2.72.0",
440
+ "version": "2.74.0",
429
441
  "elapsed_ms": 32,
430
442
  "summary": {
431
443
  "files_analyzed": 482,
@@ -811,7 +823,7 @@ fallow audit \
811
823
  ```json
812
824
  {
813
825
  "schema_version": 3,
814
- "version": "2.72.0",
826
+ "version": "2.74.0",
815
827
  "command": "audit",
816
828
  "verdict": "fail",
817
829
  "changed_files_count": 12,
@@ -884,7 +896,7 @@ fallow flags --format json --quiet --workspace my-package
884
896
  ```json
885
897
  {
886
898
  "schema_version": 3,
887
- "version": "2.72.0",
899
+ "version": "2.74.0",
888
900
  "elapsed_ms": 116,
889
901
  "feature_flags": [],
890
902
  "total_flags": 0
@@ -1303,7 +1315,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
1303
1315
  ```json
1304
1316
  {
1305
1317
  "schema_version": 3,
1306
- "version": "2.72.0",
1318
+ "version": "2.74.0",
1307
1319
  "elapsed_ms": 45,
1308
1320
  "total_issues": 12,
1309
1321
  "entry_points": {
@@ -1450,7 +1462,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
1450
1462
  ```json
1451
1463
  {
1452
1464
  "schema_version": 3,
1453
- "version": "2.72.0",
1465
+ "version": "2.74.0",
1454
1466
  "elapsed_ms": 82,
1455
1467
  "total_clones": 15,
1456
1468
  "total_lines_duplicated": 230,
@@ -1494,7 +1506,7 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
1494
1506
  {
1495
1507
  "check": {
1496
1508
  "schema_version": 3,
1497
- "version": "2.72.0",
1509
+ "version": "2.74.0",
1498
1510
  "elapsed_ms": 45,
1499
1511
  "total_issues": 12,
1500
1512
  "unused_files": [],
@@ -1516,7 +1528,7 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
1516
1528
  },
1517
1529
  "dupes": {
1518
1530
  "schema_version": 3,
1519
- "version": "2.72.0",
1531
+ "version": "2.74.0",
1520
1532
  "elapsed_ms": 82,
1521
1533
  "total_clones": 15,
1522
1534
  "total_lines_duplicated": 230,
@@ -1525,7 +1537,7 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
1525
1537
  },
1526
1538
  "health": {
1527
1539
  "schema_version": 3,
1528
- "version": "2.72.0",
1540
+ "version": "2.74.0",
1529
1541
  "elapsed_ms": 32,
1530
1542
  "summary": {},
1531
1543
  "findings": [],
@@ -1607,10 +1619,20 @@ Config files are searched in priority order: `.fallowrc.json` > `.fallowrc.jsonc
1607
1619
  "ignorePatterns": ["**/*.generated.ts"]
1608
1620
  },
1609
1621
 
1610
- // Architecture boundaries (preset or custom zones/rules)
1622
+ // Architecture boundaries (preset, custom zones/rules, or auto-discovered feature zones)
1611
1623
  // Presets: "layered", "hexagonal", "feature-sliced", "bulletproof"
1612
1624
  "boundaries": {
1613
1625
  "preset": "bulletproof"
1626
+ // Or:
1627
+ // "zones": [
1628
+ // { "name": "app", "patterns": ["src/app/**"] },
1629
+ // { "name": "features", "autoDiscover": ["src/features"] },
1630
+ // { "name": "shared", "patterns": ["src/shared/**"] }
1631
+ // ],
1632
+ // "rules": [
1633
+ // { "from": "app", "allow": ["features", "shared"] },
1634
+ // { "from": "features", "allow": ["shared"] }
1635
+ // ]
1614
1636
  },
1615
1637
 
1616
1638
  // Production mode
@@ -117,12 +117,16 @@ stale_suppressions: StaleSuppression[]
117
117
  * Entries in pnpm-workspace.yaml's catalog: or catalogs: sections not referenced by any workspace package via the catalog: protocol.
118
118
  */
119
119
  unused_catalog_entries?: UnusedCatalogEntry[]
120
+ /**
121
+ * Named groups under pnpm-workspace.yaml's catalogs: section that declare no package entries. The top-level catalog: map is not reported.
122
+ */
123
+ empty_catalog_groups?: EmptyCatalogGroup[]
120
124
  /**
121
125
  * Workspace package.json references to catalogs (catalog: or catalog:<name>) that do not declare the consumed package. pnpm install will error until the named catalog grows to include the package or the reference is switched / removed.
122
126
  */
123
127
  unresolved_catalog_references?: UnresolvedCatalogReference[]
124
128
  /**
125
- * Entries in pnpm-workspace.yaml's overrides: section, or package.json's pnpm.overrides block, whose target package no workspace package depends on. Default severity is warn because some entries are intentional pins for transitive CVEs; the hint field flags the cases the conservative algorithm cannot disambiguate.
129
+ * Entries in pnpm-workspace.yaml's overrides: section, or package.json's pnpm.overrides block, whose target package is not declared by any workspace package and is not present in pnpm-lock.yaml. Default severity is warn because projects without a readable lockfile fall back to manifest-only checks; the hint field flags those conservative cases.
126
130
  */
127
131
  unused_dependency_overrides?: UnusedDependencyOverride[]
128
132
  /**
@@ -151,7 +155,7 @@ export interface FixAction {
151
155
  /**
152
156
  * Kebab-case identifier for the fix action.
153
157
  */
154
- type: ("remove-export" | "delete-file" | "remove-dependency" | "move-dependency" | "remove-enum-member" | "remove-class-member" | "resolve-import" | "install-dependency" | "remove-duplicate" | "move-to-dev" | "refactor-cycle" | "refactor-boundary" | "export-type" | "remove-catalog-entry" | "update-catalog-reference" | "add-catalog-entry" | "remove-catalog-reference" | "remove-dependency-override" | "fix-dependency-override")
158
+ type: ("remove-export" | "delete-file" | "remove-dependency" | "move-dependency" | "remove-enum-member" | "remove-class-member" | "resolve-import" | "install-dependency" | "remove-duplicate" | "move-to-dev" | "refactor-cycle" | "refactor-boundary" | "export-type" | "remove-catalog-entry" | "remove-empty-catalog-group" | "update-catalog-reference" | "add-catalog-entry" | "remove-catalog-reference" | "remove-dependency-override" | "fix-dependency-override")
155
159
  /**
156
160
  * Whether `fallow fix` can apply this fix automatically.
157
161
  */
@@ -215,9 +219,9 @@ export interface AddToConfigAction {
215
219
  */
216
220
  type: "add-to-config"
217
221
  /**
218
- * Always false for config actions.
222
+ * True when `fallow fix` can apply this config action automatically for the current action type. `ignoreExports` duplicate-export actions are auto-fixable when a config file exists; older scalar config-ignore actions remain manual.
219
223
  */
220
- auto_fixable: false
224
+ auto_fixable: boolean
221
225
  /**
222
226
  * Human-readable description of the config change.
223
227
  */
@@ -604,6 +608,25 @@ hardcoded_consumers?: string[]
604
608
  actions?: IssueAction[]
605
609
  introduced?: AuditIntroduced
606
610
  }
611
+ /**
612
+ * A named pnpm catalog group under catalogs: with no package entries.
613
+ */
614
+ export interface EmptyCatalogGroup {
615
+ /**
616
+ * Named catalog group under the top-level catalogs: map.
617
+ */
618
+ catalog_name: string
619
+ /**
620
+ * Relative path to pnpm-workspace.yaml.
621
+ */
622
+ path: string
623
+ /**
624
+ * 1-based line number of the empty catalog group header.
625
+ */
626
+ line: number
627
+ actions?: IssueAction[]
628
+ introduced?: AuditIntroduced
629
+ }
607
630
  /**
608
631
  * A workspace package.json reference (catalog: or catalog:<name>) pointing at a catalog that does not declare the consumed package. `pnpm install` will fail with ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_CATALOG_PROTOCOL.
609
632
  */
@@ -668,7 +691,7 @@ path: string
668
691
  */
669
692
  line: number
670
693
  /**
671
- * Soft hint for cases the conservative algorithm cannot disambiguate (e.g. a transitive CVE pin not visible to static analysis). Omitted when absent.
694
+ * Soft hint reminding consumers to verify the override before removal. Present because projects without a readable lockfile use the conservative package-manifest fallback. Omitted when absent.
672
695
  */
673
696
  hint?: string
674
697
  actions?: IssueAction[]
@@ -739,6 +762,7 @@ circular_dependencies?: number
739
762
  boundary_violations?: number
740
763
  stale_suppressions?: number
741
764
  unused_catalog_entries?: number
765
+ empty_catalog_groups?: number
742
766
  unresolved_catalog_references?: number
743
767
  unused_dependency_overrides?: number
744
768
  misconfigured_dependency_overrides?: number