fallow 2.85.0 → 2.86.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/schema.json CHANGED
@@ -1,11 +1,9 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "title": "FallowConfig",
4
- "description": "User-facing configuration loaded from `.fallowrc.json`, `.fallowrc.jsonc`, `fallow.toml`, or `.fallow.toml`.\n\n# Examples\n\n```\nuse fallow_config::FallowConfig;\n\n// Default config has sensible defaults\nlet config = FallowConfig::default();\nassert!(config.entry.is_empty());\nassert!(!config.production);\n\n// Deserialize from JSON\nlet config: FallowConfig = serde_json::from_str(r#\"{\n \"entry\": [\"src/main.ts\"],\n \"production\": true\n}\"#).unwrap();\nassert_eq!(config.entry, vec![\"src/main.ts\"]);\nassert!(config.production);\n```",
5
4
  "type": "object",
6
5
  "properties": {
7
6
  "$schema": {
8
- "description": "JSON Schema reference (ignored during deserialization).",
9
7
  "type": [
10
8
  "string",
11
9
  "null"
@@ -13,7 +11,6 @@
13
11
  "writeOnly": true
14
12
  },
15
13
  "extends": {
16
- "description": "Base config files to extend from.\n\nSupports three resolution strategies:\n- **Relative paths**: `\"./base.json\"` — resolved relative to the config file.\n- **npm packages**: `\"npm:@co/config\"` — resolved by walking up `node_modules/`.\n Package resolution checks `package.json` `exports`/`main` first, then falls back\n to standard config file names. Subpaths are supported (e.g., `npm:@co/config/strict.json`).\n- **HTTPS URLs**: `\"https://example.com/fallow-base.json\"` — fetched remotely.\n Only HTTPS is supported (no plain HTTP). URL-sourced configs may extend other\n URLs or `npm:` packages, but not relative paths. Only JSON/JSONC format is\n supported for remote configs. Timeout is configurable via\n `FALLOW_EXTENDS_TIMEOUT_SECS` (default: 5s).\n\nBase configs are loaded first, then this config's values override them.\nLater entries in the array override earlier ones.\n\n**Note:** `npm:` resolution uses `node_modules/` directory walk-up and is\nincompatible with Yarn Plug'n'Play (PnP), which has no `node_modules/`.\nURL extends fetch on every run (no caching). For reliable CI, prefer `npm:`\nfor private or critical configs.",
17
14
  "type": "array",
18
15
  "items": {
19
16
  "type": "string"
@@ -21,7 +18,6 @@
21
18
  "writeOnly": true
22
19
  },
23
20
  "entry": {
24
- "description": "Additional entry point glob patterns.",
25
21
  "type": "array",
26
22
  "items": {
27
23
  "type": "string"
@@ -29,7 +25,6 @@
29
25
  "default": []
30
26
  },
31
27
  "ignorePatterns": {
32
- "description": "Glob patterns to ignore from analysis.",
33
28
  "type": "array",
34
29
  "items": {
35
30
  "type": "string"
@@ -37,7 +32,6 @@
37
32
  "default": []
38
33
  },
39
34
  "framework": {
40
- "description": "Custom framework definitions (inline plugin definitions).",
41
35
  "type": "array",
42
36
  "items": {
43
37
  "$ref": "#/$defs/ExternalPluginDef"
@@ -45,7 +39,6 @@
45
39
  "default": []
46
40
  },
47
41
  "workspaces": {
48
- "description": "Workspace overrides.",
49
42
  "anyOf": [
50
43
  {
51
44
  "$ref": "#/$defs/WorkspaceConfig"
@@ -57,7 +50,6 @@
57
50
  "default": null
58
51
  },
59
52
  "ignoreDependencies": {
60
- "description": "Dependencies to ignore (always considered used and always considered available).\n\nListed dependencies are excluded from both unused dependency and unlisted\ndependency detection. Useful for runtime-provided packages like `bun:sqlite`\nor implicitly available dependencies.",
61
53
  "type": "array",
62
54
  "items": {
63
55
  "type": "string"
@@ -65,7 +57,6 @@
65
57
  "default": []
66
58
  },
67
59
  "ignoreUnresolvedImports": {
68
- "description": "Import specifier glob patterns whose `unresolved-import` findings are\nexpected and should be suppressed.\n\nMatching is against the raw import specifier string, not a filesystem\npath. Exact specifiers and subpaths must be listed separately when both\nshould be ignored, for example `[\"@example/icons\", \"@example/icons/**\"]`.\nBroad patterns such as `\"**\"` can hide real missing modules, so keep\nthis list focused on generated or runtime-provided specifiers.",
69
60
  "type": "array",
70
61
  "items": {
71
62
  "type": "string"
@@ -73,7 +64,6 @@
73
64
  "default": []
74
65
  },
75
66
  "ignoreExports": {
76
- "description": "Export ignore rules.",
77
67
  "type": "array",
78
68
  "items": {
79
69
  "$ref": "#/$defs/IgnoreExportRule"
@@ -81,33 +71,28 @@
81
71
  "default": []
82
72
  },
83
73
  "ignoreCatalogReferences": {
84
- "description": "Rules for suppressing `unresolved-catalog-reference` findings.\n\nEach rule matches by package name, optionally scoped to a specific\ncatalog and/or consumer `package.json` glob. Useful for staged catalog\nmigrations where the catalog edit lands separately from the consumer\nedit, and for library-internal placeholder packages whose target\ncatalog isn't ready yet.",
85
74
  "type": "array",
86
75
  "items": {
87
76
  "$ref": "#/$defs/IgnoreCatalogReferenceRule"
88
77
  }
89
78
  },
90
79
  "ignoreDependencyOverrides": {
91
- "description": "Rules for suppressing `unused-dependency-override` and\n`misconfigured-dependency-override` findings.\n\nEach rule matches by override target package, optionally scoped to the\ndeclaring source file (`pnpm-workspace.yaml` or `package.json`). Useful\nfor overrides targeting purely-transitive packages (CVE-fix pattern)\nwhere the conservative static algorithm would otherwise cry wolf.",
92
80
  "type": "array",
93
81
  "items": {
94
82
  "$ref": "#/$defs/IgnoreDependencyOverrideRule"
95
83
  }
96
84
  },
97
85
  "ignoreExportsUsedInFile": {
98
- "description": "Suppress unused-export findings when the exported symbol is referenced\ninside the file that declares it. This mirrors Knip's\n`ignoreExportsUsedInFile` option while still reporting exports that have\nno references at all.",
99
86
  "$ref": "#/$defs/IgnoreExportsUsedInFileConfig",
100
87
  "default": false
101
88
  },
102
89
  "ignoreDecorators": {
103
- "description": "Decorators that fallow should NOT treat as evidence of reflective use.\nMembers carrying only these decorators are checked for usage as if they\nwere undecorated. Members carrying any decorator NOT in this list stay\nskipped (frameworks like NestJS, Angular, TypeORM rely on reflection so\nthe conservative default is to keep skipping).\n\nMatching rule: entries containing `.` (e.g. `\"decorators.log\"`) match\nthe full dotted path of a decorator. Bare entries (e.g. `\"step\"` or\n`\"decorators\"`) match the leftmost segment; a bare `\"decorators\"` entry\nthus collapses every `@decorators.*` decorator. Both `\"@step\"` and\n`\"step\"` round-trip equivalently (a leading `@` is stripped before\nmatching).\n\nEntries that never match a decorator in the analyzed codebase produce\na one-time warning at end of run, mirroring the existing\n`usedClassMembers` warn-on-unmatched-pattern behavior. See issue #471.",
104
90
  "type": "array",
105
91
  "items": {
106
92
  "type": "string"
107
93
  }
108
94
  },
109
95
  "usedClassMembers": {
110
- "description": "Class member method/property rules that should never be flagged as\nunused. Supports plain member names for global suppression and scoped\nobjects with `extends` / `implements` constraints for framework-invoked\nmethods that should only be suppressed on matching classes.",
111
96
  "type": "array",
112
97
  "items": {
113
98
  "$ref": "#/$defs/UsedClassMemberRule"
@@ -115,7 +100,6 @@
115
100
  "default": []
116
101
  },
117
102
  "duplicates": {
118
- "description": "Duplication detection settings.",
119
103
  "$ref": "#/$defs/DuplicatesConfig",
120
104
  "default": {
121
105
  "enabled": true,
@@ -135,12 +119,12 @@
135
119
  }
136
120
  },
137
121
  "health": {
138
- "description": "Complexity health metrics settings.",
139
122
  "$ref": "#/$defs/HealthConfig",
140
123
  "default": {
141
124
  "maxCyclomatic": 20,
142
125
  "maxCognitive": 15,
143
126
  "maxCrap": 30.0,
127
+ "crapRefactorBand": 5,
144
128
  "ignore": [],
145
129
  "ownership": {
146
130
  "botPatterns": [
@@ -157,7 +141,6 @@
157
141
  }
158
142
  },
159
143
  "rules": {
160
- "description": "Per-issue-type severity rules.",
161
144
  "$ref": "#/$defs/RulesConfig",
162
145
  "default": {
163
146
  "unused-files": "error",
@@ -184,11 +167,12 @@
184
167
  "empty-catalog-groups": "warn",
185
168
  "unresolved-catalog-references": "error",
186
169
  "unused-dependency-overrides": "warn",
187
- "misconfigured-dependency-overrides": "error"
170
+ "misconfigured-dependency-overrides": "error",
171
+ "security-client-server-leak": "off",
172
+ "security-sink": "off"
188
173
  }
189
174
  },
190
175
  "boundaries": {
191
- "description": "Architecture boundary enforcement configuration.",
192
176
  "$ref": "#/$defs/BoundaryConfig",
193
177
  "default": {
194
178
  "zones": [],
@@ -196,14 +180,16 @@
196
180
  }
197
181
  },
198
182
  "flags": {
199
- "description": "Feature flag detection configuration.",
200
183
  "$ref": "#/$defs/FlagsConfig",
201
184
  "default": {
202
185
  "configObjectHeuristics": false
203
186
  }
204
187
  },
188
+ "security": {
189
+ "$ref": "#/$defs/SecurityConfig",
190
+ "default": {}
191
+ },
205
192
  "fix": {
206
- "description": "Auto-fix behavior settings.",
207
193
  "$ref": "#/$defs/FixConfig",
208
194
  "default": {
209
195
  "catalog": {
@@ -212,17 +198,14 @@
212
198
  }
213
199
  },
214
200
  "resolve": {
215
- "description": "Module resolver configuration (custom conditions, etc.).",
216
201
  "$ref": "#/$defs/ResolveConfig",
217
202
  "default": {}
218
203
  },
219
204
  "production": {
220
- "description": "Production mode: exclude test/dev files, only start/build scripts.\n\nAccepts the legacy boolean form (`true` applies to all analyses) or a\nper-analysis object (`{ \"deadCode\": false, \"health\": true, \"dupes\": false }`).",
221
205
  "$ref": "#/$defs/ProductionConfig",
222
206
  "default": false
223
207
  },
224
208
  "plugins": {
225
- "description": "Paths to external plugin files or directories containing plugin files.\n\nSupports TOML, JSON, and JSONC formats.\n\nIn addition to these explicit paths, fallow automatically discovers:\n- `*.toml`, `*.json`, `*.jsonc` files in `.fallow/plugins/`\n- `fallow-plugin-*.{toml,json,jsonc}` files in the project root",
226
209
  "type": "array",
227
210
  "items": {
228
211
  "type": "string"
@@ -230,7 +213,6 @@
230
213
  "default": []
231
214
  },
232
215
  "dynamicallyLoaded": {
233
- "description": "Glob patterns for files that are dynamically loaded at runtime\n(plugin directories, locale files, etc.). These files are treated as\nalways-used and will never be flagged as unused.",
234
216
  "type": "array",
235
217
  "items": {
236
218
  "type": "string"
@@ -238,7 +220,6 @@
238
220
  "default": []
239
221
  },
240
222
  "overrides": {
241
- "description": "Per-file rule overrides matching oxlint's overrides pattern.",
242
223
  "type": "array",
243
224
  "items": {
244
225
  "$ref": "#/$defs/ConfigOverride"
@@ -246,14 +227,12 @@
246
227
  "default": []
247
228
  },
248
229
  "codeowners": {
249
- "description": "Path to a CODEOWNERS file for `--group-by owner`.\n\nWhen unset, fallow auto-probes `CODEOWNERS`, `.github/CODEOWNERS`,\n`.gitlab/CODEOWNERS`, and `docs/CODEOWNERS`. Set this to use a\nnon-standard location.",
250
230
  "type": [
251
231
  "string",
252
232
  "null"
253
233
  ]
254
234
  },
255
235
  "publicPackages": {
256
- "description": "Workspace package name patterns that are public libraries.\nExported API surface from these packages is not flagged as unused.",
257
236
  "type": "array",
258
237
  "items": {
259
238
  "type": "string"
@@ -261,7 +240,6 @@
261
240
  "default": []
262
241
  },
263
242
  "regression": {
264
- "description": "Regression detection baseline embedded in config.\nStores issue counts from a known-good state for CI regression checks.\nPopulated by `--save-regression-baseline` (no path), read by `--fail-on-regression`.",
265
243
  "anyOf": [
266
244
  {
267
245
  "$ref": "#/$defs/RegressionConfig"
@@ -272,26 +250,21 @@
272
250
  ]
273
251
  },
274
252
  "audit": {
275
- "description": "Audit command baseline paths (one per analysis: dead-code, health, dupes).\n\n`fallow audit` runs three analyses and each has its own baseline format.\nPaths in this section are resolved relative to the project root. CLI flags\n(`--dead-code-baseline`, `--health-baseline`, `--dupes-baseline`) override\nthese values when provided.",
276
253
  "$ref": "#/$defs/AuditConfig"
277
254
  },
278
255
  "sealed": {
279
- "description": "Mark this config as sealed: `extends` paths must be file-relative and\nresolve within this config's own directory. `npm:` and `https:` extends\nare rejected. Useful for library publishers and monorepo sub-packages\nthat want to guarantee their config is self-contained and not subject\nto ancestor configs being injected via `extends`.\n\nDiscovery is unaffected (first-match-wins already stops the directory\nwalk at the nearest config). This only constrains `extends`.",
280
256
  "type": "boolean",
281
257
  "default": false
282
258
  },
283
259
  "includeEntryExports": {
284
- "description": "Report unused exports in entry files instead of auto-marking them as\nused. Catches typos in framework exports (e.g. `meatdata` instead of\n`metadata`). The CLI flag `--include-entry-exports` (global) overrides\nthis when set; otherwise the config value is used.",
285
260
  "type": "boolean",
286
261
  "default": false
287
262
  },
288
263
  "autoImports": {
289
- "description": "Resolve framework convention auto-imports (Nuxt components) as real\nmodule-graph edges, and stop treating the covered convention directories\nas always-used entry points.\n\nWhen `false` (default), auto-import edges are still synthesized additively\n(so a component's default export consumed via a `<Card />` template tag is\ncredited under `includeEntryExports`), but the convention directories stay\nregistered as entry points, so genuinely-unreferenced components are never\nreported as `unused-file`.\n\nWhen `true`, the Nuxt plugin drops its component entry patterns\n(`components/**`, `app/components/**`) so an unreferenced component is\nreported as `unused-file`. This is opt-in because non-flat projects\n(custom `prefix` / `pathPrefix` / `dirs` in `nuxt.config`, dynamic\n`<component :is>`, `@nuxt/content` MDC) are not fully modeled yet and could\nproduce false positives. As a guard, if `nuxt.config` declares a\n`components:` key the entry patterns are kept regardless. Composable and\nutil entry patterns are unaffected until convention resolution covers\nthem. See issue #704.",
290
264
  "type": "boolean",
291
265
  "default": false
292
266
  },
293
267
  "cache": {
294
- "description": "Incremental cache tuning. Today the only knob is `maxSizeMb`, which\ncaps the on-disk cache and triggers LRU eviction during save. See\n[`CacheConfig`].",
295
268
  "$ref": "#/$defs/CacheConfig"
296
269
  }
297
270
  },
@@ -583,22 +556,19 @@
583
556
  ]
584
557
  },
585
558
  "IgnoreCatalogReferenceRule": {
586
- "description": "Rule for suppressing an `unresolved-catalog-reference` finding.\n\nA finding is suppressed when ALL provided fields match the finding:\n- `package` matches the consumed package name exactly (case-sensitive).\n- `catalog`, if set, matches the referenced catalog name (`\"default\"` for\n bare `catalog:` references; named catalogs use their declared key). When\n omitted, any catalog matches.\n- `consumer`, if set, is a glob matched against the consumer `package.json`\n path relative to the project root. When omitted, any consumer matches.\n\nTypical use cases:\n- Staged migrations: catalog entry is being added in a separate PR\n- Library-internal placeholder packages whose target catalog isn't ready yet",
559
+ "description": "Rule for suppressing an `unresolved-catalog-reference` finding.",
587
560
  "type": "object",
588
561
  "properties": {
589
562
  "package": {
590
- "description": "Package name being referenced via the catalog protocol (exact match).",
591
563
  "type": "string"
592
564
  },
593
565
  "catalog": {
594
- "description": "Catalog name to scope the suppression to. `None` matches any catalog.",
595
566
  "type": [
596
567
  "string",
597
568
  "null"
598
569
  ]
599
570
  },
600
571
  "consumer": {
601
- "description": "Glob (root-relative) for the consumer `package.json`. `None` matches any consumer.",
602
572
  "type": [
603
573
  "string",
604
574
  "null"
@@ -611,15 +581,13 @@
611
581
  ]
612
582
  },
613
583
  "IgnoreDependencyOverrideRule": {
614
- "description": "Rule for suppressing an `unused-dependency-override` or\n`misconfigured-dependency-override` finding.\n\nA finding is suppressed when ALL provided fields match the finding:\n- `package` matches the override's target package name exactly\n (case-sensitive). For parent-chain overrides (`react>react-dom`), the\n target is the rightmost segment (`react-dom`).\n- `source`, if set, scopes the suppression to overrides declared in that\n source file. Accepts `\"pnpm-workspace.yaml\"` or `\"package.json\"`.\n When omitted, both sources match.\n\nTypical use cases:\n- Library-internal CI tooling overrides we cannot drop yet\n- Overrides targeting purely-transitive packages (CVE-fix pattern)",
584
+ "description": "Rule for suppressing dependency-override findings.",
615
585
  "type": "object",
616
586
  "properties": {
617
587
  "package": {
618
- "description": "Override target package name (exact match; case-sensitive).",
619
588
  "type": "string"
620
589
  },
621
590
  "source": {
622
- "description": "Source file scope: `\"pnpm-workspace.yaml\"` or `\"package.json\"`.\n`None` matches both sources.",
623
591
  "type": [
624
592
  "string",
625
593
  "null"
@@ -632,29 +600,23 @@
632
600
  ]
633
601
  },
634
602
  "IgnoreExportsUsedInFileConfig": {
635
- "description": "Controls whether exports referenced only inside their defining file are\nreported as unused exports.",
636
603
  "anyOf": [
637
604
  {
638
- "description": "`true` suppresses both value and type exports that are referenced in\ntheir defining file. `false` preserves the default cross-file behavior.",
639
605
  "type": "boolean"
640
606
  },
641
607
  {
642
- "description": "Knip-compatible fine-grained form. Fallow groups type aliases and\ninterfaces under `unused_types`, so either field enables type-export\nsuppression for same-file references.",
643
608
  "$ref": "#/$defs/IgnoreExportsUsedInFileByKind"
644
609
  }
645
610
  ]
646
611
  },
647
612
  "IgnoreExportsUsedInFileByKind": {
648
- "description": "Knip-compatible `ignoreExportsUsedInFile` object form.",
649
613
  "type": "object",
650
614
  "properties": {
651
615
  "type": {
652
- "description": "Suppress same-file references for exported type aliases.",
653
616
  "type": "boolean",
654
617
  "default": false
655
618
  },
656
619
  "interface": {
657
- "description": "Suppress same-file references for exported interfaces.",
658
620
  "type": "boolean",
659
621
  "default": false
660
622
  }
@@ -828,6 +790,14 @@
828
790
  "format": "double",
829
791
  "default": 30.0
830
792
  },
793
+ "crapRefactorBand": {
794
+ "description": "Band below `maxCyclomatic` where CRAP-only findings also receive a\nsecondary `refactor-function` action (default: 5). Set to `0` to only\nsuggest refactoring when cyclomatic already meets the configured\nthreshold.",
795
+ "type": "integer",
796
+ "format": "uint16",
797
+ "minimum": 0,
798
+ "maximum": 65535,
799
+ "default": 5
800
+ },
831
801
  "ignore": {
832
802
  "description": "Glob patterns to exclude from complexity analysis.",
833
803
  "type": "array",
@@ -1012,6 +982,16 @@
1012
982
  "misconfigured-dependency-overrides": {
1013
983
  "$ref": "#/$defs/Severity",
1014
984
  "default": "error"
985
+ },
986
+ "security-client-server-leak": {
987
+ "description": "Opt-in (default off): a `\"use client\"` file that transitively imports a\nmodule reading a non-public `process.env` secret. Surfaced only by\n`fallow security`; never under bare `fallow` or the `audit` gate.",
988
+ "$ref": "#/$defs/Severity",
989
+ "default": "off"
990
+ },
991
+ "security-sink": {
992
+ "description": "Opt-in (default off): a syntactic tainted-sink candidate matched against\nthe data-driven catalogue (`security_matchers.toml`). ONE knob gates ALL\ncatalogue categories. Surfaced only by `fallow security`; never under\nbare `fallow` or the `audit` gate.",
993
+ "$ref": "#/$defs/Severity",
994
+ "default": "off"
1015
995
  }
1016
996
  }
1017
997
  },
@@ -1036,11 +1016,11 @@
1036
1016
  ]
1037
1017
  },
1038
1018
  "BoundaryConfig": {
1039
- "description": "Architecture boundary configuration.\n\nDefines zones (directory groupings) and rules (which zones may import from which).\nOptionally uses a built-in preset as a starting point.\n\n# Examples\n\n```\nuse fallow_config::BoundaryConfig;\n\nlet json = r#\"{\n \"zones\": [\n { \"name\": \"ui\", \"patterns\": [\"src/components/**\"] },\n { \"name\": \"db\", \"patterns\": [\"src/db/**\"] }\n ],\n \"rules\": [\n { \"from\": \"ui\", \"allow\": [\"db\"] }\n ]\n}\"#;\nlet config: BoundaryConfig = serde_json::from_str(json).unwrap();\nassert_eq!(config.zones.len(), 2);\nassert_eq!(config.rules.len(), 1);\n```\n\nUsing a preset:\n\n```\nuse fallow_config::BoundaryConfig;\n\nlet json = r#\"{ \"preset\": \"layered\" }\"#;\nlet mut config: BoundaryConfig = serde_json::from_str(json).unwrap();\nconfig.expand(\"src\");\nassert_eq!(config.zones.len(), 4);\nassert_eq!(config.rules.len(), 4);\n```",
1019
+ "description": "Architecture boundary configuration.",
1040
1020
  "type": "object",
1041
1021
  "properties": {
1042
1022
  "preset": {
1043
- "description": "Built-in architecture preset. When set, expands into default zones and rules.\nUser-defined zones and rules merge on top: zones with the same name replace\nthe preset zone; rules with the same `from` replace the preset rule.\nPreset patterns use `{rootDir}/{zone}/**` where rootDir is auto-detected\nfrom tsconfig.json (falls back to `src`).\nNote: preset patterns are flat (`src/<zone>/**`). For monorepos with\nper-package source directories, define zones explicitly instead.",
1023
+ "description": "Optional built-in preset.",
1044
1024
  "anyOf": [
1045
1025
  {
1046
1026
  "$ref": "#/$defs/BoundaryPreset"
@@ -1051,7 +1031,7 @@
1051
1031
  ]
1052
1032
  },
1053
1033
  "zones": {
1054
- "description": "Named zones mapping directory patterns to architectural layers.",
1034
+ "description": "Zone definitions.",
1055
1035
  "type": "array",
1056
1036
  "items": {
1057
1037
  "$ref": "#/$defs/BoundaryZone"
@@ -1059,7 +1039,7 @@
1059
1039
  "default": []
1060
1040
  },
1061
1041
  "rules": {
1062
- "description": "Import rules between zones. A zone with a rule entry can only import\nfrom the listed zones (plus itself). A zone without a rule entry is unrestricted.",
1042
+ "description": "Zone import rules.",
1063
1043
  "type": "array",
1064
1044
  "items": {
1065
1045
  "$ref": "#/$defs/BoundaryRule"
@@ -1069,54 +1049,54 @@
1069
1049
  }
1070
1050
  },
1071
1051
  "BoundaryPreset": {
1072
- "description": "Built-in architecture presets.\n\nEach preset expands into a set of zones and import rules for a common\narchitecture pattern. User-defined zones and rules merge on top of the\npreset defaults (zones with the same name replace the preset zone;\nrules with the same `from` replace the preset rule).\n\n# Examples\n\n```\nuse fallow_config::BoundaryPreset;\n\nlet preset: BoundaryPreset = serde_json::from_str(r#\"\"layered\"\"#).unwrap();\nassert!(matches!(preset, BoundaryPreset::Layered));\n```",
1052
+ "description": "Built-in architecture presets.",
1073
1053
  "oneOf": [
1074
1054
  {
1075
- "description": "Classic layered architecture: presentation → application → domain ← infrastructure.\nInfrastructure may also import from application (common in DI frameworks).",
1055
+ "description": "Layered architecture.",
1076
1056
  "type": "string",
1077
1057
  "const": "layered"
1078
1058
  },
1079
1059
  {
1080
- "description": "Hexagonal / ports-and-adapters: adapters → ports → domain.",
1060
+ "description": "Hexagonal / ports-and-adapters.",
1081
1061
  "type": "string",
1082
1062
  "const": "hexagonal"
1083
1063
  },
1084
1064
  {
1085
- "description": "Feature-Sliced Design: app > pages > widgets > features > entities > shared.\nEach layer may only import from layers below it.",
1065
+ "description": "Feature-Sliced Design.",
1086
1066
  "type": "string",
1087
1067
  "const": "feature-sliced"
1088
1068
  },
1089
1069
  {
1090
- "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.\nTop-level files in `src/features/` are classified by the logical\n`features` parent zone, so barrels can re-export child features while\nnon-barrel top-level files still obey the `features` boundary rule.",
1070
+ "description": "Bulletproof React.",
1091
1071
  "type": "string",
1092
1072
  "const": "bulletproof"
1093
1073
  }
1094
1074
  ]
1095
1075
  },
1096
1076
  "BoundaryZone": {
1097
- "description": "A named zone grouping files by directory pattern.",
1077
+ "description": "A zone grouping files by directory pattern.",
1098
1078
  "type": "object",
1099
1079
  "properties": {
1100
1080
  "name": {
1101
- "description": "Zone identifier referenced in rules (e.g., `\"ui\"`, `\"database\"`, `\"shared\"`).",
1081
+ "description": "Zone name.",
1102
1082
  "type": "string"
1103
1083
  },
1104
1084
  "patterns": {
1105
- "description": "Glob patterns (relative to project root) that define zone membership.\nA file belongs to the first zone whose pattern matches.",
1085
+ "description": "Membership patterns.",
1106
1086
  "type": "array",
1107
1087
  "items": {
1108
1088
  "type": "string"
1109
1089
  }
1110
1090
  },
1111
1091
  "autoDiscover": {
1112
- "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.",
1092
+ "description": "Directories whose children become zones.",
1113
1093
  "type": "array",
1114
1094
  "items": {
1115
1095
  "type": "string"
1116
1096
  }
1117
1097
  },
1118
1098
  "root": {
1119
- "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.",
1099
+ "description": "Optional subtree scope.",
1120
1100
  "type": [
1121
1101
  "string",
1122
1102
  "null"
@@ -1132,11 +1112,11 @@
1132
1112
  "type": "object",
1133
1113
  "properties": {
1134
1114
  "from": {
1135
- "description": "The zone this rule applies to (the importing side).",
1115
+ "description": "Source zone.",
1136
1116
  "type": "string"
1137
1117
  },
1138
1118
  "allow": {
1139
- "description": "Zones that `from` is allowed to import from. Self-imports are always allowed.\nAn empty list means the zone may not import from any other zone.",
1119
+ "description": "Allowed target zones.",
1140
1120
  "type": "array",
1141
1121
  "items": {
1142
1122
  "type": "string"
@@ -1144,7 +1124,7 @@
1144
1124
  "default": []
1145
1125
  },
1146
1126
  "allowTypeOnly": {
1147
- "description": "Zones that `from` may type-only-import from even when not listed in\n`allow`. Mirrors the `allow` shape: a list of target zone names. A\ntype-only import declaration (`import type {...}`, `import type * as ns`,\nor a per-specifier inline `type` qualifier on every named specifier) to a\nlisted zone is not reported as a boundary violation. Mixed-specifier\nimports (`import { type Foo, Bar }`) that carry at least one value\nsymbol still fire because the runtime dependency on `Bar` is real.\nType-only re-exports (`export type { Foo } from \"...\"`) participate\nin the same allowance because they surface as edges flagged\n`is_type_only: true` and, like type-only imports, are erased at\ncompile time.",
1127
+ "description": "Allowed type-only targets.",
1148
1128
  "type": "array",
1149
1129
  "items": {
1150
1130
  "type": "string"
@@ -1207,12 +1187,55 @@
1207
1187
  "function"
1208
1188
  ]
1209
1189
  },
1190
+ "SecurityConfig": {
1191
+ "description": "Scopes the data-driven security matcher catalogue used by `fallow security`.\nAn absent block (or both `include`/`exclude` unset) admits every category.",
1192
+ "type": "object",
1193
+ "properties": {
1194
+ "categories": {
1195
+ "description": "Include/exclude filter over catalogue category ids (e.g. `dangerous-html`).",
1196
+ "anyOf": [
1197
+ {
1198
+ "$ref": "#/$defs/SecurityCategories"
1199
+ },
1200
+ {
1201
+ "type": "null"
1202
+ }
1203
+ ]
1204
+ }
1205
+ },
1206
+ "additionalProperties": false
1207
+ },
1208
+ "SecurityCategories": {
1209
+ "description": "Include/exclude lists scoping the active security matcher categories. When\n`include` is set, only those categories are active; `exclude` removes\ncategories from the admitted set. Both unset admits every category.",
1210
+ "type": "object",
1211
+ "properties": {
1212
+ "include": {
1213
+ "description": "Catalogue category ids to admit. When set, all others are excluded.",
1214
+ "type": [
1215
+ "array",
1216
+ "null"
1217
+ ],
1218
+ "items": {
1219
+ "type": "string"
1220
+ }
1221
+ },
1222
+ "exclude": {
1223
+ "description": "Catalogue category ids to remove from the admitted set.",
1224
+ "type": [
1225
+ "array",
1226
+ "null"
1227
+ ],
1228
+ "items": {
1229
+ "type": "string"
1230
+ }
1231
+ }
1232
+ },
1233
+ "additionalProperties": false
1234
+ },
1210
1235
  "FixConfig": {
1211
- "description": "Auto-fix behavior settings.",
1212
1236
  "type": "object",
1213
1237
  "properties": {
1214
1238
  "catalog": {
1215
- "description": "Auto-fix behavior for pnpm catalog edits.",
1216
1239
  "$ref": "#/$defs/CatalogFixConfig",
1217
1240
  "default": {
1218
1241
  "deletePrecedingComments": "auto"
@@ -1221,34 +1244,20 @@
1221
1244
  }
1222
1245
  },
1223
1246
  "CatalogFixConfig": {
1224
- "description": "Auto-fix behavior for pnpm catalog entries.",
1225
1247
  "type": "object",
1226
1248
  "properties": {
1227
1249
  "deletePrecedingComments": {
1228
- "description": "Whether removing an unused catalog entry also removes the contiguous\nYAML comment block immediately above it.",
1229
1250
  "$ref": "#/$defs/CatalogPrecedingCommentPolicy",
1230
1251
  "default": "auto"
1231
1252
  }
1232
1253
  }
1233
1254
  },
1234
1255
  "CatalogPrecedingCommentPolicy": {
1235
- "description": "Policy for deleting comments immediately above removed catalog entries.",
1236
- "oneOf": [
1237
- {
1238
- "description": "Delete the comment block when it is separated from previous siblings by\na blank line, or when it directly follows the parent catalog header.",
1239
- "type": "string",
1240
- "const": "auto"
1241
- },
1242
- {
1243
- "description": "Always delete the contiguous comment block immediately above the entry.",
1244
- "type": "string",
1245
- "const": "always"
1246
- },
1247
- {
1248
- "description": "Never delete leading comments; leave them in place as orphan comments.",
1249
- "type": "string",
1250
- "const": "never"
1251
- }
1256
+ "type": "string",
1257
+ "enum": [
1258
+ "auto",
1259
+ "always",
1260
+ "never"
1252
1261
  ]
1253
1262
  },
1254
1263
  "ResolveConfig": {
@@ -1265,34 +1274,27 @@
1265
1274
  }
1266
1275
  },
1267
1276
  "ProductionConfig": {
1268
- "description": "Production-mode defaults.",
1269
1277
  "anyOf": [
1270
1278
  {
1271
- "description": "Legacy/global form: `production = true` or `\"production\": true`.",
1272
1279
  "type": "boolean"
1273
1280
  },
1274
1281
  {
1275
- "description": "Per-analysis form.",
1276
1282
  "$ref": "#/$defs/PerAnalysisProductionConfig"
1277
1283
  }
1278
1284
  ]
1279
1285
  },
1280
1286
  "PerAnalysisProductionConfig": {
1281
- "description": "Per-analysis production-mode defaults.",
1282
1287
  "type": "object",
1283
1288
  "properties": {
1284
1289
  "deadCode": {
1285
- "description": "Production mode for dead-code analysis.",
1286
1290
  "type": "boolean",
1287
1291
  "default": false
1288
1292
  },
1289
1293
  "health": {
1290
- "description": "Production mode for health analysis.",
1291
1294
  "type": "boolean",
1292
1295
  "default": false
1293
1296
  },
1294
1297
  "dupes": {
1295
- "description": "Production mode for duplication analysis.",
1296
1298
  "type": "boolean",
1297
1299
  "default": false
1298
1300
  }
@@ -1304,14 +1306,12 @@
1304
1306
  "type": "object",
1305
1307
  "properties": {
1306
1308
  "files": {
1307
- "description": "Glob patterns to match files against (relative to config file location).",
1308
1309
  "type": "array",
1309
1310
  "items": {
1310
1311
  "type": "string"
1311
1312
  }
1312
1313
  },
1313
1314
  "rules": {
1314
- "description": "Partial rules — only specified fields override the base rules.",
1315
1315
  "$ref": "#/$defs/PartialRulesConfig",
1316
1316
  "default": {}
1317
1317
  }
@@ -1573,15 +1573,33 @@
1573
1573
  "type": "null"
1574
1574
  }
1575
1575
  ]
1576
+ },
1577
+ "security-client-server-leak": {
1578
+ "anyOf": [
1579
+ {
1580
+ "$ref": "#/$defs/Severity"
1581
+ },
1582
+ {
1583
+ "type": "null"
1584
+ }
1585
+ ]
1586
+ },
1587
+ "security-sink": {
1588
+ "anyOf": [
1589
+ {
1590
+ "$ref": "#/$defs/Severity"
1591
+ },
1592
+ {
1593
+ "type": "null"
1594
+ }
1595
+ ]
1576
1596
  }
1577
1597
  }
1578
1598
  },
1579
1599
  "RegressionConfig": {
1580
- "description": "Regression baseline counts, embedded in the config file.\n\nWhen `--fail-on-regression` is used without `--regression-baseline <PATH>`,\nfallow reads the baseline from this config section.\nWhen `--save-regression-baseline` is used without a path argument,\nfallow writes the baseline into the config file.",
1581
1600
  "type": "object",
1582
1601
  "properties": {
1583
1602
  "baseline": {
1584
- "description": "Dead code issue counts baseline.",
1585
1603
  "anyOf": [
1586
1604
  {
1587
1605
  "$ref": "#/$defs/RegressionBaseline"
@@ -1594,7 +1612,6 @@
1594
1612
  }
1595
1613
  },
1596
1614
  "RegressionBaseline": {
1597
- "description": "Per-type issue counts for regression comparison.",
1598
1615
  "type": "object",
1599
1616
  "properties": {
1600
1617
  "totalIssues": {
@@ -1702,36 +1719,30 @@
1702
1719
  }
1703
1720
  },
1704
1721
  "AuditConfig": {
1705
- "description": "Per-analysis baseline paths for the `audit` command.\n\nEach field points to a baseline file produced by the corresponding\nsubcommand (`fallow dead-code --save-baseline`, `fallow health --save-baseline`,\n`fallow dupes --save-baseline`). `audit` passes each baseline through to its\nunderlying analysis; baseline-matched issues are excluded from the verdict.",
1706
1722
  "type": "object",
1707
1723
  "properties": {
1708
1724
  "gate": {
1709
- "description": "Which findings should make `fallow audit` fail.",
1710
1725
  "$ref": "#/$defs/AuditGate"
1711
1726
  },
1712
1727
  "deadCodeBaseline": {
1713
- "description": "Path to the dead-code baseline (produced by `fallow dead-code --save-baseline`).",
1714
1728
  "type": [
1715
1729
  "string",
1716
1730
  "null"
1717
1731
  ]
1718
1732
  },
1719
1733
  "healthBaseline": {
1720
- "description": "Path to the health baseline (produced by `fallow health --save-baseline`).",
1721
1734
  "type": [
1722
1735
  "string",
1723
1736
  "null"
1724
1737
  ]
1725
1738
  },
1726
1739
  "dupesBaseline": {
1727
- "description": "Path to the duplication baseline (produced by `fallow dupes --save-baseline`).",
1728
1740
  "type": [
1729
1741
  "string",
1730
1742
  "null"
1731
1743
  ]
1732
1744
  },
1733
1745
  "cacheMaxAgeDays": {
1734
- "description": "Maximum age (in days since last reuse or fresh create) of a persistent\nreusable base-snapshot worktree cache entry. Older entries are removed\nat the top of the next `fallow audit` invocation. The env var\n`FALLOW_AUDIT_CACHE_MAX_AGE_DAYS` wins over this field. Unset on both\nsides defaults to 30 days. Setting either source to `0` disables the\nsweep entirely (escape hatch for CI runners that prune caches\nout-of-band). Invalid env var values (non-integer, negative) silently\nfall back to this field / default rather than failing the audit.",
1735
1746
  "type": [
1736
1747
  "integer",
1737
1748
  "null"
@@ -1742,26 +1753,16 @@
1742
1753
  }
1743
1754
  },
1744
1755
  "AuditGate": {
1745
- "description": "Gating mode for `fallow audit`.",
1746
- "oneOf": [
1747
- {
1748
- "description": "Only findings introduced by the current changeset affect the verdict.",
1749
- "type": "string",
1750
- "const": "new-only"
1751
- },
1752
- {
1753
- "description": "All findings in changed files affect the verdict.",
1754
- "type": "string",
1755
- "const": "all"
1756
- }
1756
+ "type": "string",
1757
+ "enum": [
1758
+ "new-only",
1759
+ "all"
1757
1760
  ]
1758
1761
  },
1759
1762
  "CacheConfig": {
1760
- "description": "Incremental cache configuration.\n\nToday only `maxSizeMb` is exposed. The env var `FALLOW_CACHE_MAX_SIZE`\n(also in MB) wins over this field when both are set. The default cap is\n256 MB; values are interpreted as whole megabytes.",
1761
1763
  "type": "object",
1762
1764
  "properties": {
1763
1765
  "maxSizeMb": {
1764
- "description": "Maximum on-disk cache size in megabytes. When the serialized cache\nexceeds 80% of this cap during save, the oldest entries are evicted\ndown to 60% of the cap. Default: 256 MB.",
1765
1766
  "type": [
1766
1767
  "integer",
1767
1768
  "null"