fallow 3.0.0 → 3.2.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
@@ -1,8 +1,8 @@
1
1
  # fallow
2
2
 
3
- **Deterministic codebase intelligence for TypeScript, JavaScript, and styling.**
3
+ **Codebase intelligence for TypeScript and JavaScript.**
4
4
 
5
- Quality, risk, architecture, dependencies, duplication, design-system drift, and safe cleanup evidence for humans, CI, and agents.
5
+ Free static analysis of code and styles, optional paid runtime intelligence (Fallow Runtime). Quality, risk, architecture, dependencies, duplication, and design-system drift, for humans, CI, and the agents writing your code.
6
6
 
7
7
  [![CI](https://github.com/fallow-rs/fallow/actions/workflows/ci.yml/badge.svg)](https://github.com/fallow-rs/fallow/actions/workflows/ci.yml)
8
8
  [![npm](https://img.shields.io/npm/v/fallow.svg)](https://www.npmjs.com/package/fallow)
package/capabilities.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "manifest_version": "1",
5
5
  "description": "Codebase analyzer for TypeScript/JavaScript: unused code, circular dependencies, code duplication, complexity hotspots, and architecture boundary violations",
6
6
  "global_flags": [
@@ -2189,6 +2189,40 @@
2189
2189
  "license_note": null,
2190
2190
  "docs_url": "https://docs.fallow.tools/explanations/dead-code#test-only-dependencies"
2191
2191
  },
2192
+ {
2193
+ "id": "dev-dependency-in-production",
2194
+ "rule_id": "fallow/dev-dependency-in-production",
2195
+ "command": "dead-code",
2196
+ "category": "Dependencies",
2197
+ "description": "devDependency imported by production code with a runtime import",
2198
+ "label": "Dev Dependencies Used in Production",
2199
+ "config_key": "dev-dependencies-in-production",
2200
+ "registry_index": 16,
2201
+ "aliases": [],
2202
+ "lsp": true,
2203
+ "filter_flag": "--unused-deps",
2204
+ "result_key": "dev_dependencies_in_production",
2205
+ "summary_label": "Dev dependencies used in production",
2206
+ "summary_docs_anchor": "dev-dependencies-in-production",
2207
+ "sarif_rule_ids": [
2208
+ "fallow/dev-dependency-in-production"
2209
+ ],
2210
+ "codeclimate_check_names": [
2211
+ "fallow/dev-dependency-in-production"
2212
+ ],
2213
+ "ts_alias": {
2214
+ "name": "DevDependencyInProduction",
2215
+ "parent": "DevDependencyInProductionFinding"
2216
+ },
2217
+ "counts_in_total": true,
2218
+ "fixable": false,
2219
+ "suppressible": false,
2220
+ "suppress_comment": null,
2221
+ "note": null,
2222
+ "license": "free",
2223
+ "license_note": null,
2224
+ "docs_url": "https://docs.fallow.tools/explanations/dead-code#dev-dependencies-in-production"
2225
+ },
2192
2226
  {
2193
2227
  "id": "unused-enum-member",
2194
2228
  "rule_id": "fallow/unused-enum-member",
@@ -2403,7 +2437,7 @@
2403
2437
  "description": "Circular dependency chain detected",
2404
2438
  "label": "Circular Dependencies",
2405
2439
  "config_key": "circular-dependencies",
2406
- "registry_index": 16,
2440
+ "registry_index": 17,
2407
2441
  "aliases": [
2408
2442
  "circular-dependencies"
2409
2443
  ],
@@ -2439,7 +2473,7 @@
2439
2473
  "description": "Two or more barrel files re-export from each other in a loop",
2440
2474
  "label": "Re-Export Cycles",
2441
2475
  "config_key": "re-export-cycle",
2442
- "registry_index": 17,
2476
+ "registry_index": 18,
2443
2477
  "aliases": [
2444
2478
  "re-export-cycles",
2445
2479
  "reexport-cycle",
@@ -2477,7 +2511,7 @@
2477
2511
  "description": "Import crosses a configured architecture boundary",
2478
2512
  "label": "Boundary Violations",
2479
2513
  "config_key": "boundary-violation",
2480
- "registry_index": 18,
2514
+ "registry_index": 19,
2481
2515
  "aliases": [],
2482
2516
  "lsp": true,
2483
2517
  "filter_flag": "--boundary-violations",
@@ -2511,7 +2545,7 @@
2511
2545
  "description": "Source file matches no configured architecture boundary zone",
2512
2546
  "label": "Boundary Coverage",
2513
2547
  "config_key": "boundary-violation",
2514
- "registry_index": 19,
2548
+ "registry_index": 20,
2515
2549
  "aliases": [
2516
2550
  "boundary-coverage-violations"
2517
2551
  ],
@@ -2544,7 +2578,7 @@
2544
2578
  "description": "Zoned file calls a callee its zone forbids",
2545
2579
  "label": "Boundary Call Violations",
2546
2580
  "config_key": "boundary-violation",
2547
- "registry_index": 20,
2581
+ "registry_index": 21,
2548
2582
  "aliases": [
2549
2583
  "boundary-calls",
2550
2584
  "boundary-call-violations"
@@ -2578,7 +2612,7 @@
2578
2612
  "description": "Banned usage matched a rule-pack rule",
2579
2613
  "label": "Policy Violations",
2580
2614
  "config_key": "policy-violation",
2581
- "registry_index": 21,
2615
+ "registry_index": 22,
2582
2616
  "aliases": [
2583
2617
  "policy-violations"
2584
2618
  ],
@@ -2611,7 +2645,7 @@
2611
2645
  "description": "Suppression comment or tag no longer matches any issue",
2612
2646
  "label": "Stale Suppressions",
2613
2647
  "config_key": "stale-suppressions",
2614
- "registry_index": 36,
2648
+ "registry_index": 37,
2615
2649
  "aliases": [],
2616
2650
  "lsp": true,
2617
2651
  "filter_flag": "--stale-suppressions",
@@ -2644,7 +2678,7 @@
2644
2678
  "description": "Suppression comment omits a required reason",
2645
2679
  "label": "Missing Suppression Reasons",
2646
2680
  "config_key": "require-suppression-reason",
2647
- "registry_index": 37,
2681
+ "registry_index": 38,
2648
2682
  "aliases": [
2649
2683
  "missing-suppression-reasons"
2650
2684
  ],
@@ -2673,7 +2707,7 @@
2673
2707
  "description": "Catalog entry not referenced by any workspace package",
2674
2708
  "label": "Unused Catalog Entries",
2675
2709
  "config_key": "unused-catalog-entries",
2676
- "registry_index": 38,
2710
+ "registry_index": 39,
2677
2711
  "aliases": [
2678
2712
  "catalog",
2679
2713
  "unused-catalog-entries"
@@ -2710,7 +2744,7 @@
2710
2744
  "description": "Named catalog group has no entries",
2711
2745
  "label": "Empty Catalog Groups",
2712
2746
  "config_key": "empty-catalog-groups",
2713
- "registry_index": 39,
2747
+ "registry_index": 40,
2714
2748
  "aliases": [
2715
2749
  "empty-catalog",
2716
2750
  "empty-catalog-groups"
@@ -2747,7 +2781,7 @@
2747
2781
  "description": "package.json references a catalog that does not declare the package",
2748
2782
  "label": "Unresolved Catalog References",
2749
2783
  "config_key": "unresolved-catalog-references",
2750
- "registry_index": 40,
2784
+ "registry_index": 41,
2751
2785
  "aliases": [
2752
2786
  "unresolved-catalog",
2753
2787
  "unresolved-catalog-references"
@@ -2784,7 +2818,7 @@
2784
2818
  "description": "pnpm.overrides entry targets a package not declared or resolved",
2785
2819
  "label": "Unused Dependency Overrides",
2786
2820
  "config_key": "unused-dependency-overrides",
2787
- "registry_index": 41,
2821
+ "registry_index": 42,
2788
2822
  "aliases": [
2789
2823
  "unused-dependency-overrides",
2790
2824
  "unused-override",
@@ -2822,7 +2856,7 @@
2822
2856
  "description": "pnpm.overrides entry has an unparsable key or value",
2823
2857
  "label": "Misconfigured Dependency Overrides",
2824
2858
  "config_key": "misconfigured-dependency-overrides",
2825
- "registry_index": 42,
2859
+ "registry_index": 43,
2826
2860
  "aliases": [
2827
2861
  "misconfigured-dependency-overrides",
2828
2862
  "misconfigured-override",
@@ -2860,7 +2894,7 @@
2860
2894
  "description": "\"use client\" file exports a server-only / route-config name",
2861
2895
  "label": "Invalid Client Exports",
2862
2896
  "config_key": "invalid-client-export",
2863
- "registry_index": 22,
2897
+ "registry_index": 23,
2864
2898
  "aliases": [
2865
2899
  "invalid-client-exports"
2866
2900
  ],
@@ -2893,7 +2927,7 @@
2893
2927
  "description": "Barrel re-exports both a \"use client\" module and a server-only module",
2894
2928
  "label": "Mixed Client/Server Barrels",
2895
2929
  "config_key": "mixed-client-server-barrel",
2896
- "registry_index": 23,
2930
+ "registry_index": 24,
2897
2931
  "aliases": [
2898
2932
  "mixed-client-server-barrels"
2899
2933
  ],
@@ -2926,7 +2960,7 @@
2926
2960
  "description": "\"use client\" / \"use server\" directive is not in the leading position and is ignored",
2927
2961
  "label": "Misplaced Directives",
2928
2962
  "config_key": "misplaced-directive",
2929
- "registry_index": 24,
2963
+ "registry_index": 25,
2930
2964
  "aliases": [
2931
2965
  "misplaced-directives"
2932
2966
  ],
@@ -2959,7 +2993,7 @@
2959
2993
  "description": "inject() / getContext() reads a key that no provide() / setContext() supplies",
2960
2994
  "label": "Unprovided Injects",
2961
2995
  "config_key": "unprovided-injects",
2962
- "registry_index": 25,
2996
+ "registry_index": 26,
2963
2997
  "aliases": [
2964
2998
  "unprovided-injects"
2965
2999
  ],
@@ -2992,7 +3026,7 @@
2992
3026
  "description": "A Vue / Svelte component is reachable through a barrel but rendered nowhere",
2993
3027
  "label": "Unrendered Components",
2994
3028
  "config_key": "unrendered-components",
2995
- "registry_index": 26,
3029
+ "registry_index": 27,
2996
3030
  "aliases": [
2997
3031
  "unrendered-components"
2998
3032
  ],
@@ -3025,7 +3059,7 @@
3025
3059
  "description": "A Vue, Svelte, or React component prop is referenced nowhere in its own component",
3026
3060
  "label": "Unused Component Props",
3027
3061
  "config_key": "unused-component-props",
3028
- "registry_index": 27,
3062
+ "registry_index": 28,
3029
3063
  "aliases": [
3030
3064
  "unused-component-props"
3031
3065
  ],
@@ -3058,7 +3092,7 @@
3058
3092
  "description": "A Vue <script setup> defineEmits event is emitted nowhere in its own component",
3059
3093
  "label": "Unused Component Emits",
3060
3094
  "config_key": "unused-component-emits",
3061
- "registry_index": 28,
3095
+ "registry_index": 29,
3062
3096
  "aliases": [
3063
3097
  "unused-component-emits"
3064
3098
  ],
@@ -3091,7 +3125,7 @@
3091
3125
  "description": "An Angular @Input() / signal input() / model() input is read nowhere in its own component",
3092
3126
  "label": "Unused Component Inputs",
3093
3127
  "config_key": "unused-component-inputs",
3094
- "registry_index": 29,
3128
+ "registry_index": 30,
3095
3129
  "aliases": [
3096
3130
  "unused-component-inputs"
3097
3131
  ],
@@ -3124,7 +3158,7 @@
3124
3158
  "description": "An Angular @Output() / signal output() output is emitted nowhere in its own component",
3125
3159
  "label": "Unused Component Outputs",
3126
3160
  "config_key": "unused-component-outputs",
3127
- "registry_index": 30,
3161
+ "registry_index": 31,
3128
3162
  "aliases": [
3129
3163
  "unused-component-outputs"
3130
3164
  ],
@@ -3157,7 +3191,7 @@
3157
3191
  "description": "A Svelte component dispatches a createEventDispatcher event whose name is listened to nowhere in the project",
3158
3192
  "label": "Unused Svelte Events",
3159
3193
  "config_key": "unused-svelte-events",
3160
- "registry_index": 31,
3194
+ "registry_index": 32,
3161
3195
  "aliases": [
3162
3196
  "unused-svelte-events"
3163
3197
  ],
@@ -3190,7 +3224,7 @@
3190
3224
  "description": "A Next.js Server Action exported from a \"use server\" file is referenced by no code in the project",
3191
3225
  "label": "Unused Server Actions",
3192
3226
  "config_key": "unused-server-actions",
3193
- "registry_index": 32,
3227
+ "registry_index": 33,
3194
3228
  "aliases": [
3195
3229
  "unused-server-actions"
3196
3230
  ],
@@ -3223,7 +3257,7 @@
3223
3257
  "description": "A SvelteKit load() return-object key is read by no consumer",
3224
3258
  "label": "Unused Load Data Keys",
3225
3259
  "config_key": "unused-load-data-keys",
3226
- "registry_index": 33,
3260
+ "registry_index": 34,
3227
3261
  "aliases": [
3228
3262
  "unused-load-data-keys"
3229
3263
  ],
@@ -3256,7 +3290,7 @@
3256
3290
  "description": "A React/Preact prop is forwarded unchanged through 3+ pass-through components to a distant consumer",
3257
3291
  "label": "Prop Drilling",
3258
3292
  "config_key": "prop-drilling",
3259
- "registry_index": 48,
3293
+ "registry_index": 49,
3260
3294
  "aliases": [],
3261
3295
  "lsp": false,
3262
3296
  "filter_flag": null,
@@ -3285,7 +3319,7 @@
3285
3319
  "description": "A React/Preact component whose whole body is a single spread-forwarded child render (a candidate for inlining)",
3286
3320
  "label": "Thin Wrappers",
3287
3321
  "config_key": "thin-wrapper",
3288
- "registry_index": 49,
3322
+ "registry_index": 50,
3289
3323
  "aliases": [
3290
3324
  "thin-wrappers"
3291
3325
  ],
@@ -3316,7 +3350,7 @@
3316
3350
  "description": "Three or more React/Preact components across two or more files declare an identical prop-name set (a missing shared Props type)",
3317
3351
  "label": "Duplicate Prop Shapes",
3318
3352
  "config_key": "duplicate-prop-shape",
3319
- "registry_index": 50,
3353
+ "registry_index": 51,
3320
3354
  "aliases": [
3321
3355
  "duplicate-prop-shapes"
3322
3356
  ],
@@ -3347,7 +3381,7 @@
3347
3381
  "description": "Two or more Next.js App Router route files resolve to the same URL",
3348
3382
  "label": "Route Collisions",
3349
3383
  "config_key": "route-collision",
3350
- "registry_index": 34,
3384
+ "registry_index": 35,
3351
3385
  "aliases": [
3352
3386
  "route-collisions"
3353
3387
  ],
@@ -3380,7 +3414,7 @@
3380
3414
  "description": "Sibling Next.js dynamic route segments use different slug names at the same position",
3381
3415
  "label": "Dynamic Segment Conflicts",
3382
3416
  "config_key": "dynamic-segment-name-conflict",
3383
- "registry_index": 35,
3417
+ "registry_index": 36,
3384
3418
  "aliases": [
3385
3419
  "dynamic-segment-name-conflicts"
3386
3420
  ],
@@ -3548,7 +3582,7 @@
3548
3582
  "description": "CSS or CSS-in-JS hardcoded styling value bypasses the design token system",
3549
3583
  "label": "CSS Token Drift",
3550
3584
  "config_key": "css-token-drift",
3551
- "registry_index": 51,
3585
+ "registry_index": 52,
3552
3586
  "aliases": [],
3553
3587
  "lsp": false,
3554
3588
  "filter_flag": null,
@@ -3575,7 +3609,7 @@
3575
3609
  "description": "CSS or CSS-in-JS declaration block is duplicated across rules",
3576
3610
  "label": "CSS Duplicate Block",
3577
3611
  "config_key": "css-duplicate-block",
3578
- "registry_index": 52,
3612
+ "registry_index": 53,
3579
3613
  "aliases": [],
3580
3614
  "lsp": false,
3581
3615
  "filter_flag": null,
@@ -3602,7 +3636,7 @@
3602
3636
  "description": "CSS selector, nesting, or important usage is structurally complex",
3603
3637
  "label": "CSS Selector Complexity",
3604
3638
  "config_key": "css-selector-complexity",
3605
- "registry_index": 53,
3639
+ "registry_index": 54,
3606
3640
  "aliases": [],
3607
3641
  "lsp": false,
3608
3642
  "filter_flag": null,
@@ -3629,7 +3663,7 @@
3629
3663
  "description": "CSS or CSS-in-JS surface appears unused",
3630
3664
  "label": "CSS Dead Surface",
3631
3665
  "config_key": "css-dead-surface",
3632
- "registry_index": 54,
3666
+ "registry_index": 55,
3633
3667
  "aliases": [],
3634
3668
  "lsp": false,
3635
3669
  "filter_flag": null,
@@ -3656,7 +3690,7 @@
3656
3690
  "description": "CSS or CSS-in-JS reference resolves to no stylesheet definition",
3657
3691
  "label": "CSS Broken Reference",
3658
3692
  "config_key": "css-broken-reference",
3659
- "registry_index": 55,
3693
+ "registry_index": 56,
3660
3694
  "aliases": [],
3661
3695
  "lsp": false,
3662
3696
  "filter_flag": null,
@@ -4007,7 +4041,7 @@
4007
4041
  "description": "Detected feature flag pattern",
4008
4042
  "label": "Feature Flags",
4009
4043
  "config_key": "feature-flags",
4010
- "registry_index": 46,
4044
+ "registry_index": 47,
4011
4045
  "aliases": [],
4012
4046
  "lsp": false,
4013
4047
  "filter_flag": null,
@@ -544,6 +544,40 @@
544
544
  "license_note": null,
545
545
  "docs_url": "https://docs.fallow.tools/explanations/dead-code#test-only-dependencies"
546
546
  },
547
+ {
548
+ "id": "dev-dependency-in-production",
549
+ "rule_id": "fallow/dev-dependency-in-production",
550
+ "command": "dead-code",
551
+ "category": "Dependencies",
552
+ "description": "devDependency imported by production code with a runtime import",
553
+ "label": "Dev Dependencies Used in Production",
554
+ "config_key": "dev-dependencies-in-production",
555
+ "registry_index": 16,
556
+ "aliases": [],
557
+ "lsp": true,
558
+ "filter_flag": "--unused-deps",
559
+ "result_key": "dev_dependencies_in_production",
560
+ "summary_label": "Dev dependencies used in production",
561
+ "summary_docs_anchor": "dev-dependencies-in-production",
562
+ "sarif_rule_ids": [
563
+ "fallow/dev-dependency-in-production"
564
+ ],
565
+ "codeclimate_check_names": [
566
+ "fallow/dev-dependency-in-production"
567
+ ],
568
+ "ts_alias": {
569
+ "name": "DevDependencyInProduction",
570
+ "parent": "DevDependencyInProductionFinding"
571
+ },
572
+ "counts_in_total": true,
573
+ "fixable": false,
574
+ "suppressible": false,
575
+ "suppress_comment": null,
576
+ "note": null,
577
+ "license": "free",
578
+ "license_note": null,
579
+ "docs_url": "https://docs.fallow.tools/explanations/dead-code#dev-dependencies-in-production"
580
+ },
547
581
  {
548
582
  "id": "circular-dependency",
549
583
  "rule_id": "fallow/circular-dependency",
@@ -552,7 +586,7 @@
552
586
  "description": "Circular dependency chain detected",
553
587
  "label": "Circular Dependencies",
554
588
  "config_key": "circular-dependencies",
555
- "registry_index": 16,
589
+ "registry_index": 17,
556
590
  "aliases": [
557
591
  "circular-dependencies"
558
592
  ],
@@ -588,7 +622,7 @@
588
622
  "description": "Two or more barrel files re-export from each other in a loop",
589
623
  "label": "Re-Export Cycles",
590
624
  "config_key": "re-export-cycle",
591
- "registry_index": 17,
625
+ "registry_index": 18,
592
626
  "aliases": [
593
627
  "re-export-cycles",
594
628
  "reexport-cycle",
@@ -626,7 +660,7 @@
626
660
  "description": "Import crosses a configured architecture boundary",
627
661
  "label": "Boundary Violations",
628
662
  "config_key": "boundary-violation",
629
- "registry_index": 18,
663
+ "registry_index": 19,
630
664
  "aliases": [],
631
665
  "lsp": true,
632
666
  "filter_flag": "--boundary-violations",
@@ -660,7 +694,7 @@
660
694
  "description": "Source file matches no configured architecture boundary zone",
661
695
  "label": "Boundary Coverage",
662
696
  "config_key": "boundary-violation",
663
- "registry_index": 19,
697
+ "registry_index": 20,
664
698
  "aliases": [
665
699
  "boundary-coverage-violations"
666
700
  ],
@@ -693,7 +727,7 @@
693
727
  "description": "Zoned file calls a callee its zone forbids",
694
728
  "label": "Boundary Call Violations",
695
729
  "config_key": "boundary-violation",
696
- "registry_index": 20,
730
+ "registry_index": 21,
697
731
  "aliases": [
698
732
  "boundary-calls",
699
733
  "boundary-call-violations"
@@ -727,7 +761,7 @@
727
761
  "description": "Banned usage matched a rule-pack rule",
728
762
  "label": "Policy Violations",
729
763
  "config_key": "policy-violation",
730
- "registry_index": 21,
764
+ "registry_index": 22,
731
765
  "aliases": [
732
766
  "policy-violations"
733
767
  ],
@@ -760,7 +794,7 @@
760
794
  "description": "\"use client\" file exports a server-only / route-config name",
761
795
  "label": "Invalid Client Exports",
762
796
  "config_key": "invalid-client-export",
763
- "registry_index": 22,
797
+ "registry_index": 23,
764
798
  "aliases": [
765
799
  "invalid-client-exports"
766
800
  ],
@@ -793,7 +827,7 @@
793
827
  "description": "Barrel re-exports both a \"use client\" module and a server-only module",
794
828
  "label": "Mixed Client/Server Barrels",
795
829
  "config_key": "mixed-client-server-barrel",
796
- "registry_index": 23,
830
+ "registry_index": 24,
797
831
  "aliases": [
798
832
  "mixed-client-server-barrels"
799
833
  ],
@@ -826,7 +860,7 @@
826
860
  "description": "\"use client\" / \"use server\" directive is not in the leading position and is ignored",
827
861
  "label": "Misplaced Directives",
828
862
  "config_key": "misplaced-directive",
829
- "registry_index": 24,
863
+ "registry_index": 25,
830
864
  "aliases": [
831
865
  "misplaced-directives"
832
866
  ],
@@ -859,7 +893,7 @@
859
893
  "description": "inject() / getContext() reads a key that no provide() / setContext() supplies",
860
894
  "label": "Unprovided Injects",
861
895
  "config_key": "unprovided-injects",
862
- "registry_index": 25,
896
+ "registry_index": 26,
863
897
  "aliases": [
864
898
  "unprovided-injects"
865
899
  ],
@@ -892,7 +926,7 @@
892
926
  "description": "A Vue / Svelte component is reachable through a barrel but rendered nowhere",
893
927
  "label": "Unrendered Components",
894
928
  "config_key": "unrendered-components",
895
- "registry_index": 26,
929
+ "registry_index": 27,
896
930
  "aliases": [
897
931
  "unrendered-components"
898
932
  ],
@@ -925,7 +959,7 @@
925
959
  "description": "A Vue, Svelte, or React component prop is referenced nowhere in its own component",
926
960
  "label": "Unused Component Props",
927
961
  "config_key": "unused-component-props",
928
- "registry_index": 27,
962
+ "registry_index": 28,
929
963
  "aliases": [
930
964
  "unused-component-props"
931
965
  ],
@@ -958,7 +992,7 @@
958
992
  "description": "A Vue <script setup> defineEmits event is emitted nowhere in its own component",
959
993
  "label": "Unused Component Emits",
960
994
  "config_key": "unused-component-emits",
961
- "registry_index": 28,
995
+ "registry_index": 29,
962
996
  "aliases": [
963
997
  "unused-component-emits"
964
998
  ],
@@ -991,7 +1025,7 @@
991
1025
  "description": "An Angular @Input() / signal input() / model() input is read nowhere in its own component",
992
1026
  "label": "Unused Component Inputs",
993
1027
  "config_key": "unused-component-inputs",
994
- "registry_index": 29,
1028
+ "registry_index": 30,
995
1029
  "aliases": [
996
1030
  "unused-component-inputs"
997
1031
  ],
@@ -1024,7 +1058,7 @@
1024
1058
  "description": "An Angular @Output() / signal output() output is emitted nowhere in its own component",
1025
1059
  "label": "Unused Component Outputs",
1026
1060
  "config_key": "unused-component-outputs",
1027
- "registry_index": 30,
1061
+ "registry_index": 31,
1028
1062
  "aliases": [
1029
1063
  "unused-component-outputs"
1030
1064
  ],
@@ -1057,7 +1091,7 @@
1057
1091
  "description": "A Svelte component dispatches a createEventDispatcher event whose name is listened to nowhere in the project",
1058
1092
  "label": "Unused Svelte Events",
1059
1093
  "config_key": "unused-svelte-events",
1060
- "registry_index": 31,
1094
+ "registry_index": 32,
1061
1095
  "aliases": [
1062
1096
  "unused-svelte-events"
1063
1097
  ],
@@ -1090,7 +1124,7 @@
1090
1124
  "description": "A Next.js Server Action exported from a \"use server\" file is referenced by no code in the project",
1091
1125
  "label": "Unused Server Actions",
1092
1126
  "config_key": "unused-server-actions",
1093
- "registry_index": 32,
1127
+ "registry_index": 33,
1094
1128
  "aliases": [
1095
1129
  "unused-server-actions"
1096
1130
  ],
@@ -1123,7 +1157,7 @@
1123
1157
  "description": "A SvelteKit load() return-object key is read by no consumer",
1124
1158
  "label": "Unused Load Data Keys",
1125
1159
  "config_key": "unused-load-data-keys",
1126
- "registry_index": 33,
1160
+ "registry_index": 34,
1127
1161
  "aliases": [
1128
1162
  "unused-load-data-keys"
1129
1163
  ],
@@ -1156,7 +1190,7 @@
1156
1190
  "description": "Two or more Next.js App Router route files resolve to the same URL",
1157
1191
  "label": "Route Collisions",
1158
1192
  "config_key": "route-collision",
1159
- "registry_index": 34,
1193
+ "registry_index": 35,
1160
1194
  "aliases": [
1161
1195
  "route-collisions"
1162
1196
  ],
@@ -1189,7 +1223,7 @@
1189
1223
  "description": "Sibling Next.js dynamic route segments use different slug names at the same position",
1190
1224
  "label": "Dynamic Segment Conflicts",
1191
1225
  "config_key": "dynamic-segment-name-conflict",
1192
- "registry_index": 35,
1226
+ "registry_index": 36,
1193
1227
  "aliases": [
1194
1228
  "dynamic-segment-name-conflicts"
1195
1229
  ],
@@ -1222,7 +1256,7 @@
1222
1256
  "description": "Suppression comment or tag no longer matches any issue",
1223
1257
  "label": "Stale Suppressions",
1224
1258
  "config_key": "stale-suppressions",
1225
- "registry_index": 36,
1259
+ "registry_index": 37,
1226
1260
  "aliases": [],
1227
1261
  "lsp": true,
1228
1262
  "filter_flag": "--stale-suppressions",
@@ -1255,7 +1289,7 @@
1255
1289
  "description": "Suppression comment omits a required reason",
1256
1290
  "label": "Missing Suppression Reasons",
1257
1291
  "config_key": "require-suppression-reason",
1258
- "registry_index": 37,
1292
+ "registry_index": 38,
1259
1293
  "aliases": [
1260
1294
  "missing-suppression-reasons"
1261
1295
  ],
@@ -1284,7 +1318,7 @@
1284
1318
  "description": "Catalog entry not referenced by any workspace package",
1285
1319
  "label": "Unused Catalog Entries",
1286
1320
  "config_key": "unused-catalog-entries",
1287
- "registry_index": 38,
1321
+ "registry_index": 39,
1288
1322
  "aliases": [
1289
1323
  "catalog",
1290
1324
  "unused-catalog-entries"
@@ -1321,7 +1355,7 @@
1321
1355
  "description": "Named catalog group has no entries",
1322
1356
  "label": "Empty Catalog Groups",
1323
1357
  "config_key": "empty-catalog-groups",
1324
- "registry_index": 39,
1358
+ "registry_index": 40,
1325
1359
  "aliases": [
1326
1360
  "empty-catalog",
1327
1361
  "empty-catalog-groups"
@@ -1358,7 +1392,7 @@
1358
1392
  "description": "package.json references a catalog that does not declare the package",
1359
1393
  "label": "Unresolved Catalog References",
1360
1394
  "config_key": "unresolved-catalog-references",
1361
- "registry_index": 40,
1395
+ "registry_index": 41,
1362
1396
  "aliases": [
1363
1397
  "unresolved-catalog",
1364
1398
  "unresolved-catalog-references"
@@ -1395,7 +1429,7 @@
1395
1429
  "description": "pnpm.overrides entry targets a package not declared or resolved",
1396
1430
  "label": "Unused Dependency Overrides",
1397
1431
  "config_key": "unused-dependency-overrides",
1398
- "registry_index": 41,
1432
+ "registry_index": 42,
1399
1433
  "aliases": [
1400
1434
  "unused-dependency-overrides",
1401
1435
  "unused-override",
@@ -1433,7 +1467,7 @@
1433
1467
  "description": "pnpm.overrides entry has an unparsable key or value",
1434
1468
  "label": "Misconfigured Dependency Overrides",
1435
1469
  "config_key": "misconfigured-dependency-overrides",
1436
- "registry_index": 42,
1470
+ "registry_index": 43,
1437
1471
  "aliases": [
1438
1472
  "misconfigured-dependency-overrides",
1439
1473
  "misconfigured-override",
@@ -1471,7 +1505,7 @@
1471
1505
  "description": "Detected feature flag pattern",
1472
1506
  "label": "Feature Flags",
1473
1507
  "config_key": "feature-flags",
1474
- "registry_index": 46,
1508
+ "registry_index": 47,
1475
1509
  "aliases": [],
1476
1510
  "lsp": false,
1477
1511
  "filter_flag": null,
@@ -1498,7 +1532,7 @@
1498
1532
  "description": "A React/Preact prop is forwarded unchanged through 3+ pass-through components to a distant consumer",
1499
1533
  "label": "Prop Drilling",
1500
1534
  "config_key": "prop-drilling",
1501
- "registry_index": 48,
1535
+ "registry_index": 49,
1502
1536
  "aliases": [],
1503
1537
  "lsp": false,
1504
1538
  "filter_flag": null,
@@ -1527,7 +1561,7 @@
1527
1561
  "description": "A React/Preact component whose whole body is a single spread-forwarded child render (a candidate for inlining)",
1528
1562
  "label": "Thin Wrappers",
1529
1563
  "config_key": "thin-wrapper",
1530
- "registry_index": 49,
1564
+ "registry_index": 50,
1531
1565
  "aliases": [
1532
1566
  "thin-wrappers"
1533
1567
  ],
@@ -1558,7 +1592,7 @@
1558
1592
  "description": "Three or more React/Preact components across two or more files declare an identical prop-name set (a missing shared Props type)",
1559
1593
  "label": "Duplicate Prop Shapes",
1560
1594
  "config_key": "duplicate-prop-shape",
1561
- "registry_index": 50,
1595
+ "registry_index": 51,
1562
1596
  "aliases": [
1563
1597
  "duplicate-prop-shapes"
1564
1598
  ],
@@ -1589,7 +1623,7 @@
1589
1623
  "description": "CSS or CSS-in-JS hardcoded styling value bypasses the design token system",
1590
1624
  "label": "CSS Token Drift",
1591
1625
  "config_key": "css-token-drift",
1592
- "registry_index": 51,
1626
+ "registry_index": 52,
1593
1627
  "aliases": [],
1594
1628
  "lsp": false,
1595
1629
  "filter_flag": null,
@@ -1616,7 +1650,7 @@
1616
1650
  "description": "CSS or CSS-in-JS declaration block is duplicated across rules",
1617
1651
  "label": "CSS Duplicate Block",
1618
1652
  "config_key": "css-duplicate-block",
1619
- "registry_index": 52,
1653
+ "registry_index": 53,
1620
1654
  "aliases": [],
1621
1655
  "lsp": false,
1622
1656
  "filter_flag": null,
@@ -1643,7 +1677,7 @@
1643
1677
  "description": "CSS selector, nesting, or important usage is structurally complex",
1644
1678
  "label": "CSS Selector Complexity",
1645
1679
  "config_key": "css-selector-complexity",
1646
- "registry_index": 53,
1680
+ "registry_index": 54,
1647
1681
  "aliases": [],
1648
1682
  "lsp": false,
1649
1683
  "filter_flag": null,
@@ -1670,7 +1704,7 @@
1670
1704
  "description": "CSS or CSS-in-JS surface appears unused",
1671
1705
  "label": "CSS Dead Surface",
1672
1706
  "config_key": "css-dead-surface",
1673
- "registry_index": 54,
1707
+ "registry_index": 55,
1674
1708
  "aliases": [],
1675
1709
  "lsp": false,
1676
1710
  "filter_flag": null,
@@ -1697,7 +1731,7 @@
1697
1731
  "description": "CSS or CSS-in-JS reference resolves to no stylesheet definition",
1698
1732
  "label": "CSS Broken Reference",
1699
1733
  "config_key": "css-broken-reference",
1700
- "registry_index": 55,
1734
+ "registry_index": 56,
1701
1735
  "aliases": [],
1702
1736
  "lsp": false,
1703
1737
  "filter_flag": null,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "3.0.0",
4
- "description": "Deterministic codebase intelligence for TypeScript and JavaScript. Quality, risk, architecture, dependencies, duplication, and safe cleanup evidence for humans, CI, and agents. Optional runtime intelligence layer (Fallow Runtime) adds production execution evidence. Rust-native, sub-second, zero-config framework support.",
3
+ "version": "3.2.0",
4
+ "description": "Codebase intelligence for TypeScript and JavaScript. Free static analysis of code and styles, optional paid runtime intelligence (Fallow Runtime). Quality, risk, architecture, dependencies, duplication, and design-system drift for humans, CI, and the agents writing your code. Zero-config framework support.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -87,13 +87,13 @@
87
87
  "@tanstack/intent": "0.3.2"
88
88
  },
89
89
  "optionalDependencies": {
90
- "@fallow-cli/darwin-arm64": "3.0.0",
91
- "@fallow-cli/darwin-x64": "3.0.0",
92
- "@fallow-cli/linux-x64-gnu": "3.0.0",
93
- "@fallow-cli/linux-arm64-gnu": "3.0.0",
94
- "@fallow-cli/linux-x64-musl": "3.0.0",
95
- "@fallow-cli/linux-arm64-musl": "3.0.0",
96
- "@fallow-cli/win32-arm64-msvc": "3.0.0",
97
- "@fallow-cli/win32-x64-msvc": "3.0.0"
90
+ "@fallow-cli/darwin-arm64": "3.2.0",
91
+ "@fallow-cli/darwin-x64": "3.2.0",
92
+ "@fallow-cli/linux-x64-gnu": "3.2.0",
93
+ "@fallow-cli/linux-arm64-gnu": "3.2.0",
94
+ "@fallow-cli/linux-x64-musl": "3.2.0",
95
+ "@fallow-cli/linux-arm64-musl": "3.2.0",
96
+ "@fallow-cli/win32-arm64-msvc": "3.2.0",
97
+ "@fallow-cli/win32-x64-msvc": "3.2.0"
98
98
  }
99
99
  }
package/schema.json CHANGED
@@ -125,6 +125,7 @@
125
125
  "maxCognitive": 15,
126
126
  "maxCrap": 30.0,
127
127
  "crapRefactorBand": 5,
128
+ "maxUnitSize": 60,
128
129
  "coverage": null,
129
130
  "coverageRoot": null,
130
131
  "ignore": [],
@@ -177,6 +178,7 @@
177
178
  "duplicate-exports": "error",
178
179
  "type-only-dependencies": "warn",
179
180
  "test-only-dependencies": "warn",
181
+ "dev-dependencies-in-production": "warn",
180
182
  "circular-dependencies": "error",
181
183
  "re-export-cycle": "warn",
182
184
  "boundary-violation": "error",
@@ -835,6 +837,13 @@
835
837
  "maximum": 65535,
836
838
  "default": 5
837
839
  },
840
+ "maxUnitSize": {
841
+ "description": "Maximum function length in lines of code before it is reported as an\noversized \"large function\" (default: 60). Raise it globally, or per file\nvia `thresholdOverrides[].maxUnitSize`, to relax the bar for generated or\ntest files (where a `describe()` block spans hundreds of lines) without\ndisabling complexity checks on those files. This filters the reported\nlarge-functions list only; the descriptive unit-size profile and the\nhealth score still reflect raw sizes (use `health.ignore` to remove a\nfile from the score entirely).",
842
+ "type": "integer",
843
+ "format": "uint32",
844
+ "minimum": 0,
845
+ "default": 60
846
+ },
838
847
  "coverage": {
839
848
  "description": "Path to Istanbul-format coverage data for accurate per-function CRAP\nscores. Relative paths resolve against the project root. The CLI\n`--coverage` flag and `FALLOW_COVERAGE` environment variable override\nthis value.",
840
849
  "type": [
@@ -935,6 +944,15 @@
935
944
  ],
936
945
  "format": "double"
937
946
  },
947
+ "maxUnitSize": {
948
+ "description": "Local unit-size ceiling: maximum function length in lines of code before\nit is reported as an oversized \"large function\". Leave `functions` empty\nto relax the bar for every function in the matching files (which covers\nboth the `describe()` wrapper and the individual `it()` blocks in a test\nsuite).",
949
+ "type": [
950
+ "integer",
951
+ "null"
952
+ ],
953
+ "format": "uint32",
954
+ "minimum": 0
955
+ },
938
956
  "reason": {
939
957
  "description": "Human-readable rationale for the exception.",
940
958
  "type": [
@@ -1042,37 +1060,37 @@
1042
1060
  "unused-store-members": {
1043
1061
  "description": "Store members (Pinia `state` / `getters` / `actions` key, or a\nsetup-store returned key) declared but never accessed by any consumer\nproject-wide. Defaults to `warn`, not `error` like the closed-set\nclass/enum member rules: a store has an OPEN declaration surface\n(plugins, `$onAction`, dynamic dispatch) so analyzer confidence is\ngenuinely lower; warn encodes that without failing CI. Promotable to\n`error` once validated on a codebase.",
1044
1062
  "$ref": "#/$defs/Severity",
1045
- "default": "error"
1063
+ "default": "warn"
1046
1064
  },
1047
1065
  "unprovided-injects": {
1048
1066
  "description": "Vue `inject(KEY)` / Svelte `getContext(KEY)` whose symbol KEY is\n`provide`/`setContext`'d nowhere in the project (the\ninjected-never-provided dead-half). Defaults to `warn`, not `error`:\na DI key has an open provide surface (plugins, app-level provide) so\nanalyzer confidence is lower; warn encodes that without failing CI.",
1049
1067
  "$ref": "#/$defs/Severity",
1050
- "default": "error"
1068
+ "default": "warn"
1051
1069
  },
1052
1070
  "unrendered-components": {
1053
1071
  "description": "Vue/Svelte single-file component reachable in the module graph but\nrendered nowhere in the project (the imported-but-never-rendered\ndead-half). Defaults to `warn`, not `error`: a component can be rendered\nreflectively (dynamic `<component :is>`), so analyzer confidence is\nlower; warn encodes that without failing CI.",
1054
1072
  "$ref": "#/$defs/Severity",
1055
- "default": "error"
1073
+ "default": "warn"
1056
1074
  },
1057
1075
  "unused-component-props": {
1058
1076
  "description": "Vue `<script setup>` `defineProps`, Svelte 5 `$props()`, or React\ndeclared prop referenced nowhere inside its own component. The\nsingle-component dead-input direction. Defaults to `warn`, not `error`: a\nprop can be part of a deliberately-stable public component API, so\nanalyzer confidence is lower; warn encodes that without failing CI.",
1059
1077
  "$ref": "#/$defs/Severity",
1060
- "default": "error"
1078
+ "default": "warn"
1061
1079
  },
1062
1080
  "unused-component-emits": {
1063
1081
  "description": "Vue `<script setup>` `defineEmits` declared event emitted nowhere inside\nits own single-file component (no `emit('<name>')` call). The single-file\ndead-input direction. Defaults to `warn`, not `error`: an emit can be part\nof a deliberately-stable public component API, so analyzer confidence is\nlower; warn encodes that without failing CI.",
1064
1082
  "$ref": "#/$defs/Severity",
1065
- "default": "error"
1083
+ "default": "warn"
1066
1084
  },
1067
1085
  "unused-component-inputs": {
1068
1086
  "description": "Angular `@Input()` / signal `input()` / `model()` declared input read\nnowhere inside its own component (neither the inline/external template nor\nthe class body). The single-file dead-input direction, the Angular\nanalogue of `unused-component-prop`. Defaults to `warn`, not `error`: an\ninput can be part of a deliberately-stable public component API, so\nanalyzer confidence is lower; warn encodes that without failing CI.",
1069
1087
  "$ref": "#/$defs/Severity",
1070
- "default": "error"
1088
+ "default": "warn"
1071
1089
  },
1072
1090
  "unused-component-outputs": {
1073
1091
  "description": "Angular `@Output()` / signal `output()` declared output emitted nowhere\ninside its own component (no `this.<output>.emit(...)`). The single-file\ndead-output direction, the Angular analogue of `unused-component-emit`.\nDefaults to `warn`, not `error`: an output can be part of a\ndeliberately-stable public component API, so analyzer confidence is lower;\nwarn encodes that without failing CI.",
1074
1092
  "$ref": "#/$defs/Severity",
1075
- "default": "error"
1093
+ "default": "warn"
1076
1094
  },
1077
1095
  "unused-svelte-events": {
1078
1096
  "description": "Svelte component dispatching a custom event via `createEventDispatcher()`\nwhose event name is listened to nowhere in the analyzed project. The\ncross-file dead-output direction (no eslint-plugin-svelte / svelte-check\nrule covers the listener side). Defaults to `warn`, not `error`: a\ndispatched event can be part of a deliberately-stable public component\nAPI, or a listener may be added later, so analyzer confidence is lower;\nwarn encodes that without failing CI.",
@@ -1082,7 +1100,7 @@
1082
1100
  "unused-server-actions": {
1083
1101
  "description": "Next.js Server Action (an export of a `\"use server\"` file) referenced by\nno code in the project: no import-and-call, no `action={fn}` binding, no\n`<form action={fn}>`. Cross-graph dead-export direction, reclassified out\nof `unused-export` for `\"use server\"` files. Defaults to `warn`, not\n`error`: the rule is new and false-negative-preferring, and reflective\naction-dispatch shapes can hide a real consumer; warn encodes that\nwithout failing CI until corpus-validated.",
1084
1102
  "$ref": "#/$defs/Severity",
1085
- "default": "error"
1103
+ "default": "warn"
1086
1104
  },
1087
1105
  "unused-load-data-keys": {
1088
1106
  "description": "SvelteKit `+page.{ts,server.ts,js,server.js}` `load()` return-object key\nread by no consumer: not off the sibling `+page.svelte`'s `data.<key>`,\nnor project-wide via `page.data.<key>` / `$page.data.<key>`. Cross-file\ndead-input direction. Defaults to `warn`, not `error`: the rule is new and\nfalse-negative-preferring (a whole-object `data` pass abstains), and a\nload fetch can have side effects so deletion is a human call; warn encodes\nthat without failing CI until corpus-validated.",
@@ -1149,6 +1167,10 @@
1149
1167
  "$ref": "#/$defs/Severity",
1150
1168
  "default": "warn"
1151
1169
  },
1170
+ "dev-dependencies-in-production": {
1171
+ "$ref": "#/$defs/Severity",
1172
+ "default": "warn"
1173
+ },
1152
1174
  "circular-dependencies": {
1153
1175
  "$ref": "#/$defs/Severity",
1154
1176
  "default": "error"
@@ -1163,7 +1185,7 @@
1163
1185
  },
1164
1186
  "coverage-gaps": {
1165
1187
  "$ref": "#/$defs/Severity",
1166
- "default": "error"
1188
+ "default": "off"
1167
1189
  },
1168
1190
  "feature-flags": {
1169
1191
  "$ref": "#/$defs/Severity",
@@ -1982,6 +2004,16 @@
1982
2004
  }
1983
2005
  ]
1984
2006
  },
2007
+ "dev-dependencies-in-production": {
2008
+ "anyOf": [
2009
+ {
2010
+ "$ref": "#/$defs/Severity"
2011
+ },
2012
+ {
2013
+ "type": "null"
2014
+ }
2015
+ ]
2016
+ },
1985
2017
  "circular-dependencies": {
1986
2018
  "anyOf": [
1987
2019
  {
@@ -2298,6 +2330,12 @@
2298
2330
  "minimum": 0,
2299
2331
  "default": 0
2300
2332
  },
2333
+ "devDependenciesInProduction": {
2334
+ "type": "integer",
2335
+ "format": "uint",
2336
+ "minimum": 0,
2337
+ "default": 0
2338
+ },
2301
2339
  "boundaryViolations": {
2302
2340
  "type": "integer",
2303
2341
  "format": "uint",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: fallow
3
- description: Codebase intelligence for JavaScript, TypeScript, and styling. Static analysis reports changed-code risk, cleanup opportunities, duplication, circular dependencies, complexity hotspots, architecture boundaries, design-system drift, feature flags, and opt-in security candidates. Runtime coverage can merge production execution data for hot-path review, cold-path deletion confidence, and stale-flag evidence. 123 framework plugins, zero configuration, sub-second static analysis. Use when asked to audit PR risk, find unused code or dependencies, detect duplicates, check styling consistency, inspect architecture boundaries, merge runtime coverage, auto-fix supported issues, or run fallow.
3
+ description: Codebase intelligence for TypeScript and JavaScript. Static analysis of code and styles reports changed-code risk, cleanup opportunities, duplication, circular dependencies, complexity hotspots, architecture boundaries, design-system drift, feature flags, and opt-in security candidates. Runtime coverage can merge production execution data for hot-path review, cold-path deletion confidence, and stale-flag evidence. 123 framework plugins, zero configuration, sub-second static analysis. Use when asked to audit PR risk, find unused code or dependencies, detect duplicates, check styling consistency, inspect architecture boundaries, merge runtime coverage, auto-fix supported issues, or run fallow.
4
4
  license: MIT
5
5
  metadata:
6
6
  author: Bart Waardenburg
@@ -8,9 +8,9 @@ metadata:
8
8
  homepage: https://docs.fallow.tools
9
9
  ---
10
10
 
11
- # Fallow: codebase intelligence for JavaScript, TypeScript, and styling
11
+ # Fallow: codebase intelligence for TypeScript and JavaScript
12
12
 
13
- Codebase intelligence for JavaScript, TypeScript, and styling. The static layer reports quality, changed-code risk, cleanup opportunities, circular dependencies, code duplication, complexity hotspots, architecture boundary violations, design-system styling drift, feature flag patterns, and opt-in security candidates. Runtime coverage merges production execution data into the same `fallow health` report for hot-path review, cold-path deletion confidence, and stale-flag evidence, with a single local capture available by default and continuous/cloud runtime monitoring available as an optional mode. 123 framework plugins, zero configuration, sub-second static analysis.
13
+ Codebase intelligence for TypeScript and JavaScript. The static layer analyzes code and styles and reports quality, changed-code risk, cleanup opportunities, circular dependencies, code duplication, complexity hotspots, architecture boundary violations, design-system styling drift, feature flag patterns, and opt-in security candidates. Runtime coverage merges production execution data into the same `fallow health` report for hot-path review, cold-path deletion confidence, and stale-flag evidence, with a single local capture available by default and continuous/cloud runtime monitoring available as an optional mode. 123 framework plugins, zero configuration, sub-second static analysis.
14
14
 
15
15
  ## When to Use
16
16
  - Find cleanup opportunities: unused files, exports, types, members, dependencies, or stale flags.
@@ -135,6 +135,7 @@ Run `fallow <command> --help` for the full flag list per command (see also refer
135
135
  | `unused-optional-dependency` | `--unused-deps` | yes | - | Packages in `optionalDependencies` never imported (often platform-specific; verify before removing) |
136
136
  | `type-only-dependency` | `--unused-deps` | - | - | Production dependency only used via type-only imports; Only reported in --production mode; --unused-deps scopes it together with the other dependency kinds |
137
137
  | `test-only-dependency` | `--unused-deps` | - | - | Production deps only imported from test files (should be devDependencies) |
138
+ | `dev-dependency-in-production` | `--unused-deps` | - | - | devDependency imported by production code with a runtime import |
138
139
  | `unused-enum-member` | `--unused-enum-members` | yes | `// fallow-ignore-next-line unused-enum-member` | Enum values never referenced |
139
140
  | `unused-class-member` | `--unused-class-members` | - | `// fallow-ignore-next-line unused-class-member` | Methods and properties |
140
141
  | `unused-store-member` | `--unused-store-members` | - | `// fallow-ignore-next-line unused-store-member` | Pinia store state/getter/action (needs `pinia` dep) |
@@ -223,7 +224,7 @@ When using fallow via MCP (`fallow-mcp`), the following tools are available:
223
224
  | `feature_flags` | analysis | free | `workspace`, `production` | Detect feature flag patterns (env vars, SDK calls, config objects). Set `top` to limit results |
224
225
  | `impact` | introspection | free | `root` | Read the local, opt-in Fallow Impact value report (`fallow impact --format json`). Runs no analysis: current surfacing counts, trend since the last recorded run, pre-commit gate containment, and (on impact v1.5+) resolved/suppressed attribution. History is read from a per-project file in the user's private config dir (never inside the repo). Read-only and `root`-only; the mutating `enable` / `disable` / `default` lifecycle is not exposed. A never-enabled project returns a populated `{"enabled": false, ...}` report (never `{}`); branch on `enabled` and `enabled_source` (`project` / `user` / `default`) then `record_count`, recommending `fallow impact enable` only when `explicit_decision` is `false` (never asked) and staying silent when `true` (deliberately disabled here). Local-developer signal: fallow never records in CI, so empty there and not a CI metric |
225
226
  | `impact_all` | introspection | free | `sort`, `limit` | Roll every tracked fallow project on this machine into one cross-repo value report (hashed keys plus basename labels, never paths; local-dev only) |
226
- | `trace_export` | trace | free | `file`, `export_name` | Trace why an export is used or unused (`fallow dead-code --trace FILE:EXPORT_NAME --format json`). Required `file` and `export_name`. Returns file reachability, entry-point status, direct references, re-export chains, and a reason string. Use before deleting a supposedly-unused export |
227
+ | `trace_export` | trace | free | `file`, `export_name` | Trace why an export is used or unused (`fallow dead-code --trace FILE:EXPORT_NAME --format json`). Required `file` and `export_name`. Returns file reachability, entry-point status, direct references, re-export chains, and a reason string. If `export_name` is a class / enum / store MEMBER, returns a member trace instead (`member_name`, `member_kind`, `owner_export`, `owner_is_used`) plus a `--unused-<kind>-members` pointer; branch on field presence. Use before deleting a supposedly-unused export or debugging an unused-class-member finding |
227
228
  | `trace_file` | trace | free | `file` | Trace all graph edges for a file (`fallow dead-code --trace-file PATH --format json`). Required `file`. Returns reachability, exports, imports-from, imported-by, and re-exports. Use to decide whether a file is isolated, barrel-only, or imported by live entry points |
228
229
  | `trace_dependency` | trace | free | `package_name` | Trace where a dependency is imported (`fallow dead-code --trace-dependency PACKAGE --format json`). Required `package_name`. Returns importing files, type-only importers, total import count, `used_in_scripts` (true when invoked from package.json scripts or CI configs), and `is_used` (combined import + script signal; mirrors the unused-deps detector so build tools like `microbundle` or `vitest` are not falsely flagged as unused). Use before removing a dependency or moving between `dependencies` and `devDependencies` |
229
230
  | `trace_clone` | trace | free | `file`, `line`, `fingerprint` | Deep-dive a duplicate-code clone group (`fallow dupes --trace <spec> --format json`). Address by exactly one of: `file` + `line` (a source location), or `fingerprint` (a `dup:<id>` from a prior `find_dupes` `clone_groups[].fingerprint`, usually `dup:<8hex>` and widened only on rare report collisions). Returns the matched clone instance plus every clone group containing it; each traced group carries its `fingerprint`, an extract-function `suggestion` with estimated savings, and a best-effort `suggested_name` (omitted when no confident name). Supports `mode`, `min_tokens`, `min_lines`, `threshold`, `skip_local`, `cross_language`, `ignore_imports`. Use to consolidate duplication when you need exact sibling locations and a refactor target |
@@ -242,7 +243,7 @@ Most tools accept `root`, `config`, `no_cache`, and `threads` params. Exceptions
242
243
 
243
244
  All JSON responses include structured `actions` arrays on every finding (dead code, health, duplication), enabling programmatic fix application or suppression.
244
245
 
245
- `health.thresholdOverrides[]` lets projects keep known legacy functions visible as configured local ceilings instead of hiding them with suppressions. Each entry has `files` globs, optional exact `functions`, one or more of `maxCyclomatic`, `maxCognitive`, or `maxCrap`, and optional `reason`. Health JSON may include top-level `threshold_overrides[]` entries with `active`, `stale`, or `no_match` status, and complexity findings that use an override carry `effective_thresholds` plus `threshold_source: "override"`.
246
+ `health.thresholdOverrides[]` lets projects keep known legacy functions visible as configured local ceilings instead of hiding them with suppressions. Each entry has `files` globs, optional exact `functions`, one or more of `maxCyclomatic`, `maxCognitive`, `maxCrap`, or `maxUnitSize`, and optional `reason`. Health JSON may include top-level `threshold_overrides[]` entries with `active`, `stale`, or `no_match` status, and complexity findings that use an override carry `effective_thresholds` plus `threshold_source: "override"`.
246
247
 
247
248
  `dead-code`, `health`, `dupes`, bare `fallow`, and `audit` JSON output also carry a top-level `next_steps` array of read-only follow-up commands computed from the run's findings: each entry is `{ id, command, reason }`. The `command` is runnable as-is (never a placeholder, never `fix` or any other mutating command); the stable kebab-case `id` (`setup`, `impact-report`, `trace-unused-export`, `trace-clone`, `complexity-breakdown`, `scope-workspaces`, `audit-changed`) maps to a verification step you should run BEFORE acting, for example tracing an export before deleting it. A leading `setup` step (command: `fallow schema`) appears only on unconfigured, non-CI projects with findings and doubles as the onboarding trigger below; it disappears after setup or `fallow init --decline`. An at-most-weekly `impact-report` step (command: `fallow impact`) carries the local value digest when impact tracking has non-zero results; it may ride a clean run. When running via MCP, dispatch on the `id` to the matching tool / `code_execute` host call (`trace_export`, `trace_clone`, `check_health` with `complexity_breakdown: true`, `audit`) rather than shelling out the CLI string. The array is deduplicated, capped at three, and omitted when empty; set `FALLOW_SUGGESTIONS=off` to suppress it.
248
249
 
@@ -508,7 +508,7 @@ fallow health --format json --quiet --trend
508
508
  {
509
509
  "kind": "health",
510
510
  "schema_version": 7,
511
- "version": "3.0.0",
511
+ "version": "3.2.0",
512
512
  "elapsed_ms": 32,
513
513
  "summary": {
514
514
  "files_analyzed": 482,
@@ -532,7 +532,7 @@ fallow health --format json --quiet --trend
532
532
  }
533
533
  ```
534
534
 
535
- `health.thresholdOverrides[]` config entries can raise local cyclomatic, cognitive, or CRAP ceilings for matching files and optional exact function names. When an override affects output, health JSON includes top-level `threshold_overrides[]` state entries (`active`, `stale`, or `no_match`). Complexity findings evaluated with local ceilings include `effective_thresholds` and `threshold_source: "override"` so agents can see which thresholds drove the finding and avoid treating configured exceptions as hidden suppressions.
535
+ `health.thresholdOverrides[]` config entries can raise local cyclomatic, cognitive, CRAP, or unit-size (large-function line-count) ceilings for matching files and optional exact function names. When an override affects output, health JSON includes top-level `threshold_overrides[]` state entries (`active`, `stale`, or `no_match`). Complexity findings evaluated with local ceilings include `effective_thresholds` and `threshold_source: "override"` so agents can see which thresholds drove the finding and avoid treating configured exceptions as hidden suppressions.
536
536
 
537
537
  When the unit size very-high-risk percentage is >= 3%, the JSON output includes a `large_functions` array listing functions exceeding 60 lines of code:
538
538
 
@@ -906,7 +906,7 @@ fallow audit \
906
906
  {
907
907
  "kind": "audit",
908
908
  "schema_version": 7,
909
- "version": "3.0.0",
909
+ "version": "3.2.0",
910
910
  "command": "audit",
911
911
  "verdict": "fail",
912
912
  "changed_files_count": 12,
@@ -981,7 +981,7 @@ fallow flags --format json --quiet --workspace my-package
981
981
  ```json
982
982
  {
983
983
  "schema_version": 7,
984
- "version": "3.0.0",
984
+ "version": "3.2.0",
985
985
  "elapsed_ms": 116,
986
986
  "feature_flags": [],
987
987
  "total_flags": 0
@@ -1082,7 +1082,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1082
1082
  {
1083
1083
  "kind": "security",
1084
1084
  "schema_version": "4",
1085
- "version": "3.0.0",
1085
+ "version": "3.2.0",
1086
1086
  "elapsed_ms": 42,
1087
1087
  "config": {
1088
1088
  "rules": {
@@ -1111,7 +1111,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1111
1111
  {
1112
1112
  "kind": "security",
1113
1113
  "schema_version": "4",
1114
- "version": "3.0.0",
1114
+ "version": "3.2.0",
1115
1115
  "elapsed_ms": 42,
1116
1116
  "config": {
1117
1117
  "rules": {
@@ -1820,7 +1820,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
1820
1820
  {
1821
1821
  "kind": "dead-code",
1822
1822
  "schema_version": 7,
1823
- "version": "3.0.0",
1823
+ "version": "3.2.0",
1824
1824
  "elapsed_ms": 45,
1825
1825
  "total_issues": 12,
1826
1826
  "entry_points": {
@@ -1980,7 +1980,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
1980
1980
  {
1981
1981
  "kind": "dupes",
1982
1982
  "schema_version": 7,
1983
- "version": "3.0.0",
1983
+ "version": "3.2.0",
1984
1984
  "elapsed_ms": 82,
1985
1985
  "total_clones": 15,
1986
1986
  "total_lines_duplicated": 230,
@@ -2024,11 +2024,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
2024
2024
  {
2025
2025
  "kind": "combined",
2026
2026
  "schema_version": 7,
2027
- "version": "3.0.0",
2027
+ "version": "3.2.0",
2028
2028
  "elapsed_ms": 159,
2029
2029
  "check": {
2030
2030
  "schema_version": 7,
2031
- "version": "3.0.0",
2031
+ "version": "3.2.0",
2032
2032
  "elapsed_ms": 45,
2033
2033
  "total_issues": 12,
2034
2034
  "unused_files": [],
@@ -186,7 +186,7 @@ export type IssueAction = (FixAction | SuppressLineAction | SuppressFileAction |
186
186
  * Discriminant string for [`FixAction`]. Kebab-case per the JSON output
187
187
  * contract.
188
188
  */
189
- export type FixActionType = ("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-re-export-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" | "resolve-policy-violation" | "move-to-server-module" | "split-mixed-barrel" | "hoist-directive" | "wire-server-action" | "provide-inject" | "use-load-data" | "render-component" | "use-component-prop" | "emit-component-event" | "wire-svelte-event" | "resolve-route-collision" | "resolve-dynamic-segment-name-conflict" | "add-suppression-reason" | "remove-stale-suppression")
189
+ export type FixActionType = ("remove-export" | "delete-file" | "remove-dependency" | "move-dependency" | "remove-enum-member" | "remove-class-member" | "resolve-import" | "install-dependency" | "remove-duplicate" | "move-to-dev" | "move-to-prod" | "refactor-cycle" | "refactor-re-export-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" | "resolve-policy-violation" | "move-to-server-module" | "split-mixed-barrel" | "hoist-directive" | "wire-server-action" | "provide-inject" | "use-load-data" | "render-component" | "use-component-prop" | "emit-component-event" | "wire-svelte-event" | "resolve-route-collision" | "resolve-dynamic-segment-name-conflict" | "add-suppression-reason" | "remove-stale-suppression")
190
190
  /**
191
191
  * Singleton discriminant for [`SuppressLineAction`].
192
192
  */
@@ -1107,6 +1107,13 @@ type_only_dependencies: TypeOnlyDependencyFinding[]
1107
1107
  * devDependencies). Wrapped in [`TestOnlyDependencyFinding`].
1108
1108
  */
1109
1109
  test_only_dependencies?: TestOnlyDependencyFinding[]
1110
+ /**
1111
+ * devDependencies imported by production (non-test, non-config) source code
1112
+ * via a runtime/value import; they should be promoted to dependencies.
1113
+ * The promote-side mirror of [`TestOnlyDependencyFinding`]. Wrapped in
1114
+ * [`DevDependencyInProductionFinding`].
1115
+ */
1116
+ dev_dependencies_in_production?: DevDependencyInProductionFinding[]
1110
1117
  /**
1111
1118
  * Circular dependency chains detected in the module graph. Wrapped in
1112
1119
  * [`CircularDependencyFinding`] so each entry carries a typed `actions`
@@ -1465,6 +1472,11 @@ type_only_dependencies: number
1465
1472
  * devDependencies).
1466
1473
  */
1467
1474
  test_only_dependencies: number
1475
+ /**
1476
+ * devDependencies imported by production source code with a runtime/value
1477
+ * import (should be promoted to dependencies).
1478
+ */
1479
+ dev_dependencies_in_production: number
1468
1480
  /**
1469
1481
  * Cycles detected in the import graph.
1470
1482
  */
@@ -2269,6 +2281,37 @@ actions: IssueAction[]
2269
2281
  */
2270
2282
  introduced?: (AuditIntroduced | null)
2271
2283
  }
2284
+ /**
2285
+ * Wire-shape envelope for a [`DevDependencyInProduction`] finding. Carries a
2286
+ * `move-to-prod` primary (the promote-side mirror of
2287
+ * [`TestOnlyDependencyFinding`]'s `move-to-dev`) plus the standard
2288
+ * `ignoreDependencies` config suppress.
2289
+ */
2290
+ export interface DevDependencyInProductionFinding {
2291
+ /**
2292
+ * devDependency imported at runtime from production code, consider moving
2293
+ * to dependencies.
2294
+ */
2295
+ package_name: string
2296
+ /**
2297
+ * Path to the package.json where the dependency is listed.
2298
+ */
2299
+ path: string
2300
+ /**
2301
+ * 1-based line number of the dependency entry in package.json.
2302
+ */
2303
+ line: number
2304
+ /**
2305
+ * Suggested next steps. Always emitted (possibly empty for
2306
+ * forward-compat).
2307
+ */
2308
+ actions: IssueAction[]
2309
+ /**
2310
+ * Set by the audit pass when this finding is introduced relative to
2311
+ * the merge-base.
2312
+ */
2313
+ introduced?: (AuditIntroduced | null)
2314
+ }
2272
2315
  /**
2273
2316
  * Wire-shape envelope for a [`CircularDependency`] finding. Mirrors
2274
2317
  * [`UnusedFileFinding`]: flattens the bare finding and carries a typed
@@ -4224,6 +4267,12 @@ export interface HealthEffectiveThresholds {
4224
4267
  max_cyclomatic: number
4225
4268
  max_cognitive: number
4226
4269
  max_crap: number
4270
+ /**
4271
+ * Effective unit-size ceiling (maximum function length in lines) for the
4272
+ * matched file, after applying any `thresholdOverrides` on top of the
4273
+ * global `health.maxUnitSize` default.
4274
+ */
4275
+ max_unit_size: number
4227
4276
  }
4228
4277
  /**
4229
4278
  * Suggested action attached to a [`ComplexityViolation`].
@@ -4297,6 +4346,14 @@ functions_above_threshold: number
4297
4346
  max_cyclomatic_threshold: number
4298
4347
  max_cognitive_threshold: number
4299
4348
  max_crap_threshold: number
4349
+ /**
4350
+ * Effective global unit-size ceiling (`health.maxUnitSize`, maximum
4351
+ * function length in lines) for this run. Sits alongside the other three
4352
+ * `max_*_threshold` siblings so a consumer reading the summary sees every
4353
+ * configured threshold. Per-file `thresholdOverrides` are not reflected
4354
+ * here; this is the global default.
4355
+ */
4356
+ max_unit_size_threshold: number
4300
4357
  files_scored?: (number | null)
4301
4358
  average_maintainability?: (number | null)
4302
4359
  coverage_model?: (CoverageModel | null)
@@ -4328,6 +4385,7 @@ export interface HealthConfiguredThresholds {
4328
4385
  max_cyclomatic?: (number | null)
4329
4386
  max_cognitive?: (number | null)
4330
4387
  max_crap?: (number | null)
4388
+ max_unit_size?: (number | null)
4331
4389
  }
4332
4390
  /**
4333
4391
  * Current complexity metrics for a matched threshold override entry.
@@ -7861,6 +7919,13 @@ type_only_dependencies: TypeOnlyDependencyFinding[]
7861
7919
  * devDependencies). Wrapped in [`TestOnlyDependencyFinding`].
7862
7920
  */
7863
7921
  test_only_dependencies?: TestOnlyDependencyFinding[]
7922
+ /**
7923
+ * devDependencies imported by production (non-test, non-config) source code
7924
+ * via a runtime/value import; they should be promoted to dependencies.
7925
+ * The promote-side mirror of [`TestOnlyDependencyFinding`]. Wrapped in
7926
+ * [`DevDependencyInProductionFinding`].
7927
+ */
7928
+ dev_dependencies_in_production?: DevDependencyInProductionFinding[]
7864
7929
  /**
7865
7930
  * Circular dependency chains detected in the module graph. Wrapped in
7866
7931
  * [`CircularDependencyFinding`] so each entry carries a typed `actions`
@@ -9920,6 +9985,16 @@ export type BoundaryViolation = BoundaryViolationFinding;
9920
9985
  */
9921
9986
  export type CircularDependency = CircularDependencyFinding;
9922
9987
 
9988
+ /**
9989
+ * Backwards-compat alias for the pre-#384 bare `DevDependencyInProduction` name.
9990
+ * The wire shape is byte-identical: `DevDependencyInProductionFinding` flattens the bare
9991
+ * finding's fields via `#[serde(flatten)]` and adds `actions[]` plus
9992
+ * the optional audit-mode `introduced` flag. Consumers that imported
9993
+ * `DevDependencyInProduction` from `fallow/types` pre-migration continue to work via
9994
+ * this alias; new code should prefer `DevDependencyInProductionFinding`.
9995
+ */
9996
+ export type DevDependencyInProduction = DevDependencyInProductionFinding;
9997
+
9923
9998
  /**
9924
9999
  * Backwards-compat alias for the pre-#384 bare `DuplicateExport` name.
9925
10000
  * The wire shape is byte-identical: `DuplicateExportFinding` flattens the bare