fallow 3.35.0 → 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/capabilities.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "3.35.0",
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"
@@ -7034,6 +7034,7 @@
7034
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.",
7035
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.",
7036
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.",
7037
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.",
7038
7039
  "FALLOW_AUDIT_BASE": "Pins the fallow audit comparison base ref when no --base/--changed-since is passed (e.g. upstream/main).",
7039
7040
  "FALLOW_AUDIT_CACHE_MAX_AGE_DAYS": "GC threshold in days for reusable audit base-snapshot caches (default 30; 0 disables the sweep).",
@@ -8280,7 +8281,7 @@
8280
8281
  ]
8281
8282
  },
8282
8283
  "plugins": {
8283
- "count": 148,
8284
+ "count": 149,
8284
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",
8285
8286
  "names": [
8286
8287
  "nextjs",
@@ -8421,6 +8422,7 @@
8421
8422
  "nodemon",
8422
8423
  "pm2",
8423
8424
  "dependency-cruiser",
8425
+ "dotenv",
8424
8426
  "wuchale",
8425
8427
  "varlock",
8426
8428
  "pnpm",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow",
3
- "version": "3.35.0",
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.35.0",
93
- "@fallow-cli/darwin-x64": "3.35.0",
94
- "@fallow-cli/linux-x64-gnu": "3.35.0",
95
- "@fallow-cli/linux-arm64-gnu": "3.35.0",
96
- "@fallow-cli/linux-x64-musl": "3.35.0",
97
- "@fallow-cli/linux-arm64-musl": "3.35.0",
98
- "@fallow-cli/win32-arm64-msvc": "3.35.0",
99
- "@fallow-cli/win32-x64-msvc": "3.35.0",
100
- "fallow-type-aware": "3.35.0"
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
@@ -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
  },
@@ -538,13 +538,13 @@
538
538
  "description": "Plugin detected if this package is in dependencies.",
539
539
  "type": "object",
540
540
  "properties": {
541
- "package": {
542
- "description": "Exact package name looked up in the project's declared dependencies.",
543
- "type": "string"
544
- },
545
541
  "type": {
546
542
  "type": "string",
547
543
  "const": "dependency"
544
+ },
545
+ "package": {
546
+ "description": "Exact package name looked up in the project's declared dependencies.",
547
+ "type": "string"
548
548
  }
549
549
  },
550
550
  "required": [
@@ -556,13 +556,13 @@
556
556
  "description": "Plugin detected if this file pattern matches.",
557
557
  "type": "object",
558
558
  "properties": {
559
- "pattern": {
560
- "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.",
561
- "type": "string"
562
- },
563
559
  "type": {
564
560
  "type": "string",
565
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"
566
566
  }
567
567
  },
568
568
  "required": [
@@ -574,16 +574,16 @@
574
574
  "description": "All conditions must be true.",
575
575
  "type": "object",
576
576
  "properties": {
577
+ "type": {
578
+ "type": "string",
579
+ "const": "all"
580
+ },
577
581
  "conditions": {
578
582
  "description": "Sub-conditions combined with logical AND.",
579
583
  "type": "array",
580
584
  "items": {
581
585
  "$ref": "#/$defs/PluginDetection"
582
586
  }
583
- },
584
- "type": {
585
- "type": "string",
586
- "const": "all"
587
587
  }
588
588
  },
589
589
  "required": [
@@ -595,16 +595,16 @@
595
595
  "description": "Any condition must be true.",
596
596
  "type": "object",
597
597
  "properties": {
598
+ "type": {
599
+ "type": "string",
600
+ "const": "any"
601
+ },
598
602
  "conditions": {
599
603
  "description": "Sub-conditions combined with logical OR.",
600
604
  "type": "array",
601
605
  "items": {
602
606
  "$ref": "#/$defs/PluginDetection"
603
607
  }
604
- },
605
- "type": {
606
- "type": "string",
607
- "const": "any"
608
608
  }
609
609
  },
610
610
  "required": [
@@ -1269,6 +1269,10 @@
1269
1269
  "maximum": 65535,
1270
1270
  "default": 20
1271
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
+ },
1272
1276
  "maxCognitive": {
1273
1277
  "description": "Maximum allowed cognitive complexity per function (default: 15).\nFunctions exceeding this threshold are reported. Governs findings\nonly, never the health score.",
1274
1278
  "type": "integer",
@@ -1362,6 +1366,21 @@
1362
1366
  },
1363
1367
  "additionalProperties": false
1364
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
+ },
1365
1384
  "HealthThresholdOverride": {
1366
1385
  "description": "Per-file or per-function health threshold override.",
1367
1386
  "type": "object",
@@ -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`, `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). 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.35.0",
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.35.0",
740
+ "version": "3.36.0",
741
741
  "elapsed_ms": 32,
742
742
  "summary": {
743
743
  "files_analyzed": 482,
@@ -1148,7 +1148,7 @@ A complexity finding matches its base finding by file path and function name. Th
1148
1148
 
1149
1149
  Growth of a metric that the base finding already exceeded, in the same severity tier, makes the finding `worsened`, not introduced.
1150
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. The other findings then match in line order.
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
1152
 
1153
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
1154
 
@@ -1212,7 +1212,7 @@ fallow audit \
1212
1212
  {
1213
1213
  "kind": "audit",
1214
1214
  "schema_version": 7,
1215
- "version": "3.35.0",
1215
+ "version": "3.36.0",
1216
1216
  "command": "audit",
1217
1217
  "verdict": "fail",
1218
1218
  "changed_files_count": 12,
@@ -1300,7 +1300,7 @@ fallow flags --format json --quiet --workspace my-package
1300
1300
  ```json
1301
1301
  {
1302
1302
  "schema_version": 7,
1303
- "version": "3.35.0",
1303
+ "version": "3.36.0",
1304
1304
  "elapsed_ms": 116,
1305
1305
  "feature_flags": [],
1306
1306
  "total_flags": 0
@@ -1401,7 +1401,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1401
1401
  {
1402
1402
  "kind": "security",
1403
1403
  "schema_version": "4",
1404
- "version": "3.35.0",
1404
+ "version": "3.36.0",
1405
1405
  "elapsed_ms": 42,
1406
1406
  "config": {
1407
1407
  "rules": {
@@ -1430,7 +1430,7 @@ fallow security --gate newly-reachable --changed-since origin/main
1430
1430
  {
1431
1431
  "kind": "security",
1432
1432
  "schema_version": "4",
1433
- "version": "3.35.0",
1433
+ "version": "3.36.0",
1434
1434
  "elapsed_ms": 42,
1435
1435
  "config": {
1436
1436
  "rules": {
@@ -2245,7 +2245,7 @@ The HTTP layer mirrors the bash `gh_api_retry` / `curl_retry` helpers: `FALLOW_A
2245
2245
  {
2246
2246
  "kind": "dead-code",
2247
2247
  "schema_version": 7,
2248
- "version": "3.35.0",
2248
+ "version": "3.36.0",
2249
2249
  "elapsed_ms": 45,
2250
2250
  "total_issues": 12,
2251
2251
  "entry_points": {
@@ -2405,7 +2405,7 @@ When `--baseline` is used in combined output, the JSON includes a `baseline_delt
2405
2405
  {
2406
2406
  "kind": "dupes",
2407
2407
  "schema_version": 7,
2408
- "version": "3.35.0",
2408
+ "version": "3.36.0",
2409
2409
  "elapsed_ms": 82,
2410
2410
  "total_clones": 15,
2411
2411
  "total_lines_duplicated": 230,
@@ -2449,11 +2449,11 @@ When running `fallow` with no subcommand (all analyses), the JSON output combine
2449
2449
  {
2450
2450
  "kind": "combined",
2451
2451
  "schema_version": 7,
2452
- "version": "3.35.0",
2452
+ "version": "3.36.0",
2453
2453
  "elapsed_ms": 159,
2454
2454
  "check": {
2455
2455
  "schema_version": 7,
2456
- "version": "3.35.0",
2456
+ "version": "3.36.0",
2457
2457
  "elapsed_ms": 45,
2458
2458
  "total_issues": 12,
2459
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
 
@@ -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`
@@ -164,6 +164,10 @@ export type AuditWorsened = ("warn" | "error" | "off")
164
164
  * is decided here.
165
165
  */
166
166
  export type GateStatus = ("pass" | "warn" | "fail" | "skipped")
167
+ /**
168
+ * Why a file counts for the `parse-error` gate, other than a parser error.
169
+ */
170
+ export type GateFileReason = "nesting-too-deep"
167
171
  /**
168
172
  * Analysis mode stored with baselines, snapshots, audit sides, and impact data.
169
173
  */
@@ -411,6 +415,7 @@ export type PolicyViolationSeverity = ("error" | "warn")
411
415
  * The origin of a stale suppression: inline comment or JSDoc tag.
412
416
  */
413
417
  export type SuppressionOrigin = ({
418
+ type: "comment"
414
419
  /**
415
420
  * The issue kind token from the comment (e.g., "unused-exports"), or None for blanket.
416
421
  */
@@ -432,8 +437,8 @@ is_file_level: boolean
432
437
  * adopted the field stay byte-compatible. See issue #449.
433
438
  */
434
439
  kind_known?: boolean
435
- type: "comment"
436
440
  } | {
441
+ type: "jsdoc_tag"
437
442
  /**
438
443
  * The name of the export that was tagged.
439
444
  */
@@ -442,7 +447,6 @@ export_name: string
442
447
  * Human-authored reason after `--`, when present.
443
448
  */
444
449
  reason?: (string | null)
445
- type: "jsdoc_tag"
446
450
  })
447
451
  /**
448
452
  * Where an override entry was declared. Serialized as the filename label
@@ -564,66 +568,67 @@ degrades_analysis?: boolean
564
568
  export type WorkspaceDiagnostic1 = ({
565
569
  kind: "undeclared-workspace"
566
570
  } | {
571
+ kind: "malformed-package-json"
567
572
  /**
568
573
  * `serde_json` parse error text.
569
574
  */
570
575
  error: string
571
- kind: "malformed-package-json"
572
576
  } | {
577
+ kind: "glob-matched-no-package-json"
573
578
  /**
574
579
  * The glob pattern that matched the directory.
575
580
  */
576
581
  pattern: string
577
- kind: "glob-matched-no-package-json"
578
582
  } | {
583
+ kind: "workspace-glob-outside-root"
579
584
  /**
580
585
  * The workspace entry, as written in the file.
581
586
  */
582
587
  pattern: string
583
- kind: "workspace-glob-outside-root"
584
588
  } | {
589
+ kind: "workspace-pattern-dir-missing"
585
590
  /**
586
591
  * The workspace pattern, as written in the file.
587
592
  */
588
593
  pattern: string
589
- kind: "workspace-pattern-dir-missing"
590
594
  } | {
591
595
  kind: "nested-project-root"
592
596
  } | {
597
+ kind: "malformed-tsconfig"
593
598
  /**
594
599
  * JSONC parse error text.
595
600
  */
596
601
  error: string
597
- kind: "malformed-tsconfig"
598
602
  } | {
599
603
  kind: "tsconfig-reference-dir-missing"
600
604
  } | {
605
+ kind: "malformed-pnpm-workspace-yaml"
601
606
  /**
602
607
  * YAML parse error text.
603
608
  */
604
609
  error: string
605
- kind: "malformed-pnpm-workspace-yaml"
606
610
  } | {
611
+ kind: "skipped-large-file"
607
612
  /**
608
613
  * On-disk size of the skipped file in bytes.
609
614
  */
610
615
  size_bytes: number
611
- kind: "skipped-large-file"
612
616
  } | {
617
+ kind: "skipped-minified-file"
613
618
  /**
614
619
  * On-disk size of the skipped file in bytes.
615
620
  */
616
621
  size_bytes: number
617
- kind: "skipped-minified-file"
618
622
  } | {
619
623
  kind: "skipped-source-dotdir"
620
624
  } | {
625
+ kind: "source-read-failure"
621
626
  /**
622
627
  * Filesystem or UTF-8 decoding error from `read_to_string`.
623
628
  */
624
629
  error: string
625
- kind: "source-read-failure"
626
630
  } | {
631
+ kind: "source-parse-degraded"
627
632
  /**
628
633
  * Number of parser diagnostics reported for the file.
629
634
  */
@@ -633,7 +638,12 @@ error_count: number
633
638
  * the extracted module is a fragment at best.
634
639
  */
635
640
  panicked: boolean
636
- kind: "source-parse-degraded"
641
+ } | {
642
+ kind: "source-nesting-too-deep"
643
+ /**
644
+ * The bracket nesting cap of the pre-parse scan.
645
+ */
646
+ limit: number
637
647
  } | {
638
648
  kind: "bun-lockb-override-resolution-skipped"
639
649
  } | {
@@ -645,8 +655,8 @@ kind: "npm-lock-override-resolution-skipped"
645
655
  } | {
646
656
  kind: "bun-resolutions-shadowed-by-overrides"
647
657
  } | {
648
- cause: PnpmWorkspaceOverridesIgnoredCause
649
658
  kind: "pnpm-workspace-overrides-ignored"
659
+ cause: PnpmWorkspaceOverridesIgnoredCause
650
660
  } | {
651
661
  kind: "node-modules-missing"
652
662
  } | {
@@ -654,6 +664,7 @@ kind: "boundaries-not-configured"
654
664
  } | {
655
665
  kind: "rule-packs-not-configured"
656
666
  } | {
667
+ kind: "excluded-by-default-ignore"
657
668
  /**
658
669
  * The built-in glob that matched, verbatim (for example
659
670
  * `** /build/**`).
@@ -677,22 +688,22 @@ file_count: number
677
688
  * one matched location out of several.
678
689
  */
679
690
  directory_count: number
680
- kind: "excluded-by-default-ignore"
681
691
  } | {
692
+ kind: "no-source-files-analyzed"
682
693
  /**
683
694
  * Candidate source files the built-in ignore patterns removed from
684
695
  * this walk, summed across every pattern. `0` when the walk found no
685
696
  * candidate to exclude in the first place.
686
697
  */
687
698
  excluded_file_count: number
688
- kind: "no-source-files-analyzed"
689
699
  } | {
700
+ kind: "file-scores-unavailable"
690
701
  /**
691
702
  * Scoring error text.
692
703
  */
693
704
  error: string
694
- kind: "file-scores-unavailable"
695
705
  } | {
706
+ kind: "hotspots-skipped"
696
707
  /**
697
708
  * Which input stopped it, as a kebab-case token: `not-a-repository`,
698
709
  * `no-commits`, `invalid-since` or `churn-file-unreadable`. The set is
@@ -706,25 +717,25 @@ kind: "file-scores-unavailable"
706
717
  * remedy for all four.
707
718
  */
708
719
  cause: string
709
- kind: "hotspots-skipped"
710
720
  } | {
721
+ kind: "change-coupling-skipped"
711
722
  /**
712
723
  * Kebab-case token: `shallow-clone` (the fetched history is
713
724
  * incomplete) or `too-few-commits` (fewer analyzed commits than the
714
725
  * minimum revisions of one file). The set is open.
715
726
  */
716
727
  cause: string
717
- kind: "change-coupling-skipped"
718
728
  } | {
729
+ kind: "shallow-clone"
719
730
  /**
720
731
  * `true` when the run also asked for ownership attribution, which a
721
732
  * shallow clone skews further by inflating single-author dominance.
722
733
  */
723
734
  ownership_requested: boolean
724
- kind: "shallow-clone"
725
735
  } | {
726
736
  kind: "unpinned-clock"
727
737
  } | {
738
+ kind: "ownership-unavailable"
728
739
  /**
729
740
  * Which input failed, as a kebab-case token: `invalid-bot-pattern` or
730
741
  * `codeowners-parse-failed`. The set is open.
@@ -734,21 +745,21 @@ cause: string
734
745
  * Underlying error text.
735
746
  */
736
747
  error: string
737
- kind: "ownership-unavailable"
738
748
  } | {
749
+ kind: "trend-snapshot-unreadable"
739
750
  /**
740
751
  * Filesystem or JSON error text.
741
752
  */
742
753
  error: string
743
- kind: "trend-snapshot-unreadable"
744
754
  } | {
755
+ kind: "trend-group-baseline-unavailable"
745
756
  /**
746
757
  * Why the groups have no baseline, as a kebab-case token:
747
758
  * `snapshot-has-no-groups` or `grouped-by-mismatch`. The set is open.
748
759
  */
749
760
  cause: string
750
- kind: "trend-group-baseline-unavailable"
751
761
  } | {
762
+ kind: "plugin-config-unreadable"
752
763
  /**
753
764
  * The plugin that read the config, as it labels itself:
754
765
  * `module-federation` for a standalone `module-federation.config.*`,
@@ -775,8 +786,8 @@ key: string
775
786
  * option the message points at.
776
787
  */
777
788
  reason: string
778
- kind: "plugin-config-unreadable"
779
789
  } | {
790
+ kind: "plugin-effect-not-modeled"
780
791
  /**
781
792
  * The plugin that read the config, as it labels itself (`nuxt`).
782
793
  */
@@ -795,34 +806,51 @@ key: string
795
806
  * be classified at all. The set is open.
796
807
  */
797
808
  reason: string
798
- kind: "plugin-effect-not-modeled"
799
809
  } | {
800
810
  kind: "coverage-auto-detected"
801
811
  } | {
802
812
  kind: "flag-age-shallow-clone"
803
813
  } | {
814
+ kind: "flag-age-unavailable"
804
815
  /**
805
816
  * Why no history is available, as a kebab-case token:
806
817
  * `not-a-repository` or `no-commits`. The set is open.
807
818
  */
808
819
  cause: string
809
- kind: "flag-age-unavailable"
810
820
  } | {
821
+ kind: "ignore-dependencies-glob-unmatched"
811
822
  /**
812
823
  * The `ignoreDependencies` entry, as written in the config.
813
824
  */
814
825
  pattern: string
815
- kind: "ignore-dependencies-glob-unmatched"
816
826
  } | {
827
+ kind: "ignore-findings-pattern-unmatched"
817
828
  /**
818
829
  * The `ignoreFindings` entry, as written in the config.
819
830
  */
820
831
  pattern: string
821
- kind: "ignore-findings-pattern-unmatched"
822
832
  } | {
833
+ kind: "entry-pattern-redundant"
834
+ /**
835
+ * The `entry` pattern, as written in the config.
836
+ */
837
+ pattern: string
838
+ } | {
839
+ kind: "ignore-dependencies-entry-redundant"
840
+ /**
841
+ * The `ignoreDependencies` entry, as written in the config.
842
+ */
843
+ pattern: string
844
+ } | {
845
+ kind: "ignore-patterns-entry-redundant"
846
+ /**
847
+ * The `ignorePatterns` glob, as written in the config.
848
+ */
849
+ pattern: string
850
+ } | {
851
+ kind: "baseline-install-state-mismatch"
823
852
  baseline_node_modules: NodeModulesState
824
853
  run_node_modules: NodeModulesState
825
- kind: "baseline-install-state-mismatch"
826
854
  })
827
855
  /**
828
856
  * Why the declared pnpm version ignores the `overrides` section of
@@ -922,6 +950,15 @@ export type WorsenedMetricKind = ("cyclomatic" | "cognitive" | "crap" | "line_co
922
950
  * Audit-mode reason why a complexity finding does not gate the verdict.
923
951
  */
924
952
  export type AuditGateExemption = "test-file"
953
+ /**
954
+ * How `fallow health` counts cyclomatic complexity for its gates.
955
+ *
956
+ * The policy changes the cyclomatic value that the `maxCyclomatic` gate reads
957
+ * and the cyclomatic value in every CRAP score. It does not change the health
958
+ * score, the complexity density, or the hotspot ranking: those keep the
959
+ * standard count so that grades stay comparable across projects.
960
+ */
961
+ export type CyclomaticPolicy = ("standard" | "literal-membership")
925
962
  /**
926
963
  * Coverage model used for CRAP score computation.
927
964
  */
@@ -1189,12 +1226,13 @@ export type DiffRiskActionType = ("suppress-line" | "suppress-file" | "suppress-
1189
1226
  * `target` block of [`InspectOutput`], tagged by `type`.
1190
1227
  */
1191
1228
  export type InspectTargetDescriptor = ({
1229
+ type: "file"
1192
1230
  /**
1193
1231
  * File path relative to the analysed root.
1194
1232
  */
1195
1233
  file: string
1196
- type: "file"
1197
1234
  } | {
1235
+ type: "symbol"
1198
1236
  /**
1199
1237
  * File path relative to the analysed root.
1200
1238
  */
@@ -1203,7 +1241,6 @@ file: string
1203
1241
  * Name of the inspected export.
1204
1242
  */
1205
1243
  export_name: string
1206
- type: "symbol"
1207
1244
  })
1208
1245
  /**
1209
1246
  * `identity` block of [`InspectOutput`]; shape follows the target type.
@@ -2031,7 +2068,8 @@ files?: GateFile[]
2031
2068
  *
2032
2069
  * Today only `parse-error` emits it. The item carries the same facts as the
2033
2070
  * `source-parse-degraded` entry in `workspace_diagnostics[]` for that file,
2034
- * so a consumer can act on the gate without a join.
2071
+ * so a consumer can act on the gate without a join. With `reason` set, the
2072
+ * count and the parser outcome are placeholders.
2035
2073
  */
2036
2074
  export interface GateFile {
2037
2075
  /**
@@ -2039,14 +2077,19 @@ export interface GateFile {
2039
2077
  */
2040
2078
  path: string
2041
2079
  /**
2042
- * The number of parser errors for the file.
2080
+ * The number of parser errors for the file. `1` when `reason` is set.
2043
2081
  */
2044
2082
  error_count: number
2045
2083
  /**
2046
2084
  * True when the parser stopped in the file instead of recovering, so the
2047
- * analysis saw only the part before the error.
2085
+ * analysis saw only the part before the error. `true` when `reason` is set.
2048
2086
  */
2049
2087
  panicked: boolean
2088
+ /**
2089
+ * Why the file counts when it is not a parser error. Absent for a parser
2090
+ * error, the only reason before this field existed.
2091
+ */
2092
+ reason?: (GateFileReason | null)
2050
2093
  }
2051
2094
  /**
2052
2095
  * Metric and rule definitions emitted under `_meta` when `--explain` is
@@ -3431,7 +3474,7 @@ _meta?: (Meta | null)
3431
3474
  * - source discovery, during the file walk: `skipped-large-file`,
3432
3475
  * `skipped-minified-file`, `skipped-source-dotdir`,
3433
3476
  * `excluded-by-default-ignore`, `source-read-failure`,
3434
- * `source-parse-degraded`;
3477
+ * `source-parse-degraded`, `source-nesting-too-deep`;
3435
3478
  * - dead-code analysis, from the dependency-catalog and override
3436
3479
  * detectors: `malformed-pnpm-workspace-yaml`,
3437
3480
  * `bun-lockb-override-resolution-skipped`;
@@ -3450,10 +3493,11 @@ _meta?: (Meta | null)
3450
3493
  *
3451
3494
  * A diagnostic here is advisory and never withholds a finding. Where an
3452
3495
  * entry reports a source file this run never fully analyzed
3453
- * (`source-parse-degraded`, `source-read-failure`, `skipped-large-file`,
3454
- * `skipped-minified-file`, `skipped-source-dotdir`) it can distort a
3455
- * verdict, so the affected `unused_files[]`, `unused_exports[]`, and
3456
- * dependency entries additionally carry the caveat themselves in their own
3496
+ * (`source-parse-degraded`, `source-nesting-too-deep`,
3497
+ * `source-read-failure`, `skipped-large-file`, `skipped-minified-file`,
3498
+ * `skipped-source-dotdir`) it can distort a verdict, so the affected
3499
+ * `unused_files[]`, `unused_exports[]`, and dependency entries
3500
+ * additionally carry the caveat themselves in their own
3457
3501
  * optional `reachability_caveats[]` array, and a reader who never scrolls
3458
3502
  * back up to this list still sees it. `fallow fix` reads the same array
3459
3503
  * and withholds the removal while a caveat stands.
@@ -7987,9 +8031,17 @@ line: number
7987
8031
  */
7988
8032
  col: number
7989
8033
  /**
7990
- * Cyclomatic complexity of the function.
8034
+ * Cyclomatic complexity of the function, as the cyclomatic gate and the
8035
+ * CRAP score read it. Under the `literal-membership` value of
8036
+ * `health.cyclomaticPolicy`, this is the value after literal membership
8037
+ * chains count as one predicate.
7991
8038
  */
7992
8039
  cyclomatic: number
8040
+ /**
8041
+ * Standard `McCabe` cyclomatic complexity, present only when the
8042
+ * `health.cyclomaticPolicy` value gave `cyclomatic` a different value.
8043
+ */
8044
+ standard_cyclomatic?: (number | null)
7993
8045
  /**
7994
8046
  * Cognitive complexity of the function.
7995
8047
  */
@@ -8305,6 +8357,15 @@ weight: number
8305
8357
  * consumer explain a cognitive `+3` as "+1 base, +2 nesting".
8306
8358
  */
8307
8359
  nesting: number
8360
+ /**
8361
+ * This cyclomatic `||` increment is part of a literal membership chain:
8362
+ * strict equality checks of one identifier against primitive literals
8363
+ * (`kind === "a" || kind === "b"`). The `literal-membership` value of
8364
+ * `health.cyclomaticPolicy` removes these increments from the effective
8365
+ * cyclomatic value. Omitted when `false`, and always `false` under the
8366
+ * default `standard` policy.
8367
+ */
8368
+ literal_membership?: boolean
8308
8369
  }
8309
8370
  /**
8310
8371
  * Resolved thresholds used to evaluate a health finding.
@@ -8435,6 +8496,11 @@ functions_above_threshold: number
8435
8496
  * Global cyclomatic-complexity ceiling for this run.
8436
8497
  */
8437
8498
  max_cyclomatic_threshold: number
8499
+ /**
8500
+ * Cyclomatic policy of the gates (`health.cyclomaticPolicy`). Absent
8501
+ * under the default `standard` policy.
8502
+ */
8503
+ cyclomatic_policy?: (CyclomaticPolicy | null)
8438
8504
  /**
8439
8505
  * Global cognitive-complexity ceiling for this run.
8440
8506
  */
@@ -16769,7 +16835,7 @@ _meta?: (Meta | null)
16769
16835
  * - source discovery, during the file walk: `skipped-large-file`,
16770
16836
  * `skipped-minified-file`, `skipped-source-dotdir`,
16771
16837
  * `excluded-by-default-ignore`, `source-read-failure`,
16772
- * `source-parse-degraded`;
16838
+ * `source-parse-degraded`, `source-nesting-too-deep`;
16773
16839
  * - dead-code analysis, from the dependency-catalog and override
16774
16840
  * detectors: `malformed-pnpm-workspace-yaml`,
16775
16841
  * `bun-lockb-override-resolution-skipped`;
@@ -16788,10 +16854,11 @@ _meta?: (Meta | null)
16788
16854
  *
16789
16855
  * A diagnostic here is advisory and never withholds a finding. Where an
16790
16856
  * entry reports a source file this run never fully analyzed
16791
- * (`source-parse-degraded`, `source-read-failure`, `skipped-large-file`,
16792
- * `skipped-minified-file`, `skipped-source-dotdir`) it can distort a
16793
- * verdict, so the affected `unused_files[]`, `unused_exports[]`, and
16794
- * dependency entries additionally carry the caveat themselves in their own
16857
+ * (`source-parse-degraded`, `source-nesting-too-deep`,
16858
+ * `source-read-failure`, `skipped-large-file`, `skipped-minified-file`,
16859
+ * `skipped-source-dotdir`) it can distort a verdict, so the affected
16860
+ * `unused_files[]`, `unused_exports[]`, and dependency entries
16861
+ * additionally carry the caveat themselves in their own
16795
16862
  * optional `reachability_caveats[]` array, and a reader who never scrolls
16796
16863
  * back up to this list still sees it. `fallow fix` reads the same array
16797
16864
  * and withholds the removal while a caveat stands.
@@ -16829,10 +16896,12 @@ elapsed_ms: ElapsedMs
16829
16896
  /**
16830
16897
  * The verdict of every gate this run evaluated, keyed by name. The CLI
16831
16898
  * always emits it, with the default exit rule of each section that ran
16832
- * (`error-severity-findings`, `health-findings`). Without
16899
+ * (`error-severity-findings`, `health-findings`). A `coverage-gaps` rule
16900
+ * at `error` adds `health-coverage-gaps`. Without
16833
16901
  * `--fail-on-issues` or `--ci`, the machine formats of the combined run
16834
16902
  * exit 0 for findings, so most entries have `enforced: false`. With one of
16835
- * these flags, the findings rules and `duplication-threshold` are
16903
+ * these flags, the findings rules, `health-coverage-gaps` and
16904
+ * `duplication-threshold` are
16836
16905
  * `enforced`, the dupes section adds an enforced `duplication-findings`
16837
16906
  * entry, and every format exits 1 when one fails. For the default
16838
16907
  * exit rules (`error-severity-findings`,
@@ -16984,8 +17053,8 @@ total_flags: number
16984
17053
  * A flags run walks and parses the project like every other analysis, so
16985
17054
  * it records the same discovery kinds: a `skipped-large-file`,
16986
17055
  * `skipped-minified-file`, or `source-read-failure` file was never
16987
- * scanned for flags, and a `source-parse-degraded` file was scanned from
16988
- * a partial module. Each is a reason a flag can be missing from
17056
+ * scanned for flags, a `source-parse-degraded` file was scanned from a
17057
+ * partial module, and a `source-nesting-too-deep` file was not scanned. Each is a reason a flag can be missing from
16989
17058
  * `feature_flags[]`, which is exactly what a consumer reading a
16990
17059
  * zero-result run needs to know. The analysis-stage kinds appear here
16991
17060
  * too: the scan correlates flags with dead exports, so it runs the