fallow 3.38.0 → 3.39.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/capabilities.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "3.38.0",
3
+ "version": "3.39.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": [
@@ -683,7 +683,7 @@
683
683
  "name": "--unused-deps",
684
684
  "type": "bool",
685
685
  "required": false,
686
- "description": "Only report unused dependencies",
686
+ "description": "Only report dependency findings: unused dependencies, dev dependencies and optional dependencies, plus type-only, test-only and dev-in-production dependencies",
687
687
  "possible_values": [
688
688
  "true",
689
689
  "false"
@@ -1247,7 +1247,7 @@
1247
1247
  "name": "--runtime",
1248
1248
  "type": "string",
1249
1249
  "required": false,
1250
- "description": "The JavaScript runtime (default from PATH: `node` for node:test, `bun` for bun test, and `node`, then `bun`, for Vitest)"
1250
+ "description": "The JavaScript runtime: a name on PATH such as `node`, or a file path (default from PATH: `node` for node:test, `bun` for bun test, and `node`, then `bun`, for Vitest)"
1251
1251
  },
1252
1252
  {
1253
1253
  "name": "--allow-dirty",
@@ -3016,7 +3016,7 @@
3016
3016
  "default_severity": "error",
3017
3017
  "opt_in": false,
3018
3018
  "frameworks": [],
3019
- "note": "--unused-deps controls unused-dependency, unused-dev-dependency, unused-optional-dependency, type-only-dependency, and test-only-dependency",
3019
+ "note": "--unused-deps controls unused-dependency, unused-dev-dependency, unused-optional-dependency, type-only-dependency, test-only-dependency, and dev-dependency-in-production",
3020
3020
  "license": "free",
3021
3021
  "license_note": null,
3022
3022
  "docs_url": "https://fallow.tools/docs/explanations/dead-code/#unused-dependencies"
@@ -3056,7 +3056,7 @@
3056
3056
  "default_severity": "warn",
3057
3057
  "opt_in": false,
3058
3058
  "frameworks": [],
3059
- "note": "--unused-deps controls unused-dependency, unused-dev-dependency, unused-optional-dependency, type-only-dependency, and test-only-dependency",
3059
+ "note": "--unused-deps controls unused-dependency, unused-dev-dependency, unused-optional-dependency, type-only-dependency, test-only-dependency, and dev-dependency-in-production",
3060
3060
  "license": "free",
3061
3061
  "license_note": null,
3062
3062
  "docs_url": "https://fallow.tools/docs/explanations/dead-code/#unused-devdependencies"
@@ -3096,7 +3096,7 @@
3096
3096
  "default_severity": "warn",
3097
3097
  "opt_in": false,
3098
3098
  "frameworks": [],
3099
- "note": "--unused-deps controls unused-dependency, unused-dev-dependency, unused-optional-dependency, type-only-dependency, and test-only-dependency",
3099
+ "note": "--unused-deps controls unused-dependency, unused-dev-dependency, unused-optional-dependency, type-only-dependency, test-only-dependency, and dev-dependency-in-production",
3100
3100
  "license": "free",
3101
3101
  "license_note": null,
3102
3102
  "docs_url": "https://fallow.tools/docs/explanations/dead-code/#unused-optionaldependencies"
@@ -7796,7 +7796,8 @@
7796
7796
  "key_params": [
7797
7797
  "target",
7798
7798
  "production",
7799
- "include_churn"
7799
+ "include_churn",
7800
+ "cloud"
7800
7801
  ],
7801
7802
  "license": "free",
7802
7803
  "license_note": null,
@@ -8383,7 +8384,7 @@
8383
8384
  ]
8384
8385
  },
8385
8386
  "plugins": {
8386
- "count": 149,
8387
+ "count": 150,
8387
8388
  "note": "Built-in framework plugins, auto-activated when their enabler dependency is present; run fallow list --plugins for the set active in a specific project",
8388
8389
  "names": [
8389
8390
  "nextjs",
@@ -8513,6 +8514,7 @@
8513
8514
  "svgo",
8514
8515
  "svgr",
8515
8516
  "graphql-codegen",
8517
+ "graphql-config",
8516
8518
  "typedoc",
8517
8519
  "openapi-ts",
8518
8520
  "plop",
@@ -246,7 +246,7 @@
246
246
  "default_severity": "error",
247
247
  "opt_in": false,
248
248
  "frameworks": [],
249
- "note": "--unused-deps controls unused-dependency, unused-dev-dependency, unused-optional-dependency, type-only-dependency, and test-only-dependency",
249
+ "note": "--unused-deps controls unused-dependency, unused-dev-dependency, unused-optional-dependency, type-only-dependency, test-only-dependency, and dev-dependency-in-production",
250
250
  "license": "free",
251
251
  "license_note": null,
252
252
  "docs_url": "https://fallow.tools/docs/explanations/dead-code/#unused-dependencies"
@@ -286,7 +286,7 @@
286
286
  "default_severity": "warn",
287
287
  "opt_in": false,
288
288
  "frameworks": [],
289
- "note": "--unused-deps controls unused-dependency, unused-dev-dependency, unused-optional-dependency, type-only-dependency, and test-only-dependency",
289
+ "note": "--unused-deps controls unused-dependency, unused-dev-dependency, unused-optional-dependency, type-only-dependency, test-only-dependency, and dev-dependency-in-production",
290
290
  "license": "free",
291
291
  "license_note": null,
292
292
  "docs_url": "https://fallow.tools/docs/explanations/dead-code/#unused-devdependencies"
@@ -326,7 +326,7 @@
326
326
  "default_severity": "warn",
327
327
  "opt_in": false,
328
328
  "frameworks": [],
329
- "note": "--unused-deps controls unused-dependency, unused-dev-dependency, unused-optional-dependency, type-only-dependency, and test-only-dependency",
329
+ "note": "--unused-deps controls unused-dependency, unused-dev-dependency, unused-optional-dependency, type-only-dependency, test-only-dependency, and dev-dependency-in-production",
330
330
  "license": "free",
331
331
  "license_note": null,
332
332
  "docs_url": "https://fallow.tools/docs/explanations/dead-code/#unused-optionaldependencies"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "3.38.0",
3
+ "version": "3.39.0",
4
4
  "mcpName": "io.github.fallow-rs/fallow",
5
5
  "description": "Codebase intelligence for TypeScript and JavaScript: health, complexity, duplication, architecture, styling drift, and unused code from one graph. CLI, LSP, and MCP server. Zero config for over 100 frameworks.",
6
6
  "license": "MIT",
@@ -92,14 +92,14 @@
92
92
  "@tanstack/intent": "0.5.6"
93
93
  },
94
94
  "optionalDependencies": {
95
- "@fallow-cli/darwin-arm64": "3.38.0",
96
- "@fallow-cli/darwin-x64": "3.38.0",
97
- "@fallow-cli/linux-x64-gnu": "3.38.0",
98
- "@fallow-cli/linux-arm64-gnu": "3.38.0",
99
- "@fallow-cli/linux-x64-musl": "3.38.0",
100
- "@fallow-cli/linux-arm64-musl": "3.38.0",
101
- "@fallow-cli/win32-arm64-msvc": "3.38.0",
102
- "@fallow-cli/win32-x64-msvc": "3.38.0",
103
- "fallow-type-aware": "3.38.0"
95
+ "@fallow-cli/darwin-arm64": "3.39.0",
96
+ "@fallow-cli/darwin-x64": "3.39.0",
97
+ "@fallow-cli/linux-x64-gnu": "3.39.0",
98
+ "@fallow-cli/linux-arm64-gnu": "3.39.0",
99
+ "@fallow-cli/linux-x64-musl": "3.39.0",
100
+ "@fallow-cli/linux-arm64-musl": "3.39.0",
101
+ "@fallow-cli/win32-arm64-msvc": "3.39.0",
102
+ "@fallow-cli/win32-x64-msvc": "3.39.0",
103
+ "fallow-type-aware": "3.39.0"
104
104
  }
105
105
  }
@@ -37,7 +37,7 @@
37
37
  ],
