fallow 3.34.1 → 3.36.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bart Waardenburg
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/capabilities.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "3.34.1",
3
+ "version": "3.36.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": [
@@ -301,7 +301,7 @@
301
301
  "name": "--fail-on-issues",
302
302
  "type": "bool",
303
303
  "required": false,
304
- "description": "Exit with code 1 if issues are found",
304
+ "description": "Exit 1 on issues, also where the default is 0: bare json, sarif, codeclimate and dupes",
305
305
  "possible_values": [
306
306
  "true",
307
307
  "false"
@@ -1273,6 +1273,12 @@
1273
1273
  "required": false,
1274
1274
  "description": "Wait this long for each runner process to start, finish its dry run or reload, for example `5m` on a slow or busy host (default: `2m`)"
1275
1275
  },
1276
+ {
1277
+ "name": "--runner-config",
1278
+ "type": "string",
1279
+ "required": false,
1280
+ "description": "The config file of the test runner, for example `vitest.integration.config.mjs` (default: the `mutation.runnerConfig` key, else the runner's own discovery). The file must be inside the project root. Only Vitest reads it. This is not `--config`, which selects the Fallow config"
1281
+ },
1276
1282
  {
1277
1283
  "name": "--typecheck",
1278
1284
  "type": "bool",
@@ -2330,6 +2336,17 @@
2330
2336
  "all"
2331
2337
  ]
2332
2338
  },
2339
+ {
2340
+ "name": "--worsened",
2341
+ "type": "string",
2342
+ "required": false,
2343
+ "description": "How a worsened complexity finding affects the audit verdict",
2344
+ "possible_values": [
2345
+ "warn",
2346
+ "error",
2347
+ "off"
2348
+ ]
2349
+ },
2333
2350
  {
2334
2351
  "name": "--runtime-coverage",
2335
2352
  "type": "string",
@@ -7017,6 +7034,7 @@
7017
7034
  "FALLOW_MAX_FILE_SIZE": "Per-file size ceiling in megabytes for source discovery (default 5; 0 = no limit). CLI --max-file-size flag overrides this.",
7018
7035
  "FALLOW_TYPE_AWARE": "Enable or disable TypeScript semantic (type-aware) analysis for the run (true/false/1/0/yes/no/on/off). Precedence: --type-aware/--no-type-aware CLI flags, then FALLOW_TYPE_AWARE, then the audit.typeAware config field, then typeAware.enabled.",
7019
7036
  "FALLOW_TYPE_AWARE_TIMEOUT_SECS": "Wall clock in seconds allowed for one TypeScript semantic sidecar request (default 120; unset, 0, or a non-numeric value keeps the default). Raise it for very large TypeScript programs, where a semantic query scans the whole program.",
7037
+ "FALLOW_TYPE_AWARE_BACKEND": "Experimental. Type-checker backend for the TypeScript semantic sidecar: typescript-go (default) or tsrs. The tsrs backend needs the exact pinned tsrs npm package where fallow-type-aware can resolve it. An unknown value makes the type-aware pass unavailable.",
7020
7038
  "FALLOW_TYPE_AWARE_BIN": "Trusted executable override for the TypeScript semantic refinement sidecar used by dead-code --type-aware. Relative paths resolve from the caller's working directory before --root is applied; project node_modules and PATH are intentionally not searched. Default: sibling of the active Fallow executable.",
7021
7039
  "FALLOW_AUDIT_BASE": "Pins the fallow audit comparison base ref when no --base/--changed-since is passed (e.g. upstream/main).",
7022
7040
  "FALLOW_AUDIT_CACHE_MAX_AGE_DAYS": "GC threshold in days for reusable audit base-snapshot caches (default 30; 0 disables the sweep).",
@@ -8263,7 +8281,7 @@
8263
8281
  ]
8264
8282
  },
8265
8283
  "plugins": {
8266
- "count": 145,
8284
+ "count": 149,
8267
8285
  "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",
8268
8286
  "names": [
8269
8287
  "nextjs",
@@ -8272,6 +8290,7 @@
8272
8290
  "remix",
8273
8291
  "astro",
8274
8292
  "browser-extension",
8293
+ "github-action",
8275
8294
  "wxt",
8276
8295
  "angular",
8277
8296
  "react-router",
@@ -8286,6 +8305,7 @@
8286
8305
  "nestjs-trpc",
8287
8306
  "nest-commander",
8288
8307
  "adonis",
8308
+ "egg",
8289
8309
  "docusaurus",
8290
8310
  "gatsby",
8291
8311
  "sveltekit",
@@ -8324,6 +8344,7 @@
8324
8344
  "module-federation",
8325
8345
  "tsup",
8326
8346
  "tsdown",
8347
+ "vite-plus",
8327
8348
  "pkg-utils",
8328
8349
  "parcel",
8329
8350
  "vitest",
@@ -8401,6 +8422,7 @@
8401
8422
  "nodemon",
8402
8423
  "pm2",
8403
8424
  "dependency-cruiser",
8425
+ "dotenv",
8404
8426
  "wuchale",
8405
8427
  "varlock",
8406
8428
  "pnpm",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "3.34.1",
3
+ "version": "3.36.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",
@@ -89,14 +89,14 @@
89
89
  "@tanstack/intent": "0.5.5"
90
90
  },
91
91
  "optionalDependencies": {
92
- "@fallow-cli/darwin-arm64": "3.34.1",
93
- "@fallow-cli/darwin-x64": "3.34.1",
94
- "@fallow-cli/linux-x64-gnu": "3.34.1",
95
- "@fallow-cli/linux-arm64-gnu": "3.34.1",
96
- "@fallow-cli/linux-x64-musl": "3.34.1",
97
- "@fallow-cli/linux-arm64-musl": "3.34.1",
98
- "@fallow-cli/win32-arm64-msvc": "3.34.1",
99
- "@fallow-cli/win32-x64-msvc": "3.34.1",
100
- "fallow-type-aware": "3.34.1"
92
+ "@fallow-cli/darwin-arm64": "3.36.0",
93
+ "@fallow-cli/darwin-x64": "3.36.0",
94
+ "@fallow-cli/linux-x64-gnu": "3.36.0",
95
+ "@fallow-cli/linux-arm64-gnu": "3.36.0",
96
+ "@fallow-cli/linux-x64-musl": "3.36.0",
97
+ "@fallow-cli/linux-arm64-musl": "3.36.0",
98
+ "@fallow-cli/win32-arm64-msvc": "3.36.0",
99
+ "@fallow-cli/win32-x64-msvc": "3.36.0",
100
+ "fallow-type-aware": "3.36.0"
101
101
  }
102
102
  }
package/schema.json CHANGED
@@ -51,7 +51,7 @@
51
51
  "default": []
52
52
  },
53
53
  "ignoreFindings": {
54
- "description": "An array of project-root-relative glob patterns whose source-owned dead-code findings are hidden after analysis without excluding matching files from discovery, parsing, resolution, or the module graph. Use `/` as the path separator on every platform. Positive patterns select paths to hide; `!`-prefixed patterns keep matching paths reportable, and a negated-only array reports only those exception paths. A finding with multiple source owners is hidden only when every owner matches, so a cycle, duplicate-export group, or unlisted dependency with any reportable location remains visible. Architecture, policy, suppression-hygiene, and framework-correctness findings remain visible even when a referenced path matches, as do manifest-owned findings that no source file owns: unused dependencies, unused dev and optional dependencies, catalog entries, and dependency overrides. Use `ignorePatterns` instead when a generated or vendored file must not be analyzed at all. A single `*` stays within one path segment; use `**` to match across directories.",
54
+ "description": "An array of project-root-relative glob patterns whose source-owned dead-code findings are hidden after analysis without excluding matching files from discovery, parsing, resolution, or the module graph. Use `/` as the path separator on every platform. Positive patterns select paths to hide; `!`-prefixed patterns keep matching paths reportable, and a negated-only array reports only those exception paths. A finding with multiple source owners is hidden only when every owner matches, so a cycle, duplicate-export group, or unlisted dependency with any reportable location remains visible. Architecture, policy, suppression-hygiene, and framework-correctness findings remain visible even when a referenced path matches, as do manifest-owned findings that no source file owns: unused dependencies, unused dev and optional dependencies, catalog entries, and dependency overrides. Use `ignorePatterns` instead when a generated or vendored file must not be analyzed at all. A matching file that parses with errors reports no `source-parse-degraded` diagnostic, and its partial parse does not mark other findings as incomplete; use this only for files that are broken on purpose, such as syntax-error test fixtures. A single `*` stays within one path segment; use `**` to match across directories.",
55
55
  "type": "array",
56
56
  "items": {
57
57
  "type": "string"
@@ -416,7 +416,7 @@
416
416
  "default": false
417
417
  },
418
418
  "failOnParseError": {
419
- "description": "When true, the run fails with exit code 1 if fallow could not parse a source file cleanly (a `source-parse-degraded` entry in `workspace_diagnostics[]`), and the `parse-error` entry of `gate_outcomes` names each such file. Boolean, defaults to false, so a file the parser rejects only warns. Applies to `fallow dead-code`, `fallow health`, `fallow audit` and the bare `fallow` run; the CLI flag `--fail-on-parse-error` arms the same gate for one run. Older fallow versions reject this key as unknown, so pair it with `minimumVersion` set to the release that added it.",
419
+ "description": "When true, the run fails with exit code 1 if fallow could not parse a source file cleanly (a `source-parse-degraded` or `source-nesting-too-deep` entry in `workspace_diagnostics[]`), and the `parse-error` entry of `gate_outcomes` names each such file. Boolean, defaults to false, so a file the parser rejects only warns. Applies to `fallow dead-code`, `fallow health`, `fallow audit` and the bare `fallow` run; the CLI flag `--fail-on-parse-error` arms the same gate for one run. Older fallow versions reject this key as unknown, so pair it with `minimumVersion` set to the release that added it.",
420
420
  "type": "boolean",
421
421
  "default": false
422
422
  },
@@ -486,6 +486,14 @@
486
486
  },
487
487
  "default": []
488
488
  },