38
38
  "$defs": {
39
39
  "RulePackRule": {
40
- "description": "One declarative policy rule inside a rule pack.\n\n`callees` applies only to `banned-call` rules; `specifiers` and\n`ignoreTypeOnly` apply only to `banned-import` rules; `effects` applies\nonly to `banned-effect` rules; `exports` applies only to `banned-export`\nrules. `allowedFiles` and `proofKinds` apply only to `gdp-proof-producer`\nrules. `zones` can scope any rule kind to files classified into one of the\nnamed boundary zones. Setting a field on the wrong kind is a load error\n(fail loud, never silently ignore policy).",
40
+ "description": "One declarative policy rule inside a rule pack.\n\n`callees` applies only to `banned-call` rules; `specifiers`,\n`allowNames` and `ignoreTypeOnly` apply only to `banned-import` rules; `effects` applies\nonly to `banned-effect` rules; `exports` applies only to `banned-export`\nrules. `allowedFiles` and `proofKinds` apply only to `gdp-proof-producer`\nrules. `zones` can scope any rule kind to files classified into one of the\nnamed boundary zones. Setting a field on the wrong kind is a load error\n(fail loud, never silently ignore policy).",
41
41
  "type": "object",
42
42
  "properties": {
43
43
  "id": {
@@ -49,7 +49,7 @@
49
49
  "$ref": "#/$defs/RulePackRuleKind"
50
50
  },
51
51
  "callees": {
52
- "description": "Callee patterns to ban (`banned-call` only). Matching is segment-aware\nand import-resolved, identical to `boundaries.calls.forbidden`:\n`child_process.*` covers `import { exec } from \"node:child_process\"`,\nthe bare specifier, and namespace/default imports; `fetch` matches only\nthe global `fetch`; a leading `*.member` matches any object.",
52
+ "description": "Callee patterns to ban (`banned-call` only). A pattern matches a call\n(`Date()`) and a constructor call (`new Date()`). Matching is\nsegment-aware and import-resolved, identical to\n`boundaries.calls.forbidden`: `child_process.*` covers\n`import { exec } from \"node:child_process\"`, the bare specifier, and\nnamespace/default imports; a leading `*.member` matches any object. A\nbare pattern matches the written name, so it also matches a local\nfunction or class with that name.",
53
53
  "type": "array",
54
54
  "items": {
55
55
  "type": "string"
@@ -62,8 +62,15 @@
62
62
  "type": "string"
63
63
  }
64
64
  },
65
+ "allowNames": {
66
+ "description": "Imported names that a matched specifier may still bind (`banned-import`\nonly). Each entry is an exact imported name, with no globs. `\"default\"`\nallows a default import. The rule compares the source-side name, so\n`import { Mesh as Vector3 }` binds `Mesh`. The list applies to every\n`specifiers` entry and to its subpaths.\n\nA namespace import, a star re-export and a side-effect import always\nreport, because they cannot be proven to stay in the list. `allowNames`\ndoes not cover `require()`, `import()` or `import x = require()`, because\n`banned-import` does not read them.\n\nA nonempty list changes `matched` to `<specifier>: <binding>`, so it\nchanges the baseline keys of the rule. Empty or absent keeps the raw\nspecifier. For a deny list, use `banned-call` with `three.Mesh`.\n`banned-call` reports calls and `new` expressions. It does not report the\nimport, a re-export, `extends Mesh` or a value reference.",
67
+ "type": "array",
68
+ "items": {
69
+ "type": "string"
70
+ }
71
+ },
65
72
  "effects": {
66
- "description": "Effect classes to ban (`banned-effect` only). Effects are derived from\n`security_matchers.toml` catalogue rows and matched against captured\ncall sites after import-resolution canonicalization.",
73
+ "description": "Effect classes to ban (`banned-effect` only). Effects are derived from\n`security_matchers.toml` catalogue rows and matched against captured\ncall sites after import-resolution canonicalization. `banned-effect`\nreads call expressions only. It does not read `new` expressions.",
67
74
  "type": "array",
68
75
  "items": {
69
76
  "$ref": "#/$defs/EffectKind"
package/schema.json CHANGED
@@ -35,7 +35,7 @@
35
35
  "writeOnly": true
36
36
  },
37
37
  "entry": {
38
- "description": "An array of project-root-relative glob patterns whose matching files are seeded as manual entry points, on top of the framework and package.json entries fallow discovers automatically, so their transitive imports are not reported as unused. Set it (e.g. `[\"src/main.ts\"]`) when a file is a real runtime root that no plugin or manifest declares; patterns are validated at load and matched against discovered files. A single `*` stays within one path segment; use `**` to match across directories.",
38
+ "description": "An array of project-root-relative glob patterns whose matching files are seeded as manual entry points, on top of the framework and package.json entries fallow discovers automatically, so their transitive imports are not reported as unused. Set it (e.g. `[\"src/main.ts\"]`) when a file is a real runtime root that no plugin or manifest declares; patterns are validated at load and matched against discovered files. For a script that does not run in production, use a `framework[]` entry with `entryPointRole: \"support\"` and leave its glob out of `entry`: a file that also matches `entry` stays a runtime root. A single `*` stays within one path segment; use `**` to match across directories.",
39
39
  "type": "array",
40
40
  "items": {
41
41
  "type": "string"
@@ -58,7 +58,7 @@
58
58
  }
59
59
  },
60
60
  "framework": {
61
- "description": "Declares inline external framework plugins as data (array of plugin objects), each with `name` plus optional `enablers` (package names that activate it) or richer `detection` (dependency/file-existence/`all`/`any` checks, taking priority over `enablers`), `entryPoints` (+ `entryPointRole` runtime/support/test), `configPatterns`, `alwaysUsed`, `toolingDependencies`, `usedExports` (`{ pattern, exports }`), and `usedClassMembers`. Set it to keep a custom or in-house framework's entry points, config files, and conventions reachable without a Rust plugin; these definitions are appended to plugins discovered via `plugins`, `.fallow/plugins/`, and root `fallow-plugin-*` files (first occurrence of a name wins), and cannot do AST-based config parsing.",
61
+ "description": "Declares inline external framework plugins as data (array of plugin objects), each with `name` plus optional `enablers` (package names that activate it) or richer `detection` (dependency/file-existence/`all`/`any` checks, taking priority over `enablers`); an entry with neither never activates, and config load warns. Each entry can also have `entryPoints` (+ `entryPointRole` runtime/support/test), `configPatterns`, `alwaysUsed`, `toolingDependencies`, `usedExports` (`{ pattern, exports }`), and `usedClassMembers`. Set it to keep a custom or in-house framework's entry points, config files, and conventions reachable without a Rust plugin; these definitions are appended to plugins discovered via `plugins`, `.fallow/plugins/`, and root `fallow-plugin-*` files (first occurrence of a name wins), and cannot do AST-based config parsing.",
62
62
  "type": "array",
63
63
  "items": {
64
64
  "$ref": "#/$defs/ExternalPluginDef"
@@ -296,6 +296,10 @@
296
296
  "description": "Options for the `circular-dependencies` rule, currently only `ignoreLazyImports`. Set `{ \"ignoreLazyImports\": true }` to skip import edges that load on demand or on another thread (an `import()` inside a function, a template `import()`, a lazy `import.meta.glob`, a worker URL, a webpack worker loader request such as `worker-loader!./work.js`) when fallow looks for cycles. A top-level `await import()`, `require()`, an eager glob, and an edge that also has a static import stay in the cycle graph. Default `false` leaves the rule unchanged.",
297
297
  "$ref": "#/$defs/CircularDependenciesConfig"
298
298
  },
299
+ "effect": {
300
+ "description": "Options for projects that use the Effect library, currently only `schemaExportsAsTypes`. Set `{ \"schemaExportsAsTypes\": true }` to report an unused exported Effect schema (a class that extends `Schema.Class`, `Schema.TaggedClass`, `Schema.TaggedError` and the other `Schema` class factories, or a const such as `Schema.Struct({...})` or `Schema.String.pipe(Schema.brand(\"Id\"))`) as an unused type under the `unused-types` rule, not as an unused export, so `\"unused-types\": \"off\"` hides it. `Schema` must come from `effect`, `effect/Schema` or `@effect/schema`. Default `false` leaves every report unchanged.",
301
+ "$ref": "#/$defs/EffectConfig"
302
+ },
299
303
  "boundaries": {
300
304
  "description": "Configures architecture boundary enforcement: which source directories belong to which named zone and which zones may import which others, reported as boundary-violation, boundary-coverage-violation, and boundary-call-violation findings (severity via rules.boundary-violation, default error). Set to enforce a layered/module architecture; the object holds `preset` (one of layered, hexagonal, feature-sliced, bulletproof, whose default zones/rules are merged in with the user-declared zones/rules taking precedence), `zones` (each with `name`, `patterns`, `autoDiscover`, optional `root`), `rules` (each with `from`, `allow`, `allowTypeOnly` target-zone lists), `coverage` (`requireAllFiles` plus `allowUnmatched` globs for files matching no zone), and `calls` (a `forbidden` list of `{from, callee}` banned-call rules per zone).",
301
305
  "$ref": "#/$defs/BoundaryConfig",
@@ -366,7 +370,7 @@
366
370
  }
367
371
  },
368
372
  "overrides": {
369
- "description": "An ordered list of per-file rule-severity overrides: each entry re-severities specific analysis rules for files its globs match, layered on top of the top-level `rules` defaults. Set to relax or tighten rules for a subset of paths (e.g. downgrade unused-exports to warn under a generated directory); each entry has `files` (glob-pattern array) and `rules` (a partial per-rule severity map of error/warn/off). Entries apply in list order and a file matched by several entries takes every matching entry's overrides (later entries win on conflict); inter-file rules (duplicate-exports, circular-dependencies, re-export-cycle) have no effect in an override (fallow warns during analysis and points to the right mechanism: top-level `ignoreExports` for duplicate-exports, a file-level `// fallow-ignore-file` comment for the others). A single `*` stays within one path segment; use `**` to match across directories.",
373
+ "description": "An ordered list of per-file rule-severity overrides: each entry re-severities specific analysis rules for files its globs match, layered on top of the top-level `rules` defaults. Set to relax or tighten a rule for a subset of paths (e.g. downgrade unused-exports to warn under a generated directory); each entry has `files` (glob-pattern array) and `rules` (a partial per-rule severity map of error/warn/off). Entries apply in list order and a file matched by several entries takes every matching entry's overrides (later entries win on conflict). For most rules, an override cannot start a detector for a rule that is off at top level, so set the top-level rule on and use an override to set `off` for the other paths. Project-level rules ignore overrides: unused-dependencies, unused-dev-dependencies, unused-optional-dependencies, unlisted-dependencies, type-only-dependencies, test-only-dependencies, dev-dependencies-in-production, duplicate-exports, re-export-cycle and unused-catalog-entries. Fallow reports each such entry as an `override-rule-ineffective` diagnostic that names the mechanism that works. A single `*` stays within one path segment; use `**` to match across directories.",
370
374
  "type": "array",
371
375
  "items": {
372
376
  "$ref": "#/$defs/ConfigOverride"
@@ -458,7 +462,7 @@
458
462
  "default": null
459
463
  },
460
464
  "enablers": {
461
- "description": "Package names that activate this plugin when found in package.json.\nSupports exact matches and prefix patterns (ending with `/`).\nOnly used when `detection` is not set.",
465
+ "description": "Package names that activate this plugin when found in package.json.\nSupports exact matches and prefix patterns (ending with `/`).\nOnly used when `detection` is not set. A plugin with neither `detection`\nnor `enablers` never activates, and config load warns.",
462
466
  "type": "array",
463
467
  "items": {
464
468
  "type": "string"
@@ -474,7 +478,7 @@
474
478
  "default": []
475
479
  },
476
480
  "entryPointRole": {
477
- "description": "Coverage role for `entryPoints`.\n\nDefaults to `support`. Set to `runtime` for application entry points\nor `test` for test framework entry points.",
481
+ "description": "Coverage role for `entryPoints`.\n\nDefaults to `support`. Set to `runtime` for application entry points\nor `test` for test framework entry points. In production mode, a file\nthat only entries with an explicit `support` role match takes no part\nin dead-code analysis.",
478
482
  "$ref": "#/$defs/EntryPointRole",
479
483
  "default": "support"
480
484
  },
@@ -1989,6 +1993,17 @@
1989
1993
  },
1990
1994
  "additionalProperties": false
1991
1995
  },
1996
+ "EffectConfig": {
1997
+ "description": "Options for projects that use the Effect library.\n\nThe default changes nothing: each Effect export is reported like any\nother export.",
1998
+ "type": "object",
1999
+ "properties": {
2000
+ "schemaExportsAsTypes": {
2001
+ "description": "Report an unused exported Effect schema as an unused type, under the\n`unused-types` rule, and not as an unused export. An Effect schema\ndefines a runtime value and a type (`typeof User.Type`), so a team\nthat keeps exported types as contracts can keep schemas too. With\n`\"unused-types\": \"off\"`, fallow does not report them. A schema is a\nclass that extends a `Schema` class factory (`Schema.Class`,\n`Schema.TaggedClass`, `Schema.TaggedError`, `Schema.TaggedRequest`,\n`Schema.Error`, `Schema.Opaque`), or a const whose\nvalue is a `Schema` constructor, member or pipe chain\n(`Schema.Struct({...})`, `Schema.String.pipe(Schema.brand(\"Id\"))`).\nEach `pipe` argument must be a `Schema` transform.\n`Schema` must come from `effect`, `effect/Schema` or `@effect/schema`.\nDefault `false`.",
2002
+ "type": "boolean"
2003
+ }
2004
+ },
2005
+ "additionalProperties": false
2006
+ },
1992
2007
  "BoundaryConfig": {
1993
2008
  "description": "Architecture boundary configuration.",
1994
2009
  "type": "object",
@@ -2156,7 +2171,7 @@
2156
2171
  "type": "string"
2157
2172
  },
2158
2173
  "callee": {
2159
- "description": "Forbidden callee pattern(s). Matching is segment-aware, not substring:\n`child_process.*` matches `child_process.exec` (and named imports from\n`child_process` / `node:child_process`), `fetch` matches only `fetch`,\nand a leading `*.` suffix-matches any object (`*.innerHTML`).",
2174
+ "description": "Forbidden callee pattern(s). A pattern matches `Date()` and `new Date()`. Matching is\nsegment-aware, not substring: `child_process.*` matches `child_process.exec` (and named\nimports from `child_process` / `node:child_process`), `fetch` matches only `fetch`,\nand a leading `*.` suffix-matches any object (`*.innerHTML`).",
2160
2175
  "$ref": "#/$defs/ForbiddenCallee"
2161
2176
  }
2162
2177
  },
@@ -2396,7 +2411,7 @@
2396
2411
  }
2397
2412
  },
2398
2413
  "rules": {
2399
- "description": "Partial per-rule severity map applied only to files matching this entry's `files` globs; each rule key takes `error`, `warn`, or `off`, and omitted rules keep their top-level severity. Set to change how specific rules (e.g. unused-exports, unused-files) behave for the scoped paths. Inter-file rules (duplicate-exports, circular-dependencies, re-export-cycle) have no effect in an override; fallow warns during analysis and names the right mechanism instead (top-level `ignoreExports` for duplicate-exports, a file-level `// fallow-ignore-file` comment for circular-dependencies and re-export-cycle).",
2414
+ "description": "Partial per-rule severity map applied only to files matching this entry's `files` globs; each rule key takes `error`, `warn`, or `off`, and omitted rules keep their top-level severity. Set to change how specific rules (e.g. unused-exports, unused-files) behave for the scoped paths. Project-level rules ignore overrides: unused-dependencies, unused-dev-dependencies, unused-optional-dependencies, unlisted-dependencies, type-only-dependencies, test-only-dependencies, dev-dependencies-in-production, duplicate-exports, re-export-cycle and unused-catalog-entries. Fallow reports each such entry as an `override-rule-ineffective` diagnostic that names the mechanism that works. For most other rules, an override cannot start a detector for a rule that is off at top level.",
2400
2415
  "$ref": "#/$defs/PartialRulesConfig",
2401
2416
  "default": {}
2402
2417
  }
@@ -112,7 +112,7 @@ Dead-code filter flags select issue categories (`--unused-exports`, `--unused-ty
112
112
 
113
113
  `fallow architecture` reports the architecture findings: circular dependencies, re-export cycles, package cycles, boundary violations and rule-pack policy violations. Use `--cycles`, `--boundaries` or `--policy` to select one kind. The JSON output has `kind: "architecture"` and its own `schema_version`, with the same arrays and finding ids as `dead-code`. `fallow dead-code` still reports these findings until the next major version. Its `--circular-deps`, `--re-export-cycles`, `--package-cycles`, `--boundary-violations` and `--policy-violations` flags are deprecated aliases. Run `fallow guard <files>` before an edit and `fallow architecture` after it. In bare `fallow`, `--only architecture` reports only these findings and `--skip architecture` removes them.
114
114
 
115
- For suppressible findings, use the placement listed in the issue catalogue: `// fallow-ignore-next-line <issue-type>` above the finding, or `// fallow-ignore-file <issue-type>` at the top of the file. Some types support only file-level suppression; others have no suppression comment. A bare supported form without a type suppresses all matching findings at that placement.
115
+ For suppressible findings, use the placement listed in the issue catalogue: `// fallow-ignore-next-line <issue-type>` above the finding, or `// fallow-ignore-file <issue-type>` at the top of the file. Some types support only file-level suppression; others have no suppression comment. A bare supported form without a type suppresses all matching findings at that placement. Put `-- <reason>` after the types to record why. Always give a reason on a `security-sink` or `security-client-server-leak` suppression: `fallow audit` marks an added security suppression without a reason with `suppression.security_without_reason: true`. A blanket comment without a kind does not set this flag. To list existing suppressions without a reason, run `fallow suppressions --format json` and select `reason_present: false`.
116
116
 
117
117
  `fallow explain <issue-type>` describes one type without running analysis, and the MCP server serves the same catalogue as the `fallow://issue-types` resource.
118
118
 
@@ -413,6 +413,10 @@ export const keepThisToo = 2;
413
413
  // fallow-ignore-file
414
414
  // fallow-ignore-file unused-export
415
415
 
416
+ // State the reason after `--` (every kind accepts it)
417
+ // fallow-ignore-next-line security-sink -- the HTML is a constant template
418
+ el.innerHTML = TEMPLATE;
419
+
416
420
  // Mark as intentionally unused (tracked for staleness)
417
421
  /** @expected-unused */
418
422
  export const deprecatedHelper = () => {};
@@ -81,7 +81,7 @@ Every fallow command with its purpose and key flags. The table is regenerated fr
81
81
  | `explain` | Explain one issue type without running analysis | `<issue-type>`, `--format json` |
82
82
  | `audit` | Combined dead-code + complexity + duplication + styling for changed files, returns a verdict; `fallow review` is an alias for `fallow audit --brief` (advisory orientation brief, always exits 0) | `--base`, `--gate`, `--brief`, `--max-decisions`, `--walkthrough-guide`, `--walkthrough-file`, `--show-deprioritized`, `--production`, `--production-dead-code`, `--production-health`, `--production-dupes`, `--workspace`, `--changed-workspaces`, `--ci`, `--fail-on-issues`, `--explain`, `--explain-skipped`, `--dead-code-baseline`, `--health-baseline`, `--dupes-baseline`, `--max-crap`, `--coverage`, `--coverage-root`, `--only`, `--skip`, `--no-css`, `--css-deep`, `--no-css-deep`, `--include-entry-exports` |
83
83
  | `audit-cache` | Maintain reusable audit base-snapshot caches | |
84
- | `baselines` | Maintain committed baselines: `baselines prune` removes the entries of fixed findings and never adds one | `prune --check`, `prune --dead-code-baseline`, `prune --health-baseline`, `prune --dupes-baseline` |
84
+ | `baselines` | Maintain committed baselines: `baselines prune` removes the entries of fixed findings and never adds one | `prune --check`, `prune --dead-code-baseline`, `prune --health-baseline`, `prune --dupes-baseline`, `prune --production`, `prune --include-entry-exports` |
85
85
  | `decision-surface` | Surface the consequential structural DECISIONS a change embeds (the apex of the review brief), each framed as a judgment question with the routed expert to ask | `--max-decisions` |
86
86
  | `impact` | Show what fallow has done for you: how many issues it is surfacing, the trend since the last recorded run, and how many commits it contained at the pre-commit gate | `--all`, `--sort`, `--limit` |
87
87
  | `security` | Surface opt-in local security candidates for agent verification (not confirmed vulnerabilities). Rule families include the graph rule `client-server-leak`, a data-driven `tainted-sink` catalogue, and the include-required `hardcoded-secret` category for provider-prefix credentials and high-entropy literals assigned to secret-shaped identifiers. Most catalogue rows require non-literal input; narrowly literal-aware rows flag deterministic unsafe literals. Rules default off; suppress a file with `// fallow-ignore-file security-sink`; scope categories with `security.categories`. Add project-local request object names with `security.requestReceivers`; it extends the built-in `req` / `request` / `ctx` / `context` / `event` allowlist for HTTP `query`, `params`, and `body` reads. `hardcoded-secret` runs only when listed in `security.categories.include`. | `--format human\|json\|sarif`, `--changed-since`, `--file`, `--diff-file`, `--workspace`, `--changed-workspaces`, `--surface`, `--ci`, `--fail-on-issues`, `--sarif-file`, `--summary` |
@@ -129,7 +129,7 @@ Common global flags for this command: [`--format`](#global-flags), [`--quiet`](#
129
129
  |---|---|
130
130
  | `--unused-files` | Unused files |
131
131
  | `--unused-exports` | Unused exports |
132
- | `--unused-deps` | Unused dependencies, devDependencies, optionalDependencies, type-only production deps, and test-only production deps |
132
+ | `--unused-deps` | Dependency findings: unused dependencies, dev dependencies and optional dependencies, plus type-only, test-only and dev-in-production dependencies |
133
133
  | `--unused-types` | Unused types |
134
134
  | `--private-type-leaks` | Opt-in API hygiene check (default `off`) for exported signatures that reference same-file private types. Storybook `*.stories.*` story files and framework routing convention files (Next.js App + Pages Router, Gatsby, Remix v2, TanStack Router, Expo Router) are skipped to avoid noise. Enable via this flag or `private-type-leaks: "warn"` / `"error"` in [`rules`](#configuration-file-format). |
135
135
  | `--deprecated-exports-in-use` | Export marked @deprecated is still referenced |
@@ -441,7 +441,7 @@ The `--entry-weight` JSON output carries `entry_weight.entries[]`, one row per r
441
441
 
442
442
  To gate eager growth in CI, save a baseline file on the main branch with `fallow list --entry-weight --save-regression-baseline <PATH>`. A later run with `--regression-baseline <PATH>` adds `entry_weight.regression`: per-entry `baseline_eager_bytes`, `current_eager_bytes`, `new_eager_packages` and `exceeded`. The comparison is report-only until you add `--fail-on-regression`; then an entry that grew more than `--tolerance` (bytes, or a percentage such as `5%`) exits 1. A new entry never fails the gate.
443
443
 
444
- The `--workspaces` JSON output carries `workspaces[]` (name, project-root-relative path, `is_internal_dependency` bool) plus `workspace_diagnostics[]`. Each diagnostic has a `kind` discriminator (for example `undeclared-workspace`, `malformed-package-json`, `glob-matched-no-package-json`, `workspace-glob-outside-root`, `workspace-pattern-dir-missing`, `nested-project-root`, `malformed-tsconfig`, `tsconfig-reference-dir-missing`, `malformed-pnpm-workspace-yaml`, `skipped-large-file`, `skipped-minified-file`, `skipped-source-dotdir`, `source-read-failure`, `npm-lock-override-resolution-skipped`) with a typed payload (`error`, `pattern`, or none), and a `path` that is project-root-relative with forward slashes on every envelope that carries the array. The same `workspace_diagnostics[]` array is also surfaced on the `fallow dead-code --format json`, `fallow dupes --format json`, and `fallow health --format json` envelopes, at the top level of the bare combined `fallow --format json` envelope, on `fallow audit --format json` under `dead_code`, and on the `audit-brief` envelope shared by `fallow review --format json` and `fallow audit --brief --format json`, also under `dead_code` (omitted when empty). The combined carrier is the envelope root, not a section, so `--skip check`, `--only health`, and `--only dupes` all still report what their analyses recorded. The combined root is the union of what every analysis in the run recorded, deduplicated on the whole `kind` (typed payload included) plus `path`, so two overlapping globs still report the same package-less directory once per `pattern` (a declared glob's no-op `./` prefix is normalised away, so one glob written `"./apps/**"` in `package.json` and `apps/**` in `pnpm-workspace.yaml` stays one entry): a combined run walks the project once per analysis, and a per-analysis `production` mode (`production: { deadCode, health, dupes }`, `--production-health`) can give those walks different file sets, so only the union reports what the run as a whole saw. Each analysis contributes the workspace-discovery list its own config load produced, the same list `fallow list --workspaces` reports, so the combined root can carry an `undeclared-workspace` or `glob-matched-no-package-json` entry that the standalone `dead-code`, `check`, `health`, and `dupes` envelopes, which read the process diagnostics registry instead, do not. `fallow audit --format json` and the `audit-brief` envelope are on the same broad side: they fold the dead-code analysis's own list into their `dead_code.workspace_diagnostics[]`, so they too report an `undeclared-workspace` entry the standalone envelopes miss. The CLI and the programmatic route (MCP code mode, NAPI, embedders) agree on everything an analysis records: both folds close with the same process-registry read, which covers what an analysis records after its section captured its list (a `source-read-failure`, or the analysis-stage kinds a health run's own dead-code precompute records) and skips `skipped-large-file`, `skipped-minified-file`, and `skipped-source-dotdir`, since those reach an envelope only from the walk that recorded them. The analysis-stage kinds (`malformed-pnpm-workspace-yaml`, `bun-lockb-override-resolution-skipped`, `bun-lock-override-resolution-skipped`, `pnpm-lock-override-resolution-skipped`, `npm-lock-override-resolution-skipped`, `bun-resolutions-shadowed-by-overrides`, `pnpm-workspace-overrides-ignored`, `boundaries-not-configured`, `rule-packs-not-configured`) are recorded by the dead-code analyze pass, so they only appear on runs that include it: `fallow dupes --format json` and `fallow --only dupes` report the workspace-discovery and source-discovery kinds alone. A malformed ROOT `package.json` exits 2 at config load; everything else warns and continues. The dead-code result adds two more kinds, each with a `pattern` payload and `path: "."`: `ignore-dependencies-glob-unmatched` (an `ignoreDependencies` glob matched no declared dependency; omitted when the run reports no dependency finding, for example with `--unused-files` or `--file`) and `ignore-findings-pattern-unmatched` (an `ignoreFindings` pattern matched no finding). It also adds three kinds for an entry that matched but changed nothing, with the same payload: `entry-pattern-redundant` (every file the `entry` pattern matches is already a runtime entry point from another source), `ignore-dependencies-entry-redundant` (the `ignoreDependencies` entry hid no dependency finding; same gate as the unmatched glob) and `ignore-patterns-entry-redundant` (the `ignorePatterns` glob excluded no file that a built-in ignore, `.gitignore` or the file type filter does not already skip; a glob for build output has an effect only after a build). A `--production` run reports none of the three. SARIF carries the same entries under `invocations[0].toolConfigurationNotifications[]` of the dead-code run. Markdown, `github-summary`, `pr-comment-github`, `pr-comment-gitlab` and the summary `body` of `review-github` and `review-gitlab` add an `Unmatched config patterns` section, and a `Redundant config entries` section for the three redundant kinds. Human, compact, CodeClimate and `github-annotations` print a stderr note. `fallow report --from` uses the same place as the live run of that format.
444
+ The `--workspaces` JSON output carries `workspaces[]` (name, project-root-relative path, `is_internal_dependency` bool) plus `workspace_diagnostics[]`. Each diagnostic has a `kind` discriminator (for example `undeclared-workspace`, `malformed-package-json`, `glob-matched-no-package-json`, `workspace-glob-outside-root`, `workspace-pattern-dir-missing`, `nested-project-root`, `malformed-tsconfig`, `tsconfig-reference-dir-missing`, `malformed-pnpm-workspace-yaml`, `skipped-large-file`, `skipped-minified-file`, `skipped-source-dotdir`, `source-read-failure`, `npm-lock-override-resolution-skipped`) with a typed payload (`error`, `pattern`, or none), and a `path` that is project-root-relative with forward slashes on every envelope that carries the array. The same `workspace_diagnostics[]` array is also surfaced on the `fallow dead-code --format json`, `fallow dupes --format json`, and `fallow health --format json` envelopes, at the top level of the bare combined `fallow --format json` envelope, on `fallow audit --format json` under `dead_code`, and on the `audit-brief` envelope shared by `fallow review --format json` and `fallow audit --brief --format json`, also under `dead_code` (omitted when empty). The combined carrier is the envelope root, not a section, so `--skip check`, `--only health`, and `--only dupes` all still report what their analyses recorded. The combined root is the union of what every analysis in the run recorded, deduplicated on the whole `kind` (typed payload included) plus `path`, so two overlapping globs still report the same package-less directory once per `pattern` (a declared glob's no-op `./` prefix is normalised away, so one glob written `"./apps/**"` in `package.json` and `apps/**` in `pnpm-workspace.yaml` stays one entry): a combined run walks the project once per analysis, and a per-analysis `production` mode (`production: { deadCode, health, dupes }`, `--production-health`) can give those walks different file sets, so only the union reports what the run as a whole saw. Each analysis contributes the workspace-discovery list its own config load produced, the same list `fallow list --workspaces` reports, so the combined root can carry an `undeclared-workspace` or `glob-matched-no-package-json` entry that the standalone `dead-code`, `check`, `health`, and `dupes` envelopes, which read the process diagnostics registry instead, do not. `fallow audit --format json` and the `audit-brief` envelope are on the same broad side: they fold the dead-code analysis's own list into their `dead_code.workspace_diagnostics[]`, so they too report an `undeclared-workspace` entry the standalone envelopes miss. The CLI and the programmatic route (MCP code mode, NAPI, embedders) agree on everything an analysis records: both folds close with the same process-registry read, which covers what an analysis records after its section captured its list (a `source-read-failure`, or the analysis-stage kinds a health run's own dead-code precompute records) and skips `skipped-large-file`, `skipped-minified-file`, and `skipped-source-dotdir`, since those reach an envelope only from the walk that recorded them. The analysis-stage kinds (`malformed-pnpm-workspace-yaml`, `bun-lockb-override-resolution-skipped`, `bun-lock-override-resolution-skipped`, `pnpm-lock-override-resolution-skipped`, `npm-lock-override-resolution-skipped`, `bun-resolutions-shadowed-by-overrides`, `pnpm-workspace-overrides-ignored`, `boundaries-not-configured`, `rule-packs-not-configured`) are recorded by the dead-code analyze pass, so they only appear on runs that include it: `fallow dupes --format json` and `fallow --only dupes` report the workspace-discovery and source-discovery kinds alone. A malformed ROOT `package.json` exits 2 at config load; everything else warns and continues. The dead-code result adds two more kinds, each with a `pattern` payload and `path: "."`: `ignore-dependencies-glob-unmatched` (an `ignoreDependencies` glob matched no declared dependency; omitted when the run reports no dependency finding, for example with `--unused-files` or `--file`) and `ignore-findings-pattern-unmatched` (an `ignoreFindings` pattern matched no finding). It also adds three kinds for an entry that matched but changed nothing, with the same payload: `entry-pattern-redundant` (every file the `entry` pattern matches is already a runtime entry point from another source), `ignore-dependencies-entry-redundant` (the `ignoreDependencies` entry hid no dependency finding, and a `--production` run has no reason to need it; same gate as the unmatched glob) and `ignore-patterns-entry-redundant` (the `ignorePatterns` glob excluded no file that a built-in ignore, `.gitignore` or the file type filter does not already skip; a glob for build output has an effect only after a build). A `--production` run reports none of the three. It also adds `override-rule-ineffective` for an `overrides` rule that cannot change a result, with `rule` (canonical name), `files` (the globs as written), `reason` and `path: "."`. It also adds `requested-rule-off` when an explicit issue-type flag asks for a rule that the config sets to `off`, with `rule`, `flag` and `path: "."`. A zero count for that rule then measures nothing. The exit code does not change. `--lean` keeps the first entry of each kind plus a `count`, so run without `--lean` to see each rule. The `reason` token set is open. `project-level` marks a rule that reads only the top-level severity: unused-dependencies, unused-dev-dependencies, unused-optional-dependencies, unlisted-dependencies, type-only-dependencies, test-only-dependencies, dev-dependencies-in-production, duplicate-exports, re-export-cycle and unused-catalog-entries. `base-rule-off` marks a rule that is off at top level after CLI flags, so the detector does not run. The `message` names the mechanism that works. The dependency rows follow the same dependency-report gate as the unmatched glob, and `--production` keeps every row. SARIF carries the same entries under `invocations[0].toolConfigurationNotifications[]` of the dead-code run. Markdown, `github-summary`, `pr-comment-github`, `pr-comment-gitlab` and the summary `body` of `review-github` and `review-gitlab` add an `Unmatched config patterns` section, a `Redundant config entries` section for the three redundant kinds, a `Requested checks that did not run` section for `requested-rule-off`, and an `Override rules with no effect` section for `override-rule-ineffective`. Human, compact, CodeClimate and `github-annotations` print a stderr note. `fallow report --from` uses the same place as the live run of that format.
445
445
 
446
446
  The `--boundaries` JSON output carries `boundaries.logical_groups[]` alongside the existing `zones[]` / `rules[]` arrays. Each logical-group entry surfaces a user-authored `autoDiscover` parent zone (which expansion otherwise flattens into per-child zones like `features/auth` / `features/billing`): `name`, `children`, `auto_discover` (verbatim user strings), `status` (`ok` / `empty` / `invalid_path`), `source_zone_index`, summed `file_count`, optional `authored_rule` (the pre-expansion `{ allow, allowTypeOnly }` keyed on the parent), optional `fallback_zone` cross-reference when the parent also kept its own `patterns` (Bulletproof case), optional `merged_from` (parent zone indices when the user declared the same parent name twice; surfaces the duplicate in JSON instead of only in `tracing::warn!`), optional `original_zone_root` (echo of the parent's `root` subtree scope for monorepo patchers), and optional `child_source_indices` (parallel to `children`, attributing each child to a specific `auto_discover` entry when multiple paths were authored). The full shape is documented in `docs/output-schema.json` under `ListBoundariesOutput`.
447
447
 
@@ -527,7 +527,7 @@ Human output groups paths under "Shared with your team (commit these)" and "Loca
527
527
  {
528
528
  "kind": "agent-install",
529
529
  "schema_version": 1,
530
- "fallow_version": "3.38.0",
530
+ "fallow_version": "3.39.0",
531
531
  "root": "/abs/path",
532
532
  "mode": "install",
533
533
  "dry_run": false,
@@ -739,7 +739,7 @@ fallow health --format json --quiet --trend
739
739
  {
740
740
  "kind": "health",
741
741
  "schema_version": 7,
742
- "version": "3.38.0",
742
+ "version": "3.39.0",
743
743
  "elapsed_ms": 32,
744
744
  "summary": {
745
745
  "files_analyzed": 482,
@@ -1214,7 +1214,7 @@ fallow audit \
1214
1214
  {
1215
1215
  "kind": "audit",
1216
1216
  "schema_version": 7,
1217
- "version": "3.38.0",
1217
+ "version": "3.39.0",
1218
1218
  "command": "audit",
1219
1219
  "verdict": "fail",
1220
1220
  "changed_files_count": 12,
@@ -1302,7 +1302,7 @@ fallow flags --format json --quiet --workspace my-package
1302
1302
  ```json
1303
1303
  {
1304
1304
  "schema_version": 7,
1305
- "version": "3.38.0",
1305
+ "version": "3.39.0",
1306
1306
  "elapsed_ms": 116,
1307
1307
  "feature_flags": [],
1308
1308
  "total_flags": 0
@@ -1321,18 +1321,18 @@ The second rule family is a data-driven `tainted-sink` catalogue: syntactic dang
1321
1321
 
1322
1322
  | Category | CWE | Sink |
1323
1323
  |----------|-----|------|
1324
- | `dangerous-html` | 79 | `innerHTML` / `outerHTML` / `insertAdjacentHTML` / `dangerouslySetInnerHTML` |
1324
+ | `dangerous-html` | 79 | `innerHTML` / `outerHTML` / `insertAdjacentHTML` / `dangerouslySetInnerHTML` / iframe `srcdoc` (property, `setAttribute`, JSX `srcDoc`, Vue / Svelte / Angular bindings) / `Range.createContextualFragment` |
1325
1325
  | `template-escape-bypass` | 79 | template-engine `SafeString(...)` wrapping a non-literal value |
1326
1326
  | `command-injection` | 78 | `child_process` `exec` / `execSync` / `spawn` / `spawnSync` (provenance-gated to `node:child_process`) |
1327
- | `code-injection` | 94 | `eval` / `vm.runInNewContext` |
1328
- | `dynamic-regex` | 1333 | `RegExp(...)` / `new RegExp(...)` with a non-literal pattern |
1327
+ | `code-injection` | 94 | `eval` (also `(0, eval)`, `globalThis.eval`, a local alias) / `Function` constructor forms / string `setTimeout` / `vm.runInNewContext`, `vm.compileFunction` and `new vm.Script` (also a named or `require` binding) / `<script>` element `src` (property or `setAttribute`) and text |
1328
+ | `dynamic-regex` | 1333 | `RegExp(...)` / `new RegExp(...)` with a non-literal pattern that traces to untrusted input |
1329
1329
  | `redos-regex` | 1333 | vulnerable regex literals tested with source-backed input |
1330
1330
  | `resource-amplification` | 400 | source-backed size into `Array(...)` / `new Array(...)` / `Buffer.alloc*` / `String.prototype.repeat` / `padStart` / `padEnd` (directly `Math.min`-clamped sizes stay quiet) |
1331
1331
  | `dynamic-module-load` | 95 | dynamic `require(...)` |
1332
1332
  | `sql-injection` | 89 | string concat or interpolated template into `.query()` / `.execute()`, and `sql.raw(...)`. Parameterized `` sql`${x}` `` and the object form `.execute({ sql, args })` are NOT flagged |
1333
1333
  | `ssrf` | 918 | `fetch` / `got` / `ky` / `needle` / `request` / `axios` / `superagent` / `undici` / `http(s).request` |
1334
- | `path-traversal` | 22 | `path.join` / `path.resolve` / `node:fs` path methods / route `sendFile` |
1335
- | `header-injection` | 113 | response `setHeader` / `writeHead` |
1334
+ | `path-traversal` | 22 | `path.join` / `path.resolve` with untrusted input in the argument, the same module or every direct importer / `node:fs` path methods / route `sendFile` |
1335
+ | `header-injection` | 113 | response `setHeader` / `writeHead` with a value that traces to untrusted input |
1336
1336
  | `open-redirect` | 601 | `res.redirect` / `location.href` / `location.assign` / `window.open` |
1337
1337
  | `postmessage-wildcard-origin` | 346 | `postMessage(..., "*")` |
1338
1338
  | `tls-validation-disabled` | 295 | HTTPS/TLS options with `rejectUnauthorized: false`, plus `NODE_TLS_REJECT_UNAUTHORIZED = "0"` |
@@ -1403,7 +1403,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1403
1403
  {
1404
1404
  "kind": "security",
1405
1405
  "schema_version": "4",
1406
- "version": "3.38.0",
1406
+ "version": "3.39.0",
1407
1407
  "elapsed_ms": 42,
1408
1408
  "config": {
1409
1409
  "rules": {
@@ -1432,7 +1432,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1432
1432
  {
1433
1433
  "kind": "security",
1434
1434
  "schema_version": "4",
1435
- "version": "3.38.0",
1435
+ "version": "3.39.0",
1436
1436
  "elapsed_ms": 42,
1437
1437
  "config": {
1438
1438
  "rules": {
@@ -1480,7 +1480,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1480
1480
  }
1481
1481
  ```
1482
1482
 
1483
- Each finding includes `kind`, `path`, `line`, `col`, `evidence`, `trace`, `actions`, `severity`, and optional `reachability`. `severity` is a review-priority tier (`high`, `medium`, or `low`) derived from reachability, boundary, source-backed, and runtime-hot signals; it is not a verified vulnerability verdict and does not change gate or exit semantics. SARIF maps high and medium candidates to `warning`, and low candidates to `note`. `tainted-sink` findings additionally carry `category` (the catalogue id, e.g. `"dangerous-html"`) and `cwe`; `client-server-leak` findings omit both. `tainted-sink` findings can also include `reachability.untrusted_source_trace` when a module with a known untrusted source imports the sink module; it is ranking and triage context only, not proof that a specific value reaches the sink. When set, `reachability.taint_confidence` tiers the association as `"arg-level"` (the sink argument traces to a same-module source read, strong) or `"module-level"` (only the module is import-reachable from a source, weak); tier from this field rather than the evidence text. For arg-level findings the trace's first hop points at the actual source-read line, and module-level source hops carry the role `"module-source"`. `unresolved_edge_files` (client-server-leak) and `unresolved_callee_sites` (tainted-sink) are in-band blind-spot counters: a zero finding count with a non-zero counter is not a clean bill. Suppress a verified false positive with `// fallow-ignore-file security-client-server-leak` (client-server-leak) or `// fallow-ignore-file security-sink` (any tainted-sink category).
1483
+ Each finding includes `kind`, `path`, `line`, `col`, `evidence`, `trace`, `actions`, `severity`, and optional `reachability`. `severity` is a review-priority tier (`high`, `medium`, or `low`) derived from reachability, boundary, source-backed, and runtime-hot signals; it is not a verified vulnerability verdict and does not change gate or exit semantics. SARIF maps high and medium candidates to `warning`, and low candidates to `note`. `tainted-sink` findings additionally carry `category` (the catalogue id, e.g. `"dangerous-html"`) and `cwe`. `client-server-leak` findings carry `category` (`"client-server-leak"` for a secret leak, `"server-only-import"` for server-only code) and omit `cwe`. `tainted-sink` findings can also include `reachability.untrusted_source_trace` when a module with a known untrusted source imports the sink module (a test, story or tooling-config file, and a `process.env` secret, never start this trace); it is ranking and triage context only, not proof that a specific value reaches the sink. When set, `reachability.taint_confidence` tiers the association as `"arg-level"` (the sink argument traces to a same-module source read, strong) or `"module-level"` (only the module is import-reachable from a source, weak); tier from this field rather than the evidence text. For arg-level findings the trace's first hop points at the actual source-read line, and module-level source hops carry the role `"module-source"`. `unresolved_edge_files` (client-server-leak) and `unresolved_callee_sites` (tainted-sink) are in-band blind-spot counters: a zero finding count with a non-zero counter is not a clean bill. Suppress a verified false positive with `// fallow-ignore-file security-client-server-leak` (client-server-leak) or `// fallow-ignore-file security-sink` (any tainted-sink category).
1484
1484
 
1485
1485
  When present, `unresolved_callee_diagnostics` adds bounded unresolved-callee metadata for follow-up review: `sampled[]` rows with `path`, `line`, `col`, `reason`, and `expression_kind`, `top_files[]` counts, `by_reason[]` counts, and the emitted sample/top-file limits. It is blind-spot metadata, not a finding list, and follows the same `--file`, `--workspace`, `--changed-since`, and `--gate new` scoping as security candidates.
1486
1486
 
@@ -1498,7 +1498,7 @@ Every finding also carries an agent-actionable `candidate { source_kind, sink, b
1498
1498
 
1499
1499
  ## `inspect`: Target Evidence Bundle
1500
1500
 
1501
- Compose one evidence bundle before editing a file or exported symbol. This is the CLI equivalent of the MCP `inspect_target` tool.
1501
+ Compose one evidence bundle before editing a file or exported symbol. This is the CLI equivalent of the MCP `inspect_target` tool. The MCP tool takes `cloud: true` for `--cloud`, `repo` for `--repo`, and `format: "compact"` for `--format compact`.
1502
1502
 
1503
1503
  ### Usage
1504
1504
 
@@ -2079,7 +2079,7 @@ Available on all commands:
2079
2079
  | `-o, --output-file` | `string` | - | Write the report to a file instead of stdout, for any --format (no ANSI codes). Useful on large projects where the terminal scrollback truncates the top. Progress and the confirmation stay on stderr |
2080
2080
  | `--report-path-prefix` | `string` | - | Prefix prepended to every path in the CI-facing formats (`github-annotations`, `github-summary`, `codeclimate`, `review-github`, `review-gitlab`). CI platforms address files by repository-root-relative path, so when the analyzed project lives in a subdirectory (e.g. `packages/app/`), paths need that offset. fallow detects the offset via the git toplevel automatically; this flag overrides the detection. Pass an empty string to disable rebasing and emit paths relative to `--root` |
2081
2081
  | `--fail-on-regression` | `bool` | `false` | Fail if issue count increased beyond tolerance vs a regression baseline |
2082
- | `--fail-on-stale-baseline` | `bool` | `false` | Exit with code 1 if a loaded --baseline has entries that match nothing in this run |
2082
+ | `--fail-on-stale-baseline` | `bool` | `false` | Exit with code 1 if a loaded --baseline has entries that match nothing in this run. Judges a dead-code baseline in the mode that the file records. Inert when the scope of the run differs from the file, and on `health` and `dupes` in production mode |
2083
2083
  | `--fail-on-baseline-growth` | `bool` | `false` | Exit with code 1 if a loaded baseline has a key that the same file at the base ref does not have |
2084
2084
  | `--baseline-base` | `string` | - | The git ref that --fail-on-baseline-growth compares the baseline with |
2085
2085
  | `--fail-on-parse-error` | `bool` | `false` | Exit with code 1 if fallow could not parse a source file cleanly |
@@ -2312,7 +2312,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
2312
2312
  {
2313
2313
  "kind": "dead-code",
2314
2314
  "schema_version": 7,
2315
- "version": "3.38.0",
2315
+ "version": "3.39.0",
2316
2316
  "elapsed_ms": 45,
2317
2317
  "total_issues": 12,
2318
2318
  "entry_points": {
@@ -2472,7 +2472,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
2472
2472
  {
2473
2473
  "kind": "dupes",
2474
2474
  "schema_version": 7,
2475
- "version": "3.38.0",
2475
+ "version": "3.39.0",
2476
2476
  "elapsed_ms": 82,
2477
2477
  "total_clones": 15,
2478
2478
  "total_lines_duplicated": 230,
@@ -2516,11 +2516,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
2516
2516
  {
2517
2517
  "kind": "combined",
2518
2518
  "schema_version": 7,
2519
- "version": "3.38.0",
2519
+ "version": "3.39.0",
2520
2520
  "elapsed_ms": 159,
2521
2521
  "check": {
2522
2522
  "schema_version": 7,
2523
- "version": "3.38.0",
2523
+ "version": "3.39.0",
2524
2524
  "elapsed_ms": 45,
2525
2525
  "total_issues": 12,
2526
2526
  "unused_files": [],
@@ -2740,6 +2740,7 @@ preset = "bulletproof"
2740
2740
  - `resolve.conditions`: additional package.json `exports` / `imports` condition names to honor during module resolution. Baseline conditions (`development`, `import`, `require`, `default`, `types`, `node`, plus `react-native` / `browser` under RN/Expo) are always included; user entries prepend ahead of them. Use for community conditions like `worker`, `edge-light`, `deno`, or custom bundler conditions. Example: `{ "resolve": { "conditions": ["worker", "edge-light"] } }`
2741
2741
  - `unusedComponentProps.ignorePattern`: opt-in regex that exempts a component prop from `unused-component-props` when the prop's LOCAL destructure binding name matches (the leading-underscore "accepted-but-intentionally-unused" convention, mirroring TS `noUnusedParameters` + ESLint `varsIgnorePattern` / `argsIgnorePattern`). Applies to Vue, Svelte, Astro, and React/Preact props. The match is on the local alias (`_stage` in `let { stage: _stage } = $props()`), not the public prop name the finding reports (`stage`); matching is unanchored like ESLint's `RegExp.test`, so anchor with `^_`. An invalid regex fails config load. Example: `{ "unusedComponentProps": { "ignorePattern": "^_" } }`
2742
2742
  - `circularDependencies.ignoreLazyImports`: opt-in boolean, default `false`. When `true`, `circular-dependencies` skips an import edge on which every import loads its target on demand or on another thread: an `import()` inside a function, a template `import()`, a lazy `import.meta.glob`, a worker URL, or a webpack worker loader request such as `worker-loader!./work.js`. A top-level `await import()`, `require()`, an eager `import.meta.glob`, and an edge that also has a static import stay. The filter runs before cycles are counted, so lazy cycles do not use the per-group cycle limit. Example: `{ "circularDependencies": { "ignoreLazyImports": true } }`
2743
+ - `effect.schemaExportsAsTypes`: opt-in boolean, default `false`. When `true`, an unused exported Effect schema is reported under `unused-types`, not `unused-exports`, so `"unused-types": "off"` hides it. A schema is a class that extends a `Schema` class factory (`Schema.Class`, `Schema.TaggedClass`, `Schema.TaggedError`, `Schema.TaggedRequest`, `Schema.Error`, `Schema.Opaque`) or a const whose value is a `Schema` constructor, member or pipe chain (`Schema.Struct({...})`, `Schema.String.pipe(Schema.brand("Id"))`). `Schema` must be an import from `effect`, `effect/Schema` or `@effect/schema`. Each `pipe` argument must be a `Schema` transform such as `Schema.brand(...)`. Decoders such as `Schema.decodeUnknownSync(User)` and `pipe(Schema.decodeUnknownSync)` stay unused exports. Example: `{ "effect": { "schemaExportsAsTypes": true } }`
2743
2744
 
2744
2745
  ---
2745
2746
 
@@ -215,7 +215,7 @@ fallow dead-code --format json --quiet --save-baseline fallow-baselines/dead-cod
215
215
  fallow dead-code --format json --quiet --baseline fallow-baselines/dead-code.json --fail-on-issues
216
216
  ```
217
217
 
218
- Commit the baseline file to your repo. When a fix removes a finding, its entry stays in the file until you prune it. `fallow baselines prune` removes the entries that match no current finding and never adds an entry, so it is safe to run after each fix. It reads the paths in `audit.deadCodeBaseline`, `audit.healthBaseline` and `audit.dupesBaseline`.
218
+ Commit the baseline file to your repo. When a fix removes a finding, its entry stays in the file until you prune it. `fallow baselines prune` removes the entries that match no current finding and never adds an entry, so it is safe to run after each fix. It reads the paths in `audit.deadCodeBaseline`, `audit.healthBaseline` and `audit.dupesBaseline`. A prune runs in one mode and refuses a file saved in another mode, because the run cannot see the findings of that file. For a file saved with `--production` or `--include-entry-exports`, pass the same flag to the prune. The refusal exits 2 and prints the command to use. Do not save the file again to clear a refusal without a review of its findings.
219
219
 
220
220
  ---
221
221
 
@@ -16,7 +16,7 @@ The `generated:issue-types` table below is regenerated from `fallow schema` by s
16
16
  | `unused-type` | `--unused-types` | - | `// fallow-ignore-next-line unused-type` | Type aliases and interfaces |
17
17
  | `private-type-leak` | `--private-type-leaks` | - | `// fallow-ignore-next-line private-type-leak` | Opt-in API hygiene check (default `off`) for exported signatures whose type references a same-file private type |
18
18
  | `deprecated-export-in-use` | `--deprecated-exports-in-use` | - | `// fallow-ignore-next-line deprecated-export-in-use` | Export marked @deprecated is still referenced; Opt-in migration sweep; the rule defaults to off |
19
- | `unused-dependency` | `--unused-deps` | yes | - | Packages in `dependencies` never imported. In monorepos, internal workspace package names (e.g., `@repo/ui`) declared in another workspace's `package.json` but never imported are reported here too. `--unused-deps` also covers the dev/optional/type-only/test-only sibling rows below. |
19
+ | `unused-dependency` | `--unused-deps` | yes | - | Packages in `dependencies` never imported. In monorepos, internal workspace package names (e.g., `@repo/ui`) declared in another workspace's `package.json` but never imported are reported here too. `--unused-deps` also covers the dev, optional, type-only, test-only and dev-in-production sibling rows below. |
20
20
  | `unused-dev-dependency` | `--unused-deps` | yes | - | Packages in `devDependencies` never imported by test files, config files, or scripts |
21
21
  | `unused-optional-dependency` | `--unused-deps` | yes | - | Packages in `optionalDependencies` never imported (often platform-specific; verify before removing) |
22
22
  | `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 |
@@ -31,10 +31,10 @@ The `generated:issue-types` table below is regenerated from `fallow schema` by s
31
31
  | `circular-dependency` | `--circular-deps` | - | `// fallow-ignore-next-line circular-dependency` | Import cycles in the module graph |
32
32
  | `re-export-cycle` | `--re-export-cycles` | - | `// fallow-ignore-file re-export-cycle` | Barrel files re-exporting from each other in a loop (`kind: "multi-node"`) or a barrel re-exporting from itself (`kind: "self-loop"`). Chain propagation through the loop is a structural no-op so imports through any member may silently come up empty. Default `warn`. Distinct from `circular-dependencies` (runtime cycles, sometimes intentional). File-scoped suppression only: `// fallow-ignore-file re-export-cycle` on any member breaks the cycle. |
33
33
  | `package-cycle` | `--package-cycles` | - | `// fallow-ignore-next-line package-cycle` | Two or more workspace packages import each other in a loop, so they cannot be built in dependency order. Edges are resolved imports, not declared `package.json` dependencies, so a package cycle can exist without a file-level cycle. Imports from test, spec, story, fixture and tooling config files do not count. Each finding lists `packages` in cycle order and one example import per hop in `edges`. Type-only imports count, and each hop has a `type_only` flag (a type-only hop still matters for declaration builds). Default `warn`. Requires a workspace with two or more packages. A suppression removes one import; the cycle goes away when every import on one hop is suppressed. `group_truncated: true` means the package group has more cycles than listed. `--changed-since` and diff scope use the example import of each hop (`edges[].path`). |
34
- | `boundary-violation` | `--boundary-violations` | - | `// fallow-ignore-next-line boundary-violation` | Imports crossing architecture zone boundaries. Presets: `layered`, `hexagonal`, `feature-sliced`, `bulletproof`; `autoDiscover` can create one zone per feature directory; per-rule `allowTypeOnly: [zones]` admits `import type` / `export type` crossings while still blocking value imports. Named and default imports through a barrel are judged against the zone of the origin module (`to_path`), and `via_path` names the barrel. Optional sections: `boundaries.coverage.requireAllFiles` reports unzoned source files (`allowUnmatched` globs exempt intentional ones), and `boundaries.calls.forbidden` bans callee patterns per zone (segment-aware and import-resolved, so `child_process.*` covers `node:child_process` named/namespace/default imports; direct callees only, zoned files only). The whole family shares the `boundary-violation` rule and suppression token (`boundary-call-violation` and `boundary-call-violations` accepted as aliases); start the rule at `warn` for a staged rollout |
34
+ | `boundary-violation` | `--boundary-violations` | - | `// fallow-ignore-next-line boundary-violation` | Imports crossing architecture zone boundaries. Presets: `layered`, `hexagonal`, `feature-sliced`, `bulletproof`; `autoDiscover` can create one zone per feature directory; per-rule `allowTypeOnly: [zones]` admits `import type` / `export type` crossings while still blocking value imports. Named and default imports through a barrel are judged against the zone of the origin module (`to_path`), and `via_path` names the barrel. Optional sections: `boundaries.coverage.requireAllFiles` reports unzoned source files (`allowUnmatched` globs exempt intentional ones), and `boundaries.calls.forbidden` bans callee patterns per zone (segment-aware and import-resolved, so `child_process.*` covers `node:child_process` named/namespace/default imports; direct calls and new expressions only, zoned files only). The whole family shares the `boundary-violation` rule and suppression token (`boundary-call-violation` and `boundary-call-violations` accepted as aliases); start the rule at `warn` for a staged rollout |
35
35
  | `boundary-coverage` | `--boundary-violations` | - | `// fallow-ignore-file boundary-violation` | Source file matches no configured architecture boundary zone; Requires boundaries.coverage.requireAllFiles |
36
36
  | `boundary-call-violation` | `--boundary-violations` | - | `// fallow-ignore-next-line boundary-call-violation` | Zoned file calls a callee its zone forbids; Requires boundaries.calls.forbidden patterns |
37
- | `policy-violation` | `--policy-violations` | - | `// fallow-ignore-next-line policy-violation` | Calls, imports, exports, catalogue-derived effects, and graph-resolved GDP proof producer ownership checked by declarative rule packs (`rulePacks` lists standalone JSON/JSONC files; pure data, no project code executes). Rule kinds: `banned-call`, `banned-import`, `banned-export`, `banned-effect`, and `gdp-proof-producer`. GDP rules trace `@gdp-ts/core` proof producers through unambiguous re-exports and require project-relative `allowedFiles`, optionally scoped by exact static `proofKinds`; they do not prove runtime authorization. Findings identified as `<pack>/<rule-id>`. Default `warn` master; per-rule `severity` overrides per finding and the exit gate reads the effective severity. Invalid or missing packs fail config load with exit 2. `fallow rule-pack-schema` prints the pack JSON Schema. Use `policy-violation:<pack>/<rule-id>` to suppress one rule; bare `policy-violation` covers every pack rule on the line or file. |
37
+ | `policy-violation` | `--policy-violations` | - | `// fallow-ignore-next-line policy-violation` | Calls, imports, exports, catalogue-derived effects, and graph-resolved GDP proof producer ownership checked by declarative rule packs (`rulePacks` lists standalone JSON/JSONC files; pure data, no project code executes). Rule kinds: `banned-call`, `banned-import`, `banned-export`, `banned-effect`, and `gdp-proof-producer`. GDP rules trace `@gdp-ts/core` proof producers through unambiguous re-exports and require project-relative `allowedFiles`, optionally scoped by exact static `proofKinds`; they do not prove runtime authorization. A `banned-import` rule with `allowNames` allows the listed exact imported names (`default` for a default import) and reports `matched` as `<specifier>: <binding>`. A namespace import, a star re-export and a side-effect import always report. `allowNames` does not cover `require()`, `import()` or `import x = require()`, because `banned-import` does not read them. Findings identified as `<pack>/<rule-id>`. Default `warn` master; per-rule `severity` overrides per finding and the exit gate reads the effective severity. Invalid or missing packs fail config load with exit 2. `fallow rule-pack-schema` prints the pack JSON Schema. Use `policy-violation:<pack>/<rule-id>` to suppress one rule; bare `policy-violation` covers every pack rule on the line or file. |
38
38
  | `stale-suppression` | `--stale-suppressions` | - | - | `fallow-ignore` comments or `@expected-unused` JSDoc tags that no longer match any issue |
39
39
  | `missing-suppression-reason` | `--stale-suppressions` | - | - | Suppression comment omits a required reason |
40
40
  | `unused-catalog-entry` | `--unused-catalog-entries` | yes | - | `pnpm-workspace.yaml` entries no workspace package.json references via `catalog:` (default `warn`) |
@@ -20,7 +20,7 @@ When using fallow via MCP (`fallow-mcp`), the following tools are available:
20
20
  | `inspect_similar_code` | trace | free | `fallow similar-code inspect <candidate-id> --candidates <report.json> --format json --quiet` | `candidate_id`, `snapshot` | Inspect one exact unverified candidate without rerunning provider retrieval or global ranking. Pass `candidate_id` plus a bounded typed snapshot containing the unchanged discovery `schema_version`, `generation`, selected `candidate`, `completion`, and `diagnostics`. Current source is re-extracted and both endpoint hashes must still match, so stale source fails closed. Keep candidate worthiness, behavioral equivalence, and refactor safety as separate verdicts, and abstain when evidence is incomplete. |
21
21
  | `mutation_plan` | analysis | free | `fallow mutation --plan --format json --quiet` | `changed_since`, `all`, `paths`, `max_mutants`, `operators` | Static mutation plan for changed code: mutant ids, operators, dispositions, and static test files; runs no tests |
22
22
  | `mutation_inspect` | trace | free | `fallow mutation inspect <mutant-id> [--report <report.json>] --format json --quiet` | `mutant_id`, `report` | Inspect one mutant of a saved mutation report (default: the last run report), with a stale status when its source changed; runs no tests |
23
- | `inspect_target` | analysis | free | `fallow inspect --format json --quiet` | `target`, `production`, `include_churn` | Compose one evidence bundle for a file or exported symbol. File targets use `target: { type: "file", file }`; symbol targets use `target: { type: "symbol", file, export_name }`. Returns `kind: "inspect_target"`, normalized target identity, `trace_file`, optional `trace_export`, file-scoped dead-code actions, duplication groups filtered to the file, complexity findings filtered to the file, and security candidates scoped to the file. Set `include_churn: true` to add target-level git churn from the health hotspot subsystem. Churn is off by default, and unavailable git history or analysis failures remain explicit section states. Evidence sections carry `status` and `scope`; symbol targets warn when supporting evidence is file-scoped. Supports `root`, `config`, `production`, `workspace`, `include_churn`, `no_cache`, and `threads`; `production` applies to trace, dead-code, and health evidence only. Raise `FALLOW_TIMEOUT_SECS` for large repos. |
23
+ | `inspect_target` | analysis | free | `fallow inspect --format json --quiet` | `target`, `production`, `include_churn`, `cloud` | Compose one evidence bundle for a file or exported symbol. File targets use `target: { type: "file", file }`; symbol targets use `target: { type: "symbol", file, export_name }`. Returns `kind: "inspect_target"`, normalized target identity, `trace_file`, optional `trace_export`, file-scoped dead-code actions, duplication groups filtered to the file, complexity findings filtered to the file, and security candidates scoped to the file. Set `include_churn: true` to add target-level git churn from the health hotspot subsystem. Churn is off by default, and unavailable git history or analysis failures remain explicit section states. Evidence sections carry `status` and `scope`; symbol targets warn when supporting evidence is file-scoped. Set `cloud: true` on a file target to add the `cloud` evidence section of `fallow inspect --cloud` (production data per local function). The CLI reads the key: `FALLOW_API_KEY` in the server environment, else the key that `fallow cloud login` stored; the key is never a parameter. `repo` (`owner/repo`) selects the repository and needs `cloud: true`. The section `status` is `ok`, `no_key`, `plan_required`, `unavailable` or `untracked`; a status other than `ok` never fails the call. `cloud: true` with a symbol target is refused. Set `format: "compact"` to get the plain text of `fallow inspect --format compact` (about 1 KB, no JSON) instead of the JSON bundle. Supports `root`, `config`, `production`, `workspace`, `include_churn`, `cloud`, `repo`, `format`, `no_cache`, and `threads`; `production` applies to trace, dead-code, and health evidence only. Raise `FALLOW_TIMEOUT_SECS` for large repos. |
24
24
  | `guard` | introspection | free | `fallow guard <file> --format json --quiet` | `files` | Report the architecture rules that apply to given files before editing them: boundary zone, allowed import zones, forbidden calls, and rule-pack policies |
25
25
  | `find_dupes` | analysis | free | `fallow dupes --format json --quiet` | `mode`, `near`, `min_tokens`, `min_occurrences`, `top`, `threshold` | Code duplication detection. Set `near: true` for function-level clones with small structural edits. Set `changed_since` to scope to changed files since a git ref. Set `min_occurrences` (≥ 2, default 2) to hide pair-only clones and focus on widespread copy-paste. Each `clone_groups[]` entry carries a normalized `fingerprint` and `spread`; near groups also carry `similarity`. `stats.clone_groups_ignored` reports reviewed groups hidden by config, while `stats.near_candidates_skipped` signals bounded near comparisons that were skipped. Pass the fingerprint to `trace_clone` to inspect the group |
26
26
  | `check_health` | analysis | free | `fallow health --format json --quiet` | `score`, `css`, `file_scores`, `hotspots`, `targets`, `coverage`, `runtime_coverage`, `max_crap`, `group_by` | Complexity metrics, health scores, hotspots, and refactoring targets. Set `complexity_breakdown: true` to add a per-decision-point `contributions[]` array to each complexity finding (each `else-if`, nested `if`, boolean operator, loop, `case`, etc. with its source line and cyclomatic/cognitive weight) so you can explain WHY a function scored high and pinpoint refactor targets. Optional `runtime_coverage` merges a V8 or Istanbul dump; tune it with `min_invocations_hot` (default 100), `min_observation_volume` (default 5000), and `low_traffic_threshold` (default 0.001). When runtime evidence combines with static usage, test coverage, CRAP/complexity, ownership, or change scope, read `coverage_intelligence` for stable `fallow:coverage-intel:<hash>` recommendations. Set `group_by` to `owner`, `directory`, `package`, or `section` for per-group `vital_signs` / `health_score`; SARIF results gain `properties.group`, CodeClimate issues gain a top-level `group` field |
@@ -342,7 +342,7 @@ As you fix existing issues, remove their entries from the baseline. Prune remove
342
342
  fallow baselines prune --dead-code-baseline fallow-baselines/dead-code.json --format json --quiet
343
343
  ```
344
344
 
345
- With `audit.deadCodeBaseline`, `audit.healthBaseline` and `audit.dupesBaseline` in the config, `fallow baselines prune` prunes all three files in one run. Use `--save-baseline` only to record new findings on purpose. `fallow hooks install --target git --prune-baselines` runs the prune in the pre-commit hook and stages the pruned files.
345
+ With `audit.deadCodeBaseline`, `audit.healthBaseline` and `audit.dupesBaseline` in the config, `fallow baselines prune` prunes all three files in one run. Pass `--production` or `--include-entry-exports` when the file was saved with that flag, or the prune refuses the file. Use `--save-baseline` only to record new findings on purpose. `fallow hooks install --target git --prune-baselines` runs the prune in the pre-commit hook and stages the pruned files.
346
346
 
347
347
  ### Duplication baseline
348
348
 
@@ -126,7 +126,7 @@ kind: "mutation-inspect"
126
126
  /**
127
127
  * Schema projection for the audit envelope's exact version.
128
128
  */
129
- export type AuditSchemaVersion = 13
129
+ export type AuditSchemaVersion = 14
130
130
  /**
131
131
  * Fallow CLI version that produced this envelope. Renders to the JSON wire as
132
132
  * a bare string (e.g. `"2.74.0"`).
@@ -473,7 +473,8 @@ export type EffectLayerKind = ("top_level" | "static_member")
473
473
  */
474
474
  export type BaselineStalenessAdvisory = ("none" | "zero-overlap" | "partial")
475
475
  /**
476
- * One channel that narrowed a run to part of the project.
476
+ * One channel that narrows a run, or that differs between a run and a saved
477
+ * baseline.
477
478
  *
478
479
  * Serialized as kebab-case inside `scope_reasons` and published as an OPEN
479
480
  * set, the same tolerate-unknown contract `gate_outcomes` keys carry: a name
@@ -848,6 +849,31 @@ kind: "ignore-patterns-entry-redundant"
848
849
  */
849
850
  pattern: string
850
851
  } | {
852
+ kind: "override-rule-ineffective"
853
+ /**
854
+ * The canonical rule name, also when the config uses an alias.
855
+ */
856
+ rule: string
857
+ /**
858
+ * The `overrides[].files` globs, as written in the config.
859
+ */
860
+ files: string[]
861
+ /**
862
+ * Why the rule has no effect, as a kebab-case token.
863
+ */
864
+ reason: string
865
+ } | {
866
+ kind: "requested-rule-off"
867
+ /**
868
+ * The canonical rule name in `rules`.
869
+ */
870
+ rule: string
871
+ /**
872
+ * The canonical CLI selector of the request, such as `--unused-deps`.
873
+ * A typed API or MCP request reports the same selector.
874
+ */
875
+ flag: string
876
+ } | {
851
877
  kind: "baseline-install-state-mismatch"
852
878
  baseline_node_modules: NodeModulesState
853
879
  run_node_modules: NodeModulesState
@@ -1064,6 +1090,10 @@ export type RuntimeCoverageSignal = ("license-expired-grace" | "cold-code-detect
1064
1090
  * Runtime coverage source used to produce the summary.
1065
1091
  */
1066
1092
  export type RuntimeCoverageDataSource = ("local" | "cloud")
1093
+ /**
1094
+ * Capture policy of the runtime evidence behind a report.
1095
+ */
1096
+ export type RuntimeCoverageCapturePolicy = "positive_only"
1067
1097
  /**
1068
1098
  * Protocol-level per-function runtime coverage verdict derived from the
1069
1099
  * decision table in fallow-cov-protocol. The CLI's `runtime_coverage.findings`
@@ -1387,7 +1417,7 @@ export type CoverageSetupWaitOutcome = ("complete" | "timeout" | "error")
1387
1417
  /**
1388
1418
  * Schema-version discriminator for [`CoverageAnalyzeOutput`].
1389
1419
  */
1390
- export type CoverageAnalyzeSchemaVersion = ("1" | "2")
1420
+ export type CoverageAnalyzeSchemaVersion = ("1" | "2" | "3")
1391
1421
  /**
1392
1422
  * Discovery outcome for a [`LogicalGroup`].
1393
1423
  */
@@ -1395,7 +1425,7 @@ export type LogicalGroupStatus = ("ok" | "empty" | "invalid_path")
1395
1425
  /**
1396
1426
  * Exact schema version for [`HealthOutput`].
1397
1427
  */
1398
- export type HealthSchemaVersion = 12
1428
+ export type HealthSchemaVersion = 13
1399
1429
  /**
1400
1430
  * Resolver mode label for grouped envelopes (dead-code, dupes, health).
1401
1431
  *
@@ -1536,7 +1566,7 @@ export type SecurityBlindSpotsSchemaVersion = "1"
1536
1566
  /**
1537
1567
  * Schema projection for the combined envelope's exact version.
1538
1568
  */
1539
- export type CombinedSchemaVersion = 14
1569
+ export type CombinedSchemaVersion = 15
1540
1570
  /**
1541
1571
  * Schema projection for the feature-flags envelope's exact version.
1542
1572
  */
@@ -1581,7 +1611,7 @@ export type VendorFlagState = ("on" | "off" | "rolled_out" | "archived" | "exper
1581
1611
  * Independently-versioned wire-version newtype for the brief envelope.
1582
1612
  * Serializes as the integer `REVIEW_BRIEF_SCHEMA_VERSION`.
1583
1613
  */
1584
- export type ReviewBriefSchemaVersion = 13
1614
+ export type ReviewBriefSchemaVersion = 14
1585
1615
  /**
1586
1616
  * The exactly-three shippable decision categories (the SOLID-3). No cut category
1587
1617
  * (abstraction / deletion / convention / irreversibility) is representable: this
@@ -5395,7 +5425,7 @@ col: number
5395
5425
  */
5396
5426
  zone: string
5397
5427
  /**
5398
- * The callee path as written at the call site (e.g. `cp.exec`).
5428
+ * The callee path as written (e.g. `cp.exec`), with the prefix `new ` for a `new` expression.
5399
5429
  */
5400
5430
  callee: string
5401
5431
  /**
@@ -5457,11 +5487,11 @@ pack: string
5457
5487
  rule_id: string
5458
5488
  kind: PolicyRuleKind
5459
5489
  /**
5460
- * What matched: the written callee path for `banned-call` (e.g.
5461
- * `cp.exec`), the raw import specifier for `banned-import` (e.g.
5462
- * `moment/locale/nl`), `<effect>: <callee>` for `banned-effect`, or the
5463
- * exported name for `banned-export`. For `gdp-proof-producer`, the canonical
5464
- * factory with its JSON-quoted literal label or `...` for a dynamic label.
5490
+ * What matched: the written callee path for `banned-call` (e.g. `cp.exec`, or `new Date` for a `new` expression), `<effect>: <callee>` for `banned-effect`,
5491
+ * or the exported name for `banned-export`. For `gdp-proof-producer`, the canonical factory with its JSON-quoted literal label or `...` for a dynamic label.
5492
+ * For `banned-import`: the raw import specifier (e.g. `moment/locale/nl`), or `<specifier>: <binding>` when the rule sets `allowNames`.
5493
+ * `<binding>` is the source-side imported name, `default`, `*` for a namespace or star form,
5494
+ * or `side-effect`.
5465
5495
  */
5466
5496
  matched: string
5467
5497
  severity: PolicyViolationSeverity
@@ -7132,12 +7162,13 @@ matched: number
7132
7162
  * `stale: false` with `gate_trips: true`; that is the contract, not a defect.
7133
7163
  *
7134
7164
  * `change_scoped` is the member a consumer must read before dividing
7135
- * `matched_entries` by `baseline_entries`. A run narrowed to part of the
7136
- * project compares a whole-project baseline against a slice of it and can
7137
- * report `matched_entries: 0` while the baseline is perfectly healthy, so both
7138
- * `stale` and `gate_trips` are false there by construction. The remedy for a
7139
- * tripped gate is always the same: re-save the baseline from a whole-project
7140
- * run with `--save-baseline`.
7165
+ * `matched_entries` by `baseline_entries`. A run whose scope differs from the
7166
+ * file compares the file against another set of findings and can report
7167
+ * `matched_entries: 0` while the baseline is perfectly healthy, so both `stale`
7168
+ * and `gate_trips` are false there by construction. The remedy for a tripped
7169
+ * gate is always the same: remove the stale entries with
7170
+ * `fallow baselines prune`, or re-save the baseline in the same mode with
7171
+ * `--save-baseline`.
7141
7172
  */
7142
7173
  export interface BaselineStaleness {
7143
7174
  /**
@@ -7177,13 +7208,15 @@ current_findings: number
7177
7208
  */
7178
7209
  remaining_findings?: (number | null)
7179
7210
  /**
7180
- * True when this run analyzed only part of the project, so a whole-project
7181
- * baseline matches less of it for reasons that are not rot. The channels
7182
- * differ per command and include a diff, a base ref, `--changed-since`,
7183
- * `--workspace`, `--changed-workspaces`, `--scope`, `--file`, an
7184
- * issue-type filter, and production mode. Both `stale` and `gate_trips`
7185
- * are false whenever this is true. `scope_reasons` names the channels
7186
- * that fired.
7211
+ * True when this run cannot judge the loaded baseline, because its scope
7212
+ * differs from the scope the file records. `scope_reasons` names each
7213
+ * channel that differs, so on `dead-code` production can appear when only
7214
+ * the file used production mode. On `dupes` and `health` this is true when
7215
+ * the run analyzed only part of the project, and production mode always
7216
+ * counts. The channels differ per command and include a diff, a base ref,
7217
+ * `--changed-since`, `--workspace`, `--changed-workspaces`, `--scope`,
7218
+ * `--file`, an issue-type filter, and production mode. Both `stale` and
7219
+ * `gate_trips` are false whenever this is true.
7187
7220
  */
7188
7221
  change_scoped: boolean
7189
7222
  /**
@@ -7257,9 +7290,13 @@ saved_by?: string
7257
7290
  */
7258
7291
  format?: string
7259
7292
  /**
7260
- * Which channels narrowed this run, present and non-empty exactly when
7261
- * `change_scoped` is true. Both members are derived from one function, so
7262
- * the boolean and the array cannot disagree.
7293
+ * Which channels stop this run from judging the file, present and
7294
+ * non-empty exactly when `change_scoped` is true. Both members are derived
7295
+ * from one function, so the boolean and the array cannot disagree.
7296
+ *
7297
+ * On `dead-code`, `production` and `include-entry-exports` appear only when
7298
+ * the run and the file differ on that channel, and `issue-type-filter`
7299
+ * appears only when the file holds a category that the run does not report.
7263
7300
  *
7264
7301
  * Read it to decide whether the narrowing is removable: a run narrowed
7265
7302
  * only by `diff`, `changed-since`, `changed-files`, `scope`, `file` or
@@ -9751,17 +9788,19 @@ data_source: RuntimeCoverageDataSource
9751
9788
  last_received_at?: (string | null)
9752
9789
  /**
9753
9790
  * Number of functions the sidecar could observe in the V8 or Istanbul
9754
- * dump.
9791
+ * dump. Null for a positive-only capture, which cannot observe a
9792
+ * function that did not run.
9755
9793
  */
9756
- functions_tracked: number
9794
+ functions_tracked?: (number | null)
9757
9795
  /**
9758
9796
  * Tracked functions that received at least one invocation.
9759
9797
  */
9760
9798
  functions_hit: number
9761
9799
  /**
9762
- * Tracked functions that were never invoked.
9800
+ * Tracked functions that were never invoked. Null for a positive-only
9801
+ * capture.
9763
9802
  */
9764
- functions_unhit: number
9803
+ functions_unhit?: (number | null)
9765
9804
  /**
9766
9805
  * Functions the sidecar could not track (lazy-parsed, worker thread,
9767
9806
  * dynamic code, unresolved source map).
@@ -9769,8 +9808,9 @@ functions_unhit: number
9769
9808
  functions_untracked: number
9770
9809
  /**
9771
9810
  * Ratio of functions_hit / functions_tracked, expressed as a percent.
9811
+ * Null for a positive-only capture.
9772
9812
  */
9773
- coverage_percent: number
9813
+ coverage_percent?: (number | null)
9774
9814
  /**
9775
9815
  * Total number of observed invocations across all functions. Denominator
9776
9816
  * for low-traffic classification.
@@ -9793,6 +9833,12 @@ deployments_seen: number
9793
9833
  * agent pipelines can surface the same signal.
9794
9834
  */
9795
9835
  capture_quality?: (RuntimeCoverageCaptureQuality | null)
9836
+ /**
9837
+ * Capture policy of the runtime evidence. Absent for a full capture.
9838
+ * `positive_only` means the tracked and unhit totals and the coverage
9839
+ * percent are null.
9840
+ */
9841
+ capture_policy?: (RuntimeCoverageCapturePolicy | null)
9796
9842
  }
9797
9843
  /**
9798
9844
  * Quality-of-capture signals emitted by the sidecar so the CLI can explain
@@ -12120,6 +12166,12 @@ related?: DiffRiskLocation[]
12120
12166
  * Suppression actions.
12121
12167
  */
12122
12168
  actions: DiffRiskAction[]
12169
+ /**
12170
+ * The parsed `fallow-ignore` comment. Present only on an
12171
+ * `added-suppression` finding whose evidence is `fallow-ignore` and
12172
+ * whose comment Fallow can parse.
12173
+ */
12174
+ suppression?: (DiffRiskSuppression | null)
12123
12175
  }
12124
12176
  /**
12125
12177
  * A location that relates to a diff-risk finding, such as an unchanged clone
@@ -12153,6 +12205,26 @@ description: string
12153
12205
  */
12154
12206
  comment?: (string | null)
12155
12207
  }
12208
+ /**
12209
+ * The parsed content of an added `fallow-ignore` comment.
12210
+ */
12211
+ export interface DiffRiskSuppression {
12212
+ /**
12213
+ * The suppressed kinds in kebab-case, in source order. Empty for a
12214
+ * blanket comment that suppresses every kind.
12215
+ */
12216
+ kinds: string[]
12217
+ /**
12218
+ * The human-authored reason after `--`, or `null` when absent.
12219
+ */
12220
+ reason?: (string | null)
12221
+ /**
12222
+ * `true` when the comment names `security-sink` or
12223
+ * `security-client-server-leak` and has no reason. An advisory signal
12224
+ * only: it does not change the severity of the finding.
12225
+ */
12226
+ security_without_reason: boolean
12227
+ }
12156
12228
  /**
12157
12229
  * Envelope emitted by `fallow explain <issue-type> --format json`.
12158
12230
  *
@@ -15870,9 +15942,9 @@ finding_id: string
15870
15942
  kind: SecurityFindingKind
15871
15943
  /**
15872
15944
  * The catalogue category id (e.g. `"dangerous-html"`). `Some` for
15873
- * `TaintedSink`. For `ClientServerLeak` this is `None` for the secret-leak
15874
- * finding, and `Some("server-only-import")` when a `"use client"` cone
15875
- * reaches server-only code.
15945
+ * `TaintedSink`. For `ClientServerLeak` it is `"client-server-leak"` for the
15946
+ * secret leak (older releases omit it) or `"server-only-import"` when a
15947
+ * `"use client"` cone reaches server-only code.
15876
15948
  */
15877
15949
  category?: (string | null)
15878
15950
  /**
@@ -16106,9 +16178,9 @@ line: number
16106
16178
  col: number
16107
16179
  /**
16108
16180
  * Catalogue category id of the sink (e.g. `"dangerous-html"`). For
16109
- * `client-server-leak` this is `None` for the secret-leak finding, and
16110
- * `Some("server-only-import")` when a `"use client"` cone reaches
16111
- * server-only code.
16181
+ * `client-server-leak` this is `"client-server-leak"` for the secret-leak
16182
+ * finding, and `"server-only-import"` when a `"use client"` cone reaches
16183
+ * server-only code. Older releases left it absent for the secret leak.
16112
16184
  */
16113
16185
  category?: (string | null)
16114
16186
  /**
@@ -18178,7 +18250,7 @@ affected_count: number
18178
18250
  * representative files, NEVER to enumerate the blast radius or to infer its
18179
18251
  * shape. Because it is a prefix of the sorted set, it clusters in whichever
18180
18252
  * directory sorts first. To reconstruct the full set, run
18181
- * `fallow check --impact-closure <path>` once per changed file and union the
18253
+ * `fallow dead-code --impact-closure <path>` once per changed file and union the
18182
18254
  * results: that flag seeds from a single file, so no single command
18183
18255
  * reproduces this changeset-wide union.
18184
18256
  */
@@ -20731,8 +20803,13 @@ file?: (string | null)
20731
20803
  */
20732
20804
  test_id?: (string | null)
20733
20805
  /**
20734
- * The error message, for an error outside a test, or the reason for a
20735
- * test that did not pass in the dry run of a restarted companion.
20806
+ * The error message. For an error outside a test, the error. For a test
20807
+ * that failed in the first dry run, the first error of the test. For a
20808
+ * test that failed in a control run or in the dry run of a restarted
20809
+ * companion, the reason, then `: ` and the first error of the test. A
20810
+ * test error has its name and message on the first line and the stack
20811
+ * frames of the project after it. Absent for a test of the first dry
20812
+ * run when the companion sent no error.
20736
20813
  */
20737
20814
  message?: (string | null)
20738
20815
  /**