489
+ "configDefaultExportOptOut": {
490
+ "description": "Entries of `configPatterns` whose files do not get the `default`\nexport credit under `includeEntryExports`. Use it when the tool reads\nnamed exports, or when the pattern also matches ordinary source files.\nWrite each entry exactly as in `configPatterns`. Other entries have\nno effect.",
491
+ "type": "array",
492
+ "items": {
493
+ "type": "string"
494
+ },
495
+ "default": []
496
+ },
489
497
  "alwaysUsed": {
490
498
  "description": "Files that are always considered \"used\" when this plugin is active.",
491
499
  "type": "array",
@@ -530,13 +538,13 @@
530
538
  "description": "Plugin detected if this package is in dependencies.",
531
539
  "type": "object",
532
540
  "properties": {
533
- "package": {
534
- "description": "Exact package name looked up in the project's declared dependencies.",
535
- "type": "string"
536
- },
537
541
  "type": {
538
542
  "type": "string",
539
543
  "const": "dependency"
544
+ },
545
+ "package": {
546
+ "description": "Exact package name looked up in the project's declared dependencies.",
547
+ "type": "string"
540
548
  }
541
549
  },
542
550
  "required": [
@@ -548,13 +556,13 @@
548
556
  "description": "Plugin detected if this file pattern matches.",
549
557
  "type": "object",
550
558
  "properties": {
551
- "pattern": {
552
- "description": "Project-root-relative glob; the plugin activates when any\ndiscovered file matches it. Validated at load like other user\nglobs (no absolute paths or `..` segments).\nA single `*` stays within one path segment; use `**` to match across directories.",
553
- "type": "string"
554
- },
555
559
  "type": {
556
560
  "type": "string",
557
561
  "const": "fileExists"
562
+ },
563
+ "pattern": {
564
+ "description": "Project-root-relative glob; the plugin activates when any\ndiscovered file matches it. Validated at load like other user\nglobs (no absolute paths or `..` segments).\nA single `*` stays within one path segment; use `**` to match across directories.",
565
+ "type": "string"
558
566
  }
559
567
  },
560
568
  "required": [
@@ -566,16 +574,16 @@
566
574
  "description": "All conditions must be true.",
567
575
  "type": "object",
568
576
  "properties": {
577
+ "type": {
578
+ "type": "string",
579
+ "const": "all"
580
+ },
569
581
  "conditions": {
570
582
  "description": "Sub-conditions combined with logical AND.",
571
583
  "type": "array",
572
584
  "items": {
573
585
  "$ref": "#/$defs/PluginDetection"
574
586
  }
575
- },
576
- "type": {
577
- "type": "string",
578
- "const": "all"
579
587
  }
580
588
  },
581
589
  "required": [
@@ -587,16 +595,16 @@
587
595
  "description": "Any condition must be true.",
588
596
  "type": "object",
589
597
  "properties": {
598
+ "type": {
599
+ "type": "string",
600
+ "const": "any"
601
+ },
590
602
  "conditions": {
591
603
  "description": "Sub-conditions combined with logical OR.",
592
604
  "type": "array",
593
605
  "items": {
594
606
  "$ref": "#/$defs/PluginDetection"
595
607
  }
596
- },
597
- "type": {
598
- "type": "string",
599
- "const": "any"
600
608
  }
601
609
  },
602
610
  "required": [
@@ -1143,6 +1151,13 @@
1143
1151
  "boolean",
1144
1152
  "null"
1145
1153
  ]
1154
+ },
1155
+ "runnerConfig": {
1156
+ "description": "The config file of the test runner, relative to the project root, for\nexample `vitest.integration.config.mjs`. The file must exist inside\nthe project root. Only the Vitest runner reads it. Unset uses the\ndefault config discovery of the runner. `--runner-config` wins.",
1157
+ "type": [
1158
+ "string",
1159
+ "null"
1160
+ ]
1146
1161
  }
1147
1162
  },
1148
1163
  "additionalProperties": false
@@ -1234,6 +1249,11 @@
1234
1249
  "description": "One token of a regular expression literal changed: an anchor\nremoved, a character class negated, or a class escape with the\nopposite case.",
1235
1250
  "type": "string",
1236
1251
  "const": "regex-literal"
1252
+ },
1253
+ {
1254
+ "description": "`z.number().int()` to `z.number()` in a module-level `const`\ninitializer: one call link of a fluent chain removed.",
1255
+ "type": "string",
1256
+ "const": "removed-chain-link"
1237
1257
  }
1238
1258
  ]
1239
1259
  },
@@ -1249,6 +1269,10 @@
1249
1269
  "maximum": 65535,
1250
1270
  "default": 20
1251
1271
  },
1272
+ "cyclomaticPolicy": {
1273
+ "description": "How the cyclomatic gate counts complexity (default: `standard`).\n`literal-membership` counts a chain of strict equality checks of one\nidentifier against primitive literals (`kind === \"a\" || kind === \"b\"`)\nas one predicate. The policy applies to the `maxCyclomatic` gate and\nto every CRAP score, so the `maxCrap` gate reads the same value.\nFindings then report the standard value as `standard_cyclomatic`. The\nhealth score, complexity density, and hotspots keep the standard\ncount. Older fallow versions reject this key as unknown, so pair it\nwith `minimumVersion`.",
1274
+ "$ref": "#/$defs/CyclomaticPolicy"
1275
+ },
1252
1276
  "maxCognitive": {
1253
1277
  "description": "Maximum allowed cognitive complexity per function (default: 15).\nFunctions exceeding this threshold are reported. Governs findings\nonly, never the health score.",
1254
1278
  "type": "integer",
@@ -1342,6 +1366,21 @@
1342
1366
  },
1343
1367
  "additionalProperties": false
1344
1368
  },
1369
+ "CyclomaticPolicy": {
1370
+ "description": "How `fallow health` counts cyclomatic complexity for its gates.\n\nThe policy changes the cyclomatic value that the `maxCyclomatic` gate reads\nand the cyclomatic value in every CRAP score. It does not change the health\nscore, the complexity density, or the hotspot ranking: those keep the\nstandard count so that grades stay comparable across projects.",
1371
+ "oneOf": [
1372
+ {
1373
+ "description": "Count every decision point, `&&`, `||`, and `??` operator (`McCabe`).",
1374
+ "type": "string",
1375
+ "const": "standard"
1376
+ },
1377
+ {
1378
+ "description": "Count a literal membership chain as one predicate. A chain such as\n`kind === \"a\" || kind === \"b\" || kind === \"c\"` adds no cyclomatic\nincrements for its `||` operators. Only strict equality checks of one\nidentifier against string, number, bigint, boolean, or `null` literals\nqualify. Member access, calls, mixed operators, and different operands\nkeep the standard count.",
1379
+ "type": "string",
1380
+ "const": "literal-membership"
1381
+ }
1382
+ ]
1383
+ },
1345
1384
  "HealthThresholdOverride": {
1346
1385
  "description": "Per-file or per-function health threshold override.",
1347
1386
  "type": "object",
@@ -3394,6 +3433,10 @@
3394
3433
  "description": "Selects which findings affect the `fallow audit` verdict: `new-only` (default) fails only on findings introduced by the current changeset (running a base-snapshot attribution pass), while `all` fails on every finding in changed files and skips that pass. Set to `all` to gate the full backlog in changed files; the `--gate` CLI flag overrides this.",
3395
3434
  "$ref": "#/$defs/AuditGate"
3396
3435
  },
3436
+ "worsened": {
3437
+ "description": "Selects how a worsened complexity finding affects the `fallow audit` verdict under `gate: new-only`. A worsened finding was already in the base snapshot, and a measured value is higher now. Any growth of cyclomatic, cognitive or CRAP counts. The line count must grow by at least 5 lines, and it includes comments and blank lines. `warn` (default) makes the verdict at least `warn` and does not fail the run. `error` counts a worsened finding like an introduced finding. `off` reports only the growth of a metric that the finding exceeds as worsened, which gives at least `warn`. A finding whose exceeded metric grew is worsened when it stays in its severity tier (`moderate`, `high`, `critical`), and introduced when it moves to a higher tier or exceeds a metric that the base finding did not exceed. The `--worsened` CLI flag overrides this.",
3438
+ "$ref": "#/$defs/AuditWorsened"
3439
+ },
3397
3440
  "deadCode": {
3398
3441
  "description": "Toggles the dead-code sub-analysis in `fallow audit` (unused files, exports, types, members and dependencies, plus boundary violations and circular dependencies, which come from the same pass). Defaults to on when unset; set `false` to leave it out of the verdict, summary and attribution, on the head and on the base snapshot. `fallow audit --skip dead-code` turns it off for one run and `--only` replaces this selection. The review brief (`--brief`, `fallow review`) keeps every sub-analysis because it reads the module graph.",
3399
3442
  "type": [
@@ -3483,6 +3526,26 @@
3483
3526
  }
3484
3527
  ]
3485
3528
  },
3529
+ "AuditWorsened": {
3530
+ "description": "Value of `audit.worsened`: how a worsened complexity finding affects the\n`fallow audit` verdict.",
3531
+ "oneOf": [
3532
+ {
3533
+ "description": "A worsened finding makes the verdict at least `warn` and does not fail\nthe run. The default.",
3534
+ "type": "string",
3535
+ "const": "warn"
3536
+ },
3537
+ {
3538
+ "description": "A worsened finding counts like an introduced finding.",
3539
+ "type": "string",
3540
+ "const": "error"
3541
+ },
3542
+ {
3543
+ "description": "Growth only in metrics that the finding does not exceed stays\ninherited. Growth of an exceeded metric stays worsened and gives at\nleast `warn`.",
3544
+ "type": "string",
3545
+ "const": "off"
3546
+ }
3547
+ ]
3548
+ },
3486
3549
  "CacheConfig": {
3487
3550
  "description": "The `cache` config section: location and size ceiling of fallow's\npersistent caches (default directory `<root>/.fallow`).",
3488
3551
  "type": "object",
@@ -158,6 +158,28 @@ fallow audit --format json --quiet --base main
158
158
 
159
159
  Returns a pass/warn/fail verdict for issues introduced by the PR. Only analyzes files changed since the `main` branch.
160
160
 
161
+ ### Review a change and let fallow check the verdict
162
+ ```bash
163
+ review="${TMPDIR:-/tmp}"
164
+ fallow audit --format json --quiet # 1. gate the change
165
+ fallow audit --walkthrough-guide --format json --quiet > "$review/fallow-guide.json" # 2. read the guide
166
+ fallow audit --walkthrough-file "$review/fallow-judgment.json" --format json --quiet # 4. fallow checks the judgment
167
+ ```
168
+
169
+ Step 3 is your review. Read `digest.decisions[]` and `change_anchors[]` in the guide, then write `fallow-judgment.json` in the same folder:
170
+
171
+ ```json
172
+ {
173
+ "graph_snapshot_hash": "<copy from the guide>",
174
+ "judgments": [
175
+ { "signal_id": "sig:<from digest.decisions>", "framing": "Callers keep their imports.", "action": "fyi" },
176
+ { "change_anchor": "chg:<from change_anchors>", "framing": "unusedHelper has no caller.", "action": "address" }
177
+ ]
178
+ }
179
+ ```
180
+
181
+ Fallow validates the judgment against the live graph. It rejects an id that it did not emit. It refuses the whole file as `stale` when the hash no longer matches. Report only the `accepted` judgments. Keep the guide outside the project: a new file in the tree changes the hash. Do not create `.fallow/` for it, because a folder that you create has no ignore file. On `stale: true`, fetch the guide again.
182
+
161
183
  ### Find code duplication
162
184
  ```bash
163
185
  fallow dupes --format json --quiet
@@ -53,7 +53,7 @@ Every fallow command with its purpose and key flags. The table is regenerated fr
53
53
  | `type-aware` | Inspect the optional TypeScript semantic companion | |
54
54
  | `doctor` | Diagnose project readiness without analysis or mutation | |
55
55
  | `similar-code` | Find semantically similar functions with a pinned local model (opt-in) | `--threshold`, `--min-lines`, `--top`, `--file` |
56
- | `mutation` | Measure how well the tests detect changes in the code (opt-in) | `--all`, `--plan`, `--max-mutants`, `--time-budget`, `--concurrency`, `--runtime`, `--allow-dirty`, `--allow-side-effects` |
56
+ | `mutation` | Measure how well the tests detect changes in the code (opt-in) | `--all`, `--plan`, `--max-mutants`, `--time-budget`, `--concurrency`, `--runtime`, `--runner-config`, `--allow-dirty`, `--allow-side-effects` |
57
57
  | `inspect` | Compose one evidence bundle for a file or exported symbol | `--file <path>`, `--symbol <file>:<export>` |
58
58
  | `trace` | Trace a symbol's call chain, or with `--path` the shortest import path between two modules (best-effort, syntactic; OFF the ranked path) | `symbol`, `--path <FROM> <TO>`, `--eager-only`, `--callers`, `--callees`, `--depth` |
59
59
  | `trace-error` | Resolve a runtime stack trace's frames to the definitions they name (best-effort, syntactic; OFF the ranked path) | `trace_file` |
@@ -440,7 +440,7 @@ The `--entry-weight` JSON output carries `entry_weight.entries[]`, one row per r
440
440
 
441
441
  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.
442
442
 
443
- 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`, `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). 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. Human, compact, CodeClimate and `github-annotations` print a stderr note. `fallow report --from` uses the same place as the live run of that format.
443
+ 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
444
 
445
445
  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`.
446
446
 
@@ -526,7 +526,7 @@ Human output groups paths under "Shared with your team (commit these)" and "Loca
526
526
  {
527
527
  "kind": "agent-install",
528
528
  "schema_version": 1,
529
- "fallow_version": "3.34.1",
529
+ "fallow_version": "3.36.0",
530
530
  "root": "/abs/path",
531
531
  "mode": "install",
532
532
  "dry_run": false,
@@ -737,7 +737,7 @@ fallow health --format json --quiet --trend
737
737
  {
738
738
  "kind": "health",
739
739
  "schema_version": 7,
740
- "version": "3.34.1",
740
+ "version": "3.36.0",
741
741
  "elapsed_ms": 32,
742
742
  "summary": {
743
743
  "files_analyzed": 482,
@@ -1112,6 +1112,7 @@ Audits changed files for dead code, complexity, duplication, and styling. Return
1112
1112
  | `--css-deep` | `bool` | `false` | Enable deep CSS analysis for audit explicitly: project-wide styling reachability, narrowed back to changed anchors. Deep CSS is on by default; use this to override `audit.cssDeep = false` |
1113
1113
  | `--no-css-deep` | `bool` | `false` | Disable deep CSS analysis while keeping local styling analytics on |
1114
1114
  | `--gate` | `new-only\|all` | - | Which findings affect the verdict. `new-only` gates only introduced findings; `all` gates every finding in changed files and skips the extra base-snapshot attribution pass. |
1115
+ | `--worsened` | `warn\|error\|off` | - | How a worsened complexity finding affects the verdict under `new-only`. A worsened finding was already in the base, and a measured value is higher now. The line count must grow by at least 5 lines, and it includes comments and blank lines. `warn` (default) gives at least a `warn` verdict. `error` counts the finding like an introduced finding. `off` reports no worsened finding. A grown exceeded metric stays introduced in both modes. |
1115
1116
  | `--runtime-coverage` | `string` | - | Runtime coverage input. Accepts a V8 directory, a single V8 JSON file, or an Istanbul coverage map JSON. Runs the `fallow-cov` sidecar inside the audit, so the `hot-path-touched` verdict shows next to the dead-code and complexity findings without a second `fallow health` run in CI. The verdict is informational and does not change the exit code. A single local capture is free. Continuous or multi-capture monitoring needs a license (see `fallow license`) |
1116
1117
  | `--min-invocations-hot` | `string` | `100` | Threshold for hot-path classification, forwarded to the sidecar when `--runtime-coverage` is set |
1117
1118
  | `--gate-marker` | `string` | - | Internal marker identifying a gate run (e.g. `pre-commit`), set by the generated git hook so Fallow Impact can record a containment event when the gate blocks then clears. Hidden; never changes the verdict, exit code, or output |
@@ -1142,9 +1143,20 @@ A merge of the default branch brings in code that the default branch already has
1142
1143
  A complexity finding matches its base finding by file path and function name. The line and the exceeded category do not affect the match, and a renamed file keeps its match. A complexity finding is introduced in these cases:
1143
1144
 
1144
1145
  - No base finding matches it.
1145
- - A metric that the head finding exceeds has a higher value than in the base finding.
1146
+ - The head finding exceeds a metric that the base finding did not exceed, and that metric grew.
1147
+ - A metric that the head finding exceeds grew, and the finding moved to a higher severity tier (`moderate`, `high`, `critical`).
1146
1148
 
1147
- An unchanged or decreased metric keeps the finding inherited, also when the exceeded category changes (for example from `both` to `cyclomatic`). When one file has more than one finding with the same function name, each base finding matches at most one head finding. Head findings with unchanged metric values match first. The other findings then match in line order.
1149
+ Growth of a metric that the base finding already exceeded, in the same severity tier, makes the finding `worsened`, not introduced.
1150
+
1151
+ An unchanged or decreased metric keeps the finding inherited, also when the exceeded category changes (for example from `both` to `cyclomatic`). When one file has more than one finding with the same function name, each base finding matches at most one head finding. Head findings with unchanged metric values and an unchanged line count match first. Head findings with unchanged metric values match next. Then head findings with unchanged cyclomatic and cognitive values match, also when the CRAP score changed, because a change of the coverage input changes the CRAP score. The other findings then match in line order.
1152
+
1153
+ A matched finding that is not introduced is `worsened` when a measured value is higher than in the base finding. The measured values are cyclomatic, cognitive, CRAP (only when both sides have a score) and the line count of the function. The line count must grow by at least 5 lines. It includes comments and blank lines, so the minimum keeps a finding inherited after a short comment or a formatter rewrap. Each complexity finding carries `attribution` (`introduced`, `worsened` or `inherited`). When a value grew, it also carries `worsened_metrics` with the `metric`, the `base` value and the `head` value. An introduced finding that matched a base finding also carries `worsened_metrics`. `attribution.worsened` gives the active mode. `attribution.complexity_worsened` counts the worsened findings, which are also in `complexity_inherited`. `--worsened` (config `audit.worsened`) decides their effect on the verdict:
1154
+
1155
+ - `warn` (default): a worsened finding makes the verdict at least `warn`. The exit code stays 0.
1156
+ - `error`: a worsened finding counts like an introduced finding, so its `complexity-*` rule decides the verdict.
1157
+ - `off`: growth only in metrics that the finding does not exceed stays inherited. Growth of an exceeded metric stays `worsened` and makes the verdict at least `warn`.
1158
+
1159
+ A complexity finding in a test file does not gate the audit verdict, under either gate. A test file is a spec (`*.test.*`, `*.spec.*`, `*.e2e.*`), a file below a test directory (`__tests__/`, `test/`, `tests/`, `e2e/`), a mock or a fixture. Such a finding has `effective_severity: warn` and `gate_exemption: "test-file"`, and it stays in the report with its attribution. It makes the verdict at least `warn`. `fallow health` still reports it at the severity of its rule.
1148
1160
 
1149
1161
  ### JSON contract: which fields are severity-aware
1150
1162
 
@@ -1200,7 +1212,7 @@ fallow audit \
1200
1212
  {
1201
1213
  "kind": "audit",
1202
1214
  "schema_version": 7,
1203
- "version": "3.34.1",
1215
+ "version": "3.36.0",
1204
1216
  "command": "audit",
1205
1217
  "verdict": "fail",
1206
1218
  "changed_files_count": 12,
@@ -1288,7 +1300,7 @@ fallow flags --format json --quiet --workspace my-package
1288
1300
  ```json
1289
1301
  {
1290
1302
  "schema_version": 7,
1291
- "version": "3.34.1",
1303
+ "version": "3.36.0",
1292
1304
  "elapsed_ms": 116,
1293
1305
  "feature_flags": [],
1294
1306
  "total_flags": 0
@@ -1389,7 +1401,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1389
1401
  {
1390
1402
  "kind": "security",
1391
1403
  "schema_version": "4",
1392
- "version": "3.34.1",
1404
+ "version": "3.36.0",
1393
1405
  "elapsed_ms": 42,
1394
1406
  "config": {
1395
1407
  "rules": {
@@ -1418,7 +1430,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1418
1430
  {
1419
1431
  "kind": "security",
1420
1432
  "schema_version": "4",
1421
- "version": "3.34.1",
1433
+ "version": "3.36.0",
1422
1434
  "elapsed_ms": 42,
1423
1435
  "config": {
1424
1436
  "rules": {
@@ -2233,7 +2245,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
2233
2245
  {
2234
2246
  "kind": "dead-code",
2235
2247
  "schema_version": 7,
2236
- "version": "3.34.1",
2248
+ "version": "3.36.0",
2237
2249
  "elapsed_ms": 45,
2238
2250
  "total_issues": 12,
2239
2251
  "entry_points": {
@@ -2393,7 +2405,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
2393
2405
  {
2394
2406
  "kind": "dupes",
2395
2407
  "schema_version": 7,
2396
- "version": "3.34.1",
2408
+ "version": "3.36.0",
2397
2409
  "elapsed_ms": 82,
2398
2410
  "total_clones": 15,
2399
2411
  "total_lines_duplicated": 230,
@@ -2437,11 +2449,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
2437
2449
  {
2438
2450
  "kind": "combined",
2439
2451
  "schema_version": 7,
2440
- "version": "3.34.1",
2452
+ "version": "3.36.0",
2441
2453
  "elapsed_ms": 159,
2442
2454
  "check": {
2443
2455
  "schema_version": 7,
2444
- "version": "3.34.1",
2456
+ "version": "3.36.0",
2445
2457
  "elapsed_ms": 45,
2446
2458
  "total_issues": 12,
2447
2459
  "unused_files": [],
@@ -179,7 +179,7 @@ Error-severity findings can trigger exit code 1. Default severity varies by rule
179
179
  }
180
180
  ```
181
181
 
182
- Exit code 1 always means that an enforced gate failed. A load warning or a workspace diagnostic (for example `node-modules-missing` or a tsconfig `extends` that does not resolve) never changes the exit code. The only exception is `source-parse-degraded` with `--fail-on-parse-error`. To find the gate, read `gate_outcomes` in the JSON output and look for an entry with `status: "fail"` and `enforced: true`. Under `--quiet` and in every machine format, fallow also prints one stderr line that names the failed gates, for example `[X] Exit code 1: gate health-findings (3 at or above error) failed.` On `health`, the `complexity-*` rules default to `error`, so each complexity finding fails the run. Set them to `warn` or pass `--report-only` to report without a failure.
182
+ Exit code 1 always means that an enforced gate failed. A load warning or a workspace diagnostic (for example `node-modules-missing` or a tsconfig `extends` that does not resolve) never changes the exit code. The only exceptions are `source-parse-degraded` and `source-nesting-too-deep` with `--fail-on-parse-error`. To find the gate, read `gate_outcomes` in the JSON output and look for an entry with `status: "fail"` and `enforced: true`. Under `--quiet` and in every machine format, fallow also prints one stderr line that names the failed gates, for example `[X] Exit code 1: gate health-findings (3 at or above error) failed.` On `health`, the `complexity-*` rules default to `error`, so each complexity finding fails the run. Set them to `warn` or pass `--report-only` to report without a failure.
183
183
 
184
184
  ---
185
185
 
@@ -45,7 +45,7 @@ When using fallow via MCP (`fallow-mcp`), the following tools are available:
45
45
  | `list_suppressions` | analysis | free | `fallow suppressions --format json --quiet` | `workspace`, `changed_since`, `file` | List active fallow-ignore suppression markers grouped per file (line, kind, level, reason, and a stale cross-reference); a read-only governance inventory that always exits 0 |
46
46
  | `impact` | introspection | free | `fallow impact --format json --quiet` | `root` | Read the local, opt-in Fallow Impact value report (`fallow impact --format json`). Runs no analysis: current surfacing counts, trend since the last recorded run, pre-commit gate containment, and (on impact v1.5+) resolved/suppressed attribution. History is read from a per-project file in the user's private config dir (never inside the repo). Read-only and `root`-only; the mutating `enable` / `disable` / `default` lifecycle is not exposed. A never-enabled project returns a populated `{"enabled": false, ...}` report (never `{}`); branch on `enabled` and `enabled_source` (`project` / `user` / `default`) then `record_count`, recommending `fallow impact enable` only when `explicit_decision` is `false` (never asked) and staying silent when `true` (deliberately disabled here). Local-developer signal: fallow never records in CI, so empty there and not a CI metric |
47
47
  | `impact_all` | introspection | free | `fallow impact --all --format json --quiet` | `sort`, `limit` | Roll every tracked fallow project on this machine into one cross-repo value report (hashed keys plus basename labels, never paths; local-dev only) |
48
- | `trace_export` | trace | free | `fallow dead-code --trace <file:export> --format json --quiet` | `file`, `export_name` | Trace why an export is used or unused (`fallow dead-code --trace FILE:EXPORT_NAME --format json`). Required `file` and `export_name`. Returns file reachability, entry-point status, direct references, re-export chains, and a reason string. If `export_name` is a class / enum / store MEMBER, returns a member trace instead (`member_name`, `member_kind`, `owner_export`, `owner_is_used`) plus a `--unused-<kind>-members` pointer; branch on field presence. Use before deleting a supposedly-unused export or debugging an unused-class-member finding. Each reference has `search_visibility` (`found`, `renamed`, `missed`). When `search_as` is present, search for that name in that consumer file |
48
+ | `trace_export` | trace | free | `fallow dead-code --trace <file:export> --format json --quiet` | `file`, `export_name` | Trace why an export is used or unused (`fallow dead-code --trace FILE:EXPORT_NAME --format json`). Required `file` and `export_name`. Returns file reachability, entry-point status, direct references, re-export chains, `plugin_credits` (the framework plugin rules that mark the export as used, for example a Next.js page `default`), and a reason string. A credited export of a reachable file traces as `is_used: true`, so it is not dead code. If `export_name` is a class / enum / store MEMBER, returns a member trace instead (`member_name`, `member_kind`, `owner_export`, `owner_is_used`) plus a `--unused-<kind>-members` pointer; branch on field presence. Use before deleting a supposedly-unused export or debugging an unused-class-member finding. Each reference has `search_visibility` (`found`, `renamed`, `missed`). When `search_as` is present, search for that name in that consumer file |
49
49
  | `trace_symbol` | trace | free | `fallow dead-code --type-aware --trace <file:export> --format json --quiet` | `file`, `export_name`, `type_aware_projects`, `type_aware_require` | Trace an exact TypeScript symbol with checker-backed references, namespace identity, aliases, and re-export hops. Root trace fields preserve syntactic context; treat `semantic.references`, `semantic.status`, and `semantic.identity` as the authoritative exact evidence. The proof covers only the lane named by `semantic.target.namespace`, so a root trace that lists a reference the proof does not is wider evidence rather than stale. This is project-wide evidence for Fallow decisions, not a compiler-diagnostic or lint-rule surface. |
50
50
  | `symbol_impact` | impact | free | `fallow dead-code --type-aware --symbol-impact <file:export-or-class.member> --format json --quiet` | `file`, `export_name`, `class_name`, `member_name`, `type_aware_projects`, `type_aware_require` | Return exact-symbol consumers, affected files, and targeted tests for a TypeScript export or exported class method. Select either `export_name`, or both `class_name` and `member_name`. Advisory change-impact evidence, not a substitute for `tsc` or Oxlint |
51
51
  | `trace_file` | trace | free | `fallow dead-code --trace-file <file> --format json --quiet` | `file` | Trace all graph edges for a file (`fallow dead-code --trace-file PATH --format json`). Required `file`. Returns reachability, exports, imports-from, imported-by, and re-exports. Use to decide whether a file is isolated, barrel-only, or imported by live entry points |
@@ -153,7 +153,7 @@ Every dead-code, health, and duplication finding in JSON responses includes a st
153
153
 
154
154
  `dead-code`, `health`, `dupes`, bare `fallow`, and `audit` JSON output may include a top-level `next_steps` array of read-only follow-up commands computed from the run's findings. Each entry is `{ id, command, reason }`. The `command` is runnable as-is (never a placeholder, never `fix` or any other mutating command); the stable kebab-case `id` (`setup`, `impact-report`, `trace-unused-export`, `trace-deprecated-export`, `trace-clone`, `complexity-breakdown`, `scope-workspaces`, `audit-changed`) maps to a verification step to run before acting, for example tracing an export before deleting it.
155
155
 
156
- A leading `setup` step (command: `fallow schema`) appears only on unconfigured, non-CI projects with findings and doubles as an onboarding trigger; it disappears after setup or `fallow init --decline`.
156
+ A leading `setup` step (command: `fallow agent install --dry-run`) appears only on unconfigured, non-CI projects with findings and doubles as an onboarding trigger; it disappears after setup or `fallow init --decline`.
157
157
 
158
158
  An at-most-weekly `impact-report` step (command: `fallow impact`) carries the local value digest when impact tracking has non-zero results; it may appear in a clean run.
159
159
 
@@ -784,11 +784,11 @@ Prefer `fallow hooks install --target agent` to install this file. The script is
784
784
  Behavior you can rely on:
785
785
  - The handler walks up from the session directory to the nearest directory that holds `.claude/hooks/fallow-gate.sh` and runs the audit there, so a session in a package directory audits the install root. The walk stops at the first `.git` entry. When it finds no script, the handler runs the script under `$CLAUDE_PROJECT_DIR` from the session directory.
786
786
  - The hook process can start in a directory that is not the session directory, for example in the main checkout when the session works in a nested git worktree. The script reads the session directory from the `cwd` field of the hook input and finds the audit root from it with the same walk. When the walk finds no script, for example with the user-scope script in `$HOME`, the audit root is the directory of the hook process when it is in the git work tree of the session. Else it is the git top level of the session directory, or the session directory outside a git repository. The audit root is never `$HOME`. The script compares physical paths, so a symlink does not change the walk. When the hook input has no usable `cwd`, the audit runs in the directory of the hook process.
787
- - A `git commit` or `git push` can name another work tree. The script audits only that target when the command has the strict form `[cd <dir> &&] git [-C <dir>]... commit|push [<option or name>]...` and git reports another work tree for the target (`git rev-parse --show-toplevel --absolute-git-dir` gives a different top level and a different git directory than for the session directory). For a push, the common git directory must differ too: linked worktrees share refs, so a push from another linked worktree of the session repository audits the session tree and the target. A target in the same work tree keeps the session audit, because `git commit` commits the whole index. Each word in the strict form must be inert: a `-C` directory resolves physically as in git, a `cd` directory starts with `/`, `./` or `../`, a double-quoted word holds no `$`, backquote or backslash, and only a short list of options such as `-m`, `--amend`, `-S` and `-u` is allowed. Any other command audits the session tree as before, plus each directory that it names in another work tree. The gate blocks when one audit fails.
787
+ - A `git commit` or `git push` can name another work tree. The script audits only that target when the command has the strict form `[cd <dir> &&] git [-C <dir>]... commit|push [<option or name>]...` and git reports another work tree for the target (`git rev-parse --show-toplevel --absolute-git-dir` gives a different top level and a different git directory than for the session directory). For a push, the common git directory must differ too: linked worktrees share refs, so a push from another linked worktree of the session repository audits the session tree and the target. The one exception is a push that cannot send the session branch: the options are only `-u`, `-q`, `--set-upstream`, `--force-with-lease` and `--force-if-includes`, `git rev-parse --symbolic-full-name` gives the checked-out branch of the target for each refspec source, that branch is not the session branch, and a push without a refspec has `push.default` unset or `simple`, `current`, `upstream` or `nothing` and no remote `push` or `mirror` setting. That push audits only the target. A git error during this check keeps the session audit. A target in the same work tree keeps the session audit, because `git commit` commits the whole index. Each word in the strict form must be inert: a `-C` directory resolves physically as in git, a `cd` directory starts with `/`, `./` or `../`, a double-quoted word holds no `$`, backquote or backslash, and only a short list of options such as `-m`, `--amend`, `-S` and `-u` is allowed. Any other command audits the session tree as before, plus each directory that it names in another work tree. The gate blocks when one audit fails.
788
788
  - Runs only when the intercepted command is a `git commit` or `git push`, including invocations that pass git-level options before the subcommand (`git -c user.name=x commit`, `git --no-pager commit`, `git -C dir push`, `git --git-dir=/x push`); anything else exits 0. Set `FALLOW_GATE_DEBUG=1` to log skipped commands to stderr.
789
789
  - Resolves `fallow` from PATH first, then `npx --no-install fallow` as a fallback. Skips with a stderr notice if neither is available or if `jq` is missing.
790
790
  - Enforces a version floor via `FALLOW_GATE_MIN_VERSION`. The installed gate script holds the default floor (currently `2.85.0`). Fallow maintainers raise that default by hand; `fallow hooks install` does not set it to the installed version. Binaries below the floor are blocked with an upgrade hint. Set the env var to the empty string to disable the check.
791
- - Runs `fallow audit --format json --quiet --explain --gate-marker agent` and, on verdict=`fail`, writes the full JSON envelope to stderr preceded by `fallow-gate: blocked by fallow <version> at <binary>` so the responsible binary is always identifiable. The gate marker lets local Impact record blocked-then-cleared agent gate events when Impact is enabled.
791
+ - Runs `fallow audit --format json --quiet --explain --gate-marker agent`. On verdict=`fail`, it writes a short block message to stderr: the line `fallow-gate: blocked by fallow <version> at <binary>`, the verdict, one line for each finding that caused the block (`<kind> <path>:<line> <name>`, at most 50 lines), and the path of the full JSON report. Under the default `gate = "new-only"` the listed findings are the ones with `introduced: true`; under `gate = "all"` they are all findings that are not warnings. The full report goes to `.fallow/gate-report.json` in the audited tree, or to `FALLOW_GATE_REPORT` when set (a relative path resolves against the audited tree). Set `FALLOW_GATE_OUTPUT=json` to write the full JSON envelope to stderr instead, preceded by `fallow-gate: blocked by fallow <version> at <binary>` so the responsible binary is always identifiable. The gate marker lets local Impact record blocked-then-cleared agent gate events when Impact is enabled.
792
792
  - On runtime error (`{"error": true, ...}`) or unexpected non-zero exit, fails open with a one-line stderr notice; warn verdicts pass through silently.
793
793
 
794
794
  ### `.codex/hooks.json`
@@ -7,8 +7,8 @@ Inspect the repository before you change it. Record what you find, because the l
7
7
  | Tool area | Signals |
8
8
  |---|---|
9
9
  | Package manager | `packageManager` in `package.json`; the lockfile: `pnpm-lock.yaml`, `yarn.lock`, `bun.lock` or `bun.lockb`, `package-lock.json` |
10
- | Formatter | `.oxfmtrc.json`, `.prettierrc*`, `prettier.config.*`, `biome.json`, `biome.jsonc`, a `format` script in `package.json` |
11
- | Linter | `.oxlintrc.json`, `eslint.config.*`, `.eslintrc*`, `biome.json`, a `lint` script in `package.json` |
10
+ | Formatter | `.oxfmtrc.json`, `.oxfmtrc.jsonc`, `oxfmt.config.*`, `.prettierrc*`, `prettier.config.*`, `biome.json`, `biome.jsonc`, a `format` script in `package.json` |
11
+ | Linter | `.oxlintrc.json`, `.oxlintrc.jsonc`, `oxlint.config.{ts,mts}`, `eslint.config.*`, `.eslintrc*`, `biome.json`, a `lint` script in `package.json` |
12
12
  | TypeScript | `tsconfig.json`, `tsconfig.*.json`, a `typecheck` script, `typescript` in `devDependencies` |
13
13
  | CI | `.github/workflows/*.yml`, `.gitlab-ci.yml`, other CI config files |
14
14
  | Existing analysis | `knip.json`, `knip.jsonc`, `.knip.json`, `.knip.jsonc`, a `knip` field in `package.json`; `.jscpd.json`; `.dependency-cruiser.*` |